Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ stm32h562 = ["stm32h5/stm32h562", "device-selected", "rm0481", "h56x_h573"]
stm32h563 = ["stm32h5/stm32h563", "device-selected", "rm0481", "h56x_h573", "sdmmc2", "ethernet"]
stm32h573 = ["stm32h5/stm32h573", "device-selected", "rm0481", "h56x_h573", "otfdec", "sdmmc2", "ethernet"]

# Flags for async APIs
futures = ["dep:futures-util"]
gpdma-futures = ["futures"]
async = ["gpdma-futures"]

# Flags for examples
log = ["dep:log"]
log-itm = ["log"]
Expand All @@ -71,6 +76,7 @@ embedded-hal = "1.0.0"
defmt = { version = "1.0.0", optional = true }
paste = "1.0.15"
log = { version = "0.4.20", optional = true}
futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"], optional = true}
stm32-usbd = "0.8.0"

[dev-dependencies]
Expand Down
27 changes: 17 additions & 10 deletions src/gpdma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ use embedded_dma::{ReadBuffer, Word as DmaWord, WriteBuffer};

mod ch;
pub mod config;
#[cfg(feature = "gpdma-futures")]
mod future;
pub mod periph;

pub use ch::{
Expand Down Expand Up @@ -146,6 +148,7 @@ impl<DMA: Instance> GpdmaExt<DMA> for DMA {
}
}

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

Expand Down Expand Up @@ -228,16 +231,20 @@ impl<DMA: Instance> DmaChannels<DMA> {
/// Splits the DMA peripheral into channels.
pub(super) fn new(_regs: DMA, rec: DMA::Rec) -> Self {
let _ = rec.reset().enable();
Self(
DmaChannel0::new(),
DmaChannel1::new(),
DmaChannel2::new(),
DmaChannel3::new(),
DmaChannel4::new(),
DmaChannel5::new(),
DmaChannel6::new(),
DmaChannel7::new(),
)
// Safety: The channels are only initialized for user code here once, so no copies can be
// made available outside this module.
unsafe {
Self(
DmaChannel0::new_unsafe(),
DmaChannel1::new_unsafe(),
DmaChannel2::new_unsafe(),
DmaChannel3::new_unsafe(),
DmaChannel4::new_unsafe(),
DmaChannel5::new_unsafe(),
DmaChannel6::new_unsafe(),
DmaChannel7::new_unsafe(),
)
}
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/gpdma/ch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use super::{
DmaConfig, Error, Instance, Word,
};

trait ChannelRegs: Sealed {
pub(super) trait ChannelRegs: Sealed {
#[allow(unused)] // TODO: this will be used for linked-list transfers
fn lbar(&self) -> &LBAR;
fn fcr(&self) -> &FCR;
Expand Down Expand Up @@ -131,7 +131,7 @@ where
DMA: Instance,
CH: ChannelRegs,
{
pub(super) fn new() -> Self {
pub(super) unsafe fn new_unsafe() -> Self {
DmaChannelRef {
_dma: PhantomData,
_ch: PhantomData,
Expand Down Expand Up @@ -741,11 +741,19 @@ where
}
}

#[cfg(feature = "gpdma-futures")]
pub use super::future::DmaChannel;

/// DmaChannel trait provides the API contract that all GPDMA channels exposed to the user
/// implement.
// Note: This trait is defined differently (in the `future` module) when the `gpdma-futures`
// feature is enabled, to support async operations.
#[cfg(not(feature = "gpdma-futures"))]
#[allow(private_bounds)]
pub trait DmaChannel: Channel {}

#[cfg(not(feature = "gpdma-futures"))]
#[allow(private_bounds)]
impl<DMA, CH, const N: usize> DmaChannel for DmaChannelRef<DMA, CH, N>
where
DMA: Instance,
Expand Down
258 changes: 258 additions & 0 deletions src/gpdma/future.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
//! This module provides adds a Future implementation for GPDMA transfers, allowing DMA transfers
//! to be awaited asynchronously. GPDMA futures are enabled with the `gpdma-futures`. This future
//! implentation uses GPDMA channel interrupts to drive the future.
//!
//! Note that when the `gpdma-futures` feature is enabled, a set of `AtomicWaker`s are created for
//! each GPDMA channel and defined statically. They are used to wake up the task that is waiting
//! for the transfer to complete.
//!
//! It is necessary to unmask each required GPDMA channel interrupts in the NVIC to use this
//! feature. This is NOT done automatically so as to allow fine grained control by the user over
//! which interrupts are enabled in a system. To do so:
//!```
//! use stm32h5xx_hal::pac::{NVIC, interrupt};
//!
//! // Un-mask the interrupt for GPDMA 1 channel 0
//! unsafe {
//! NVIC::unmask(interrupt::GPDMA1_CH0);
//! };
//! ```
use core::{
future::{Future, IntoFuture},
ops::{Deref, DerefMut},
pin::Pin,
task::{Context, Poll},
};

use embedded_dma::{ReadBuffer, WriteBuffer};
use futures_util::task::AtomicWaker;

use crate::interrupt;
use crate::stm32::{GPDMA1, GPDMA2};

use super::{
ch::{
Channel, ChannelRegs, DmaChannel0, DmaChannel1, DmaChannel2,
DmaChannel3, DmaChannel4, DmaChannel5, DmaChannel6, DmaChannel7,
DmaChannelRef,
},
DmaTransfer, Error, Instance, Word,
};

/// DmaChannel trait provides the API contract that all GPDMA channels exposed to the user
/// implement.
// Note: This trait is defined to be bound by ChannelWaker when futures are enabled via the
// `gpdma-futures` feature. This is to ensure that the waker associated with a channel can
// be accessed from the interrupt handler and the DmaTransfer `poll` function.
// Specifically, this alternate definition is needed to ensure that the DmaChannel implementations
// that are exposed to user code are bound to the ChannelWaker trait, which is also defined and
// and implemented for DmaChannelRef in this module. Without defining this trait, the futures
// implementation would be leaked into the channel definitions when futures are not enabled.
// Given that not everyone will use futures, and enabling them requires statically allocating RAM,
// it's better to redefine the trait here.
#[allow(private_bounds)]
pub trait DmaChannel: Channel + ChannelWaker {}

impl<DMA, CH, const N: usize> DmaChannel for DmaChannelRef<DMA, CH, N>
where
DMA: Instance + InstanceWaker,
CH: ChannelRegs,
Self: Deref<Target = CH>,
{
}

#[allow(private_bounds)]
impl<'a, CH, S, D> IntoFuture for DmaTransfer<'a, CH, S, D>
where
CH: DmaChannel,
S: ReadBuffer<Word: Word>,
D: WriteBuffer<Word: Word>,
{
type Output = Result<(), Error>;
type IntoFuture = DmaTransferFuture<'a, CH, S, D>;

fn into_future(mut self) -> DmaTransferFuture<'a, CH, S, D> {
self.enable_interrupts();
DmaTransferFuture { transfer: self }
}
}

/// The `DmaTransferFuture` struct is a wrapper around the `DmaTransfer` struct that implements
/// the `Future` trait. It allows the DMA transfer to be awaited asynchronously, enabling the
/// use of DMA transfers in an asynchronous context, such as with the RTIC framework. It is created
/// when the `DmaTransfer` is awaited via the `IntoFuture` trait implementation. It's main function
/// is to ensure that interrupts are enabled when the transfer is awaited so that the future can
/// be driven by the channel interrupt.
#[doc(hidden)]
pub struct DmaTransferFuture<'a, CH, S, D>
where
CH: DmaChannel,
S: ReadBuffer<Word: Word>,
D: WriteBuffer<Word: Word>,
{
transfer: DmaTransfer<'a, CH, S, D>,
}

impl<'a, CH, S, D> Deref for DmaTransferFuture<'a, CH, S, D>
where
CH: DmaChannel,
S: ReadBuffer<Word: Word>,
D: WriteBuffer<Word: Word>,
{
type Target = DmaTransfer<'a, CH, S, D>;

fn deref(&self) -> &Self::Target {
&self.transfer
}
}

impl<'a, CH, S, D> DerefMut for DmaTransferFuture<'a, CH, S, D>
where
CH: DmaChannel,
S: ReadBuffer<Word: Word>,
D: WriteBuffer<Word: Word>,
{
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.transfer
}
}

impl<'a, CH, S, D> Unpin for DmaTransferFuture<'a, CH, S, D>
where
CH: DmaChannel,
S: ReadBuffer<Word: Word>,
D: WriteBuffer<Word: Word>,
{
}

impl<'a, CH, S, D> Future for DmaTransferFuture<'a, CH, S, D>
where
CH: DmaChannel + ChannelWaker,
S: ReadBuffer<Word: Word>,
D: WriteBuffer<Word: Word>,
{
type Output = Result<(), Error>;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
self.channel.waker().register(cx.waker());
if self.channel.check_transfer_complete()? {
Poll::Ready(Ok(()))
} else {
Poll::Pending
}
}
}

#[allow(private_bounds)]
impl<DMA, CH, const N: usize> DmaChannelRef<DMA, CH, N>
where
DMA: Instance + InstanceWaker,
CH: ChannelRegs,
Self: Deref<Target = CH>,
{
#[inline(always)]
fn handle_interrupt() {
// Safety:
// This creates a DmaChannelRef instance for channel N, which is be a duplicate to the
// DmaChannelRef instance that is held as a mutable reference in the DmaTransfer struct
// while a transfer is in progress. However, it is only used to disable the transfer
// interrupts for the channel and wake up the task that is waiting for the transfer to
// complete, which can be done safely.
Comment on lines +156 to +160
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like the the motivation of an unsafe block. Should DmaChannelRef::new be made unsafe?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was thinking the same while writing it, so I think it makes sense to mark new as unsafe

//
// When the DmaTransfer struct is dropped, interrupts are disabled for the channel,
// preventing this interrupt from being triggered. Interrupts are only enabled when the
// transfer is awaited (calling IntoFuture::into_future).
let mut ch = unsafe { Self::new_unsafe() };

// This is a single volatile write to the channel's interrupt status register to disable
// interrupts for the channel so this interrupt doesn't trigger again while the transfer
// struct is being polled.
ch.disable_transfer_interrupts();

// This is an atomic operation to wake up the task that is waiting for the transfer to
// complete.
ch.waker().wake();
}
}

impl<DMA, CH, const N: usize> ChannelWaker for DmaChannelRef<DMA, CH, N>
where
DMA: Instance + InstanceWaker,
CH: ChannelRegs,
Self: Deref<Target = CH>,
{
#[inline(always)]
fn waker(&self) -> &'static AtomicWaker {
DMA::waker(N)
}
}

/// Private trait that provides access to the [`AtomicWaker`]s for a particular GPDMA . It is only
/// implemented when the `gpdma-futures` feature is enabled.
pub(super) trait InstanceWaker {
fn waker(idx: usize) -> &'static AtomicWaker;
}

/// Private trait that provides access to the [`AtomicWaker`] for a specific channel. It is only
/// implemented when the `gpdma-futures` feature is enabled.
pub(super) trait ChannelWaker {
/// Returns a reference to the AtomicWaker for the channel.
fn waker(&self) -> &'static AtomicWaker;
}

macro_rules! gpdma_irq {
($GPDMA:ident, $CH:literal) => {
paste::item! {
#[interrupt]
fn [<$GPDMA _CH $CH>]() {
[< DmaChannel $CH>]::<$GPDMA>::handle_interrupt();
}
}
};
}

mod gpdma1 {
use super::*;

static WAKERS_GPDMA1: [AtomicWaker; 8] = [const { AtomicWaker::new() }; 8];

#[allow(private_bounds)]
impl InstanceWaker for GPDMA1 {
#[inline(always)]
fn waker(idx: usize) -> &'static AtomicWaker {
&WAKERS_GPDMA1[idx]
}
}

gpdma_irq!(GPDMA1, 0);
gpdma_irq!(GPDMA1, 1);
gpdma_irq!(GPDMA1, 2);
gpdma_irq!(GPDMA1, 3);
gpdma_irq!(GPDMA1, 4);
gpdma_irq!(GPDMA1, 5);
gpdma_irq!(GPDMA1, 6);
gpdma_irq!(GPDMA1, 7);
}

mod gpdma2 {
use super::*;

static WAKERS_GPDMA2: [AtomicWaker; 8] = [const { AtomicWaker::new() }; 8];

#[allow(private_bounds)]
impl InstanceWaker for GPDMA2 {
#[inline(always)]
fn waker(idx: usize) -> &'static AtomicWaker {
&WAKERS_GPDMA2[idx]
}
}

gpdma_irq!(GPDMA2, 0);
gpdma_irq!(GPDMA2, 1);
gpdma_irq!(GPDMA2, 2);
gpdma_irq!(GPDMA2, 3);
gpdma_irq!(GPDMA2, 4);
gpdma_irq!(GPDMA2, 5);
gpdma_irq!(GPDMA2, 6);
gpdma_irq!(GPDMA2, 7);
}