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}