Skip to content

Commit acd47be

Browse files
bors[bot]jordens
andauthored
Merge #226
226: Fast double-buffered DMA r=richardeoin a=jordens When handling multiple double-buffered DMA streams the current safe-and-universal API becomes slower than necessary. This adds `next_dbm_transfer_with()`, a closure-based API to the inactive buffer, without address poisoning, compiler fences, data barriers, or buffer swapping. For some applications this reduces the overhead significantly (speedup of 5). Co-authored-by: Robert Jördens <[email protected]>
2 parents c344fa9 + b0b8a93 commit acd47be

File tree

4 files changed

+67
-3
lines changed

4 files changed

+67
-3
lines changed

src/dma/bdma.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,11 +288,18 @@ macro_rules! bdma_stream {
288288
}
289289

290290
#[inline(always)]
291-
fn clear_transfer_complete_interrupt(&mut self) {
291+
fn clear_transfer_complete_flag(&mut self) {
292292
//NOTE(unsafe) Atomic write with no side-effects and we only access the bits
293293
// that belongs to the StreamX
294294
let dma = unsafe { &*I::ptr() };
295295
dma.$ifcr.write(|w| w.$tcif().set_bit());
296+
}
297+
298+
#[inline(always)]
299+
fn clear_transfer_complete_interrupt(&mut self) {
300+
self.clear_transfer_complete_flag();
301+
//NOTE(unsafe) Atomic read with no side-effects.
302+
let dma = unsafe { &*I::ptr() };
296303
let _ = dma.$isr.read();
297304
let _ = dma.$isr.read(); // Delay 2 peripheral clocks
298305
}

src/dma/dma.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,11 +369,18 @@ macro_rules! dma_stream {
369369
}
370370

371371
#[inline(always)]
372-
fn clear_transfer_complete_interrupt(&mut self) {
372+
fn clear_transfer_complete_flag(&mut self) {
373373
//NOTE(unsafe) Atomic write with no side-effects and we only access the bits
374374
// that belongs to the StreamX
375375
let dma = unsafe { &*I::ptr() };
376376
dma.$ifcr.write(|w| w.$tcif().set_bit());
377+
}
378+
379+
#[inline(always)]
380+
fn clear_transfer_complete_interrupt(&mut self) {
381+
self.clear_transfer_complete_flag();
382+
//NOTE(unsafe) Atomic read with no side-effects.
383+
let dma = unsafe { &*I::ptr() };
377384
let _ = dma.$isr.read();
378385
let _ = dma.$isr.read(); // Delay 2 peripheral clocks
379386
}

src/dma/mod.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub enum DMAError {
4646
NotReady,
4747
/// The user provided a buffer that is not big enough while double buffering.
4848
SmallBuffer,
49+
/// DMA started transfer on the inactive buffer while the user was processing it.
50+
Overflow,
4951
}
5052

5153
/// Possible DMA's directions.
@@ -602,6 +604,48 @@ macro_rules! db_transfer_def {
602604
Ok((buf, current, last_remaining))
603605
}
604606

607+
/// Wait for the transfer of the currently active buffer to complete,
608+
/// then call a function on the now inactive buffer and acknowledge the
609+
/// transfer complete flag.
610+
///
611+
/// NOTE(panic): This will panic then used in single buffer mode (not DBM).
612+
///
613+
/// NOTE(unsafe): Memory safety is not guaranteed.
614+
/// The user must ensure that the user function called on the inactive
615+
/// buffer completes before the running DMA transfer of the active buffer
616+
/// completes. If the DMA wins the race to the inactive buffer
617+
/// a `DMAError::Overflow` is returned but processing continues.
618+
///
619+
/// NOTE(fence): The user function must ensure buffer access ordering
620+
/// against the flag accesses. Call
621+
/// `core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst)`
622+
/// before and after accessing the buffer.
623+
pub unsafe fn next_dbm_transfer_with<F, T>(
624+
&mut self,
625+
func: F,
626+
) -> Result<T, DMAError>
627+
where
628+
F: FnOnce(&mut BUF, CurrentBuffer) -> T,
629+
{
630+
while !STREAM::get_transfer_complete_flag() { }
631+
self.stream.clear_transfer_complete_flag();
632+
633+
// NOTE(panic): Panic if stream not configured in double buffer mode.
634+
let inactive = STREAM::get_inactive_buffer().unwrap();
635+
636+
// This buffer is inactive now and can be accessed.
637+
// NOTE(panic): We always hold ownership in lieu of the DMA peripheral.
638+
let buf = self.buf[inactive as usize].as_mut().unwrap();
639+
640+
let result = func(buf, inactive);
641+
642+
if STREAM::get_transfer_complete_flag() {
643+
Err(DMAError::Overflow)
644+
} else {
645+
Ok(result)
646+
}
647+
}
648+
605649
/// Clear half transfer interrupt (htif) for the DMA stream.
606650
#[inline(always)]
607651
pub fn clear_half_transfer_interrupt(&mut self) {

src/dma/traits.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@ pub trait Stream: Sealed {
3232
/// Clear all interrupts for the DMA stream.
3333
fn clear_interrupts(&mut self);
3434

35-
/// Clear transfer complete interrupt (tcif) for the DMA stream.
35+
/// Clear transfer complete interrupt flag (tcif) for the DMA stream
36+
/// but do not insert artificial delays.
37+
fn clear_transfer_complete_flag(&mut self);
38+
39+
/// Clear transfer complete interrupt (tcif) for the DMA stream and delay
40+
/// to ensure the change has settled through the bridge, peripheral, and
41+
/// synchronizers.
3642
fn clear_transfer_complete_interrupt(&mut self);
3743

3844
/// Clear transfer error interrupt (teif) for the DMA stream.

0 commit comments

Comments
 (0)