Skip to content

Commit 7446101

Browse files
timviseeithinuel
authored andcommitted
Add functions to take/release just producer or consumer
See jamesmunns#40 See jamesmunns#67
1 parent 02b7533 commit 7446101

File tree

1 file changed

+237
-10
lines changed

1 file changed

+237
-10
lines changed

Diff for: core/src/bbbuffer.rs

+237-10
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ 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
};
19+
20+
/// Bit to define producer taken.
21+
const BIT_PRODUCER: u8 = 1 << 0;
22+
23+
/// Bit to define consumer taken.
24+
const BIT_CONSUMER: u8 = 1 << 1;
25+
1926
#[derive(Debug)]
2027
/// A backing structure for a BBQueue. Can be used to create either
2128
/// a BBQueue or a split Producer/Consumer pair
@@ -49,6 +56,11 @@ pub struct BBBuffer<const N: usize> {
4956

5057
/// Have we already split?
5158
already_split: AtomicBool,
59+
60+
/// Whether we have split the producer and/or consumer parts off.
61+
///
62+
/// See the `BIT_PRODUCER` and `BIT_CONSUMER` bits which define what parts have been split off.
63+
split_prod_cons: AtomicU8,
5264
}
5365

5466
unsafe impl<const A: usize> Sync for BBBuffer<A> {}
@@ -63,7 +75,7 @@ impl<'a, const N: usize> BBBuffer<N> {
6375
/// is placed at `static` scope within the `.bss` region, the explicit initialization
6476
/// will be elided (as it is already performed as part of memory initialization)
6577
///
66-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical section
78+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical section
6779
/// while splitting.
6880
///
6981
/// ```rust
@@ -86,7 +98,12 @@ impl<'a, const N: usize> BBBuffer<N> {
8698
/// # }
8799
/// ```
88100
pub fn try_split(&'a self) -> Result<(Producer<'a, N>, Consumer<'a, N>)> {
89-
if atomic::swap(&self.already_split, true, AcqRel) {
101+
// Set producer/consumer taken bit, error and reset if one was already set
102+
let prev = self
103+
.split_prod_cons
104+
.fetch_or(BIT_PRODUCER | BIT_CONSUMER, AcqRel);
105+
if prev > 0 {
106+
self.split_prod_cons.store(prev, Release);
90107
return Err(Error::AlreadySplit);
91108
}
92109

@@ -123,14 +140,82 @@ impl<'a, const N: usize> BBBuffer<N> {
123140
/// is placed at `static` scope within the `.bss` region, the explicit initialization
124141
/// will be elided (as it is already performed as part of memory initialization)
125142
///
126-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
143+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
127144
/// section while splitting.
128145
pub fn try_split_framed(&'a self) -> Result<(FrameProducer<'a, N>, FrameConsumer<'a, N>)> {
129146
let (producer, consumer) = self.try_split()?;
130147
Ok((FrameProducer { producer }, FrameConsumer { consumer }))
131148
}
132149

133-
/// Attempt to release the Producer and Consumer
150+
/// Attempt to take a `Producer` from the `BBBuffer` to gain access to the
151+
/// buffer. If a producer has already been taken, an error will be returned.
152+
///
153+
/// NOTE: When splitting, the underlying buffer will be explicitly initialized
154+
/// to zero. This may take a measurable amount of time, depending on the size
155+
/// of the buffer. This is necessary to prevent undefined behavior. If the buffer
156+
/// is placed at `static` scope within the `.bss` region, the explicit initialization
157+
/// will be elided (as it is already performed as part of memory initialization)
158+
///
159+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical section
160+
/// while splitting.
161+
pub fn try_take_producer(&'a self) -> Result<Producer<'a, N>> {
162+
// Set producer taken bit, error if already set
163+
if self.0.split_prod_cons.fetch_or(BIT_PRODUCER, AcqRel) & BIT_PRODUCER > 0 {
164+
return Err(Error::AlreadySplit);
165+
}
166+
167+
unsafe {
168+
// TODO: do we need to zero buffer here, like try_split?
169+
// // Explicitly zero the data to avoid undefined behavior.
170+
// // This is required, because we hand out references to the buffers,
171+
// // which mean that creating them as references is technically UB for now
172+
// let mu_ptr = self.0.buf.get();
173+
// (*mu_ptr).as_mut_ptr().write_bytes(0u8, 1);
174+
175+
let nn1 = NonNull::new_unchecked(self as *const _ as *mut _);
176+
177+
Ok(Producer {
178+
bbq: nn1,
179+
pd: PhantomData,
180+
})
181+
}
182+
}
183+
184+
/// Attempt to take a `Consumer` from the `BBBuffer` to gain access to the
185+
/// buffer. If a consumer has already been taken, an error will be returned.
186+
///
187+
/// NOTE: When splitting, the underlying buffer will be explicitly initialized
188+
/// to zero. This may take a measurable amount of time, depending on the size
189+
/// of the buffer. This is necessary to prevent undefined behavior. If the buffer
190+
/// is placed at `static` scope within the `.bss` region, the explicit initialization
191+
/// will be elided (as it is already performed as part of memory initialization)
192+
///
193+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical section
194+
/// while splitting.
195+
pub fn try_take_consumer(&'a self) -> Result<Consumer<'a, N>> {
196+
// Set producer taken bit, error if already set
197+
if self.0.split_prod_cons.fetch_or(BIT_CONSUMER, AcqRel) & BIT_CONSUMER > 0 {
198+
return Err(Error::AlreadySplit);
199+
}
200+
201+
unsafe {
202+
// TODO: do we need to zero buffer here, like try_split?
203+
// // Explicitly zero the data to avoid undefined behavior.
204+
// // This is required, because we hand out references to the buffers,
205+
// // which mean that creating them as references is technically UB for now
206+
// let mu_ptr = self.0.buf.get();
207+
// (*mu_ptr).as_mut_ptr().write_bytes(0u8, 1);
208+
209+
let nn1 = NonNull::new_unchecked(self as *const _ as *mut _);
210+
211+
Ok(Consumer {
212+
bbq: nn1,
213+
pd: PhantomData,
214+
})
215+
}
216+
}
217+
218+
/// Attempt to release the `Producer` and `Consumer`
134219
///
135220
/// This re-initializes the buffer so it may be split in a different mode at a later
136221
/// time. There must be no read or write grants active, or an error will be returned.
@@ -204,7 +289,7 @@ impl<'a, const N: usize> BBBuffer<N> {
204289
self.last.store(0, Release);
205290

206291
// Mark the buffer as ready to be split
207-
self.already_split.store(false, Release);
292+
self.split_prod_cons.store(0, Release);
208293

209294
Ok(())
210295
}
@@ -227,6 +312,148 @@ impl<'a, const N: usize> BBBuffer<N> {
227312
(FrameProducer { producer }, FrameConsumer { consumer })
228313
})
229314
}
315+
316+
/// Attempt to release the `Producer`.
317+
///
318+
/// This re-initializes the buffer so it may be split in a different mode at a later
319+
/// time. There must be no read or write grants active, or an error will be returned.
320+
///
321+
/// The `Producer` ust be from THIS `BBBuffer`, or an error will be returned.
322+
///
323+
/// ```rust
324+
/// # // bbqueue test shim!
325+
/// # fn bbqtest() {
326+
/// use bbqueue::{BBBuffer, consts::*};
327+
///
328+
/// // Create and split a new buffer
329+
/// let buffer: BBBuffer<U6> = BBBuffer::new();
330+
/// let (prod, cons) = buffer.try_split().unwrap();
331+
///
332+
/// // Not possible to split twice
333+
/// assert!(buffer.try_split().is_err());
334+
///
335+
/// // Release the producer and consumer
336+
/// assert!(buffer.try_release(prod, cons).is_ok());
337+
///
338+
/// // Split the buffer in framed mode
339+
/// let (fprod, fcons) = buffer.try_split_framed().unwrap();
340+
/// # // bbqueue test shim!
341+
/// # }
342+
/// #
343+
/// # fn main() {
344+
/// # #[cfg(not(feature = "thumbv6"))]
345+
/// # bbqtest();
346+
/// # }
347+
/// ```
348+
pub fn try_release_producer(
349+
&'a self,
350+
prod: Producer<'a, N>,
351+
) -> CoreResult<(), Producer<'a, N>> {
352+
// Note: Re-entrancy is not possible because we require ownership
353+
// of the producer, which are not cloneable. We also
354+
// can assume the buffer has been split, because
355+
356+
// Is this our producer?
357+
let our_prod = prod.bbq.as_ptr() as *const Self == self;
358+
359+
if !(our_prod) {
360+
// Can't release, not our producer
361+
return Err(prod);
362+
}
363+
364+
let wr_in_progress = self.0.write_in_progress.load(Acquire);
365+
let rd_in_progress = self.0.read_in_progress.load(Acquire);
366+
367+
if wr_in_progress || rd_in_progress {
368+
// Can't release, active grant(s) in progress
369+
return Err(prod);
370+
}
371+
372+
// Drop the producer
373+
drop(prod);
374+
375+
// Re-initialize the buffer (not totally needed, but nice to do)
376+
self.0.write.store(0, Release);
377+
self.0.read.store(0, Release);
378+
self.0.reserve.store(0, Release);
379+
self.0.last.store(0, Release);
380+
381+
// Mark the buffer as ready to retake producer
382+
self.0.split_prod_cons.fetch_and(!BIT_PRODUCER, Release);
383+
384+
Ok(())
385+
}
386+
387+
/// Attempt to release the `Consumer`.
388+
///
389+
/// This re-initializes the buffer so it may be split in a different mode at a later
390+
/// time. There must be no read or write grants active, or an error will be returned.
391+
///
392+
/// The `Consumer` must be from THIS `BBBuffer`, or an error will be returned.
393+
///
394+
/// ```rust
395+
/// # // bbqueue test shim!
396+
/// # fn bbqtest() {
397+
/// use bbqueue::{BBBuffer, consts::*};
398+
///
399+
/// // Create and split a new buffer
400+
/// let buffer: BBBuffer<U6> = BBBuffer::new();
401+
/// let (prod, cons) = buffer.try_split().unwrap();
402+
///
403+
/// // Not possible to split twice
404+
/// assert!(buffer.try_split().is_err());
405+
///
406+
/// // Release the producer and consumer
407+
/// assert!(buffer.try_release(prod, cons).is_ok());
408+
///
409+
/// // Split the buffer in framed mode
410+
/// let (fprod, fcons) = buffer.try_split_framed().unwrap();
411+
/// # // bbqueue test shim!
412+
/// # }
413+
/// #
414+
/// # fn main() {
415+
/// # #[cfg(not(feature = "thumbv6"))]
416+
/// # bbqtest();
417+
/// # }
418+
/// ```
419+
pub fn try_release_consumer(
420+
&'a self,
421+
cons: Consumer<'a, N>,
422+
) -> CoreResult<(), Consumer<'a, N>> {
423+
// Note: Re-entrancy is not possible because we require ownership
424+
// of the consumer, which are not cloneable. We also
425+
// can assume the buffer has been split, because
426+
427+
// Is this our consumer?
428+
let our_cons = cons.bbq.as_ptr() as *const Self == self;
429+
430+
if !(our_cons) {
431+
// Can't release, not our consumer
432+
return Err(cons);
433+
}
434+
435+
let wr_in_progress = self.0.write_in_progress.load(Acquire);
436+
let rd_in_progress = self.0.read_in_progress.load(Acquire);
437+
438+
if wr_in_progress || rd_in_progress {
439+
// Can't release, active grant(s) in progress
440+
return Err(cons);
441+
}
442+
443+
// Drop the consumer
444+
drop(cons);
445+
446+
// Re-initialize the buffer (not totally needed, but nice to do)
447+
self.0.write.store(0, Release);
448+
self.0.read.store(0, Release);
449+
self.0.reserve.store(0, Release);
450+
self.0.last.store(0, Release);
451+
452+
// Mark the buffer as ready to retake consumer
453+
self.0.split_prod_cons.fetch_and(!BIT_CONSUMER, Release);
454+
455+
Ok(())
456+
}
230457
}
231458

232459
impl<const A: usize> BBBuffer<A> {
@@ -280,7 +507,7 @@ impl<const A: usize> BBBuffer<A> {
280507
write_in_progress: AtomicBool::new(false),
281508

282509
// We haven't split at the start
283-
already_split: AtomicBool::new(false),
510+
split_prod_cons: AtomicU8::new(0),
284511
}
285512
}
286513
}
@@ -744,7 +971,7 @@ impl<'a, const N: usize> GrantW<'a, N> {
744971
/// If `used` is larger than the given grant, the maximum amount will
745972
/// be commited
746973
///
747-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
974+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
748975
/// section while committing.
749976
pub fn commit(mut self, used: usize) {
750977
self.commit_inner(used);
@@ -861,7 +1088,7 @@ impl<'a, const N: usize> GrantR<'a, N> {
8611088
/// If `used` is larger than the given grant, the full grant will
8621089
/// be released.
8631090
///
864-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
1091+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
8651092
/// section while releasing.
8661093
pub fn release(mut self, used: usize) {
8671094
// Saturate the grant release
@@ -969,7 +1196,7 @@ impl<'a, const N: usize> SplitGrantR<'a, N> {
9691196
/// If `used` is larger than the given grant, the full grant will
9701197
/// be released.
9711198
///
972-
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
1199+
/// NOTE: If the `thumbv6` feature is selected, this function takes a short critical
9731200
/// section while releasing.
9741201
pub fn release(mut self, used: usize) {
9751202
// Saturate the grant release

0 commit comments

Comments
 (0)