From f89be68fccff02e144ede33fd83da6d1eb25489c Mon Sep 17 00:00:00 2001 From: James Munns Date: Sun, 4 Jan 2026 23:57:27 +0100 Subject: [PATCH 1/4] Add a minimal coat of docs paint --- bbq2/src/lib.rs | 120 +++++++++++++++++++++++---- bbq2/src/nicknames.rs | 50 +++++------ bbq2/src/prod_cons/framed.rs | 22 ++++- bbq2/src/prod_cons/stream.rs | 26 ++++-- bbq2/src/queue.rs | 42 +++++++++- bbq2/src/traits/bbqhdl.rs | 14 +++- bbq2/src/traits/coordination/cas.rs | 7 +- bbq2/src/traits/coordination/cs.rs | 7 +- bbq2/src/traits/coordination/mod.rs | 33 ++++++-- bbq2/src/traits/notifier/blocking.rs | 14 ---- bbq2/src/traits/notifier/maitake.rs | 5 +- bbq2/src/traits/notifier/mod.rs | 6 +- bbq2/src/traits/notifier/polling.rs | 21 +++++ bbq2/src/traits/storage.rs | 24 ++++-- 14 files changed, 311 insertions(+), 80 deletions(-) delete mode 100644 bbq2/src/traits/notifier/blocking.rs create mode 100644 bbq2/src/traits/notifier/polling.rs diff --git a/bbq2/src/lib.rs b/bbq2/src/lib.rs index 2e21af3..85f5bd1 100644 --- a/bbq2/src/lib.rs +++ b/bbq2/src/lib.rs @@ -1,11 +1,103 @@ -//! bbq2 +//! # BBQueue //! -//! A new and improved bipbuffer queue. +//! BBQueue, short for "BipBuffer Queue", is a Single Producer Single Consumer, +//! lockless, no_std, thread safe, queue, based on [BipBuffers]. For more info on +//! the design of the lock-free algorithm used by bbqueue, see [this blog post]. //! -//! NOTE: This will soon be moved into the `bbqueue` crate, and `bbq2` will be -//! deprecated! +//! [BipBuffers]: https://www.codeproject.com/Articles/3479/%2FArticles%2F3479%2FThe-Bip-Buffer-The-Circular-Buffer-with-a-Twist +//! [this blog post]: https://ferrous-systems.com/blog/lock-free-ring-buffer/ +//! +//! BBQueue is designed (primarily) to be a First-In, First-Out queue for use with DMA on embedded +//! systems. +//! +//! While Circular/Ring Buffers allow you to send data between two threads (or from an interrupt to +//! main code), you must push the data one piece at a time. With BBQueue, you instead are granted a +//! block of contiguous memory, which can be filled (or emptied) by a DMA engine. +//! +//! ## Local usage +//! +//! ```rust +//! // The "Churrasco" flavor has inline storage, hardware atomic +//! // support, no async support, and is not reference counted. +//! use bbq2::nicknames::Churrasco; +//! +//! // Create a buffer with six elements +//! let bb: Churrasco<6> = Churrasco::new(); +//! let prod = bb.stream_producer(); +//! let cons = bb.stream_consumer(); +//! +//! // Request space for one byte +//! let mut wgr = prod.grant_exact(1).unwrap(); +//! +//! // Set the data +//! wgr[0] = 123; +//! +//! assert_eq!(wgr.len(), 1); +//! +//! // Make the data ready for consuming +//! wgr.commit(1); +//! +//! // Read all available bytes +//! let rgr = cons.read().unwrap(); +//! +//! assert_eq!(rgr[0], 123); +//! +//! // Release the space for later writes +//! rgr.release(1); +//! ``` +//! +//! ## Static usage +//! +//! ```rust +//! use bbq2::nicknames::Churrasco; +//! use std::{thread::{sleep, spawn}, time::Duration}; +//! +//! // Create a buffer with six elements +//! static BB: Churrasco<6> = Churrasco::new(); +//! +//! fn receiver() { +//! let cons = BB.stream_consumer(); +//! loop { +//! if let Ok(rgr) = cons.read() { +//! assert_eq!(rgr.len(), 1); +//! assert_eq!(rgr[0], 123); +//! rgr.release(1); +//! break; +//! } +//! // don't do this in real code, use Notify! +//! sleep(Duration::from_millis(10)); +//! } +//! } +//! +//! fn main() { +//! let prod = BB.stream_producer(); +//! +//! // spawn the consumer +//! let hdl = spawn(receiver); +//! +//! // Request space for one byte +//! let mut wgr = prod.grant_exact(1).unwrap(); +//! +//! // Set the data +//! wgr[0] = 123; +//! +//! assert_eq!(wgr.len(), 1); +//! +//! // Make the data ready for consuming +//! wgr.commit(1); +//! +//! // make sure the receiver terminated +//! hdl.join().unwrap(); +//! } +//! ``` +//! +//! ## Features +//! +//! TODO #![cfg_attr(not(any(test, feature = "std")), no_std)] +#![deny(missing_docs)] +#![deny(warnings)] #[cfg(feature = "alloc")] extern crate alloc; @@ -48,19 +140,19 @@ mod test { #[cfg(all(target_has_atomic = "ptr", feature = "alloc"))] #[test] fn ux() { - use crate::traits::{notifier::blocking::Blocking, storage::BoxedSlice}; + use crate::traits::{notifier::polling::Polling, storage::BoxedSlice}; - static BBQ: BBQueue, AtomicCoord, Blocking> = BBQueue::new(); + static BBQ: BBQueue, AtomicCoord, Polling> = BBQueue::new(); let _ = BBQ.stream_producer(); let _ = BBQ.stream_consumer(); let buf2 = Inline::<64>::new(); - let bbq2: BBQueue<_, AtomicCoord, Blocking> = BBQueue::new_with_storage(&buf2); + let bbq2: BBQueue<_, AtomicCoord, Polling> = BBQueue::new_with_storage(&buf2); let _ = bbq2.stream_producer(); let _ = bbq2.stream_consumer(); let buf3 = BoxedSlice::new(64); - let bbq3: BBQueue<_, AtomicCoord, Blocking> = BBQueue::new_with_storage(buf3); + let bbq3: BBQueue<_, AtomicCoord, Polling> = BBQueue::new_with_storage(buf3); let _ = bbq3.stream_producer(); let _ = bbq3.stream_consumer(); } @@ -68,10 +160,10 @@ mod test { #[cfg(target_has_atomic = "ptr")] #[test] fn smoke() { - use crate::traits::notifier::blocking::Blocking; + use crate::traits::notifier::polling::Polling; use core::ops::Deref; - static BBQ: BBQueue, AtomicCoord, Blocking> = BBQueue::new(); + static BBQ: BBQueue, AtomicCoord, Polling> = BBQueue::new(); let prod = BBQ.stream_producer(); let cons = BBQ.stream_consumer(); @@ -94,10 +186,10 @@ mod test { #[cfg(target_has_atomic = "ptr")] #[test] fn smoke_framed() { - use crate::traits::notifier::blocking::Blocking; + use crate::traits::notifier::polling::Polling; use core::ops::Deref; - static BBQ: BBQueue, AtomicCoord, Blocking> = BBQueue::new(); + static BBQ: BBQueue, AtomicCoord, Polling> = BBQueue::new(); let prod = BBQ.framed_producer(); let cons = BBQ.framed_consumer(); @@ -116,9 +208,9 @@ mod test { #[cfg(target_has_atomic = "ptr")] #[test] fn framed_misuse() { - use crate::traits::notifier::blocking::Blocking; + use crate::traits::notifier::polling::Polling; - static BBQ: BBQueue, AtomicCoord, Blocking> = BBQueue::new(); + static BBQ: BBQueue, AtomicCoord, Polling> = BBQueue::new(); let prod = BBQ.stream_producer(); let cons = BBQ.framed_consumer(); diff --git a/bbq2/src/nicknames.rs b/bbq2/src/nicknames.rs index 99b1c9a..318173a 100644 --- a/bbq2/src/nicknames.rs +++ b/bbq2/src/nicknames.rs @@ -2,20 +2,20 @@ //! //! | Storage | Coordination | Notifier | Arc? | Nickname | Source | //! | :--- | :--- | :--- | :--- | :--- | :--- | -//! | Inline | Critical Section | Blocking | No | Jerk | Jamaica | -//! | Inline | Critical Section | Blocking | Yes | Asado | Argentina | +//! | Inline | Critical Section | Polling | No | Jerk | Jamaica | +//! | Inline | Critical Section | Polling | Yes | Asado | Argentina | //! | Inline | Critical Section | Async | No | Memphis | USA | //! | Inline | Critical Section | Async | Yes | Carolina | USA | -//! | Inline | Atomic | Blocking | No | Churrasco | Brazil | -//! | Inline | Atomic | Blocking | Yes | Barbacoa | Mexico | +//! | Inline | Atomic | Polling | No | Churrasco | Brazil | +//! | Inline | Atomic | Polling | Yes | Barbacoa | Mexico | //! | Inline | Atomic | Async | No | Texas | USA | //! | Inline | Atomic | Async | Yes | KansasCity | USA | -//! | Heap | Critical Section | Blocking | No | Braai | South Africa | -//! | Heap | Critical Section | Blocking | Yes | Kebab | Türkiye | +//! | Heap | Critical Section | Polling | No | Braai | South Africa | +//! | Heap | Critical Section | Polling | Yes | Kebab | Türkiye | //! | Heap | Critical Section | Async | No | SiuMei | Hong Kong | //! | Heap | Critical Section | Async | Yes | Satay | SE Asia | -//! | Heap | Atomic | Blocking | No | YakiNiku | Japan | -//! | Heap | Atomic | Blocking | Yes | GogiGui | South Korea | +//! | Heap | Atomic | Polling | No | YakiNiku | Japan | +//! | Heap | Atomic | Polling | Yes | GogiGui | South Korea | //! | Heap | Atomic | Async | No | Tandoori | India | //! | Heap | Atomic | Async | Yes | Lechon | Philippines | @@ -31,68 +31,68 @@ use crate::traits::coordination::cs::CsCoord; use crate::traits::storage::BoxedSlice; use crate::{ queue::BBQueue, - traits::{notifier::blocking::Blocking, storage::Inline}, + traits::{notifier::polling::Polling, storage::Inline}, }; -/// Inline Storage, Critical Section, Blocking, Borrowed +/// Inline Storage, Critical Section, Polling, Borrowed #[cfg(feature = "critical-section")] -pub type Jerk = BBQueue, CsCoord, Blocking>; +pub type Jerk = BBQueue, CsCoord, Polling>; /// Inline Storage, Critical Section, Async, Borrowed #[cfg(feature = "critical-section")] pub type Memphis = BBQueue, CsCoord, A>; -/// Inline Storage, Atomics, Blocking, Borrowed +/// Inline Storage, Atomics, Polling, Borrowed #[cfg(target_has_atomic = "ptr")] -pub type Churrasco = BBQueue, AtomicCoord, Blocking>; +pub type Churrasco = BBQueue, AtomicCoord, Polling>; /// Inline Storage, Atomics, Async, Borrowed #[cfg(target_has_atomic = "ptr")] pub type Texas = BBQueue, AtomicCoord, A>; -/// Heap Buffer, Critical Section, Blocking, Borrowed +/// Heap Buffer, Critical Section, Polling, Borrowed #[cfg(all(feature = "alloc", feature = "critical-section"))] -pub type Braai = BBQueue; +pub type Braai = BBQueue; /// Heap Buffer, Critical Section, Async, Borrowed #[cfg(all(feature = "alloc", feature = "critical-section"))] pub type SiuMei = BBQueue; -/// Heap Buffer, Atomics, Blocking, Borrowed +/// Heap Buffer, Atomics, Polling, Borrowed #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] -pub type YakiNiku = BBQueue; +pub type YakiNiku = BBQueue; /// Heap Buffer, Atomics, Async, Borrowed #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] pub type Tandoori = BBQueue; -/// Inline Storage, Critical Section, Blocking, Arc +/// Inline Storage, Critical Section, Polling, Arc #[cfg(all(feature = "alloc", feature = "critical-section"))] -pub type Asado = ArcBBQueue, CsCoord, Blocking>; +pub type Asado = ArcBBQueue, CsCoord, Polling>; /// Inline Storage, Critical Section, Async, Arc #[cfg(all(feature = "alloc", feature = "critical-section"))] pub type Carolina = ArcBBQueue, CsCoord, A>; -/// Inline Storage, Atomics, Blocking, Arc +/// Inline Storage, Atomics, Polling, Arc #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] -pub type Barbacoa = ArcBBQueue, AtomicCoord, Blocking>; +pub type Barbacoa = ArcBBQueue, AtomicCoord, Polling>; /// Inline Storage, Atomics, Async, Arc #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] pub type KansasCity = ArcBBQueue, AtomicCoord, A>; -/// Heap Buffer, Critical Section, Blocking, Arc +/// Heap Buffer, Critical Section, Polling, Arc #[cfg(all(feature = "alloc", feature = "critical-section"))] -pub type Kebab = ArcBBQueue; +pub type Kebab = ArcBBQueue; /// Heap Buffer, Critical Section, Async, Arc #[cfg(all(feature = "alloc", feature = "critical-section"))] pub type Satay = ArcBBQueue; -/// Heap Buffer, Atomics, Blocking, Arc +/// Heap Buffer, Atomics, Polling, Arc #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] -pub type GogiGui = ArcBBQueue; +pub type GogiGui = ArcBBQueue; /// Heap Buffer, Atomics, Async, Arc #[cfg(all(feature = "alloc", target_has_atomic = "ptr"))] diff --git a/bbq2/src/prod_cons/framed.rs b/bbq2/src/prod_cons/framed.rs index c005832..5d583f5 100644 --- a/bbq2/src/prod_cons/framed.rs +++ b/bbq2/src/prod_cons/framed.rs @@ -132,7 +132,9 @@ where /// a smaller size may be committed. Dropping the grant without calling /// commit means that no data will be made visible to the consumer. pub fn grant(&self, sz: H) -> Result, WriteGrantError> { - let (ptr, cap) = self.bbq.sto.ptr_len(); + let (ptr, cap) = unsafe { + self.bbq.sto.ptr_len() + }; let needed = sz.into() + core::mem::size_of::(); let offset = self.bbq.cor.grant_exact(cap, needed)?; @@ -182,7 +184,9 @@ where /// /// The returned grant must be released to free the space in the buffer. pub fn read(&self) -> Result, ReadGrantError> { - let (ptr, _cap) = self.bbq.sto.ptr_len(); + let (ptr, _cap) = unsafe { + self.bbq.sto.ptr_len() + }; let (offset, grant_len) = self.bbq.cor.read()?; // Calculate the size so we can figure out where the body @@ -230,6 +234,12 @@ where Q::Notifier: AsyncNotifier, H: LenHeader, { + /// Wait for a single frame + /// + /// The FramedConsumer has no control over the size of the read grant, + /// we see whatever size was written by the FramedProducer. + /// + /// The returned grant must be released to free the space in the buffer. pub async fn wait_read(&self) -> FramedGrantR { self.bbq.not.wait_for_not_empty(|| self.read().ok()).await } @@ -247,7 +257,9 @@ where /// If `used` is greater than the `sz` used to create this grant, the /// amount will be clamped to `sz`. pub fn commit(self, used: H) { - let (_ptr, cap) = self.bbq.sto.ptr_len(); + let (_ptr, cap) = unsafe { + self.bbq.sto.ptr_len() + }; let hdrlen: usize = const { core::mem::size_of::() }; let grant_len = hdrlen + self.hdr.into(); let clamp_hdr = self.hdr.min(used); @@ -313,7 +325,9 @@ where { fn drop(&mut self) { // Default drop performs an "abort" - let (_ptr, cap) = self.bbq.sto.ptr_len(); + let (_ptr, cap) = unsafe { + self.bbq.sto.ptr_len() + }; let hdrlen: usize = const { core::mem::size_of::() }; let grant_len = hdrlen + self.hdr.into(); self.bbq.cor.commit_inner(cap, grant_len, 0); diff --git a/bbq2/src/prod_cons/stream.rs b/bbq2/src/prod_cons/stream.rs index bcf18a6..5c00fb0 100644 --- a/bbq2/src/prod_cons/stream.rs +++ b/bbq2/src/prod_cons/stream.rs @@ -87,7 +87,9 @@ where /// useful to call `grant_max_remaining` in a loop until `Err(WriteGrantError::InsufficientSize)` /// is returned. pub fn grant_max_remaining(&self, max: usize) -> Result, WriteGrantError> { - let (ptr, cap) = self.bbq.sto.ptr_len(); + let (ptr, cap) = unsafe { + self.bbq.sto.ptr_len() + }; let (offset, len) = self.bbq.cor.grant_max_remaining(cap, max)?; let ptr = unsafe { let p = ptr.as_ptr().byte_add(offset); @@ -107,7 +109,9 @@ where /// the ring buffer, this method WILL cause a wrap-around to occur to attempt to /// find the requested write capacity. pub fn grant_exact(&self, sz: usize) -> Result, WriteGrantError> { - let (ptr, cap) = self.bbq.sto.ptr_len(); + let (ptr, cap) = unsafe { + self.bbq.sto.ptr_len() + }; let offset = self.bbq.cor.grant_exact(cap, sz)?; let ptr = unsafe { let p = ptr.as_ptr().byte_add(offset); @@ -161,7 +165,9 @@ where /// in a loop until `Err(ReadGrantError::Empty)` is returned if you want to /// drain the queue entirely. pub fn read(&self) -> Result, ReadGrantError> { - let (ptr, _cap) = self.bbq.sto.ptr_len(); + let (ptr, _cap) = unsafe { + self.bbq.sto.ptr_len() + }; let (offset, len) = self.bbq.cor.read()?; let ptr = unsafe { let p = ptr.as_ptr().byte_add(offset); @@ -195,8 +201,13 @@ impl StreamGrantW where Q: BbqHandle, { + /// Make `used` bytes available to be read. + /// + /// `used` is capped to the length of the grant. pub fn commit(self, used: usize) { - let (_, cap) = self.bbq.sto.ptr_len(); + let (_, cap) = unsafe { + self.bbq.sto.ptr_len() + }; let used = used.min(self.len); self.bbq.cor.commit_inner(cap, self.len, used); if used != 0 { @@ -237,7 +248,9 @@ where len, to_commit, } = self; - let (_, cap) = bbq.sto.ptr_len(); + let (_, cap) = unsafe { + bbq.sto.ptr_len() + }; let len = *len; let used = (*to_commit).min(len); bbq.cor.commit_inner(cap, len, used); @@ -255,6 +268,9 @@ impl StreamGrantR where Q: BbqHandle, { + /// Make `used` bytes available for writing. + /// + /// `used` is capped to the length of the grant pub fn release(self, used: usize) { let used = used.min(self.len); self.bbq.cor.release_inner(used); diff --git a/bbq2/src/queue.rs b/bbq2/src/queue.rs index f1b5b29..6dc0b59 100644 --- a/bbq2/src/queue.rs +++ b/bbq2/src/queue.rs @@ -23,6 +23,7 @@ pub struct BBQueue { } impl BBQueue { + /// Create a new [`BBQueue`] with the given [`Storage`] impl pub fn new_with_storage(sto: S) -> Self { Self { sto, @@ -38,6 +39,7 @@ pub struct ArcBBQueue(pub(crate) alloc::sync::Arc>); #[cfg(feature = "alloc")] impl ArcBBQueue { + /// Create a new [`BBQueue`] with the given [`Storage`] impl pub fn new_with_storage(sto: S) -> Self { Self(alloc::sync::Arc::new(BBQueue::new_with_storage(sto))) } @@ -45,6 +47,7 @@ impl ArcBBQueue { #[allow(clippy::new_without_default)] impl BBQueue { + /// Create a new `BBQueue` in a const context pub const fn new() -> Self { Self { sto: S::INIT, @@ -55,6 +58,10 @@ impl BBQueue { } impl BBQueue { + /// Create a new [`FramedProducer`] for this [`BBQueue`] + /// + /// Although mixing stream and framed consumer/producers will not result in UB, + /// it will also not work correctly. pub const fn framed_producer(&self) -> FramedProducer<&'_ Self> { FramedProducer { bbq: self, @@ -62,6 +69,10 @@ impl BBQueue { } } + /// Create a new [`FramedConsumer`] for this [`BBQueue`] + /// + /// Although mixing stream and framed consumer/producers will not result in UB, + /// it will also not work correctly. pub const fn framed_consumer(&self) -> FramedConsumer<&'_ Self> { FramedConsumer { bbq: self, @@ -69,17 +80,30 @@ impl BBQueue { } } + /// Create a new [`StreamProducer`] for this [`BBQueue`] + /// + /// Although mixing stream and framed consumer/producers will not result in UB, + /// it will also not work correctly. pub const fn stream_producer(&self) -> StreamProducer<&'_ Self> { StreamProducer { bbq: self } } + /// Create a new [`StreamConsumer`] for this [`BBQueue`] + /// + /// Although mixing stream and framed consumer/producers will not result in UB, + /// it will also not work correctly. pub const fn stream_consumer(&self) -> StreamConsumer<&'_ Self> { StreamConsumer { bbq: self } } } #[cfg(feature = "alloc")] -impl crate::queue::ArcBBQueue { +impl + crate::queue::ArcBBQueue { + /// Create a new [`FramedProducer`] for this [`BBQueue`] + /// + /// Although mixing stream and framed consumer/producers will not result in UB, + /// it will also not work correctly. pub fn framed_producer(&self) -> FramedProducer>> { FramedProducer { bbq: self.0.bbq_ref(), @@ -87,6 +111,10 @@ impl crate::queue::ArcBBQueue { } } + /// Create a new [`FramedConsumer`] for this [`BBQueue`] + /// + /// Although mixing stream and framed consumer/producers will not result in UB, + /// it will also not work correctly. pub fn framed_consumer(&self) -> FramedConsumer>> { FramedConsumer { bbq: self.0.bbq_ref(), @@ -94,12 +122,20 @@ impl crate::queue::ArcBBQueue { } } + /// Create a new [`StreamProducer`] for this [`BBQueue`] + /// + /// Although mixing stream and framed consumer/producers will not result in UB, + /// it will also not work correctly. pub fn stream_producer(&self) -> StreamProducer>> { StreamProducer { bbq: self.0.bbq_ref(), } } + /// Create a new [`StreamConsumer`] for this [`BBQueue`] + /// + /// Although mixing stream and framed consumer/producers will not result in UB, + /// it will also not work correctly. pub fn stream_consumer(&self) -> StreamConsumer>> { StreamConsumer { bbq: self.0.bbq_ref(), @@ -110,12 +146,12 @@ impl crate::queue::ArcBBQueue { #[cfg(test)] mod test { use crate::traits::{ - coordination::cas::AtomicCoord, notifier::blocking::Blocking, storage::Inline, + coordination::cas::AtomicCoord, notifier::polling::Polling, storage::Inline, }; use super::*; - type Queue = BBQueue, AtomicCoord, Blocking>; + type Queue = BBQueue, AtomicCoord, Polling>; static QUEUE: Queue = BBQueue::new(); static PRODUCER: FramedProducer<&'static Queue, u16> = QUEUE.framed_producer(); static CONSUMER: FramedConsumer<&'static Queue, u16> = QUEUE.framed_consumer(); diff --git a/bbq2/src/traits/bbqhdl.rs b/bbq2/src/traits/bbqhdl.rs index 025a625..314f410 100644 --- a/bbq2/src/traits/bbqhdl.rs +++ b/bbq2/src/traits/bbqhdl.rs @@ -38,21 +38,30 @@ pub trait BbqHandle: Sized { /// How we notify the producer/consumers of this BBQueue type Notifier: Notifier; - // Obtain a reference to + /// Obtain a reference to the pointed-to [`BBQueue`] fn bbq_ref(&self) -> Self::Target; + /// Create a [`StreamProducer`] from our `Target`'s `BBQueue`. + /// + /// Must be equivalent to `self.bbq_ref().stream_producer()`. fn stream_producer(&self) -> StreamProducer { StreamProducer { bbq: self.bbq_ref(), } } + /// Create a [`StreamConsumer`] from our `Target`'s `BBQueue`. + /// + /// Must be equivalent to `self.bbq_ref().stream_consumer()`. fn stream_consumer(&self) -> StreamConsumer { StreamConsumer { bbq: self.bbq_ref(), } } + /// Create a [`FramedProducer`] from our `Target`'s `BBQueue`. + /// + /// Must be equivalent to `self.bbq_ref().framed_producer()`. fn framed_producer(&self) -> FramedProducer { FramedProducer { bbq: self.bbq_ref(), @@ -60,6 +69,9 @@ pub trait BbqHandle: Sized { } } + /// Create a [`FramedConsumer`] from our `Target`'s `BBQueue`. + /// + /// Must be equivalent to `self.bbq_ref().framed_consumer()`. fn framed_consumer(&self) -> FramedConsumer { FramedConsumer { bbq: self.bbq_ref(), diff --git a/bbq2/src/traits/coordination/cas.rs b/bbq2/src/traits/coordination/cas.rs index 11c6d86..ea19e35 100644 --- a/bbq2/src/traits/coordination/cas.rs +++ b/bbq2/src/traits/coordination/cas.rs @@ -1,5 +1,7 @@ //! Lock-free coordination based on Compare and Swap atomics +use const_init::ConstInit; + use super::{Coord, ReadGrantError, WriteGrantError}; use core::{ cmp::min, @@ -35,6 +37,7 @@ pub struct AtomicCoord { } impl AtomicCoord { + /// Create a new coordination structure that uses atomic CAS operations pub const fn new() -> Self { Self { write: AtomicUsize::new(0), @@ -53,10 +56,12 @@ impl Default for AtomicCoord { } } -unsafe impl Coord for AtomicCoord { +impl ConstInit for AtomicCoord { #[allow(clippy::declare_interior_mutable_const)] const INIT: Self = Self::new(); +} +unsafe impl Coord for AtomicCoord { fn reset(&self) { // Re-initialize the buffer (not totally needed, but nice to do) self.write.store(0, Ordering::Release); diff --git a/bbq2/src/traits/coordination/cs.rs b/bbq2/src/traits/coordination/cs.rs index 16ec22c..6704af4 100644 --- a/bbq2/src/traits/coordination/cs.rs +++ b/bbq2/src/traits/coordination/cs.rs @@ -3,6 +3,8 @@ //! This is provided so bbq2 is usable on bare metal targets that don't //! have CAS atomics, like `cortex-m0`/`thumbv6m` targets. +use const_init::ConstInit; + use super::{Coord, ReadGrantError, WriteGrantError}; use core::{ cmp::min, @@ -41,6 +43,7 @@ pub struct CsCoord { } impl CsCoord { + /// Create a new critical-section based coordination pub const fn new() -> Self { Self { write: AtomicUsize::new(0), @@ -59,10 +62,12 @@ impl Default for CsCoord { } } -unsafe impl Coord for CsCoord { +impl ConstInit for CsCoord { #[allow(clippy::declare_interior_mutable_const)] const INIT: Self = Self::new(); +} +unsafe impl Coord for CsCoord { fn reset(&self) { // Re-initialize the buffer (not totally needed, but nice to do) self.write.store(0, Ordering::Release); diff --git a/bbq2/src/traits/coordination/mod.rs b/bbq2/src/traits/coordination/mod.rs index 5eba2f8..0b49697 100644 --- a/bbq2/src/traits/coordination/mod.rs +++ b/bbq2/src/traits/coordination/mod.rs @@ -8,6 +8,8 @@ //! //! The `cas` module is toggled automatically based on `#[cfg(target_has_atomic = "ptr")]`. +use const_init::ConstInit; + #[cfg(target_has_atomic = "ptr")] pub mod cas; @@ -41,29 +43,50 @@ pub enum ReadGrantError { /// Coordination Handler /// -/// The coordination handler is responsible for arbitrating access to the storage +/// The coordination handler is responsible for arbitrating access to the storage. /// /// # Safety /// /// you must implement these correctly, or UB could happen -pub unsafe trait Coord { - const INIT: Self; - - // Reset all EXCEPT taken values back to the initial empty state +pub unsafe trait Coord: ConstInit { + /// Reset all values back to the initial empty state fn reset(&self); // Write Grants + /// Obtain a non-zero length grant UP TO `sz` of available writing space, + /// e.g. `0 < len <= sz`. + /// + /// On success, the producer will have exclusive access to this region. + /// + /// If a non-zero length grant is available, a tuple is returned that contains: + /// + /// * `.0`: the offset in bytes from the base storage pointer + /// * `.1`: the length in bytes of the region + /// + /// The returned grant must remain valid until `commit_inner` is called. fn grant_max_remaining( &self, capacity: usize, sz: usize, ) -> Result<(usize, usize), WriteGrantError>; + + /// Obtain a grant of EXACTLY `sz` bytes. + /// + /// On success, the producer will have exclusive access to this region, and the + /// offset in bytes from the base storage pointer will be returned. + /// + /// The returned grant must remain valid until `commit_inner` is called. fn grant_exact(&self, capacity: usize, sz: usize) -> Result; + + /// Make `used` bytes available fn commit_inner(&self, capacity: usize, grant_len: usize, used: usize); // Read Grants + /// Attempt to obtain a read grant. fn read(&self) -> Result<(usize, usize), ReadGrantError>; + + /// Mark `used` bytes as available for writing fn release_inner(&self, used: usize); } diff --git a/bbq2/src/traits/notifier/blocking.rs b/bbq2/src/traits/notifier/blocking.rs deleted file mode 100644 index e171ac6..0000000 --- a/bbq2/src/traits/notifier/blocking.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::Notifier; -use const_init::ConstInit; - -pub struct Blocking; - -// Blocking performs no notification -impl Notifier for Blocking { - fn wake_one_consumer(&self) {} - fn wake_one_producer(&self) {} -} - -impl ConstInit for Blocking { - const INIT: Self = Blocking; -} diff --git a/bbq2/src/traits/notifier/maitake.rs b/bbq2/src/traits/notifier/maitake.rs index ea8af99..5d430ad 100644 --- a/bbq2/src/traits/notifier/maitake.rs +++ b/bbq2/src/traits/notifier/maitake.rs @@ -1,3 +1,5 @@ +//! A Maitake-Sync based notifications + use const_init::ConstInit; use maitake_sync::WaitCell; @@ -13,7 +15,8 @@ pub struct MaiNotSpsc { } impl MaiNotSpsc { - pub fn new() -> Self { + /// Create a new Maitake-Sync based notifier + pub const fn new() -> Self { Self::INIT } } diff --git a/bbq2/src/traits/notifier/mod.rs b/bbq2/src/traits/notifier/mod.rs index 29c48c3..cec7470 100644 --- a/bbq2/src/traits/notifier/mod.rs +++ b/bbq2/src/traits/notifier/mod.rs @@ -7,17 +7,21 @@ use const_init::ConstInit; #[cfg(feature = "maitake-sync-0_2")] pub mod maitake; -pub mod blocking; +pub mod polling; /// Non-async notifications pub trait Notifier: ConstInit { + /// Inform one consumer that there is data available to read fn wake_one_consumer(&self); + /// Inform one producer that there is room available to write fn wake_one_producer(&self); } /// Async notifications #[allow(async_fn_in_trait)] pub trait AsyncNotifier: Notifier { + /// Wait until the queue is NOT empty, e.g. data is available to read async fn wait_for_not_empty Option>(&self, f: F) -> T; + /// Wait until the queue is NOT full, e.g. room is available to write async fn wait_for_not_full Option>(&self, f: F) -> T; } diff --git a/bbq2/src/traits/notifier/polling.rs b/bbq2/src/traits/notifier/polling.rs new file mode 100644 index 0000000..657ca39 --- /dev/null +++ b/bbq2/src/traits/notifier/polling.rs @@ -0,0 +1,21 @@ +//! Polling Notifier +//! +//! The "Polling" notifier is the simplest notifier: it does NO notification! + +use super::Notifier; +use const_init::ConstInit; + +/// A Blocking/Polling coordination +/// +/// This performs no notifiication +pub struct Polling; + +// Blocking performs no notification +impl Notifier for Polling { + fn wake_one_consumer(&self) {} + fn wake_one_producer(&self) {} +} + +impl ConstInit for Polling { + const INIT: Self = Polling; +} diff --git a/bbq2/src/traits/storage.rs b/bbq2/src/traits/storage.rs index 555a6ac..6c1b129 100644 --- a/bbq2/src/traits/storage.rs +++ b/bbq2/src/traits/storage.rs @@ -20,7 +20,18 @@ use alloc::{boxed::Box, vec::Vec}; /// /// Must always return the same ptr/len forever. pub trait Storage { - fn ptr_len(&self) -> (NonNull, usize); + /// Return a pointer and length pair of the underlying storage. + /// + /// ## Safety + /// + /// Implementations of this function MUST always return the same pointer an length + /// for all calls. + /// + /// The ownership of the data pointed to by this ptr+len are exclusive to the [`BBQueue`](crate::queue::BBQueue) + /// that contains this [`Storage`]. + /// + /// The pointee does not necessarily need to be initialized. + unsafe fn ptr_len(&self) -> (NonNull, usize); } /// A marker trait that the item is BOTH storage and can be initialized as a constant @@ -38,6 +49,7 @@ pub struct Inline { unsafe impl Sync for Inline {} impl Inline { + /// Create a new inline storage, e.g. `[u8; N]`. pub const fn new() -> Self { Self { buf: UnsafeCell::new(MaybeUninit::zeroed()), @@ -52,7 +64,7 @@ impl Default for Inline { } impl Storage for Inline { - fn ptr_len(&self) -> (NonNull, usize) { + unsafe fn ptr_len(&self) -> (NonNull, usize) { if N == 0 { return (NonNull::dangling(), N); } @@ -71,7 +83,7 @@ impl ConstInit for Inline { } impl Storage for &'_ Inline { - fn ptr_len(&self) -> (NonNull, usize) { + unsafe fn ptr_len(&self) -> (NonNull, usize) { let len = N; let ptr: *mut MaybeUninit<[u8; N]> = self.buf.get(); @@ -113,7 +125,7 @@ impl BoxedSlice { #[cfg(feature = "alloc")] impl Storage for BoxedSlice { - fn ptr_len(&self) -> (NonNull, usize) { + unsafe fn ptr_len(&self) -> (NonNull, usize) { let len = self.buf.len(); let ptr: *const UnsafeCell> = self.buf.as_ptr(); @@ -133,7 +145,9 @@ mod test { fn provenance_slice() { let sli = Inline::<64>::new(); let sli = &sli; - let (ptr, len) = <&Inline<64> as Storage>::ptr_len(&sli); + let (ptr, len) = unsafe { + <&Inline<64> as Storage>::ptr_len(&sli) + }; // This test ensures that obtaining the pointer for ptr_len through a single // element is sound From ca5427f28d735b59fffd9109132ece63b2257b01 Mon Sep 17 00:00:00 2001 From: James Munns Date: Sun, 4 Jan 2026 23:57:52 +0100 Subject: [PATCH 2/4] Cargo fmt --- bbq2/src/prod_cons/framed.rs | 16 ++++------------ bbq2/src/prod_cons/stream.rs | 20 +++++--------------- bbq2/src/queue.rs | 3 +-- bbq2/src/traits/storage.rs | 4 +--- 4 files changed, 11 insertions(+), 32 deletions(-) diff --git a/bbq2/src/prod_cons/framed.rs b/bbq2/src/prod_cons/framed.rs index 5d583f5..268de80 100644 --- a/bbq2/src/prod_cons/framed.rs +++ b/bbq2/src/prod_cons/framed.rs @@ -132,9 +132,7 @@ where /// a smaller size may be committed. Dropping the grant without calling /// commit means that no data will be made visible to the consumer. pub fn grant(&self, sz: H) -> Result, WriteGrantError> { - let (ptr, cap) = unsafe { - self.bbq.sto.ptr_len() - }; + let (ptr, cap) = unsafe { self.bbq.sto.ptr_len() }; let needed = sz.into() + core::mem::size_of::(); let offset = self.bbq.cor.grant_exact(cap, needed)?; @@ -184,9 +182,7 @@ where /// /// The returned grant must be released to free the space in the buffer. pub fn read(&self) -> Result, ReadGrantError> { - let (ptr, _cap) = unsafe { - self.bbq.sto.ptr_len() - }; + let (ptr, _cap) = unsafe { self.bbq.sto.ptr_len() }; let (offset, grant_len) = self.bbq.cor.read()?; // Calculate the size so we can figure out where the body @@ -257,9 +253,7 @@ where /// If `used` is greater than the `sz` used to create this grant, the /// amount will be clamped to `sz`. pub fn commit(self, used: H) { - let (_ptr, cap) = unsafe { - self.bbq.sto.ptr_len() - }; + let (_ptr, cap) = unsafe { self.bbq.sto.ptr_len() }; let hdrlen: usize = const { core::mem::size_of::() }; let grant_len = hdrlen + self.hdr.into(); let clamp_hdr = self.hdr.min(used); @@ -325,9 +319,7 @@ where { fn drop(&mut self) { // Default drop performs an "abort" - let (_ptr, cap) = unsafe { - self.bbq.sto.ptr_len() - }; + let (_ptr, cap) = unsafe { self.bbq.sto.ptr_len() }; let hdrlen: usize = const { core::mem::size_of::() }; let grant_len = hdrlen + self.hdr.into(); self.bbq.cor.commit_inner(cap, grant_len, 0); diff --git a/bbq2/src/prod_cons/stream.rs b/bbq2/src/prod_cons/stream.rs index 5c00fb0..bfcd7b9 100644 --- a/bbq2/src/prod_cons/stream.rs +++ b/bbq2/src/prod_cons/stream.rs @@ -87,9 +87,7 @@ where /// useful to call `grant_max_remaining` in a loop until `Err(WriteGrantError::InsufficientSize)` /// is returned. pub fn grant_max_remaining(&self, max: usize) -> Result, WriteGrantError> { - let (ptr, cap) = unsafe { - self.bbq.sto.ptr_len() - }; + let (ptr, cap) = unsafe { self.bbq.sto.ptr_len() }; let (offset, len) = self.bbq.cor.grant_max_remaining(cap, max)?; let ptr = unsafe { let p = ptr.as_ptr().byte_add(offset); @@ -109,9 +107,7 @@ where /// the ring buffer, this method WILL cause a wrap-around to occur to attempt to /// find the requested write capacity. pub fn grant_exact(&self, sz: usize) -> Result, WriteGrantError> { - let (ptr, cap) = unsafe { - self.bbq.sto.ptr_len() - }; + let (ptr, cap) = unsafe { self.bbq.sto.ptr_len() }; let offset = self.bbq.cor.grant_exact(cap, sz)?; let ptr = unsafe { let p = ptr.as_ptr().byte_add(offset); @@ -165,9 +161,7 @@ where /// in a loop until `Err(ReadGrantError::Empty)` is returned if you want to /// drain the queue entirely. pub fn read(&self) -> Result, ReadGrantError> { - let (ptr, _cap) = unsafe { - self.bbq.sto.ptr_len() - }; + let (ptr, _cap) = unsafe { self.bbq.sto.ptr_len() }; let (offset, len) = self.bbq.cor.read()?; let ptr = unsafe { let p = ptr.as_ptr().byte_add(offset); @@ -205,9 +199,7 @@ where /// /// `used` is capped to the length of the grant. pub fn commit(self, used: usize) { - let (_, cap) = unsafe { - self.bbq.sto.ptr_len() - }; + let (_, cap) = unsafe { self.bbq.sto.ptr_len() }; let used = used.min(self.len); self.bbq.cor.commit_inner(cap, self.len, used); if used != 0 { @@ -248,9 +240,7 @@ where len, to_commit, } = self; - let (_, cap) = unsafe { - bbq.sto.ptr_len() - }; + let (_, cap) = unsafe { bbq.sto.ptr_len() }; let len = *len; let used = (*to_commit).min(len); bbq.cor.commit_inner(cap, len, used); diff --git a/bbq2/src/queue.rs b/bbq2/src/queue.rs index 6dc0b59..b24feba 100644 --- a/bbq2/src/queue.rs +++ b/bbq2/src/queue.rs @@ -98,8 +98,7 @@ impl BBQueue { } #[cfg(feature = "alloc")] -impl - crate::queue::ArcBBQueue { +impl crate::queue::ArcBBQueue { /// Create a new [`FramedProducer`] for this [`BBQueue`] /// /// Although mixing stream and framed consumer/producers will not result in UB, diff --git a/bbq2/src/traits/storage.rs b/bbq2/src/traits/storage.rs index 6c1b129..e6db778 100644 --- a/bbq2/src/traits/storage.rs +++ b/bbq2/src/traits/storage.rs @@ -145,9 +145,7 @@ mod test { fn provenance_slice() { let sli = Inline::<64>::new(); let sli = &sli; - let (ptr, len) = unsafe { - <&Inline<64> as Storage>::ptr_len(&sli) - }; + let (ptr, len) = unsafe { <&Inline<64> as Storage>::ptr_len(&sli) }; // This test ensures that obtaining the pointer for ptr_len through a single // element is sound From 6f673ad09c6287c5fce26bd3ea30e7efc81c0fb0 Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 5 Jan 2026 00:01:07 +0100 Subject: [PATCH 3/4] test on non-atomic target --- .github/workflows/build.yml | 8 +++----- bbq2/Cargo.toml | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4bff01d..b78f9e7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,9 +18,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install embedded target - # Note: once https://github.com/hawkw/mycelium/pull/538 lands we can test on - # thumbv6m-none-eabi - run: rustup target add thumbv7em-none-eabi + run: rustup target add thumbv6m-none-eabi # # BUILD + TEST # @@ -39,8 +37,8 @@ jobs: # no features, on mcu - name: Check bbq2 (no features, on mcu) - run: cargo build --no-default-features --target=thumbv7em-none-eabi + run: cargo build --no-default-features --target=thumbv6m-none-eabi # default features, on mcu - name: Check bbq2 (no features, on mcu) - run: cargo build --target=thumbv7em-none-eabi + run: cargo build --target=thumbv6m-none-eabi diff --git a/bbq2/Cargo.toml b/bbq2/Cargo.toml index c7c3b19..e791f72 100644 --- a/bbq2/Cargo.toml +++ b/bbq2/Cargo.toml @@ -45,6 +45,8 @@ default = [ ] critical-section = [ "dep:critical-section", + # TODO: I don't think this should be necessary... + "maitake-sync?/critical-section", ] disable-cache-padding = [ "maitake-sync?/no-cache-pad", From fc66b9263d17eaf1305c627c54661e7bfbb9673f Mon Sep 17 00:00:00 2001 From: James Munns Date: Mon, 5 Jan 2026 00:11:03 +0100 Subject: [PATCH 4/4] Fix TODO --- bbq2/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/bbq2/Cargo.toml b/bbq2/Cargo.toml index e791f72..75456c7 100644 --- a/bbq2/Cargo.toml +++ b/bbq2/Cargo.toml @@ -45,7 +45,6 @@ default = [ ] critical-section = [ "dep:critical-section", - # TODO: I don't think this should be necessary... "maitake-sync?/critical-section", ] disable-cache-padding = [