defmt_rtt/
lib.rs

1//! [`defmt`](https://github.com/knurling-rs/defmt) global logger over RTT.
2//!
3//! NOTE when using this crate it's not possible to use (link to) the `rtt-target` crate
4//!
5//! To use this crate, link to it by importing it somewhere in your project.
6//!
7//! ```
8//! // src/main.rs or src/bin/my-app.rs
9//! use defmt_rtt as _;
10//! ```
11//!
12//! # Blocking/Non-blocking
13//!
14//! `probe-run` puts RTT into blocking-mode, to avoid losing data.
15//!
16//! As an effect this implementation may block forever if `probe-run` disconnects on runtime. This
17//! is because the RTT buffer will fill up and writing will eventually halt the program execution.
18//!
19//! `defmt::flush` would also block forever in that case.
20//!
21//! # Critical section implementation
22//!
23//! This crate uses [`critical-section`](https://github.com/rust-embedded/critical-section) to ensure only one thread
24//! is writing to the buffer at a time. You must import a crate that provides a `critical-section` implementation
25//! suitable for the current target. See the `critical-section` README for details.
26//!
27//! For example, for single-core privileged-mode Cortex-M targets, you can add the following to your Cargo.toml.
28//!
29//! ```toml
30//! [dependencies]
31//! cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]}
32//! ```
33
34#![no_std]
35
36mod channel;
37mod consts;
38
39use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
40
41use crate::{channel::Channel, consts::BUF_SIZE};
42
43#[defmt::global_logger]
44struct Logger;
45
46/// Global logger lock.
47static TAKEN: AtomicBool = AtomicBool::new(false);
48static mut CS_RESTORE: critical_section::RestoreState = critical_section::RestoreState::invalid();
49static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
50
51unsafe impl defmt::Logger for Logger {
52    fn acquire() {
53        // safety: Must be paired with corresponding call to release(), see below
54        let restore = unsafe { critical_section::acquire() };
55
56        // safety: accessing the `static mut` is OK because we have acquired a critical section.
57        if TAKEN.load(Ordering::Relaxed) {
58            panic!("defmt logger taken reentrantly")
59        }
60
61        // safety: accessing the `static mut` is OK because we have acquired a critical section.
62        TAKEN.store(true, Ordering::Relaxed);
63
64        // safety: accessing the `static mut` is OK because we have acquired a critical section.
65        unsafe { CS_RESTORE = restore };
66
67        // safety: accessing the `static mut` is OK because we have acquired a critical section.
68        unsafe { ENCODER.start_frame(do_write) }
69    }
70
71    unsafe fn flush() {
72        // safety: accessing the `&'static _` is OK because we have acquired a critical section.
73        handle().flush();
74    }
75
76    unsafe fn release() {
77        // safety: accessing the `static mut` is OK because we have acquired a critical section.
78        ENCODER.end_frame(do_write);
79
80        // safety: accessing the `static mut` is OK because we have acquired a critical section.
81        TAKEN.store(false, Ordering::Relaxed);
82
83        // safety: accessing the `static mut` is OK because we have acquired a critical section.
84        let restore = CS_RESTORE;
85
86        // safety: Must be paired with corresponding call to acquire(), see above
87        critical_section::release(restore);
88    }
89
90    unsafe fn write(bytes: &[u8]) {
91        // safety: accessing the `static mut` is OK because we have acquired a critical section.
92        ENCODER.write(bytes, do_write);
93    }
94}
95
96fn do_write(bytes: &[u8]) {
97    unsafe { handle().write_all(bytes) }
98}
99
100#[repr(C)]
101struct Header {
102    id: [u8; 16],
103    max_up_channels: usize,
104    max_down_channels: usize,
105    up_channel: Channel,
106}
107
108const MODE_MASK: usize = 0b11;
109/// Block the application if the RTT buffer is full, wait for the host to read data.
110const MODE_BLOCK_IF_FULL: usize = 2;
111/// Don't block if the RTT buffer is full. Truncate data to output as much as fits.
112const MODE_NON_BLOCKING_TRIM: usize = 1;
113
114// make sure we only get shared references to the header/channel (avoid UB)
115/// # Safety
116/// `Channel` API is not re-entrant; this handle should not be held from different execution
117/// contexts (e.g. thread-mode, interrupt context)
118unsafe fn handle() -> &'static Channel {
119    // NOTE the `rtt-target` API is too permissive. It allows writing arbitrary data to any
120    // channel (`set_print_channel` + `rprint*`) and that can corrupt defmt log frames.
121    // So we declare the RTT control block here and make it impossible to use `rtt-target` together
122    // with this crate.
123    #[no_mangle]
124    static mut _SEGGER_RTT: Header = Header {
125        id: *b"SEGGER RTT\0\0\0\0\0\0",
126        max_up_channels: 1,
127        max_down_channels: 0,
128        up_channel: Channel {
129            name: &NAME as *const _ as *const u8,
130            #[allow(static_mut_refs)]
131            buffer: unsafe { &mut BUFFER as *mut _ as *mut u8 },
132            size: BUF_SIZE,
133            write: AtomicUsize::new(0),
134            read: AtomicUsize::new(0),
135            flags: AtomicUsize::new(MODE_NON_BLOCKING_TRIM),
136        },
137    };
138
139    #[cfg_attr(target_os = "macos", link_section = ".uninit,defmt-rtt.BUFFER")]
140    #[cfg_attr(not(target_os = "macos"), link_section = ".uninit.defmt-rtt.BUFFER")]
141    static mut BUFFER: [u8; BUF_SIZE] = [0; BUF_SIZE];
142
143    // Place NAME in data section, so the whole RTT header can be read from RAM.
144    // This is useful if flash access gets disabled by the firmware at runtime.
145    #[link_section = ".data"]
146    static NAME: [u8; 6] = *b"defmt\0";
147
148    &_SEGGER_RTT.up_channel
149}