defmt_brtt/
bbq.rs

1use bbqueue::{BBBuffer, GrantW, Producer};
2use core::{
3    cell::UnsafeCell,
4    mem::MaybeUninit,
5    sync::atomic::{AtomicU8, AtomicUsize, Ordering},
6};
7
8#[cfg(feature = "async-await")]
9use crate::csec_waker::CriticalSectionWakerRegistration as Waker;
10
11/// BBQueue buffer size. Default: 1024; can be customized by setting the
12/// `DEFMT_BRTT_BUFFER_SIZE` environment variable at compile time
13pub use crate::consts::BUF_SIZE;
14
15/// The `defmt-brtt` BBQueue error type
16#[derive(Debug, defmt::Format, PartialEq, Eq)]
17pub enum Error {
18    /// An internal latching fault has occured. No more logs will be returned.
19    /// This indicates a coding error in `defmt-bbq`. Please open an issue.
20    InternalLatchingFault,
21
22    /// The user attempted to log before initializing the `defmt-bbq` structure.
23    /// This is a latching fault, and not recoverable, but is not an indicator of
24    /// a failure in the library. If you see this error, please ensure that you
25    /// call `defmt-bbq::init()` before making any defmt logging statements.
26    UseBeforeInitLatchingFault,
27
28    /// This indicates some potentially recoverable bbqerror (including no
29    /// data currently available).
30    Bbq(BBQError),
31}
32
33impl From<BBQError> for Error {
34    fn from(other: BBQError) -> Self {
35        Error::Bbq(other)
36    }
37}
38
39/// This is the consumer type given to the user to drain the logging queue.
40///
41/// It is a re-export of the [bbqueue::Consumer](https://docs.rs/bbqueue/latest/bbqueue/struct.Consumer.html) type.
42///
43pub use bbqueue::Consumer;
44
45/// An error returned by the underlying bbqueue storage
46///
47/// It is a re-export of the [bbqueue::Error](https://docs.rs/bbqueue/latest/bbqueue/enum.Error.html) type.
48///
49pub use bbqueue::Error as BBQError;
50
51/// This is the Reader Grant type given to the user as a view of the logging queue.
52///
53/// It is a re-export of the [bbqueue::GrantR](https://docs.rs/bbqueue/latest/bbqueue/struct.GrantR.html) type.
54///
55pub use bbqueue::GrantR;
56
57/// This is the Split Reader Grant type given to the user as a view of the logging queue.
58///
59/// It is a re-export of the [bbqueue::SplitGrantR](https://docs.rs/bbqueue/latest/bbqueue/struct.SplitGrantR.html) type.
60///
61pub use bbqueue::SplitGrantR;
62
63// ----------------------------------------------------------------------------
64// init() function - this is the (only) user facing interface
65// ----------------------------------------------------------------------------
66
67/// Initialize the BBQueue based global defmt sink. MUST be called before
68/// the first `defmt` log, or it will latch a fault.
69///
70/// On the first call to this function, the Consumer end of the logging
71/// queue will be returned. On any subsequent call, an error will be
72/// returned.
73///
74/// For more information on the Consumer interface, see the [Consumer docs] in the `bbqueue`
75/// crate documentation.
76///
77/// [Consumer docs]: https://docs.rs/bbqueue/latest/bbqueue/struct.Consumer.html
78pub fn init() -> Result<DefmtConsumer, Error> {
79    let (prod, cons) = BBQ.try_split()?;
80
81    // NOTE: We are okay to treat the following as safe, as the BBQueue
82    // split operation is guaranteed to only return Ok once in a
83    // thread-safe manner.
84    unsafe {
85        BBQ_PRODUCER.uc_mu_fp.get().write(MaybeUninit::new(prod));
86    }
87
88    // MUST be done LAST
89    BBQ_STATE.store(logstate::INIT_NO_STORED_GRANT, Ordering::Release);
90
91    Ok(DefmtConsumer { cons })
92}
93
94// ----------------------------------------------------------------------------
95// Defmt Consumer
96// ----------------------------------------------------------------------------
97
98/// The consumer interface of `defmt-log`.
99///
100/// This type is a wrapper around the
101/// [bbqueue::Consumer](https://docs.rs/bbqueue/latest/bbqueue/struct.Consumer.html) type,
102/// and returns defmt-brtt's [Error](crate::bbq::Error) type instead of bbqueue's
103/// [bbqueue::Error](https://docs.rs/bbqueue/latest/bbqueue/enum.Error.html) type.
104pub struct DefmtConsumer {
105    cons: Consumer<'static, BUF_SIZE>,
106}
107
108impl DefmtConsumer {
109    /// Obtains a contiguous slice of committed bytes. This slice may not
110    /// contain ALL available bytes, if the writer has wrapped around. The
111    /// remaining bytes will be available after all readable bytes are
112    /// released.
113    pub fn read<'a>(&'a mut self) -> Result<GrantR<'a, BUF_SIZE>, Error> {
114        self.read_static()
115    }
116
117    fn read_static(&mut self) -> Result<GrantR<'static, BUF_SIZE>, Error> {
118        Ok(self.cons.read()?)
119    }
120
121    /// Obtains two disjoint slices, which are each contiguous of committed bytes.
122    /// Combined these contain all previously commited data at the time of read
123    pub fn split_read(&mut self) -> Result<SplitGrantR<'static, BUF_SIZE>, Error> {
124        Ok(self.cons.split_read()?)
125    }
126}
127
128#[cfg(feature = "async-await")]
129impl DefmtConsumer {
130    pub(crate) fn waker() -> &'static Waker {
131        static WAKER: Waker = Waker::new();
132        &WAKER
133    }
134
135    pub async fn wait_for_log<'a>(&'a mut self) -> GrantR<'a, BUF_SIZE> {
136        let mut polled_once = false;
137
138        loop {
139            let awaited_grant = core::future::poll_fn(|ctx| {
140                Self::waker().register(ctx.waker());
141
142                let grant = self.read_static();
143
144                if let Ok(grant) = grant {
145                    core::task::Poll::Ready(Some(grant))
146                } else if !polled_once {
147                    polled_once = true;
148                    core::task::Poll::Pending
149                } else {
150                    core::task::Poll::Ready(None)
151                }
152            })
153            .await;
154
155            if let Some(grant) = awaited_grant.or(self.read_static().ok()) {
156                break grant;
157            }
158        }
159    }
160}
161
162// ----------------------------------------------------------------------------
163// logstate, and state helper functions
164// ----------------------------------------------------------------------------
165
166mod logstate {
167    // BBQ has NOT been initialized
168    // BBQ_PRODUCER has NOT been initialized
169    // BBQ_GRANT has NOT been initialized
170    pub const UNINIT: u8 = 0;
171
172    // BBQ HAS been initialized
173    // BBQ_PRODUCER HAS been initialized
174    // BBQ_GRANT has NOT been initialized
175    pub const INIT_NO_STORED_GRANT: u8 = 1;
176
177    // BBQ HAS been initialized
178    // BBQ_PRODUCER HAS been initialized
179    // BBQ_GRANT HAS been initialized
180    pub const INIT_GRANT_IS_STORED: u8 = 2;
181
182    // All state codes above 100 are a latching fault
183
184    // A latching fault has occurred.
185    pub const LATCH_INTERNAL_ERROR: u8 = 100;
186
187    // The user attempted to log before init
188    pub const LATCH_USE_BEFORE_INIT: u8 = 101;
189}
190
191#[inline]
192pub(crate) fn check_latch(ordering: Ordering) -> Result<(), Error> {
193    match BBQ_STATE.load(ordering) {
194        i if i < logstate::LATCH_INTERNAL_ERROR => Ok(()),
195        logstate::LATCH_USE_BEFORE_INIT => Err(Error::UseBeforeInitLatchingFault),
196        _ => Err(Error::InternalLatchingFault),
197    }
198}
199
200#[inline]
201fn latch_assert_eq<T: PartialEq>(left: T, right: T) -> Result<(), Error> {
202    if left == right {
203        Ok(())
204    } else {
205        BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Release);
206        Err(Error::InternalLatchingFault)
207    }
208}
209
210// ----------------------------------------------------------------------------
211// UnsafeProducer
212// ----------------------------------------------------------------------------
213
214/// A storage structure for holding the maybe initialized producer with inner mutability
215struct UnsafeProducer {
216    uc_mu_fp: UnsafeCell<MaybeUninit<Producer<'static, BUF_SIZE>>>,
217}
218
219impl UnsafeProducer {
220    const fn new() -> Self {
221        Self {
222            uc_mu_fp: UnsafeCell::new(MaybeUninit::uninit()),
223        }
224    }
225
226    // TODO: Could be made safe if we ensure the reference is only taken
227    // once. For now, leave unsafe
228    unsafe fn get_mut(&self) -> Result<&mut Producer<'static, BUF_SIZE>, Error> {
229        latch_assert_eq(
230            logstate::INIT_NO_STORED_GRANT,
231            BBQ_STATE.load(Ordering::Relaxed),
232        )?;
233
234        // NOTE: `UnsafeCell` and `MaybeUninit` are both `#[repr(Transparent)],
235        // meaning this direct cast is acceptable
236        let const_ptr: *const Producer<'static, BUF_SIZE> = self.uc_mu_fp.get().cast();
237        let mut_ptr: *mut Producer<'static, BUF_SIZE> = const_ptr as *mut _;
238        let ref_mut: &mut Producer<'static, BUF_SIZE> = &mut *mut_ptr;
239
240        Ok(ref_mut)
241    }
242}
243
244unsafe impl Sync for UnsafeProducer {}
245
246// ----------------------------------------------------------------------------
247// UnsafeGrantW
248// ----------------------------------------------------------------------------
249
250struct UnsafeGrantW {
251    uc_mu_fgw: UnsafeCell<MaybeUninit<GrantW<'static, BUF_SIZE>>>,
252
253    /// Note: This stores the offset into the *current grant*, IFF a grant
254    /// is stored in BBQ_GRANT_W. If there is no grant active, or if the
255    /// grant is currently "taken" by the `do_write()` function, the value
256    /// is meaningless.
257    offset: AtomicUsize,
258}
259
260impl UnsafeGrantW {
261    const fn new() -> Self {
262        Self {
263            uc_mu_fgw: UnsafeCell::new(MaybeUninit::uninit()),
264            offset: AtomicUsize::new(0),
265        }
266    }
267
268    // TODO: Could be made safe if we ensure the reference is only taken
269    // once. For now, leave unsafe.
270    //
271    /// This function STORES
272    /// MUST be done in a critical section.
273    unsafe fn put(&self, grant: GrantW<'static, BUF_SIZE>, offset: usize) -> Result<(), Error> {
274        // Note: This also catches the "already latched" state check
275        latch_assert_eq(
276            logstate::INIT_NO_STORED_GRANT,
277            BBQ_STATE.load(Ordering::Relaxed),
278        )?;
279
280        self.uc_mu_fgw.get().write(MaybeUninit::new(grant));
281        self.offset.store(offset, Ordering::Relaxed);
282        BBQ_STATE.store(logstate::INIT_GRANT_IS_STORED, Ordering::Relaxed);
283        Ok(())
284    }
285
286    // The take function will attempt to provide us with a grant. This grant could
287    // come from an existing stored grant (if we are in the INIT_GRANT_IS_STORED
288    // state), or from a new grant (if we are in the INIT_NO_STORED_GRANT state).
289    //
290    // This call to `take()` may fail if we have no space available remaining in the
291    // queue, or if we have encountered some kind of latching fault.
292    unsafe fn take(&self) -> Result<Option<(GrantW<'static, BUF_SIZE>, usize)>, Error> {
293        check_latch(Ordering::Relaxed)?;
294
295        Ok(match BBQ_STATE.load(Ordering::Relaxed) {
296            // We have a stored grant. Take it out of the global, and return it to the user
297            logstate::INIT_GRANT_IS_STORED => {
298                // NOTE: UnsafeCell and MaybeUninit are #[repr(Transparent)], so this
299                // cast is acceptable
300                let grant = self
301                    .uc_mu_fgw
302                    .get()
303                    .cast::<GrantW<'static, BUF_SIZE>>()
304                    .read();
305
306                BBQ_STATE.store(logstate::INIT_NO_STORED_GRANT, Ordering::Relaxed);
307
308                Some((grant, self.offset.load(Ordering::Relaxed)))
309            }
310
311            // We *don't* have a stored grant. Attempt to retrieve a new one, and return
312            // that to the user, without storing it in the global.
313            logstate::INIT_NO_STORED_GRANT => {
314                let producer = BBQ_PRODUCER.get_mut()?;
315
316                // We have a new grant, reset the current grant offset back to zero
317                self.offset.store(0, Ordering::Relaxed);
318                producer.grant_max_remaining(BUF_SIZE).ok().map(|g| (g, 0))
319            }
320
321            // We're in a bad place. Store a latching fault, and move on.
322            n => {
323                // If we aren't already in a latching fault of some kind, set one
324                if n < logstate::LATCH_INTERNAL_ERROR {
325                    BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Relaxed);
326                }
327
328                return Err(Error::InternalLatchingFault);
329            }
330        })
331    }
332}
333
334unsafe impl Sync for UnsafeGrantW {}
335
336// ----------------------------------------------------------------------------
337// Globals
338// ----------------------------------------------------------------------------
339
340// The underlying byte storage containing the logs. Always valid
341static BBQ: BBBuffer<BUF_SIZE> = BBBuffer::new();
342
343// A tracking variable for ensuring state. Always valid.
344static BBQ_STATE: AtomicU8 = AtomicU8::new(logstate::UNINIT);
345
346// The producer half of the logging queue. This field is ONLY
347// valid if `init()` has been called.
348static BBQ_PRODUCER: UnsafeProducer = UnsafeProducer::new();
349
350// An active write grant to a portion of the `BBQ`, obtained through
351// the `BBQ_PRODUCER`. This field is ONLY valid if we are in the
352// `INIT_GRANT` state.
353static BBQ_GRANT_W: UnsafeGrantW = UnsafeGrantW::new();
354
355// ----------------------------------------------------------------------------
356// defmt::Logger interface
357//
358// This is the implementation of the defmt::Logger interace
359// ----------------------------------------------------------------------------
360
361pub(crate) fn should_bail() -> bool {
362    let state = BBQ_STATE.load(Ordering::Relaxed);
363
364    let bail = match state {
365        // Fast case: all good.
366        logstate::INIT_NO_STORED_GRANT => false,
367
368        // We tried to use before initialization. Regardless of the taken state,
369        // this is an error. We *might* be able to recover from this in the future,
370        // but it is more complicated. For now, just latch the error and signal
371        // the user
372        logstate::UNINIT => {
373            BBQ_STATE.store(logstate::LATCH_USE_BEFORE_INIT, Ordering::Relaxed);
374            true
375        }
376
377        // Either the taken flag is already set, or we are in an unexpected state
378        // on acquisition. Either way, refuse to move forward.
379        _ => {
380            BBQ_STATE.store(logstate::LATCH_INTERNAL_ERROR, Ordering::Relaxed);
381            true
382        }
383    };
384
385    bail
386}
387
388pub(crate) unsafe fn commit_w_grant() {
389    // If a grant is active, take it and commit it
390    match BBQ_GRANT_W.take() {
391        Ok(Some((grant, offset))) => grant.commit(offset),
392
393        // If we have no grant, or an internal error, keep going. We don't
394        // want to early return, as that would prevent us from re-enabling
395        // interrupts
396        _ => {}
397    }
398}
399
400// ----------------------------------------------------------------------------
401// do_write() - This is the main engine of loading bytes into the defmt queue,
402// as requested by the defmt::Logger interface.
403// ----------------------------------------------------------------------------
404
405// Drain as many bytes to the queue as possible. If the queue is filled,
406// then any remaining bytes will be discarded.
407pub(crate) fn do_write(mut remaining: &[u8]) {
408    while !remaining.is_empty() {
409        // The take function will attempt to provide us with a grant. This grant could
410        // come from an existing stored grant (if we are in the INIT_GRANT_IS_STORED
411        // state), or from a new grant (if we are in the INIT_NO_STORED_GRANT state).
412        //
413        // This call to `take()` may fail if we have no space available remaining in the
414        // queue, or if we have encountered some kind of latching fault.
415        match unsafe { BBQ_GRANT_W.take() } {
416            // We currently have a grant that is stored. Write as many bytes as possible
417            // into this grant
418            Ok(Some((mut grant, mut offset))) => {
419                let glen = grant.len();
420
421                let min = remaining.len().min(grant.len() - offset);
422                grant[offset..][..min].copy_from_slice(&remaining[..min]);
423                offset += min;
424
425                remaining = &remaining[min..];
426
427                if offset >= glen {
428                    // We have filled the current grant with the requested bytes. Commit the
429                    // grant, in order to allow us to potentially get the next grant.
430                    //
431                    // This leaves the state as `INIT_NO_STORED_GRANT`.
432                    grant.commit(offset);
433                } else {
434                    // We have loaded all bytes into the grant, but we haven't hit the end of
435                    // the grant. Store the grant back into the global storage, so we can continue
436                    // to re-use it until the `release()` function is called.
437                    //
438                    // This leaves the state as `INIT_GRANT_IS_STORED`, unless the `put()` fails
439                    // (which would set some kind of latching fault).
440                    unsafe {
441                        // If the put failed, return early
442                        if BBQ_GRANT_W.put(grant, offset).is_err() {
443                            return;
444                        }
445                    }
446                }
447            }
448
449            // No grant available, just return. Bytes are dropped
450            Ok(None) => return,
451
452            // A latching fault is active. just return.
453            Err(_) => return,
454        }
455    }
456}