Skip to main content

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}