Skip to content

Commit 2f6d46e

Browse files
committed
Add functions to take/release just producer or consumer
See jamesmunns#40 See jamesmunns#67
1 parent cb51821 commit 2f6d46e

File tree

1 file changed

+236
-12
lines changed

1 file changed

+236
-12
lines changed

Diff for: core/src/bbbuffer.rs

+236-12
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ use core::{
1212
result::Result as CoreResult,
1313
slice::from_raw_parts_mut,
1414
sync::atomic::{
15-
AtomicBool, AtomicUsize,
15+
AtomicBool, AtomicU8, AtomicUsize,
1616
Ordering::{AcqRel, Acquire, Release},
1717
},
1818
};
1919
pub use generic_array::typenum::consts;
2020
use generic_array::{ArrayLength, GenericArray};
2121

22+
/// Bit to define producer taken.
23+
const BIT_PRODUCER: u8 = 1 << 0;
24+
25+
/// Bit to define consumer taken.
26+
const BIT_CONSUMER: u8 = 1 << 1;
27+
2228
/// A backing structure for a BBQueue. Can be used to create either
2329
/// a BBQueue or a split Producer/Consumer pair
2430
pub struct BBBuffer<N: ArrayLength<u8>>(
@@ -41,7 +47,7 @@ where
4147
/// is placed at `static` scope within the `.bss` region, the explicit initialization
4248
/// will be elided (as it is already performed as part of memory initialization)
4349
///
44-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical section
50+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical section
4551
/// while splitting.
4652
///
4753
/// ```rust
@@ -64,7 +70,13 @@ where
6470
/// # }
6571
/// ```
6672
pub fn try_split(&'a self) -> Result<(Producer<'a, N>, Consumer<'a, N>)> {
67-
if atomic::swap(&self.0.already_split, true, AcqRel) {
73+
// Set producer/consumer taken bit, error and reset if one was already set
74+
let prev = self
75+
.0
76+
.split_prod_cons
77+
.fetch_or(BIT_PRODUCER & BIT_CONSUMER, AcqRel);
78+
if prev > 0 {
79+
self.0.split_prod_cons.store(prev, Release);
6880
return Err(Error::AlreadySplit);
6981
}
7082

@@ -101,14 +113,82 @@ where
101113
/// is placed at `static` scope within the `.bss` region, the explicit initialization
102114
/// will be elided (as it is already performed as part of memory initialization)
103115
///
104-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
116+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
105117
/// section while splitting.
106118
pub fn try_split_framed(&'a self) -> Result<(FrameProducer<'a, N>, FrameConsumer<'a, N>)> {
107119
let (producer, consumer) = self.try_split()?;
108120
Ok((FrameProducer { producer }, FrameConsumer { consumer }))
109121
}
110122

111-
/// Attempt to release the Producer and Consumer
123+
/// Attempt to take a `Producer` from the `BBBuffer` to gain access to the
124+
/// buffer. If a producer has already been taken, an error will be returned.
125+
///
126+
/// NOTE: When splitting, the underlying buffer will be explicitly initialized
127+
/// to zero. This may take a measurable amount of time, depending on the size
128+
/// of the buffer. This is necessary to prevent undefined behavior. If the buffer
129+
/// is placed at `static` scope within the `.bss` region, the explicit initialization
130+
/// will be elided (as it is already performed as part of memory initialization)
131+
///
132+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical section
133+
/// while splitting.
134+
pub fn try_take_producer(&'a self) -> Result<Producer<'a, N>> {
135+
// Set producer taken bit, error if already set
136+
if self.0.split_prod_cons.fetch_or(BIT_PRODUCER, AcqRel) & BIT_PRODUCER > 0 {
137+
return Err(Error::AlreadySplit);
138+
}
139+
140+
unsafe {
141+
// TODO: do we need to zero buffer here, like try_split?
142+
// // Explicitly zero the data to avoid undefined behavior.
143+
// // This is required, because we hand out references to the buffers,
144+
// // which mean that creating them as references is technically UB for now
145+
// let mu_ptr = self.0.buf.get();
146+
// (*mu_ptr).as_mut_ptr().write_bytes(0u8, 1);
147+
148+
let nn1 = NonNull::new_unchecked(self as *const _ as *mut _);
149+
150+
Ok(Producer {
151+
bbq: nn1,
152+
pd: PhantomData,
153+
})
154+
}
155+
}
156+
157+
/// Attempt to take a `Consumer` from the `BBBuffer` to gain access to the
158+
/// buffer. If a consumer has already been taken, an error will be returned.
159+
///
160+
/// NOTE: When splitting, the underlying buffer will be explicitly initialized
161+
/// to zero. This may take a measurable amount of time, depending on the size
162+
/// of the buffer. This is necessary to prevent undefined behavior. If the buffer
163+
/// is placed at `static` scope within the `.bss` region, the explicit initialization
164+
/// will be elided (as it is already performed as part of memory initialization)
165+
///
166+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical section
167+
/// while splitting.
168+
pub fn try_take_consumer(&'a self) -> Result<Consumer<'a, N>> {
169+
// Set producer taken bit, error if already set
170+
if self.0.split_prod_cons.fetch_or(BIT_CONSUMER, AcqRel) & BIT_CONSUMER > 0 {
171+
return Err(Error::AlreadySplit);
172+
}
173+
174+
unsafe {
175+
// TODO: do we need to zero buffer here, like try_split?
176+
// // Explicitly zero the data to avoid undefined behavior.
177+
// // This is required, because we hand out references to the buffers,
178+
// // which mean that creating them as references is technically UB for now
179+
// let mu_ptr = self.0.buf.get();
180+
// (*mu_ptr).as_mut_ptr().write_bytes(0u8, 1);
181+
182+
let nn1 = NonNull::new_unchecked(self as *const _ as *mut _);
183+
184+
Ok(Consumer {
185+
bbq: nn1,
186+
pd: PhantomData,
187+
})
188+
}
189+
}
190+
191+
/// Attempt to release the `Producer` and `Consumer`
112192
///
113193
/// This re-initializes the buffer so it may be split in a different mode at a later
114194
/// time. There must be no read or write grants active, or an error will be returned.
@@ -178,7 +258,7 @@ where
178258
self.0.last.store(0, Release);
179259

180260
// Mark the buffer as ready to be split
181-
self.0.already_split.store(false, Release);
261+
self.0.split_prod_cons.store(0, Release);
182262

183263
Ok(())
184264
}
@@ -201,6 +281,148 @@ where
201281
(FrameProducer { producer }, FrameConsumer { consumer })
202282
})
203283
}
284+
285+
/// Attempt to release the `Producer`.
286+
///
287+
/// This re-initializes the buffer so it may be split in a different mode at a later
288+
/// time. There must be no read or write grants active, or an error will be returned.
289+
///
290+
/// The `Producer` ust be from THIS `BBBuffer`, or an error will be returned.
291+
///
292+
/// ```rust
293+
/// # // bbqueue test shim!
294+
/// # fn bbqtest() {
295+
/// use bbqueue::{BBBuffer, consts::*};
296+
///
297+
/// // Create and split a new buffer
298+
/// let buffer: BBBuffer<U6> = BBBuffer::new();
299+
/// let (prod, cons) = buffer.try_split().unwrap();
300+
///
301+
/// // Not possible to split twice
302+
/// assert!(buffer.try_split().is_err());
303+
///
304+
/// // Release the producer and consumer
305+
/// assert!(buffer.try_release(prod, cons).is_ok());
306+
///
307+
/// // Split the buffer in framed mode
308+
/// let (fprod, fcons) = buffer.try_split_framed().unwrap();
309+
/// # // bbqueue test shim!
310+
/// # }
311+
/// #
312+
/// # fn main() {
313+
/// # #[cfg(not(feature = "thumbv6"))]
314+
/// # bbqtest();
315+
/// # }
316+
/// ```
317+
pub fn try_release_producer(
318+
&'a self,
319+
prod: Producer<'a, N>,
320+
) -> CoreResult<(), Producer<'a, N>> {
321+
// Note: Re-entrancy is not possible because we require ownership
322+
// of the producer, which are not cloneable. We also
323+
// can assume the buffer has been split, because
324+
325+
// Is this our producer?
326+
let our_prod = prod.bbq.as_ptr() as *const Self == self;
327+
328+
if !(our_prod) {
329+
// Can't release, not our producer
330+
return Err(prod);
331+
}
332+
333+
let wr_in_progress = self.0.write_in_progress.load(Acquire);
334+
let rd_in_progress = self.0.read_in_progress.load(Acquire);
335+
336+
if wr_in_progress || rd_in_progress {
337+
// Can't release, active grant(s) in progress
338+
return Err(prod);
339+
}
340+
341+
// Drop the producer
342+
drop(prod);
343+
344+
// Re-initialize the buffer (not totally needed, but nice to do)
345+
self.0.write.store(0, Release);
346+
self.0.read.store(0, Release);
347+
self.0.reserve.store(0, Release);
348+
self.0.last.store(0, Release);
349+
350+
// Mark the buffer as ready to retake producer
351+
self.0.split_prod_cons.fetch_and(!BIT_PRODUCER, Release);
352+
353+
Ok(())
354+
}
355+
356+
/// Attempt to release the `Consumer`.
357+
///
358+
/// This re-initializes the buffer so it may be split in a different mode at a later
359+
/// time. There must be no read or write grants active, or an error will be returned.
360+
///
361+
/// The `Consumer` must be from THIS `BBBuffer`, or an error will be returned.
362+
///
363+
/// ```rust
364+
/// # // bbqueue test shim!
365+
/// # fn bbqtest() {
366+
/// use bbqueue::{BBBuffer, consts::*};
367+
///
368+
/// // Create and split a new buffer
369+
/// let buffer: BBBuffer<U6> = BBBuffer::new();
370+
/// let (prod, cons) = buffer.try_split().unwrap();
371+
///
372+
/// // Not possible to split twice
373+
/// assert!(buffer.try_split().is_err());
374+
///
375+
/// // Release the producer and consumer
376+
/// assert!(buffer.try_release(prod, cons).is_ok());
377+
///
378+
/// // Split the buffer in framed mode
379+
/// let (fprod, fcons) = buffer.try_split_framed().unwrap();
380+
/// # // bbqueue test shim!
381+
/// # }
382+
/// #
383+
/// # fn main() {
384+
/// # #[cfg(not(feature = "thumbv6"))]
385+
/// # bbqtest();
386+
/// # }
387+
/// ```
388+
pub fn try_release_consumer(
389+
&'a self,
390+
cons: Consumer<'a, N>,
391+
) -> CoreResult<(), Consumer<'a, N>> {
392+
// Note: Re-entrancy is not possible because we require ownership
393+
// of the consumer, which are not cloneable. We also
394+
// can assume the buffer has been split, because
395+
396+
// Is this our consumer?
397+
let our_cons = cons.bbq.as_ptr() as *const Self == self;
398+
399+
if !(our_cons) {
400+
// Can't release, not our consumer
401+
return Err(cons);
402+
}
403+
404+
let wr_in_progress = self.0.write_in_progress.load(Acquire);
405+
let rd_in_progress = self.0.read_in_progress.load(Acquire);
406+
407+
if wr_in_progress || rd_in_progress {
408+
// Can't release, active grant(s) in progress
409+
return Err(cons);
410+
}
411+
412+
// Drop the consumer
413+
drop(cons);
414+
415+
// Re-initialize the buffer (not totally needed, but nice to do)
416+
self.0.write.store(0, Release);
417+
self.0.read.store(0, Release);
418+
self.0.reserve.store(0, Release);
419+
self.0.last.store(0, Release);
420+
421+
// Mark the buffer as ready to retake consumer
422+
self.0.split_prod_cons.fetch_and(!BIT_CONSUMER, Release);
423+
424+
Ok(())
425+
}
204426
}
205427

206428
/// `const-fn` version BBBuffer
@@ -237,8 +459,10 @@ pub struct ConstBBBuffer<A> {
237459
/// Is there an active write grant?
238460
write_in_progress: AtomicBool,
239461

240-
/// Have we already split?
241-
already_split: AtomicBool,
462+
/// Whether we have split the producer and/or consumer parts off.
463+
///
464+
/// See the `BIT_PRODUCER` and `BIT_CONSUMER` bits which define what parts have been split off.
465+
split_prod_cons: AtomicU8,
242466
}
243467

244468
impl<A> ConstBBBuffer<A> {
@@ -292,7 +516,7 @@ impl<A> ConstBBBuffer<A> {
292516
write_in_progress: AtomicBool::new(false),
293517

294518
/// We haven't split at the start
295-
already_split: AtomicBool::new(false),
519+
split_prod_cons: AtomicU8::new(0),
296520
}
297521
}
298522
}
@@ -820,7 +1044,7 @@ where
8201044
/// If `used` is larger than the given grant, the maximum amount will
8211045
/// be commited
8221046
///
823-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
1047+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
8241048
/// section while committing.
8251049
pub fn commit(mut self, used: usize) {
8261050
self.commit_inner(used);
@@ -947,7 +1171,7 @@ where
9471171
/// If `used` is larger than the given grant, the full grant will
9481172
/// be released.
9491173
///
950-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
1174+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
9511175
/// section while releasing.
9521176
pub fn release(mut self, used: usize) {
9531177
// Saturate the grant release
@@ -1065,7 +1289,7 @@ where
10651289
/// If `used` is larger than the given grant, the full grant will
10661290
/// be released.
10671291
///
1068-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
1292+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
10691293
/// section while releasing.
10701294
pub fn release(mut self, used: usize) {
10711295
// Saturate the grant release

0 commit comments

Comments
 (0)