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 | |
44 | mod channel; |
45 | mod consts; |
46 | |
47 | use core::{ |
48 | cell::UnsafeCell, |
49 | sync::atomic::{AtomicBool, AtomicUsize, Ordering}, |
50 | }; |
51 | |
52 | use crate::{channel::Channel, consts::BUF_SIZE}; |
53 | |
54 | /// The relevant bits in the mode field in the Header |
55 | const MODE_MASK: usize = 0b11; |
56 | |
57 | /// Block the application if the RTT buffer is full, wait for the host to read data. |
58 | const 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. |
61 | const 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 ] |
68 | struct Logger; |
69 | |
70 | /// Our defmt encoder state |
71 | static 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 ] |
82 | static _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" )] |
99 | static 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" )] |
107 | static NAME: [u8; 6] = *b"defmt \0" ; |
108 | |
109 | struct 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 | |
121 | impl 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 | |
210 | unsafe impl Sync for RttEncoder {} |
211 | |
212 | unsafe 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)] |
237 | struct Header { |
238 | id: [u8; 16], |
239 | max_up_channels: usize, |
240 | max_down_channels: usize, |
241 | up_channel: Channel, |
242 | } |
243 | |
244 | unsafe impl Sync for Header {} |
245 | |
246 | struct Buffer { |
247 | inner: UnsafeCell<[u8; BUF_SIZE]>, |
248 | } |
249 | |
250 | impl 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 | |
262 | unsafe impl Sync for Buffer {} |
263 | |