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}