Skip to content

Commit fe59cf0

Browse files
committed
gpdma: implement futures and IRQ handling for GPDMA
1 parent b53fee6 commit fe59cf0

File tree

4 files changed

+254
-1
lines changed

4 files changed

+254
-1
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: 3 additions & 0 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

src/gpdma/ch.rs

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

0 commit comments

Comments
 (0)