Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DMA 6/8] More helper types & working split #2532

Merged
merged 7 commits into from
Nov 22, 2024
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
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