Skip to content

Commit

Permalink
[DMA 6/8] More helper types & working split (#2532)
Browse files Browse the repository at this point in the history
* Add helper traits to simplify DMA channel trait bounds

* Document changes

* Update doc example

* Remove clutter from MG

* Move DmaEligible down to place helper types closer

* Rename

* Include split in the MG
  • Loading branch information
bugadani authored Nov 22, 2024
1 parent c1f0c13 commit e98674e
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 98 deletions.
3 changes: 2 additions & 1 deletion esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ESP32-S3: Added SDMMC signals (#2556)
- Added `set_priority` to the `DmaChannel` trait on GDMA devices (#2403, #2526)
- Added `into_async` and `into_blocking` functions for `ParlIoTxOnly`, `ParlIoRxOnly` (#2526)
- ESP32-C6, H2, S3: Added `split` function to the `DmaChannel` trait. (#2526)
- ESP32-C6, H2, S3: Added `split` function to the `DmaChannel` trait. (#2526, #2532)
- DMA: `PeripheralDmaChannel` type aliasses and `DmaChannelFor` traits to improve usability. (#2532)

### Changed

Expand Down
74 changes: 73 additions & 1 deletion esp-hal/MIGRATING-0.22.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Migration Guide from 0.22.x to v1.0.0-beta.0

## DMA configuration changes
## DMA changes

### Configuration changes

- `configure_for_async` and `configure` have been removed
- PDMA devices (ESP32, ESP32-S2) provide no configurability
Expand All @@ -27,6 +29,76 @@
+.with_dma(dma_channel);
```

### Usability changes affecting applications

Individual channels are no longer wrapped in `Channel`, but they implement the `DmaChannel` trait.
This means that if you want to split them into an `rx` and a `tx` half (which is only supported on
the H2, C6 and S3 currently), you can't move out of the channel but instead you need to call
the `split` method.

```diff
-let tx = channel.tx;
+use esp_hal::dma::DmaChannel;
+let (rx, tx) = channel.split();
```

The `Channel` types remain available for use in peripheral drivers.

It is now simpler to work with DMA channels in generic contexts. esp-hal now provides convenience
traits and type aliasses to specify peripheral compatibility. The `ChannelCreator` types have been
removed, further simplifying use.

For example, previously you may have needed to write something like this to accept a DMA channel
in a generic function:

```rust
fn new_foo<'d, T>(
dma_channel: ChannelCreator<2>, // It wasn't possible to accept a generic ChannelCreator.
peripheral: impl Peripheral<P = T> + 'd,
)
where
T: SomePeripheralInstance,
ChannelCreator<2>: DmaChannelConvert<<T as DmaEligible>::Dma>,
{
let dma_channel = dma_channel.configure_for_async(false, DmaPriority::Priority0);

let driver = PeripheralDriver::new(peripheral, config).with_dma(dma_channel);

// ...
}
```

From now on a similar, but more flexible implementation may look like:

```rust
fn new_foo<'d, T, CH>(
dma_channel: impl Peripheral<P = CH> + 'd,
peripheral: impl Peripheral<P = T> + 'd,
)
where
T: SomePeripheralInstance,
CH: DmaChannelFor<T>,
{
// Optionally: dma_channel.set_priority(DmaPriority::Priority2);

let driver = PeripheralDriver::new(peripheral, config).with_dma(dma_channel);

// ...
}
```

### Usability changes affecting third party peripheral drivers

If you are writing a driver and need to store a channel in a structure, you can use one of the
`ChannelFor` type aliasses.

```diff
struct Aes<'d> {
- channel: ChannelTx<'d, Blocking, <AES as DmaEligible>::Dma>,
+ channel: ChannelTx<'d, Blocking, PeripheralTxChannel<AES>>,
}
```

## Timer changes

The low level timers, `SystemTimer` and `TimerGroup` are now "dumb". They contain no logic for operating modes or trait implementations (except the low level `Timer` trait).
Expand Down
14 changes: 7 additions & 7 deletions esp-hal/src/aes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,16 +241,16 @@ pub mod dma {
ChannelRx,
ChannelTx,
DescriptorChain,
DmaChannelConvert,
DmaChannelFor,
DmaDescriptor,
DmaPeripheral,
DmaTransferRxTx,
PeripheralDmaChannel,
PeripheralRxChannel,
PeripheralTxChannel,
ReadBuffer,
Rx,
RxChannelFor,
Tx,
TxChannelFor,
WriteBuffer,
},
peripheral::Peripheral,
Expand Down Expand Up @@ -281,7 +281,7 @@ pub mod dma {
/// The underlying [`Aes`](super::Aes) driver
pub aes: super::Aes<'d>,

channel: Channel<'d, Blocking, DmaChannelFor<AES>>,
channel: Channel<'d, Blocking, PeripheralDmaChannel<AES>>,
rx_chain: DescriptorChain,
tx_chain: DescriptorChain,
}
Expand All @@ -295,7 +295,7 @@ pub mod dma {
tx_descriptors: &'static mut [DmaDescriptor],
) -> AesDma<'d>
where
CH: DmaChannelConvert<DmaChannelFor<AES>>,
CH: DmaChannelFor<AES>,
{
let channel = Channel::new(channel.map(|ch| ch.degrade()));
channel.runtime_ensure_compatible(&self.aes);
Expand Down Expand Up @@ -331,7 +331,7 @@ pub mod dma {
}

impl<'d> DmaSupportTx for AesDma<'d> {
type TX = ChannelTx<'d, Blocking, TxChannelFor<AES>>;
type TX = ChannelTx<'d, Blocking, PeripheralTxChannel<AES>>;

fn tx(&mut self) -> &mut Self::TX {
&mut self.channel.tx
Expand All @@ -343,7 +343,7 @@ pub mod dma {
}

impl<'d> DmaSupportRx for AesDma<'d> {
type RX = ChannelRx<'d, Blocking, RxChannelFor<AES>>;
type RX = ChannelRx<'d, Blocking, PeripheralRxChannel<AES>>;

fn rx(&mut self) -> &mut Self::RX {
&mut self.channel.rx
Expand Down
12 changes: 12 additions & 0 deletions esp-hal/src/dma/gdma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ impl Peripheral for AnyGdmaRxChannel {
}
}

impl DmaChannelConvert<AnyGdmaRxChannel> for AnyGdmaRxChannel {
fn degrade(self) -> AnyGdmaRxChannel {
self
}
}

/// An arbitrary GDMA TX channel
pub struct AnyGdmaTxChannel(u8);

Expand All @@ -71,6 +77,12 @@ impl Peripheral for AnyGdmaTxChannel {
}
}

impl DmaChannelConvert<AnyGdmaTxChannel> for AnyGdmaTxChannel {
fn degrade(self) -> AnyGdmaTxChannel {
self
}
}

use embassy_sync::waitqueue::AtomicWaker;

static TX_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT];
Expand Down
154 changes: 121 additions & 33 deletions esp-hal/src/dma/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -944,38 +944,6 @@ pub trait DmaEligible {
fn dma_peripheral(&self) -> DmaPeripheral;
}

/// Helper type to get the DMA (Rx and Tx) channel for a peripheral.
pub type DmaChannelFor<T> = <T as DmaEligible>::Dma;
/// Helper type to get the DMA Rx channel for a peripheral.
pub type RxChannelFor<T> = <DmaChannelFor<T> as DmaChannel>::Rx;
/// Helper type to get the DMA Tx channel for a peripheral.
pub type TxChannelFor<T> = <DmaChannelFor<T> as DmaChannel>::Tx;

#[doc(hidden)]
#[macro_export]
macro_rules! impl_dma_eligible {
([$dma_ch:ident] $name:ident => $dma:ident) => {
impl $crate::dma::DmaEligible for $crate::peripherals::$name {
type Dma = $dma_ch;

fn dma_peripheral(&self) -> $crate::dma::DmaPeripheral {
$crate::dma::DmaPeripheral::$dma
}
}
};

(
$dma_ch:ident {
$($(#[$cfg:meta])? $name:ident => $dma:ident,)*
}
) => {
$(
$(#[$cfg])?
$crate::impl_dma_eligible!([$dma_ch] $name => $dma);
)*
};
}

#[doc(hidden)]
#[derive(Debug)]
pub struct DescriptorChain {
Expand Down Expand Up @@ -1593,6 +1561,38 @@ impl RxCircularState {
}
}

#[doc(hidden)]
#[macro_export]
macro_rules! impl_dma_eligible {
([$dma_ch:ident] $name:ident => $dma:ident) => {
impl $crate::dma::DmaEligible for $crate::peripherals::$name {
type Dma = $dma_ch;

fn dma_peripheral(&self) -> $crate::dma::DmaPeripheral {
$crate::dma::DmaPeripheral::$dma
}
}
};

(
$dma_ch:ident {
$($(#[$cfg:meta])? $name:ident => $dma:ident,)*
}
) => {
$(
$(#[$cfg])?
$crate::impl_dma_eligible!([$dma_ch] $name => $dma);
)*
};
}

/// Helper type to get the DMA (Rx and Tx) channel for a peripheral.
pub type PeripheralDmaChannel<T> = <T as DmaEligible>::Dma;
/// Helper type to get the DMA Rx channel for a peripheral.
pub type PeripheralRxChannel<T> = <PeripheralDmaChannel<T> as DmaChannel>::Rx;
/// Helper type to get the DMA Tx channel for a peripheral.
pub type PeripheralTxChannel<T> = <PeripheralDmaChannel<T> as DmaChannel>::Tx;

#[doc(hidden)]
pub trait DmaRxChannel:
RxRegisterAccess + InterruptAccess<DmaRxInterrupt> + Peripheral<P = Self>
Expand Down Expand Up @@ -1647,7 +1647,7 @@ pub trait DmaChannelExt: DmaChannel {
note = "Not all channels are useable with all peripherals"
)]
#[doc(hidden)]
pub trait DmaChannelConvert<DEG>: DmaChannel {
pub trait DmaChannelConvert<DEG> {
fn degrade(self) -> DEG;
}

Expand All @@ -1657,6 +1657,94 @@ impl<DEG: DmaChannel> DmaChannelConvert<DEG> for DEG {
}
}

/// Trait implemented for DMA channels that are compatible with a particular
/// peripheral.
///
/// You can use this in places where a peripheral driver would expect a
/// `DmaChannel` implementation.
#[cfg_attr(pdma, doc = "")]
#[cfg_attr(
pdma,
doc = "Note that using mismatching channels (e.g. trying to use `spi2channel` with SPI3) may compile, but will panic in runtime."
)]
#[cfg_attr(pdma, doc = "")]
/// ## Example
///
/// The following example demonstrates how this trait can be used to only accept
/// types compatible with a specific peripheral.
///
/// ```rust,no_run
#[doc = crate::before_snippet!()]
/// use esp_hal::spi::master::{Spi, SpiDma, Config, Instance as SpiInstance};
/// use esp_hal::dma::DmaChannelFor;
/// use esp_hal::peripheral::Peripheral;
/// use esp_hal::Blocking;
/// use esp_hal::dma::Dma;
///
/// fn configures_spi_dma<'d, S, CH>(
/// spi: Spi<'d, Blocking, S>,
/// channel: impl Peripheral<P = CH> + 'd,
/// ) -> SpiDma<'d, Blocking, S>
/// where
/// S: SpiInstance,
/// CH: DmaChannelFor<S> + 'd,
/// {
/// spi.with_dma(channel)
/// }
///
/// let dma = Dma::new(peripherals.DMA);
#[cfg_attr(pdma, doc = "let dma_channel = dma.spi2channel;")]
#[cfg_attr(gdma, doc = "let dma_channel = dma.channel0;")]
#[doc = ""]
/// let spi = Spi::new_with_config(
/// peripherals.SPI2,
/// Config::default(),
/// );
///
/// let spi_dma = configures_spi_dma(spi, dma_channel);
/// # }
/// ```
pub trait DmaChannelFor<P: DmaEligible>:
DmaChannel + DmaChannelConvert<PeripheralDmaChannel<P>>
{
}
impl<P, CH> DmaChannelFor<P> for CH
where
P: DmaEligible,
CH: DmaChannel + DmaChannelConvert<PeripheralDmaChannel<P>>,
{
}

/// Trait implemented for the RX half of split DMA channels that are compatible
/// with a particular peripheral. Accepts complete DMA channels or split halves.
///
/// This trait is similar in use to [`DmaChannelFor`].
///
/// You can use this in places where a peripheral driver would expect a
/// `DmaRxChannel` implementation.
pub trait RxChannelFor<P: DmaEligible>: DmaChannelConvert<PeripheralRxChannel<P>> {}
impl<P, RX> RxChannelFor<P> for RX
where
P: DmaEligible,
RX: DmaChannelConvert<PeripheralRxChannel<P>>,
{
}

/// Trait implemented for the TX half of split DMA channels that are compatible
/// with a particular peripheral. Accepts complete DMA channels or split halves.
///
/// This trait is similar in use to [`DmaChannelFor`].
///
/// You can use this in places where a peripheral driver would expect a
/// `DmaTxChannel` implementation.
pub trait TxChannelFor<PER: DmaEligible>: DmaChannelConvert<PeripheralTxChannel<PER>> {}
impl<P, TX> TxChannelFor<P> for TX
where
P: DmaEligible,
TX: DmaChannelConvert<PeripheralTxChannel<P>>,
{
}

/// The functions here are not meant to be used outside the HAL
#[doc(hidden)]
pub trait Rx: crate::private::Sealed {
Expand Down
Loading

0 comments on commit e98674e

Please sign in to comment.