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
4//! `rtt-target` crate
5//!
6//! To use this crate, link to it by importing it somewhere in your project.
7//!
8//! ```
9//! // src/main.rs or src/bin/my-app.rs
10//! use defmt_rtt as _;
11//! ```
12//!
13//! # Blocking/Non-blocking
14//!
15//! `probe-rs` puts RTT into blocking-mode, to avoid losing data.
16//!
17//! As an effect this implementation may block forever if `probe-rs` disconnects
18//! at runtime. This is because the RTT buffer will fill up and writing will
19//! eventually halt the program execution.
20//!
21//! `defmt::flush` would also block forever in that case.
22//!
23//! If losing data is not an concern you can disable blocking mode by enabling
24//! the feature `disable-blocking-mode`
25//!
26//! # Critical section implementation
27//!
28//! This crate uses
29//! [`critical-section`](https://github.com/rust-embedded/critical-section) to
30//! ensure only one thread is writing to the buffer at a time. You must import a
31//! crate that provides a `critical-section` implementation suitable for the
32//! current target. See the `critical-section` README for details.
33//!
34//! For example, for single-core privileged-mode Cortex-M targets, you can add
35//! the following to your Cargo.toml.
36//!
37//! ```toml
38//! [dependencies]
39//! cortex-m = { version = "0.7.6", features = ["critical-section-single-core"]}
40//! ```
41
42#![no_std]
43
44mod channel;
45mod consts;
46
47use core::{
48 cell::UnsafeCell,
49 sync::atomic::{AtomicBool, AtomicUsize, Ordering},
50};
51
52use crate::{channel::Channel, consts::BUF_SIZE};
53
54/// The relevant bits in the mode field in the Header
55const MODE_MASK: usize = 0b11;
56
57/// Block the application if the RTT buffer is full, wait for the host to read data.
58const MODE_BLOCK_IF_FULL: usize = 2;
59
60/// Don't block if the RTT buffer is full. Truncate data to output as much as fits.
61const MODE_NON_BLOCKING_TRIM: usize = 1;
62
63/// The defmt global logger
64///
65/// The defmt crate requires that this be a unit type, so our state is stored in
66/// [`RTT_ENCODER`] instead.
67#[defmt::global_logger]
68struct Logger;
69
70/// Our defmt encoder state
71static RTT_ENCODER: RttEncoder = RttEncoder::new();
72
73/// Our shared header structure.
74///
75/// The host will read this structure so it must be arranged as expected.
76///
77/// NOTE the `rtt-target` API is too permissive. It allows writing arbitrary
78/// data to any channel (`set_print_channel` + `rprint*`) and that can corrupt
79/// defmt log frames. So we declare the RTT control block here and make it
80/// impossible to use `rtt-target` together with this crate.
81#[no_mangle]
82static _SEGGER_RTT: Header = Header {
83 id: *b"SEGGER RTT\0\0\0\0\0\0",
84 max_up_channels: 1,
85 max_down_channels: 0,
86 up_channel: Channel {
87 name: NAME.as_ptr(),
88 buffer: BUFFER.get(),
89 size: BUF_SIZE,
90 write: AtomicUsize::new(0),
91 read: AtomicUsize::new(0),
92 flags: AtomicUsize::new(MODE_NON_BLOCKING_TRIM),
93 },
94};
95
96/// Our shared buffer
97#[cfg_attr(target_os = "macos", link_section = ".uninit,defmt-rtt.BUFFER")]
98#[cfg_attr(not(target_os = "macos"), link_section = ".uninit.defmt-rtt.BUFFER")]
99static BUFFER: Buffer = Buffer::new();
100
101/// The name of our channel.
102///
103/// This is in a data section, so the whole RTT header can be read from RAM.
104/// This is useful if flash access gets disabled by the firmware at runtime.
105#[cfg_attr(target_os = "macos", link_section = ".data,defmt-rtt.NAME")]
106#[cfg_attr(not(target_os = "macos"), link_section = ".data.defmt-rtt.NAME")]
107static NAME: [u8; 6] = *b"defmt\0";
108
109struct RttEncoder {
110 /// A boolean lock
111 ///
112 /// Is `true` when `acquire` has been called and we have exclusive access to
113 /// the rest of this structure.
114 taken: AtomicBool,
115 /// We need to remember this to exit a critical section
116 cs_restore: UnsafeCell<critical_section::RestoreState>,
117 /// A defmt::Encoder for encoding frames
118 encoder: UnsafeCell<defmt::Encoder>,
119}
120
121impl RttEncoder {
122 /// Create a new semihosting-based defmt-encoder
123 const fn new() -> RttEncoder {
124 RttEncoder {
125 taken: AtomicBool::new(false),
126 cs_restore: UnsafeCell::new(critical_section::RestoreState::invalid()),
127 encoder: UnsafeCell::new(defmt::Encoder::new()),
128 }
129 }
130
131 /// Acquire the defmt encoder.
132 fn acquire(&self) {
133 // safety: Must be paired with corresponding call to release(), see below
134 let restore = unsafe { critical_section::acquire() };
135
136 // NB: You can re-enter critical sections but we need to make sure
137 // no-one does that.
138 if self.taken.load(Ordering::Relaxed) {
139 panic!("defmt logger taken reentrantly")
140 }
141
142 // no need for CAS because we are in a critical section
143 self.taken.store(true, Ordering::Relaxed);
144
145 // safety: accessing the cell is OK because we have acquired a critical
146 // section.
147 unsafe {
148 self.cs_restore.get().write(restore);
149 let encoder: &mut defmt::Encoder = &mut *self.encoder.get();
150 encoder.start_frame(|b| {
151 _SEGGER_RTT.up_channel.write_all(b);
152 });
153 }
154 }
155
156 /// Write bytes to the defmt encoder.
157 ///
158 /// # Safety
159 ///
160 /// Do not call unless you have called `acquire`.
161 unsafe fn write(&self, bytes: &[u8]) {
162 // safety: accessing the cell is OK because we have acquired a critical
163 // section.
164 unsafe {
165 let encoder: &mut defmt::Encoder = &mut *self.encoder.get();
166 encoder.write(bytes, |b| {
167 _SEGGER_RTT.up_channel.write_all(b);
168 });
169 }
170 }
171
172 /// Flush the encoder
173 ///
174 /// # Safety
175 ///
176 /// Do not call unless you have called `acquire`.
177 unsafe fn flush(&self) {
178 // safety: accessing the `&'static _` is OK because we have acquired a
179 // critical section.
180 _SEGGER_RTT.up_channel.flush();
181 }
182
183 /// Release the defmt encoder.
184 ///
185 /// # Safety
186 ///
187 /// Do not call unless you have called `acquire`. This will release
188 /// your lock - do not call `flush` and `write` until you have done another
189 /// `acquire`.
190 unsafe fn release(&self) {
191 if !self.taken.load(Ordering::Relaxed) {
192 panic!("defmt release out of context")
193 }
194
195 // safety: accessing the cell is OK because we have acquired a critical
196 // section.
197 unsafe {
198 let encoder: &mut defmt::Encoder = &mut *self.encoder.get();
199 encoder.end_frame(|b| {
200 _SEGGER_RTT.up_channel.write_all(b);
201 });
202 let restore = self.cs_restore.get().read();
203 self.taken.store(false, Ordering::Relaxed);
204 // paired with exactly one acquire call
205 critical_section::release(restore);
206 }
207 }
208}
209
210unsafe impl Sync for RttEncoder {}
211
212unsafe impl defmt::Logger for Logger {
213 fn acquire() {
214 RTT_ENCODER.acquire();
215 }
216
217 unsafe fn write(bytes: &[u8]) {
218 unsafe {
219 RTT_ENCODER.write(bytes);
220 }
221 }
222
223 unsafe fn flush() {
224 unsafe {
225 RTT_ENCODER.flush();
226 }
227 }
228
229 unsafe fn release() {
230 unsafe {
231 RTT_ENCODER.release();
232 }
233 }
234}
235
236#[repr(C)]
237struct Header {
238 id: [u8; 16],
239 max_up_channels: usize,
240 max_down_channels: usize,
241 up_channel: Channel,
242}
243
244unsafe impl Sync for Header {}
245
246struct Buffer {
247 inner: UnsafeCell<[u8; BUF_SIZE]>,
248}
249
250impl Buffer {
251 const fn new() -> Buffer {
252 Buffer {
253 inner: UnsafeCell::new([0; BUF_SIZE]),
254 }
255 }
256
257 const fn get(&self) -> *mut u8 {
258 self.inner.get() as _
259 }
260}
261
262unsafe impl Sync for Buffer {}
263