defmt_bbq/lib.rs
1//! # `defmt-bbq`
2//!
3//! > A generic [`bbqueue`] based transport for [`defmt`] log messages
4//!
5//! [`defmt`]: https://github.com/knurling-rs/defmt
6//! [`bbqueue`]: https://github.com/jamesmunns/bbqueue
7//!
8//! `defmt` ("de format", short for "deferred formatting") is a highly efficient logging framework that targets resource-constrained devices, like microcontrollers.
9//!
10//! This crate stores the logged messages into a thread-safe FIFO queue, which can then be transferred
11//! across any medium, such as USB, RS-485, or other transports.
12//!
13//! Although this crate acts as a `global_logger` implementer for `defmt`, it still requires
14//! you to *do* something with the messages. This is intended to be a reusable building block
15//! for a variety of different transport methods.
16//!
17//! ## Usage
18//!
19//! This crates requires it's users to perform the following actions:
20//!
21//! 1. (optional): If you'd like to select a different queue size than the default
22//! 1024, you'll need to set the `DEFMT_BBQ_BUFFER_SIZE` environment variable at
23//! build time to configure the size. e.g.: `DEFMT_BBQ_BUFFER_SIZE=4096 cargo build`.
24//! 2. Prior to the first `defmt` log, the user MUST call `defmt_bbq::init()`, which will
25//! initialize the logging buffer, and return the `Consumer` half of the queue, which
26//! gives access to incoming logging messages
27//! 3. The user must regularly drain the logged messages. If the queue is filled, any
28//! additional bytes will be discarded, potentially corrupting (some) logging messages
29//!
30//! For more information on the Consumer interface, see the [Consumer docs] in the `bbqueue`
31//! crate documentation.
32//!
33//! [Consumer docs]: https://docs.rs/bbqueue/latest/bbqueue/struct.Consumer.html
34//!
35//! ### Example
36//!
37//! ```rust,no_run
38//! #[entry]
39//! fn main() {
40//! // MUST be called before the first `defmt::*` call!
41//! let mut consumer = defmt_bbq::init().unwrap();
42//!
43//! loop {
44//! defmt::println!("Hello, world!");
45//!
46//! if let Some(grant) = consumer.read() {
47//! // do something with `bytes`, like send
48//! // it over a serial port..
49//!
50//! // Then when done, make sure you release the grant
51//! // to free the space for future logging.
52//! let glen = grant.len();
53//! grant.release(glen);
54//! }
55//! }
56//! }
57//! ```
58//!
59//! For a more detailed end-to-end example over USB Serial, please see the
60//! project's [example folder].
61//!
62//! [example folder]: https://github.com/jamesmunns/defmt-bbq/blob/main/examples/README.md
63//!
64//! ## Default Feature(s)
65//!
66//! This crate has a single default feature, which enables the `encoding-rzcobs`
67//! feature of the `defmt` crate.
68//!
69//! It is **strongly recommended** to leave this feature enabled (and to
70//! use rzcobs encoding) when using this crate. If the buffer is ever overfilled,
71//! then the remaining bytes will be discarded, which will temporarily corrupt
72//! the message stream.
73//!
74//! Because rzcobs is delimited by zero bytes, it is possible to recover
75//! from this corruption, with the loss of a limited number of messages.
76//! When using the "raw" encoding, you MUST ensure that the buffer is
77//! **never** overfilled, as it will NOT be possible to recover from this
78//! error condition.
79//!
80//! ## Provenance
81//!
82//! This repository is a fork of `defmt-rtt`, obtained from the [`defmt`] repository.
83//!
84//! This repository was forked as of upstream commit `50e3db37d5429ed3344726f01e1bc4bf04902251`.
85//!
86//! ## License
87//!
88//! Licensed under either of
89//!
90//! - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
91//! <http://www.apache.org/licenses/LICENSE-2.0>)
92//!
93//! - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
94//!
95//! at your option.
96//!
97//! ### Contribution
98//!
99//! Unless you explicitly state otherwise, any contribution intentionally submitted
100//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
101//! licensed as above, without any additional terms or conditions.
102
103#![no_std]
104
105pub(crate) mod consts;
106
107use bbqueue::{BBBuffer, GrantW, Producer};
108use core::{
109 cell::UnsafeCell,
110 mem::MaybeUninit,
111 sync::atomic::{AtomicBool, AtomicUsize, AtomicU8, Ordering},
112};
113use cortex_m::{interrupt, register};
114
115/// BBQueue buffer size. Default: 1024; can be customized by setting the
116/// `DEFMT_RTT_BUFFER_SIZE` environment variable at compile time
117pub use crate::consts::BUF_SIZE;
118
119/// The `defmt-bbq` error type
120#[derive(Debug, defmt::Format, PartialEq, Eq)]
121pub enum Error {
122 /// An internal latching fault has occured. No more logs will be returned.
123 /// This indicates a coding error in `defmt-bbq`. Please open an issue.
124 InternalLatchingFault,
125
126 /// The user attempted to log before initializing the `defmt-bbq` structure.
127 /// This is a latching fault, and not recoverable, but is not an indicator of
128 /// a failure in the library. If you see this error, please ensure that you
129 /// call `defmt-bbq::init()` before making any defmt logging statements.
130 UseBeforeInitLatchingFault,
131
132 /// This indicates some potentially recoverable bbqerror (including no
133 /// data currently available).
134 Bbq(BBQError),
135}
136
137impl From<BBQError> for Error {
138 fn from(other: BBQError) -> Self {
139 Error::Bbq(other)
140 }
141}
142
143/// This is the consumer type given to the user to drain the logging queue.
144///
145/// It is a re-export of the [bbqueue::Consumer](https://docs.rs/bbqueue/latest/bbqueue/struct.Consumer.html) type.
146///
147pub use bbqueue::Consumer;
148
149/// An error returned by the underlying bbqueue storage
150///
151/// It is a re-export of the [bbqueue::Error](https://docs.rs/bbqueue/latest/bbqueue/enum.Error.html) type.
152///
153pub use bbqueue::Error as BBQError;
154
155/// This is the Reader Grant type given to the user as a view of the logging queue.
156///
157/// It is a re-export of the [bbqueue::GrantR](https://docs.rs/bbqueue/latest/bbqueue/struct.GrantR.html) type.
158///
159pub use bbqueue::GrantR;
160
161/// This is the Split Reader Grant type given to the user as a view of the logging queue.
162///
163/// It is a re-export of the [bbqueue::SplitGrantR](https://docs.rs/bbqueue/latest/bbqueue/struct.SplitGrantR.html) type.
164///
165pub use bbqueue::SplitGrantR;
166
167// ----------------------------------------------------------------------------
168// init() function - this is the (only) user facing interface
169// ----------------------------------------------------------------------------
170
171/// Initialize the BBQueue based global defmt sink. MUST be called before
172/// the first `defmt` log, or it will latch a fault.
173///
174/// On the first call to this function, the Consumer end of the logging
175/// queue will be returned. On any subsequent call, an error will be
176/// returned.
177///
178/// For more information on the Consumer interface, see the [Consumer docs] in the `bbqueue`
179/// crate documentation.
180///
181/// [Consumer docs]: https://docs.rs/bbqueue/latest/bbqueue/struct.Consumer.html
182pub fn init() -> Result<DefmtConsumer, Error> {
183 let (prod, cons) = BBQ.try_split()?;
184
185 // NOTE: We are okay to treat the following as safe, as the BBQueue
186 // split operation is guaranteed to only return Ok once in a
187 // thread-safe manner.
188 unsafe {
189 BBQ_PRODUCER.uc_mu_fp.get().write(MaybeUninit::new(prod));
190 }
191
192 // MUST be done LAST
193 BBQ_STATE.store(logstate::INIT_NO_STORED_GRANT, Ordering::Release);
194
195 Ok(DefmtConsumer { cons })
196}
197
198// ----------------------------------------------------------------------------
199// Defmt Consumer
200// ----------------------------------------------------------------------------
201
202/// The consumer interface of `defmt-log`.
203///
204/// This type is a wrapper around the
205/// [bbqueue::Consumer](https://docs.rs/bbqueue/latest/bbqueue/struct.Consumer.html) type,
206/// and returns defmt-bbq's [Error](crate::Error) type instead of bbqueue's
207/// [bbqueue::Error](https://docs.rs/bbqueue/latest/bbqueue/enum.Error.html) type.
208pub struct DefmtConsumer {
209 cons: Consumer<'static, BUF_SIZE>,
210}
211
212impl DefmtConsumer {
213 /// Obtains a contiguous slice of committed bytes. This slice may not
214 /// contain ALL available bytes, if the writer has wrapped around. The
215 /// remaining bytes will be available after all readable bytes are
216 /// released
217 pub fn read(&mut self) -> Result<GrantR<'static, BUF_SIZE>, Error> {
218 Ok(self.cons.read()?)
219 }
220
221 /// Obtains two disjoint slices, which are each contiguous of committed bytes.
222 /// Combined these contain all previously commited data at the time of read
223 pub fn split_read(&mut self) -> Result<SplitGrantR<'static, BUF_SIZE>, Error> {
224 Ok(self.cons.split_read()?)
225 }
226}
227
228// ----------------------------------------------------------------------------
229// logstate, and state helper functions
230// ----------------------------------------------------------------------------
231
232mod logstate {
233 // BBQ has NOT been initialized
234 // BBQ_PRODUCER has NOT been initialized
235 // BBQ_GRANT has NOT been initialized
236 pub const UNINIT: u8 = 0;
237
238 // BBQ HAS been initialized
239 // BBQ_PRODUCER HAS been initialized
240 // BBQ_GRANT has NOT been initialized
241 pub const INIT_NO_STORED_GRANT: u8 = 1;
242
243 // BBQ HAS been initialized
244 // BBQ_PRODUCER HAS been initialized
245 // BBQ_GRANT HAS been initialized
246 pub const INIT_GRANT_IS_STORED: u8 = 2;
247
248 // All state codes above 100 are a latching fault
249
250 // A latching fault has occurred.
251 pub const LATCH_INTERNAL_ERROR: u8 = 100;
252
253 // The user attempted to log before init
254 pub const LATCH_USE_BEFORE_INIT: u8 = 101;
255}
256
257#[inline]
258fn check_latch(ordering: Ordering) -> Result<(), Error> {
259 match BBQ_STATE.load(ordering) {
260 i if i < logstate::LATCH_INTERNAL_ERROR => Ok(()),
261 logstate::LATCH_USE_BEFORE_INIT => Err(Error::UseBeforeInitLatchingFault),
262 _ => Err(Error::InternalLatchingFault),
263 }
264}
265
266#[inline]
267fn latch_assert_eq<T: PartialEq>(left: T, right: T) -> Result<(), Error> {
268 if left == right {
269 Ok(())
270 } else {
271 BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Release);
272 Err(Error::InternalLatchingFault)
273 }
274}
275
276// ----------------------------------------------------------------------------
277// UnsafeProducer
278// ----------------------------------------------------------------------------
279
280/// A storage structure for holding the maybe initialized producer with inner mutability
281struct UnsafeProducer {
282 uc_mu_fp: UnsafeCell<MaybeUninit<Producer<'static, BUF_SIZE>>>,
283}
284
285impl UnsafeProducer {
286 const fn new() -> Self {
287 Self {
288 uc_mu_fp: UnsafeCell::new(MaybeUninit::uninit()),
289 }
290 }
291
292 // TODO: Could be made safe if we ensure the reference is only taken
293 // once. For now, leave unsafe
294 unsafe fn get_mut(&self) -> Result<&mut Producer<'static, BUF_SIZE>, Error> {
295 latch_assert_eq(logstate::INIT_NO_STORED_GRANT, BBQ_STATE.load(Ordering::Relaxed))?;
296
297 // NOTE: `UnsafeCell` and `MaybeUninit` are both `#[repr(Transparent)],
298 // meaning this direct cast is acceptable
299 let const_ptr: *const Producer<'static, BUF_SIZE> = self.uc_mu_fp.get().cast();
300 let mut_ptr: *mut Producer<'static, BUF_SIZE> = const_ptr as *mut _;
301 let ref_mut: &mut Producer<'static, BUF_SIZE> = &mut *mut_ptr;
302
303 Ok(ref_mut)
304 }
305}
306
307unsafe impl Sync for UnsafeProducer {}
308
309// ----------------------------------------------------------------------------
310// UnsafeGrantW
311// ----------------------------------------------------------------------------
312
313struct UnsafeGrantW {
314 uc_mu_fgw: UnsafeCell<MaybeUninit<GrantW<'static, BUF_SIZE>>>,
315
316 /// Note: This stores the offset into the *current grant*, IFF a grant
317 /// is stored in BBQ_GRANT_W. If there is no grant active, or if the
318 /// grant is currently "taken" by the `do_write()` function, the value
319 /// is meaningless.
320 offset: AtomicUsize,
321}
322
323impl UnsafeGrantW {
324 const fn new() -> Self {
325 Self {
326 uc_mu_fgw: UnsafeCell::new(MaybeUninit::uninit()),
327 offset: AtomicUsize::new(0),
328 }
329 }
330
331 // TODO: Could be made safe if we ensure the reference is only taken
332 // once. For now, leave unsafe.
333 //
334 /// This function STORES
335 /// MUST be done in a critical section.
336 unsafe fn put(&self, grant: GrantW<'static, BUF_SIZE>, offset: usize) -> Result<(), Error> {
337 // Note: This also catches the "already latched" state check
338 latch_assert_eq(logstate::INIT_NO_STORED_GRANT, BBQ_STATE.load(Ordering::Relaxed))?;
339
340 self.uc_mu_fgw.get().write(MaybeUninit::new(grant));
341 self.offset.store(offset, Ordering::Relaxed);
342 BBQ_STATE.store(logstate::INIT_GRANT_IS_STORED, Ordering::Relaxed);
343 Ok(())
344 }
345
346 // The take function will attempt to provide us with a grant. This grant could
347 // come from an existing stored grant (if we are in the INIT_GRANT_IS_STORED
348 // state), or from a new grant (if we are in the INIT_NO_STORED_GRANT state).
349 //
350 // This call to `take()` may fail if we have no space available remaining in the
351 // queue, or if we have encountered some kind of latching fault.
352 unsafe fn take(&self) -> Result<Option<(GrantW<'static, BUF_SIZE>, usize)>, Error> {
353 check_latch(Ordering::Relaxed)?;
354
355 Ok(match BBQ_STATE.load(Ordering::Relaxed) {
356 // We have a stored grant. Take it out of the global, and return it to the user
357 logstate::INIT_GRANT_IS_STORED => {
358 // NOTE: UnsafeCell and MaybeUninit are #[repr(Transparent)], so this
359 // cast is acceptable
360 let grant = self
361 .uc_mu_fgw
362 .get()
363 .cast::<GrantW<'static, BUF_SIZE>>()
364 .read();
365
366 BBQ_STATE.store(logstate::INIT_NO_STORED_GRANT, Ordering::Relaxed);
367
368 Some((grant, self.offset.load(Ordering::Relaxed)))
369 }
370
371 // We *don't* have a stored grant. Attempt to retrieve a new one, and return
372 // that to the user, without storing it in the global.
373 logstate::INIT_NO_STORED_GRANT => {
374 let producer = BBQ_PRODUCER.get_mut()?;
375
376 // We have a new grant, reset the current grant offset back to zero
377 self.offset.store(0, Ordering::Relaxed);
378 producer.grant_max_remaining(BUF_SIZE).ok().map(|g| (g, 0))
379 }
380
381 // We're in a bad place. Store a latching fault, and move on.
382 n => {
383 // If we aren't already in a latching fault of some kind, set one
384 if n < logstate::LATCH_INTERNAL_ERROR {
385 BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Relaxed);
386 }
387
388 return Err(Error::InternalLatchingFault);
389 },
390 })
391 }
392}
393
394unsafe impl Sync for UnsafeGrantW {}
395
396// ----------------------------------------------------------------------------
397// Globals
398// ----------------------------------------------------------------------------
399
400// The underlying byte storage containing the logs. Always valid
401static BBQ: BBBuffer<BUF_SIZE> = BBBuffer::new();
402
403// A tracking variable for ensuring state. Always valid.
404static BBQ_STATE: AtomicU8 = AtomicU8::new(logstate::UNINIT);
405
406// The producer half of the logging queue. This field is ONLY
407// valid if `init()` has been called.
408static BBQ_PRODUCER: UnsafeProducer = UnsafeProducer::new();
409
410// An active write grant to a portion of the `BBQ`, obtained through
411// the `BBQ_PRODUCER`. This field is ONLY valid if we are in the
412// `INIT_GRANT` state.
413static BBQ_GRANT_W: UnsafeGrantW = UnsafeGrantW::new();
414
415/// Global logger lock.
416static TAKEN: AtomicBool = AtomicBool::new(false);
417static INTERRUPTS_ACTIVE: AtomicBool = AtomicBool::new(false);
418static mut ENCODER: defmt::Encoder = defmt::Encoder::new();
419
420// ----------------------------------------------------------------------------
421// defmt::Logger interface
422//
423// This is the implementation of the defmt::Logger interace
424// ----------------------------------------------------------------------------
425
426#[defmt::global_logger]
427struct Logger;
428
429unsafe impl defmt::Logger for Logger {
430 fn acquire() {
431 let primask = register::primask::read();
432 interrupt::disable();
433
434 let state = BBQ_STATE.load(Ordering::Relaxed);
435 let taken = TAKEN.load(Ordering::Relaxed);
436
437 let bail = match (taken, state) {
438 // Fast case: all good.
439 (false, logstate::INIT_NO_STORED_GRANT) => false,
440
441 // We tried to use before initialization. Regardless of the taken state,
442 // this is an error. We *might* be able to recover from this in the future,
443 // but it is more complicated. For now, just latch the error and signal
444 // the user
445 (_, logstate::UNINIT) => {
446 BBQ_STATE.store(logstate::LATCH_USE_BEFORE_INIT, Ordering::Relaxed);
447 true
448 }
449
450 // Either the taken flag is already set, or we are in an unexpected state
451 // on acquisition. Either way, refuse to move forward.
452 _ => {
453 BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Relaxed);
454 true
455 }
456 };
457
458 if bail {
459 // If we just disabled interrupts, re-enable interrupts, then return
460 if primask.is_active() {
461 unsafe { interrupt::enable(); }
462 }
463 return;
464 }
465
466 // no need for CAS because interrupts are disabled
467 TAKEN.store(true, Ordering::Relaxed);
468 INTERRUPTS_ACTIVE.store(primask.is_active(), Ordering::Relaxed);
469
470 // safety: accessing the `static mut` is OK because we have disabled interrupts.
471 unsafe { ENCODER.start_frame(do_write) }
472 }
473
474 unsafe fn flush() {
475 // We can't really do anything to flush, as the consumer is
476 // in "userspace". Oh well.
477 }
478
479 unsafe fn release() {
480 // Don't return early, as we may need to re-enable interrupts.
481 // `do_write` and `BBQ_GRANT_W.take()` will already early-return on
482 // a latching fault condition
483
484 // safety: accessing the `static mut` is OK because we have disabled interrupts.
485 ENCODER.end_frame(do_write);
486
487 // If a grant is active, take it and commit it
488 match BBQ_GRANT_W.take() {
489 Ok(Some((grant, offset))) => grant.commit(offset),
490
491 // If we have no grant, or an internal error, keep going. We don't
492 // want to early return, as that would prevent us from re-enabling
493 // interrupts
494 _ => {}
495 }
496
497 TAKEN.store(false, Ordering::Relaxed);
498 if INTERRUPTS_ACTIVE.load(Ordering::Relaxed) {
499 // re-enable interrupts
500 interrupt::enable()
501 }
502 }
503
504 unsafe fn write(bytes: &[u8]) {
505 // Return early to avoid the encoder having to encode bytes we are going to throw away
506 if check_latch(Ordering::Relaxed).is_err() {
507 return;
508 }
509
510 // safety: accessing the `static mut` is OK because we have disabled interrupts.
511 ENCODER.write(bytes, do_write);
512 }
513}
514
515// ----------------------------------------------------------------------------
516// do_write() - This is the main engine of loading bytes into the defmt queue,
517// as requested by the defmt::Logger interface.
518// ----------------------------------------------------------------------------
519
520// Drain as many bytes to the queue as possible. If the queue is filled,
521// then any remaining bytes will be discarded.
522fn do_write(mut remaining: &[u8]) {
523 while !remaining.is_empty() {
524 // The take function will attempt to provide us with a grant. This grant could
525 // come from an existing stored grant (if we are in the INIT_GRANT_IS_STORED
526 // state), or from a new grant (if we are in the INIT_NO_STORED_GRANT state).
527 //
528 // This call to `take()` may fail if we have no space available remaining in the
529 // queue, or if we have encountered some kind of latching fault.
530 match unsafe { BBQ_GRANT_W.take() } {
531 // We currently have a grant that is stored. Write as many bytes as possible
532 // into this grant
533 Ok(Some((mut grant, mut offset))) => {
534 let glen = grant.len();
535
536 let min = remaining.len().min(grant.len() - offset);
537 grant[offset..][..min].copy_from_slice(&remaining[..min]);
538 offset += min;
539
540 remaining = &remaining[min..];
541
542 if offset >= glen {
543 // We have filled the current grant with the requested bytes. Commit the
544 // grant, in order to allow us to potentially get the next grant.
545 //
546 // This leaves the state as `INIT_NO_STORED_GRANT`.
547 grant.commit(offset);
548 } else {
549 // We have loaded all bytes into the grant, but we haven't hit the end of
550 // the grant. Store the grant back into the global storage, so we can continue
551 // to re-use it until the `release()` function is called.
552 //
553 // This leaves the state as `INIT_GRANT_IS_STORED`, unless the `put()` fails
554 // (which would set some kind of latching fault).
555 unsafe {
556 // If the put failed, return early
557 if BBQ_GRANT_W.put(grant, offset).is_err() {
558 return;
559 }
560 }
561 }
562 },
563
564 // No grant available, just return. Bytes are dropped
565 Ok(None) => return,
566
567 // A latching fault is active. just return.
568 Err(_) => return,
569 }
570 }
571}