Skip to main content

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    /// The DMAC only supports up to `u16::MAX` beats in a single transfer.
272    TooManyBeats,
273
274    /// Operation is not valid in the current state of the object.
275    InvalidState,
276
277    /// Chip reported an error during transfer
278    TransferError,
279}
280
281impl From<Error> for crate::sercom::spi::Error {
282    fn from(value: Error) -> Self {
283        crate::sercom::spi::Error::Dma(value)
284    }
285}
286
287impl From<Error> for crate::sercom::i2c::Error {
288    fn from(value: Error) -> Self {
289        crate::sercom::i2c::Error::Dma(value)
290    }
291}
292
293impl From<Error> for crate::sercom::uart::Error {
294    fn from(value: Error) -> Self {
295        crate::sercom::uart::Error::Dma(value)
296    }
297}
298
299/// Result for DMAC operations
300pub type Result<T> = core::result::Result<T, Error>;
301
302#[cfg(feature = "max-channels")]
303#[hal_cfg("dmac-d11")]
304#[macro_export]
305macro_rules! with_num_channels {
306    ($some_macro:ident) => {
307        $some_macro! {6}
308    };
309}
310
311#[cfg(feature = "max-channels")]
312#[hal_cfg("dmac-d21")]
313#[macro_export]
314macro_rules! with_num_channels {
315    ($some_macro:ident) => {
316        $some_macro! {12}
317    };
318}
319
320#[cfg(feature = "max-channels")]
321#[hal_cfg("dmac-d5x")]
322#[macro_export]
323macro_rules! with_num_channels {
324    ($some_macro:ident) => {
325        $some_macro! {32}
326    };
327}
328
329#[cfg(not(feature = "max-channels"))]
330#[hal_cfg("dmac-d11")]
331#[macro_export]
332macro_rules! with_num_channels {
333    ($some_macro:ident) => {
334        $some_macro! {3}
335    };
336}
337
338#[cfg(not(feature = "max-channels"))]
339#[hal_cfg("dmac-d21")]
340#[macro_export]
341macro_rules! with_num_channels {
342    ($some_macro:ident) => {
343        $some_macro! {6}
344    };
345}
346
347#[cfg(not(feature = "max-channels"))]
348#[hal_cfg("dmac-d5x")]
349#[macro_export]
350macro_rules! with_num_channels {
351    ($some_macro:ident) => {
352        $some_macro! {16}
353    };
354}
355
356macro_rules! get {
357    ($literal:literal) => {
358        $literal
359    };
360}
361
362/// Number of DMA channels used by the driver
363pub const NUM_CHANNELS: usize = with_num_channels!(get);
364
365/// DMAC SRAM registers
366pub(crate) mod sram {
367    #![allow(dead_code, unused_braces)]
368
369    use core::cell::UnsafeCell;
370    use core::ptr::null_mut;
371
372    use super::{BeatSize, NUM_CHANNELS};
373
374    use modular_bitfield::{
375        bitfield,
376        specifiers::{B2, B3},
377    };
378
379    /// Wrapper type around a [`DmacDescriptor`] to allow interior mutability
380    /// while keeping them in static storage
381    #[repr(transparent)]
382    pub struct DescriptorCell(UnsafeCell<DmacDescriptor>);
383
384    impl DescriptorCell {
385        const fn default() -> Self {
386            Self(UnsafeCell::new(DmacDescriptor::default()))
387        }
388    }
389
390    // DescriptorCell is not not *really* sync; we must manually uphold the sync
391    // guarantees on every access.
392    unsafe impl Sync for DescriptorCell {}
393
394    impl core::ops::Deref for DescriptorCell {
395        type Target = UnsafeCell<DmacDescriptor>;
396
397        fn deref(&self) -> &Self::Target {
398            &self.0
399        }
400    }
401
402    impl core::ops::DerefMut for DescriptorCell {
403        fn deref_mut(&mut self) -> &mut Self::Target {
404            &mut self.0
405        }
406    }
407
408    /// Bitfield representing the BTCTRL SRAM DMAC register
409    #[allow(unused_braces)]
410    #[bitfield]
411    #[derive(Clone, Copy)]
412    #[repr(u16)]
413    pub(super) struct BlockTransferControl {
414        pub(super) valid: bool,
415        pub(super) evosel: B2,
416        pub(super) blockact: B2,
417        #[skip]
418        _reserved: B3,
419        #[bits = 2]
420        pub(super) beatsize: BeatSize,
421        pub(super) srcinc: bool,
422        pub(super) dstinc: bool,
423        pub(super) stepsel: bool,
424        pub(super) stepsize: B3,
425    }
426
427    impl Default for BlockTransferControl {
428        fn default() -> Self {
429            Self::new()
430        }
431    }
432
433    /// Descriptor representing a SRAM register. Datasheet section 19.8.2
434    #[derive(Clone, Copy)]
435    #[repr(C, align(16))]
436    pub struct DmacDescriptor {
437        pub(super) btctrl: BlockTransferControl,
438        pub(super) btcnt: u16,
439        pub(super) srcaddr: *const (),
440        pub(super) dstaddr: *const (),
441        pub(super) descaddr: *const DmacDescriptor,
442    }
443
444    impl DmacDescriptor {
445        pub const fn default() -> Self {
446            Self {
447                btctrl: BlockTransferControl::new(),
448                btcnt: 0,
449                srcaddr: null_mut(),
450                dstaddr: null_mut(),
451                descaddr: null_mut(),
452            }
453        }
454
455        pub fn next_descriptor(&self) -> *const DmacDescriptor {
456            self.descaddr
457        }
458
459        pub fn set_next_descriptor(&mut self, next: *mut DmacDescriptor) {
460            self.descaddr = next;
461        }
462
463        pub fn beat_count(&self) -> u16 {
464            self.btcnt
465        }
466    }
467
468    /// Writeback section.
469    ///
470    /// # Safety
471    ///
472    /// This variable should never be accessed. The only thing we need
473    /// to know about it is its starting address, given by
474    /// [`writeback_addr`].
475    static WRITEBACK: [DescriptorCell; NUM_CHANNELS] =
476        [const { DescriptorCell::default() }; NUM_CHANNELS];
477
478    // We only ever need to know its starting address.
479    pub(super) fn writeback_addr() -> *mut DmacDescriptor {
480        WRITEBACK[0].get()
481    }
482
483    /// Descriptor section.
484    ///
485    /// # Safety
486    ///
487    /// All accesses to this variable should be synchronized. Elements of the
488    /// array should only ever be accessed using [`UnsafeCell::get`]. Any other
489    /// access method, such as taking a reference to the [`UnsafeCell`] itself,
490    /// is UB and *will* break DMA transfers - speaking from personal
491    /// experience.
492    static DESCRIPTOR_SECTION: [DescriptorCell; NUM_CHANNELS] =
493        [const { DescriptorCell::default() }; NUM_CHANNELS];
494
495    #[inline]
496    pub(super) fn descriptor_section_addr() -> *mut DmacDescriptor {
497        DESCRIPTOR_SECTION[0].get()
498    }
499
500    /// Get a mutable pointer to the specified channel's DMAC descriptor
501    ///
502    /// # Safety
503    ///
504    /// The caller must manually synchronize any access to the pointee
505    /// [`DmacDescriptor`].
506    ///
507    /// Additionnally, if the pointer is used to create references to
508    /// [`DmacDescriptor`], the caller must guarantee that there will **never**
509    /// be overlapping `&mut` references (or overlapping `&mut` and `&`
510    /// references) to the pointee *at any given time*, as it would be
511    /// instantaneous undefined behaviour.
512    #[inline]
513    pub(super) unsafe fn get_descriptor(channel_id: usize) -> *mut DmacDescriptor {
514        DESCRIPTOR_SECTION[channel_id].get()
515    }
516}
517
518pub mod channel;
519pub mod dma_controller;
520pub mod transfer;
521
522#[cfg(feature = "async")]
523pub mod async_api;
524#[cfg(feature = "async")]
525pub use async_api::*;
526
527#[cfg(feature = "async")]
528mod waker {
529    use embassy_sync::waitqueue::AtomicWaker;
530
531    #[allow(clippy::declare_interior_mutable_const)]
532    const NEW_WAKER: AtomicWaker = AtomicWaker::new();
533    pub(super) static WAKERS: [AtomicWaker; with_num_channels!(get)] =
534        [NEW_WAKER; with_num_channels!(get)];
535}