Skip to content

Commit 48acbcd

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

File tree

4 files changed

+273
-1
lines changed

4 files changed

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

0 commit comments

Comments
 (0)