atsamd_hal/dmac/
mod.rs

1//! # Direct Memory Access Controller
2//!
3//! This library provides a type-safe API with compile-time guarantees
4//! that the peripheral and individual DMA channels are correctly configured
5//! before launching a DMA transfer.
6//!
7//! This module currently supports most basic DMA
8//! functions, including memory-to-memory,
9//! memory-to-peripheral, peripheral-to-memory,
10//! and peripheral-to-peripheral transfers.
11//! One-shot and circular transfers are supported. More complex
12//! transfer configurations, including multi-buffer
13//! (linked-list descriptor) transfers, are not currently supported.
14//!
15//! Transfers are supported for `i8`, `u8`, `i16`, `u16`, `i32`, `u32` and `f32`
16//! beat sizes.
17//!
18//! # Enabling DMA support
19//!
20//! You must enable the `dma` feature in your board support crate
21//! or final executable.
22//!
23//! Add this to your `Cargo.toml`:
24//! ```
25//! [features]
26//! dma = ["atsamd-hal/dma"]
27//! ```
28//!
29//! # Channels and RAM
30//!
31//! Using DMA channels require a certain amount of RAM - 32 bytes per channel,
32//! to be exact. RAM will be not allocated unless the `dma` feature is enabled
33//! for the HAL. By default, half the channels available on the chip are
34//! enabled. If you need all DMA channels enabled, enable the `max-channels`
35//! feature in your board support crate or final executable.
36//!
37//! `Cargo.toml`
38//! ```
39//! [features]
40//! dma = ["atsamd-hal/dma"]
41//! max-channels = ["dma", "atsamd-hal/max-channels"]
42//! ```
43//!
44//! RAM usage per chip family:
45//!
46//! * `ATSAMD11` - 3 channels (default): 96 bytes
47//!
48//! * `ATSAMD11` - 6 channels (max): 192 bytes
49//!
50//! * `ATSAMD21` - 6 channels (default): 192 bytes
51//!
52//! * `ATSAMD21`: - 12 channels (max): 384 bytes
53//!
54//! * `ATSAMD51/ATSAME5x`: - 16 channels (default): 512 bytes
55//!
56//! * `ATSAMD51/ATSAME5x`: - 32 channels (max): 1024 bytes
57//!
58//! # Priority levels and Arbitration
59//!
60//! The DMAC features 4 priority levels. Level 3 has the highest priority
61//! and level 0 has the lowest. Each channel can be assigned to one priority
62//! level. If two channels with the same priority level are requested to
63//! execute a transfer at the same time, the lowest channel number will have
64//! priority (in the default, ie static, arbitration scheme).
65//!
66//! By default, all priority levels are enabled when initializing the DMAC
67//! (see [`DmaController::init`]). Levels
68//! can be enabled or disabled through the
69//! [`DmaController::enable_levels`] and
70//! [`DmaController::disable_levels`] methods. These methods must be supplied a
71//! [`PriorityLevelMask`].
72//!
73//! Round-Robin Arbitration can be enabled for multiple priority levels
74//! simultaneously by using the
75//! [`DmaController::round_robin_arbitration`] and
76//! [`DmaController::static_arbitration`] methods. These methods must be
77//! supplied a [`RoundRobinMask`]. By default, all priority levels are
78//! initialized with a static arbitration scheme. See ATSAMD21 datasheet section
79//! 19.6.2.4 for more information.
80//!
81//! # Interrupts
82//!
83//! This driver does not use or manage interrupts issued by the DMAC. Individual
84//! channels can be configured to generate interrupts when the transfer is
85//! complete, an error is detected or the channel is suspended. However, these
86//! interrupts will not be triggered unless the DMAC interrupt is unmasked in
87//! the NVIC. You will be responsible for clearing the interrupt flags in the
88//! ISR.
89//!
90//! # About static lifetimes
91//!
92//! The safe API this driver offers requires all buffers (source and
93//! destination) to have `'static` lifetimes. This is because
94//! [`mem::forget`](core::mem::forget) is a safe API, and therefore relying on
95//! [`mem::drop`](core::mem::drop) to terminate or abort a transfer
96//! does not guarantee the transfer will be terminated (specifically if
97//! [`mem::forget`](core::mem::forget) is called on a `Transfer` containaing
98//! a `Channel<Id, Busy>`). This could cause the compiler to reclaim
99//! stack-allocated buffers for reuse while the DMAC is still writing to/reading
100//! from them! Needless to say that is very unsafe.
101//! Refer [here](https://docs.rust-embedded.org/embedonomicon/dma.html#memforget)
102//! or [here](https://blog.japaric.io/safe-dma/#leakpocalypse) for more information.
103//! You may choose to forgo the `'static` lifetimes by using the unsafe API and
104//! the [`Transfer::new_unchecked`](transfer::Transfer::new_unchecked) method.
105//!
106//! # Unsafe API
107//!
108//! This driver also offers an `unsafe` API through the
109//! [`Transfer::new_unchecked`] method. It
110//! does not enforce `'static` lifetimes, and allow using buffers of different
111//! lengths. If you choose to use these methods, you MUST prove that
112//! a `Transfer` containing a `Channel<Id, Busy>` will NEVER be dropped. You
113//! *must* call `wait()` or `stop()` manually on every
114//! `Transfer` that has been created using the unsafe API. No destructor or
115//! `Drop` implementation is offered for `Transfer`s.
116//!
117//! Additionally, you can (unsafely) implement your own buffer types through the
118//! unsafe [`Buffer`] trait.
119//!
120//! # Example
121//! ```
122//! let mut peripherals = Peripherals::take().unwrap();
123//! let mut dmac = DmaController::init(peripherals.DMAC, &mut peripherals.PM);
124//! // Get individual handles to DMA channels
125//! let channels = dmac.split();
126//!
127//! // Initialize DMA Channel 0
128//! let chan0 = channels.0.init(PriorityLevel::LVL0, false, &mut dmac);
129//!
130//! // Setup a DMA transfer (memory-to-memory -> incrementing source, incrementing destination)
131//! // NOTE: buf_src and buf_dest should be either:
132//! // &'static mut T, &'static mut [T], or &'static mut [T; N] where T: BeatSize
133//! let xfer = Transfer::new(chan0, buf_src, buf_dest, false).begin(
134//!     &mut dmac,
135//!     TriggerSource::DISABLE,
136//!     TriggerAction::BLOCK,
137//! );
138//!
139//! // Wait for transfer to complete and grab resulting buffers
140//! let (chan0, buf_src, buf_dest, _) = xfer.wait(&mut dmac);
141//!
142//! // (Optional) free the [`DmaController`] struct and return the underlying PAC struct
143//! channels.0 = chan0.into();
144//! let dmac = dmac.free(channels, &mut peripherals.PM);
145//! ```
146//!
147//! # [`Transfer`] recycling
148//!
149//! A common use-case with DMAC transfers is to trigger a new transfer as soon
150//! as the old transfer is completed. To avoid having to
151//! [`stop`](Transfer::stop) a [`Transfer`], build a new [`Transfer`] (with
152//! [`new`](Transfer::new) or [`new_from_arrays`](Transfer::new_from_arrays))
153//! then call [`begin`](Transfer::begin), a [`Transfer::recycle`] method
154//! is provided. If the buffer lengths match and the previous transfer is
155//! completed, a new transfer will immediately be triggered using the provided
156//! source and destination buffers. If the recycling operation is succesful,
157//! `Ok((source, destination))` containing the old source and destination
158//! buffers is returned. Otherwise, `Err(_)` is returned.
159//!
160//! ```
161//! let new_source = produce_source();
162//! let new_destination = produce_destination();
163//!
164//! // Assume xfer is a `Busy` `Transfer`
165//! let (old_source, old_dest) = xfer.recycle(new_source, new_destination).unwrap();
166//! ```
167//!
168//! # Waker operation
169//!
170//! A [`Transfer`] can also accept a function or closure that will be called on
171//! completion of the transaction, acting like a waker.
172//!
173//! ```
174//! fn wake_up() {
175//!     //...
176//! }
177//!
178//! fn use_waker<const N: usize>(dmac: DmaController,
179//!     source: &'static mut [u8; N],
180//!     destination: &'static mut [u8; N]
181//! ){
182//!     let chan0 = dmac.split().0;
183//!     let xfer = Transfer::new_from_arrays(chan0, source, destination, false)
184//!         .with_waker(wake_up)
185//!         .begin();
186//!     //...
187//! }
188//! ```
189//!
190//! ## RTIC example
191//!
192//! The [RTIC] framework provides a convenient way to store a `static`ally
193//! allocated [`Transfer`], so that it can be accessed by both the interrupt
194//! handlers and user code. The following example shows how [`Transfer`]s might
195//! be used for a series of transactions. It uses features from the latest
196//! release of [RTIC], `v0.6-alpha.4`.
197//!
198//! ```
199//! use atsamd_hal::dmac::*;
200//!
201//! const LENGTH: usize = 50;
202//! type TransferBuffer = &'static mut [u8; LENGTH];
203//! type Xfer = Transfer<Channel<Ch0, Busy>, TransferBuffer, TransferBuffer>;
204//!
205//! #[resources]
206//! struct Resources {
207//!     #[lock_free]
208//!     #[init(None)]
209//!     opt_xfer: Option<Xfer>,
210//!
211//!     #[lock_free]
212//!     #[init(None)]
213//!     opt_channel: Option<Channel<Ch0, Ready>>,
214//! }
215//!
216//! // Note: Assume interrupts have already been enabled for the concerned channel
217//! #[task(resources = [opt_xfer, opt_channel])]
218//! fn task(ctx: task::Context) {
219//!     let task::Context { opt_xfer } = ctx;
220//!     match opt_xfer {
221//!         Some(xfer) => {
222//!             if xfer.complete() {
223//!                 let (chan0, _source, dest, _payload) = xfer.take().unwrap().stop();
224//!                 *opt_channel = Some(chan0);
225//!                 consume_data(buf);
226//!             }
227//!         }
228//!         None => {
229//!             if let Some(chan0) = opt_channel.take() {
230//!                 let source: [u8; 50] = produce_source();
231//!                 let dest: [u8; 50] = produce_destination();
232//!                 let xfer = opt_xfer.get_or_insert(
233//!                     Transfer::new_from_arrays(channel0, source, destination)
234//!                         .with_waker(|| { task::spawn().ok(); })
235//!                         .begin()
236//!                 );
237//!             }
238//!         }
239//!     }
240//! }
241//!
242//! #[task(binds = DMAC, resources = [opt_future])]
243//! fn tcmpl(ctx: tcmpl::Context) {
244//!     ctx.resources.opt_xfer.as_mut().unwrap().callback();
245//! }
246//! ```
247//! [RTIC]: https://rtic.rs
248
249// This is necessary until modular_bitfield fixes all their identity_op warnings
250#![allow(clippy::identity_op)]
251
252use atsamd_hal_macros::hal_cfg;
253
254pub use channel::*;
255pub use dma_controller::*;
256pub use transfer::*;
257
258#[derive(Debug, Clone, Copy, Eq, PartialEq)]
259#[cfg_attr(feature = "defmt", derive(defmt::Format))]
260/// Runtime errors that may occur when dealing with DMA transfers.
261pub enum Error {
262    /// Supplied buffers both have lengths > 1 beat, but not equal to each other
263    ///
264    /// Buffers need to either have the same length in beats, or one should have
265    /// length == 1.  In cases where one buffer is length 1, that buffer will be
266    /// the source or destination of each beat in the transfer.  If both buffers
267    /// had length >1, but not equal to each other, then it would not be clear
268    /// how to structure the transfer.
269    LengthMismatch,
270
271    /// Operation is not valid in the current state of the object.
272    InvalidState,
273    /// Chip reported an error during transfer
274    TransferError,
275}
276
277impl From<Error> for crate::sercom::spi::Error {
278    fn from(value: Error) -> Self {
279        crate::sercom::spi::Error::Dma(value)
280    }
281}
282
283impl From<Error> for crate::sercom::i2c::Error {
284    fn from(value: Error) -> Self {
285        crate::sercom::i2c::Error::Dma(value)
286    }
287}
288
289impl From<Error> for crate::sercom::uart::Error {
290    fn from(value: Error) -> Self {
291        crate::sercom::uart::Error::Dma(value)
292    }
293}
294
295/// Result for DMAC operations
296pub type Result<T> = core::result::Result<T, Error>;
297
298#[cfg(feature = "max-channels")]
299#[hal_cfg("dmac-d11")]
300#[macro_export]
301macro_rules! with_num_channels {
302    ($some_macro:ident) => {
303        $some_macro! {6}
304    };
305}
306
307#[cfg(feature = "max-channels")]
308#[hal_cfg("dmac-d21")]
309#[macro_export]
310macro_rules! with_num_channels {
311    ($some_macro:ident) => {
312        $some_macro! {12}
313    };
314}
315
316#[cfg(feature = "max-channels")]
317#[hal_cfg("dmac-d5x")]
318#[macro_export]
319macro_rules! with_num_channels {
320    ($some_macro:ident) => {
321        $some_macro! {32}
322    };
323}
324
325#[cfg(not(feature = "max-channels"))]
326#[hal_cfg("dmac-d11")]
327#[macro_export]
328macro_rules! with_num_channels {
329    ($some_macro:ident) => {
330        $some_macro! {3}
331    };
332}
333
334#[cfg(not(feature = "max-channels"))]
335#[hal_cfg("dmac-d21")]
336#[macro_export]
337macro_rules! with_num_channels {
338    ($some_macro:ident) => {
339        $some_macro! {6}
340    };
341}
342
343#[cfg(not(feature = "max-channels"))]
344#[hal_cfg("dmac-d5x")]
345#[macro_export]
346macro_rules! with_num_channels {
347    ($some_macro:ident) => {
348        $some_macro! {16}
349    };
350}
351
352macro_rules! get {
353    ($literal:literal) => {
354        $literal
355    };
356}
357
358/// Number of DMA channels used by the driver
359pub const NUM_CHANNELS: usize = with_num_channels!(get);
360
361/// DMAC SRAM registers
362pub(crate) mod sram {
363    #![allow(dead_code, unused_braces)]
364
365    use core::cell::UnsafeCell;
366
367    use super::{BeatSize, NUM_CHANNELS};
368
369    use modular_bitfield::{
370        bitfield,
371        specifiers::{B2, B3},
372    };
373
374    /// Wrapper type around a [`DmacDescriptor`] to allow interior mutability
375    /// while keeping them in static storage
376    #[repr(transparent)]
377    pub struct DescriptorCell(UnsafeCell<DmacDescriptor>);
378
379    impl DescriptorCell {
380        const fn default() -> Self {
381            Self(UnsafeCell::new(DmacDescriptor::default()))
382        }
383    }
384
385    // DescriptorCell is not not *really* sync; we must manually uphold the sync
386    // guarantees on every access.
387    unsafe impl Sync for DescriptorCell {}
388
389    impl core::ops::Deref for DescriptorCell {
390        type Target = UnsafeCell<DmacDescriptor>;
391
392        fn deref(&self) -> &Self::Target {
393            &self.0
394        }
395    }
396
397    impl core::ops::DerefMut for DescriptorCell {
398        fn deref_mut(&mut self) -> &mut Self::Target {
399            &mut self.0
400        }
401    }
402
403    /// Bitfield representing the BTCTRL SRAM DMAC register
404    #[allow(unused_braces)]
405    #[bitfield]
406    #[derive(Clone, Copy)]
407    #[repr(u16)]
408    pub(super) struct BlockTransferControl {
409        pub(super) valid: bool,
410        pub(super) evosel: B2,
411        pub(super) blockact: B2,
412        #[skip]
413        _reserved: B3,
414        #[bits = 2]
415        pub(super) beatsize: BeatSize,
416        pub(super) srcinc: bool,
417        pub(super) dstinc: bool,
418        pub(super) stepsel: bool,
419        pub(super) stepsize: B3,
420    }
421
422    impl Default for BlockTransferControl {
423        fn default() -> Self {
424            Self::new()
425        }
426    }
427
428    /// Descriptor representing a SRAM register. Datasheet section 19.8.2
429    #[derive(Clone, Copy)]
430    #[repr(C, align(16))]
431    pub struct DmacDescriptor {
432        pub(super) btctrl: BlockTransferControl,
433        pub(super) btcnt: u16,
434        pub(super) srcaddr: *const (),
435        pub(super) dstaddr: *const (),
436        pub(super) descaddr: *const DmacDescriptor,
437    }
438
439    impl DmacDescriptor {
440        pub const fn default() -> Self {
441            Self {
442                btctrl: BlockTransferControl::new(),
443                btcnt: 0,
444                srcaddr: 0 as *mut _,
445                dstaddr: 0 as *mut _,
446                descaddr: 0 as *mut _,
447            }
448        }
449
450        pub fn next_descriptor(&self) -> *const DmacDescriptor {
451            self.descaddr
452        }
453
454        pub fn set_next_descriptor(&mut self, next: *mut DmacDescriptor) {
455            self.descaddr = next;
456        }
457
458        pub fn beat_count(&self) -> u16 {
459            self.btcnt
460        }
461    }
462
463    /// Writeback section.
464    ///
465    /// # Safety
466    ///
467    /// This variable should never be accessed. The only thing we need
468    /// to know about it is its starting address, given by
469    /// [`writeback_addr`].
470    static WRITEBACK: [DescriptorCell; NUM_CHANNELS] =
471        [const { DescriptorCell::default() }; NUM_CHANNELS];
472
473    // We only ever need to know its starting address.
474    pub(super) fn writeback_addr() -> *mut DmacDescriptor {
475        WRITEBACK[0].get()
476    }
477
478    /// Descriptor section.
479    ///
480    /// # Safety
481    ///
482    /// All accesses to this variable should be synchronized. Elements of the
483    /// array should only ever be accessed using [`UnsafeCell::get`]. Any other
484    /// access method, such as taking a reference to the [`UnsafeCell`] itself,
485    /// is UB and *will* break DMA transfers - speaking from personal
486    /// experience.
487    static DESCRIPTOR_SECTION: [DescriptorCell; NUM_CHANNELS] =
488        [const { DescriptorCell::default() }; NUM_CHANNELS];
489
490    #[inline]
491    pub(super) fn descriptor_section_addr() -> *mut DmacDescriptor {
492        DESCRIPTOR_SECTION[0].get()
493    }
494
495    /// Get a mutable pointer to the specified channel's DMAC descriptor
496    ///
497    /// # Safety
498    ///
499    /// The caller must manually synchronize any access to the pointee
500    /// [`DmacDescriptor`].
501    ///
502    /// Additionnally, if the pointer is used to create references to
503    /// [`DmacDescriptor`], the caller must guarantee that there will **never**
504    /// be overlapping `&mut` references (or overlapping `&mut` and `&`
505    /// references) to the pointee *at any given time*, as it would be
506    /// instantaneous undefined behaviour.
507    #[inline]
508    pub(super) unsafe fn get_descriptor(channel_id: usize) -> *mut DmacDescriptor {
509        DESCRIPTOR_SECTION[channel_id].get()
510    }
511}
512
513pub mod channel;
514pub mod dma_controller;
515pub mod transfer;
516
517#[cfg(feature = "async")]
518pub mod async_api;
519#[cfg(feature = "async")]
520pub use async_api::*;
521
522#[cfg(feature = "async")]
523mod waker {
524    use embassy_sync::waitqueue::AtomicWaker;
525
526    #[allow(clippy::declare_interior_mutable_const)]
527    const NEW_WAKER: AtomicWaker = AtomicWaker::new();
528    pub(super) static WAKERS: [AtomicWaker; with_num_channels!(get)] =
529        [NEW_WAKER; with_num_channels!(get)];
530}