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}