defmt_embassy_usbserial/
lib.rs

1//! [`defmt`] global logger over USB serial for use with [Embassy].
2//!
3//! To use this crate spawn the [`run`] task, and use `defmt` as normal. Messages will be sent via
4//! USB-CDC to the host, where you should use something such as the [`defmt-print`] CLI tool to
5//! print them to your terminal.
6//!
7//! ## Quickstart
8//!
9//! Here's an example of using it with [`embassy_rp`], with the general HAL setup elided.
10//!
11//! ```no_run
12//! # #![no_std]
13//! # #![no_main]
14//! # use embassy_rp::{bind_interrupts, Peri};
15//! # use embassy_time::Instant;
16//! # use embedded_hal_async::delay::DelayNs;
17//! # use panic_halt as _;
18//! # bind_interrupts!(struct Irqs {
19//! #     USBCTRL_IRQ => embassy_rp::usb::InterruptHandler<embassy_rp::peripherals::USB>;
20//! # });
21//! #[embassy_executor::task]
22//! async fn defmtusb_wrapper(usb: Peri<'static, embassy_rp::peripherals::USB>) {
23//!     let driver = embassy_rp::usb::Driver::new(usb, Irqs);
24//!     let usb_config = {
25//!         let mut c = embassy_usb::Config::new(0x1234, 0x5678);
26//!         c.serial_number = Some("defmt");
27//!         c.max_packet_size_0 = 64;
28//!         c.composite_with_iads = true;
29//!         c.device_class = 0xEF;
30//!         c.device_sub_class = 0x02;
31//!         c.device_protocol = 0x01;
32//!         c
33//!     };
34//!     defmt_embassy_usbserial::run(driver, usb_config).await;
35//! }
36//! #
37//! # #[embassy_executor::main]
38//! # async fn main(spawner: embassy_executor::Spawner) {
39//! #     let peripherals = embassy_rp::init(Default::default());
40//! #     let mut delay = embassy_time::Delay;
41//!
42//! // Inside your main function.
43//! spawner.must_spawn(defmtusb_wrapper(peripherals.USB));
44//! loop {
45//!     defmt::info!("Hello! {=u64:ts}", Instant::now().as_secs());
46//!     delay.delay_ms(1000).await;
47//! }
48//! # }
49//! ```
50//!
51//! ## Wrapper task
52//!
53//! A wrapper task is required because this library can't provide a task for you to spawn, since it
54//! has to be generic over the USB driver struct. While the quickstart example provides a
55//! straightforward example of constructing both the driver and the configuration in this task,
56//! ultimately the only requirement is that it awaits [`defmt_embassy_usbserial::run`].
57//!
58//! Of course, `defmt_embassy_usbserial::run` is just an async function whose returned future can
59//! be `join`ed, etc.
60//!
61//! ## Configuration
62//!
63//! For USB-CDC to be set up properly, you _must_ set the correct values in the configuration
64//! struct. If `composite_with_iads` is `true` (the default), you _must_ use the following values
65//! as `embassy-usb` will [fail an assertion][eusb-assert] if you do not:
66//!
67//! | Field | Value |
68//! |-------|-------|
69//! |`device_class`|`0xEF`|
70//! |`device_sub_class`|`0x02`|
71//! |`device_protocol`|`0x01`|
72//!
73//! If `composite_with_iads` is `false`, you do not have to use these exact values: the standard
74//! CDC device class code (`device_class`) is `0x02`. You should choose the values appropriate to
75//! your application. If your only concern is transporting defmt logs over USB serial, default to
76//! the values in the table above.
77//!
78//! ## Examples
79//!
80//! Please see the `device-examples/` directory in the repository for device-specific "hello world"
81//! examples. These examples have all been tested on real hardware and are known to work.
82//!
83//! ## Known limitations
84//!
85//! ### Old or corrupt defmt messages are received after reconnecting
86//!
87//! If you stop reading the logs from the USB serial port, for example by closing `defmt-print`,
88//! when you reconnect you will likely receive part of one old defmt message (and potentially
89//! several complete out-out-date messages), and the first up-to-date defmt message will be
90//! corrupt.
91//!
92//! This is ultimately because the internal buffers are not aware of defmt frame boundaries. The
93//! first case will occur because the writing task will block part-way through writing an internal
94//! buffer to the USB serial port, and continues writing that now-stale buffer when you start
95//! reading again. (This may be avoided in a future release by way of a timeout.)
96//!
97//! The second is because that buffer may end part-way through a defmt message, and the next buffer
98//! that is written will likely start part-way through a defmt message. `defmt-print` may
99//! explicitly report these frames as malformed, or may silently misinterpret values to be included
100//! in a format message.
101//!
102//! Note as well that ceasing to read from the serial port does not disable defmt logging; it seems
103//! that only disconnecting from USB will trigger the event that toggles the logger.
104//!
105//! ### High message latency
106//!
107//! It may take some time for you to start receiving messages, and they may come through in bursts.
108//! This is due to the implementation waiting until one of its internal buffers is full before
109//! writing to the USB serial port. This effect will be more pronounced if you choose a larger
110//! buffer size feature, and if you have messages with few (or no) formatting
111//! parameters, as this greatly reduces the size of the data that needs to be transferred.
112//!
113//! It may be possible to make the implementation aware of defmt messages, so that they come
114//! through in a more stream-like manner. Suggestions and contributions on this would be greatly
115//! appreciated.
116//!
117//! ### Buffers flushed only every 100ms
118//!
119//! Conversely, if you have a high volume of messages, there is at present a 100ms delay after
120//! writing an internal buffer to USB. It is planned to make this configurable.
121//!
122//! ## Acknowledgements
123//!
124//! Thank you to spcan, the original author of defmtusb. Thanks as well to the friendly and helpful
125//! members of the various embedded Rust Matrix rooms.
126//!
127//! ## License
128//!
129//! Dual-licensed under the Mozilla Public License 2.0 and the MIT license, at your option.
130//!
131//! [`defmt`]: https://github.com/knurling-rs/defmt
132//! [`defmt-print`]: https://crates.io/crates/defmt-print
133//! [`defmtusb_embassy_usbserial::run`]: crate::task::run
134//! [eusb-assert]: https://github.com/embassy-rs/embassy/blob/4bff7cea1a26267ec3671250e954d9d4242fabde/embassy-usb/src/builder.rs#L175-L177
135//! [Embassy]: https://embassy.dev
136//! [`embassy_rp`]: https://docs.embassy.dev/embassy-rp/git/rp2040/index.html
137
138#![no_std]
139
140mod buffer;
141mod controller;
142mod task;
143
144use core::{
145    cell::UnsafeCell,
146    sync::atomic::{AtomicBool, Ordering},
147};
148
149pub use task::{logger, run};
150
151static USB_ENCODER: UsbEncoder = UsbEncoder::new();
152
153struct UsbEncoder {
154    /// A boolean lock
155    ///
156    /// Is `true` when `acquire` has been called and we have exclusive access to the
157    /// rest of this struct.
158    taken: AtomicBool,
159    /// Critical section restore state
160    ///
161    /// Needed to exit a critical section.
162    restore: UnsafeCell<critical_section::RestoreState>,
163    /// A defmt Encoder for encoding frames
164    encoder: UnsafeCell<defmt::Encoder>,
165}
166
167unsafe impl Sync for UsbEncoder {}
168
169impl UsbEncoder {
170    const fn new() -> Self {
171        Self {
172            taken: AtomicBool::new(false),
173            restore: UnsafeCell::new(critical_section::RestoreState::invalid()),
174            encoder: UnsafeCell::new(defmt::Encoder::new()),
175        }
176    }
177
178    /// Acquire the defmt logger
179    ///
180    /// This acquires a critical section and begins a defmt frame.
181    ///
182    /// # Panics
183    ///
184    /// This will panic if you attempt to acquire the logger re-entrantly.
185    fn acquire(&self) {
186        // Get in a critical section.
187        //
188        // SAFETY: Must be paired with a call to release, as it is in the contract of
189        // the Logger trait.
190        let restore_state = unsafe { critical_section::acquire() };
191
192        // Fail if the logger is acquired re-entrantly, to avoid two places with
193        // mutable access to the logger state.
194        if self.taken.load(Ordering::Relaxed) {
195            panic!("defmt logger taken reentrantly");
196        }
197
198        // Set the boolean lock now that we're in a critical section and we know
199        // it is not already taken.
200        self.taken.store(true, Ordering::Relaxed);
201
202        // SAFETY: Accessing the UnsafeCells is OK because we are in a critical section.
203        unsafe {
204            // Store the value needed to exit the critical section.
205            self.restore.get().write(restore_state);
206
207            // Start the defmt frame.
208            let encoder = &mut *self.encoder.get();
209            encoder.start_frame(Self::inner);
210        }
211    }
212
213    /// Release the defmt logger
214    ///
215    /// This finishes the defmt frame and releases the critical section.
216    ///
217    /// # Safety
218    ///
219    /// Must be called exactly once after calling acquire.
220    unsafe fn release(&self) {
221        // Ensure we are not attempting to release while not in a critical section.
222        if !self.taken.load(Ordering::Relaxed) {
223            panic!("defmt release outside of critical section.")
224        }
225
226        // SAFETY: Accessing the UnsafeCells and finally releasing the critical section
227        // is OK because we know we are in a critical section at this point.
228        unsafe {
229            let encoder = &mut *self.encoder.get();
230            encoder.end_frame(Self::inner);
231
232            let restore_state = self.restore.get().read();
233            self.taken.store(false, Ordering::Relaxed);
234            critical_section::release(restore_state);
235        }
236    }
237
238    /// Flush the current buffer.
239    ///
240    /// # Safety
241    ///
242    /// Must be called after calling `acquire` and before calling `release`.
243    unsafe fn flush(&self) {
244        unsafe { controller::CONTROLLER.swap() }
245    }
246
247    /// Write bytes to the defmt encoder.
248    ///
249    /// # Safety
250    ///
251    /// Must be called after calling `acquire` and before calling `release`.
252    unsafe fn write(&self, bytes: &[u8]) {
253        let encoder = unsafe { &mut *self.encoder.get() };
254        encoder.write(bytes, Self::inner)
255    }
256
257    fn inner(bytes: &[u8]) {
258        // SAFETY: Always called from within a critical section by the defmt logger.
259        unsafe {
260            controller::CONTROLLER.write(bytes);
261        }
262    }
263}
264
265/// The logger implementation.
266#[defmt::global_logger]
267struct USBLogger;
268
269unsafe impl defmt::Logger for USBLogger {
270    fn acquire() {
271        USB_ENCODER.acquire();
272    }
273
274    unsafe fn release() {
275        unsafe { USB_ENCODER.release() };
276    }
277
278    unsafe fn flush() {
279        unsafe { USB_ENCODER.flush() };
280    }
281
282    unsafe fn write(bytes: &[u8]) {
283        unsafe { USB_ENCODER.write(bytes) };
284    }
285}