Skip to content

Commit e7a4397

Browse files
authored
DMA Futures (#65)
This implements futures for GPDMA transfers by adding a wrapper for `DmaTransfer`, `DmaTransferFuture`, implementing Future for `DmaTransferFuture` in the `gpdma::future` module, which is created when a `DmaTransfer` is awaited via the `IntoFuture` trait. Under the hood, the implementation uses an `AtomicWaker` from the [`futures-util`](https://docs.rs/futures-util/latest/futures_util/) crate and channel interrupts to drive the future. Because the wakers need to be defined statically in order to be accessible from the channel interrupts, and to prevent accidental colliding implementations of interrupt handlers for the DMA channels, the futures implementation is hidden behind the `gpdma-future` feature flag.
1 parent 1e1e9e4 commit e7a4397

File tree

4 files changed

+291
-12
lines changed

4 files changed

+291
-12
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ stm32h562 = ["stm32h5/stm32h562", "device-selected", "rm0481", "h56x_h573"]
5050
stm32h563 = ["stm32h5/stm32h563", "device-selected", "rm0481", "h56x_h573", "sdmmc2", "ethernet"]
5151
stm32h573 = ["stm32h5/stm32h573", "device-selected", "rm0481", "h56x_h573", "otfdec", "sdmmc2", "ethernet"]
5252

53+
# Flags for async APIs
54+
futures = ["dep:futures-util"]
55+
gpdma-futures = ["futures"]
56+
async = ["gpdma-futures"]
57+
5358
# Flags for examples
5459
log = ["dep:log"]
5560
log-itm = ["log"]
@@ -71,6 +76,7 @@ embedded-hal = "1.0.0"
7176
defmt = { version = "1.0.0", optional = true }
7277
paste = "1.0.15"
7378
log = { version = "0.4.20", optional = true}
79+
futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"], optional = true}
7480
stm32-usbd = "0.8.0"
7581

7682
[dev-dependencies]

src/gpdma.rs

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ use embedded_dma::{ReadBuffer, Word as DmaWord, WriteBuffer};
9090

9191
mod ch;
9292
pub mod config;
93+
#[cfg(feature = "gpdma-futures")]
94+
mod future;
9395
pub mod periph;
9496

9597
pub use ch::{
@@ -146,6 +148,7 @@ impl<DMA: Instance> GpdmaExt<DMA> for DMA {
146148
}
147149
}
148150

151+
#[allow(private_bounds)]
149152
pub trait Instance: Sealed + Deref<Target = gpdma1::RegisterBlock> {
150153
type Rec: ResetEnable;
151154

@@ -228,16 +231,20 @@ impl<DMA: Instance> DmaChannels<DMA> {
228231
/// Splits the DMA peripheral into channels.
229232
pub(super) fn new(_regs: DMA, rec: DMA::Rec) -> Self {
230233
let _ = rec.reset().enable();
231-
Self(
232-
DmaChannel0::new(),
233-
DmaChannel1::new(),
234-
DmaChannel2::new(),
235-
DmaChannel3::new(),
236-
DmaChannel4::new(),
237-
DmaChannel5::new(),
238-
DmaChannel6::new(),
239-
DmaChannel7::new(),
240-
)
234+
// Safety: The channels are only initialized for user code here once, so no copies can be
235+
// made available outside this module.
236+
unsafe {
237+
Self(
238+
DmaChannel0::new_unsafe(),
239+
DmaChannel1::new_unsafe(),
240+
DmaChannel2::new_unsafe(),
241+
DmaChannel3::new_unsafe(),
242+
DmaChannel4::new_unsafe(),
243+
DmaChannel5::new_unsafe(),
244+
DmaChannel6::new_unsafe(),
245+
DmaChannel7::new_unsafe(),
246+
)
247+
}
241248
}
242249
}
243250

src/gpdma/ch.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use super::{
1515
DmaConfig, Error, Instance, Word,
1616
};
1717

18-
trait ChannelRegs: Sealed {
18+
pub(super) trait ChannelRegs: Sealed {
1919
#[allow(unused)] // TODO: this will be used for linked-list transfers
2020
fn lbar(&self) -> &LBAR;
2121
fn fcr(&self) -> &FCR;
@@ -131,7 +131,7 @@ where
131131
DMA: Instance,
132132
CH: ChannelRegs,
133133
{
134-
pub(super) fn new() -> Self {
134+
pub(super) unsafe fn new_unsafe() -> Self {
135135
DmaChannelRef {
136136
_dma: PhantomData,
137137
_ch: PhantomData,
@@ -741,11 +741,19 @@ where
741741
}
742742
}
743743

744+
#[cfg(feature = "gpdma-futures")]
745+
pub use super::future::DmaChannel;
746+
744747
/// DmaChannel trait provides the API contract that all GPDMA channels exposed to the user
745748
/// implement.
749+
// Note: This trait is defined differently (in the `future` module) when the `gpdma-futures`
750+
// feature is enabled, to support async operations.
751+
#[cfg(not(feature = "gpdma-futures"))]
746752
#[allow(private_bounds)]
747753
pub trait DmaChannel: Channel {}
748754

755+
#[cfg(not(feature = "gpdma-futures"))]
756+
#[allow(private_bounds)]
749757
impl<DMA, CH, const N: usize> DmaChannel for DmaChannelRef<DMA, CH, N>
750758
where
751759
DMA: Instance,

src/gpdma/future.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
//! This module provides adds a Future implementation for GPDMA transfers, allowing DMA transfers
2+
//! to be awaited asynchronously. GPDMA futures are enabled with the `gpdma-futures`. This future
3+
//! implentation uses GPDMA channel interrupts to drive the future.
4+
//!
5+
//! Note that when the `gpdma-futures` feature is enabled, a set of `AtomicWaker`s are created for
6+
//! each GPDMA channel and defined statically. They are used to wake up the task that is waiting
7+
//! for the transfer to complete.
8+
//!
9+
//! It is necessary to unmask each required GPDMA channel interrupts in the NVIC to use this
10+
//! feature. This is NOT done automatically so as to allow fine grained control by the user over
11+
//! which interrupts are enabled in a system. To do so:
12+
//!```
13+
//! use stm32h5xx_hal::pac::{NVIC, interrupt};
14+
//!
15+
//! // Un-mask the interrupt for GPDMA 1 channel 0
16+
//! unsafe {
17+
//! NVIC::unmask(interrupt::GPDMA1_CH0);
18+
//! };
19+
//! ```
20+
use core::{
21+
future::{Future, IntoFuture},
22+
ops::{Deref, DerefMut},
23+
pin::Pin,
24+
task::{Context, Poll},
25+
};
26+
27+
use embedded_dma::{ReadBuffer, WriteBuffer};
28+
use futures_util::task::AtomicWaker;
29+
30+
use crate::interrupt;
31+
use crate::stm32::{GPDMA1, GPDMA2};
32+
33+
use super::{
34+
ch::{
35+
Channel, ChannelRegs, DmaChannel0, DmaChannel1, DmaChannel2,
36+
DmaChannel3, DmaChannel4, DmaChannel5, DmaChannel6, DmaChannel7,
37+
DmaChannelRef,
38+
},
39+
DmaTransfer, Error, Instance, Word,
40+
};
41+
42+
/// DmaChannel trait provides the API contract that all GPDMA channels exposed to the user
43+
/// implement.
44+
// Note: This trait is defined to be bound by ChannelWaker when futures are enabled via the
45+
// `gpdma-futures` feature. This is to ensure that the waker associated with a channel can
46+
// be accessed from the interrupt handler and the DmaTransfer `poll` function.
47+
// Specifically, this alternate definition is needed to ensure that the DmaChannel implementations
48+
// that are exposed to user code are bound to the ChannelWaker trait, which is also defined and
49+
// and implemented for DmaChannelRef in this module. Without defining this trait, the futures
50+
// implementation would be leaked into the channel definitions when futures are not enabled.
51+
// Given that not everyone will use futures, and enabling them requires statically allocating RAM,
52+
// it's better to redefine the trait here.
53+
#[allow(private_bounds)]
54+
pub trait DmaChannel: Channel + ChannelWaker {}
55+
56+
impl<DMA, CH, const N: usize> DmaChannel for DmaChannelRef<DMA, CH, N>
57+
where
58+
DMA: Instance + InstanceWaker,
59+
CH: ChannelRegs,
60+
Self: Deref<Target = CH>,
61+
{
62+
}
63+
64+
#[allow(private_bounds)]
65+
impl<'a, CH, S, D> IntoFuture for DmaTransfer<'a, CH, S, D>
66+
where
67+
CH: DmaChannel,
68+
S: ReadBuffer<Word: Word>,
69+
D: WriteBuffer<Word: Word>,
70+
{
71+
type Output = Result<(), Error>;
72+
type IntoFuture = DmaTransferFuture<'a, CH, S, D>;
73+
74+
fn into_future(mut self) -> DmaTransferFuture<'a, CH, S, D> {
75+
self.enable_interrupts();
76+
DmaTransferFuture { transfer: self }
77+
}
78+
}
79+
80+
/// The `DmaTransferFuture` struct is a wrapper around the `DmaTransfer` struct that implements
81+
/// the `Future` trait. It allows the DMA transfer to be awaited asynchronously, enabling the
82+
/// use of DMA transfers in an asynchronous context, such as with the RTIC framework. It is created
83+
/// when the `DmaTransfer` is awaited via the `IntoFuture` trait implementation. It's main function
84+
/// is to ensure that interrupts are enabled when the transfer is awaited so that the future can
85+
/// be driven by the channel interrupt.
86+
#[doc(hidden)]
87+
pub struct DmaTransferFuture<'a, CH, S, D>
88+
where
89+
CH: DmaChannel,
90+
S: ReadBuffer<Word: Word>,
91+
D: WriteBuffer<Word: Word>,
92+
{
93+
transfer: DmaTransfer<'a, CH, S, D>,
94+
}
95+
96+
impl<'a, CH, S, D> Deref for DmaTransferFuture<'a, CH, S, D>
97+
where
98+
CH: DmaChannel,
99+
S: ReadBuffer<Word: Word>,
100+
D: WriteBuffer<Word: Word>,
101+
{
102+
type Target = DmaTransfer<'a, CH, S, D>;
103+
104+
fn deref(&self) -> &Self::Target {
105+
&self.transfer
106+
}
107+
}
108+
109+
impl<'a, CH, S, D> DerefMut for DmaTransferFuture<'a, CH, S, D>
110+
where
111+
CH: DmaChannel,
112+
S: ReadBuffer<Word: Word>,
113+
D: WriteBuffer<Word: Word>,
114+
{
115+
fn deref_mut(&mut self) -> &mut Self::Target {
116+
&mut self.transfer
117+
}
118+
}
119+
120+
impl<'a, CH, S, D> Unpin for DmaTransferFuture<'a, CH, S, D>
121+
where
122+
CH: DmaChannel,
123+
S: ReadBuffer<Word: Word>,
124+
D: WriteBuffer<Word: Word>,
125+
{
126+
}
127+
128+
impl<'a, CH, S, D> Future for DmaTransferFuture<'a, CH, S, D>
129+
where
130+
CH: DmaChannel + ChannelWaker,
131+
S: ReadBuffer<Word: Word>,
132+
D: WriteBuffer<Word: Word>,
133+
{
134+
type Output = Result<(), Error>;
135+
136+
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
137+
self.channel.waker().register(cx.waker());
138+
if self.channel.check_transfer_complete()? {
139+
Poll::Ready(Ok(()))
140+
} else {
141+
Poll::Pending
142+
}
143+
}
144+
}
145+
146+
#[allow(private_bounds)]
147+
impl<DMA, CH, const N: usize> DmaChannelRef<DMA, CH, N>
148+
where
149+
DMA: Instance + InstanceWaker,
150+
CH: ChannelRegs,
151+
Self: Deref<Target = CH>,
152+
{
153+
#[inline(always)]
154+
fn handle_interrupt() {
155+
// Safety:
156+
// This creates a DmaChannelRef instance for channel N, which is be a duplicate to the
157+
// DmaChannelRef instance that is held as a mutable reference in the DmaTransfer struct
158+
// while a transfer is in progress. However, it is only used to disable the transfer
159+
// interrupts for the channel and wake up the task that is waiting for the transfer to
160+
// complete, which can be done safely.
161+
//
162+
// When the DmaTransfer struct is dropped, interrupts are disabled for the channel,
163+
// preventing this interrupt from being triggered. Interrupts are only enabled when the
164+
// transfer is awaited (calling IntoFuture::into_future).
165+
let mut ch = unsafe { Self::new_unsafe() };
166+
167+
// This is a single volatile write to the channel's interrupt status register to disable
168+
// interrupts for the channel so this interrupt doesn't trigger again while the transfer
169+
// struct is being polled.
170+
ch.disable_transfer_interrupts();
171+
172+
// This is an atomic operation to wake up the task that is waiting for the transfer to
173+
// complete.
174+
ch.waker().wake();
175+
}
176+
}
177+
178+
impl<DMA, CH, const N: usize> ChannelWaker for DmaChannelRef<DMA, CH, N>
179+
where
180+
DMA: Instance + InstanceWaker,
181+
CH: ChannelRegs,
182+
Self: Deref<Target = CH>,
183+
{
184+
#[inline(always)]
185+
fn waker(&self) -> &'static AtomicWaker {
186+
DMA::waker(N)
187+
}
188+
}
189+
190+
/// Private trait that provides access to the [`AtomicWaker`]s for a particular GPDMA . It is only
191+
/// implemented when the `gpdma-futures` feature is enabled.
192+
pub(super) trait InstanceWaker {
193+
fn waker(idx: usize) -> &'static AtomicWaker;
194+
}
195+
196+
/// Private trait that provides access to the [`AtomicWaker`] for a specific channel. It is only
197+
/// implemented when the `gpdma-futures` feature is enabled.
198+
pub(super) trait ChannelWaker {
199+
/// Returns a reference to the AtomicWaker for the channel.
200+
fn waker(&self) -> &'static AtomicWaker;
201+
}
202+
203+
macro_rules! gpdma_irq {
204+
($GPDMA:ident, $CH:literal) => {
205+
paste::item! {
206+
#[interrupt]
207+
fn [<$GPDMA _CH $CH>]() {
208+
[< DmaChannel $CH>]::<$GPDMA>::handle_interrupt();
209+
}
210+
}
211+
};
212+
}
213+
214+
mod gpdma1 {
215+
use super::*;
216+
217+
static WAKERS_GPDMA1: [AtomicWaker; 8] = [const { AtomicWaker::new() }; 8];
218+
219+
#[allow(private_bounds)]
220+
impl InstanceWaker for GPDMA1 {
221+
#[inline(always)]
222+
fn waker(idx: usize) -> &'static AtomicWaker {
223+
&WAKERS_GPDMA1[idx]
224+
}
225+
}
226+
227+
gpdma_irq!(GPDMA1, 0);
228+
gpdma_irq!(GPDMA1, 1);
229+
gpdma_irq!(GPDMA1, 2);
230+
gpdma_irq!(GPDMA1, 3);
231+
gpdma_irq!(GPDMA1, 4);
232+
gpdma_irq!(GPDMA1, 5);
233+
gpdma_irq!(GPDMA1, 6);
234+
gpdma_irq!(GPDMA1, 7);
235+
}
236+
237+
mod gpdma2 {
238+
use super::*;
239+
240+
static WAKERS_GPDMA2: [AtomicWaker; 8] = [const { AtomicWaker::new() }; 8];
241+
242+
#[allow(private_bounds)]
243+
impl InstanceWaker for GPDMA2 {
244+
#[inline(always)]
245+
fn waker(idx: usize) -> &'static AtomicWaker {
246+
&WAKERS_GPDMA2[idx]
247+
}
248+
}
249+
250+
gpdma_irq!(GPDMA2, 0);
251+
gpdma_irq!(GPDMA2, 1);
252+
gpdma_irq!(GPDMA2, 2);
253+
gpdma_irq!(GPDMA2, 3);
254+
gpdma_irq!(GPDMA2, 4);
255+
gpdma_irq!(GPDMA2, 5);
256+
gpdma_irq!(GPDMA2, 6);
257+
gpdma_irq!(GPDMA2, 7);
258+
}

0 commit comments

Comments
 (0)