aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBogdan Petru Chircu Mare <[email protected]>2025-12-01 17:07:58 -0800
committerBogdan Petru Chircu Mare <[email protected]>2025-12-01 17:44:03 -0800
commitbc21f9b35e09fe9ef2556adf53f1d600af909d03 (patch)
tree7a9c2fc12a5e1061a6fc81c6ca0b039e210592ed
parent230237b73cbe6f31780ea407ed13bee1adf8eaa2 (diff)
refactor(dma): move DMA_MAX_TRANSFER_SIZE to dma module and init during HAL startup
Address felipebalbi's review comments on PR #52: - Move DMA_MAX_TRANSFER_SIZE constant from lpuart/mod.rs to dma.rs where it logically belongs (describes eDMA4 hardware limitation) - Add public dma::init() function called during hal::init() instead of lazy initialization via ensure_init() - Remove ensure_init() entirely since it's no longer needed - Remove ensure_init() calls from DmaChannel::new() and from_token() - Remove ensure_init() calls from examples (dma_channel_link, dma_scatter_gather) - Refactor lpuart_ring_buffer example to use LpuartDma::new() + split() pattern instead of separate TX/RX drivers - Add [lints.rust] section to suppress unexpected_cfgs warning for 'rt' feature used by embassy_hal_internal::interrupt_mod! macro This makes DMA initialization explicit during HAL startup (like GPIO) and keeps DMA-specific constants in the DMA module.
-rw-r--r--Cargo.toml6
-rw-r--r--examples/src/bin/dma_channel_link.rs5
-rw-r--r--examples/src/bin/dma_scatter_gather.rs3
-rw-r--r--examples/src/bin/lpuart_ring_buffer.rs57
-rw-r--r--src/dma.rs35
-rw-r--r--src/lib.rs3
-rw-r--r--src/lpuart/mod.rs67
7 files changed, 116 insertions, 60 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 96b7d6b0f..f86b92c32 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -46,3 +46,9 @@ time = [
46 "dep:embassy-time", 46 "dep:embassy-time",
47 "dep:embassy-time-driver", 47 "dep:embassy-time-driver",
48] 48]
49
50[lints.rust]
51# The `rt` cfg is checked by embassy_hal_internal::interrupt_mod! macro
52# even though it's defined in the macro's crate, not ours. This suppresses
53# the "unexpected cfg condition value" warning.
54unexpected_cfgs = { level = "warn", check-cfg = ["cfg(feature, values(\"rt\"))"] }
diff --git a/examples/src/bin/dma_channel_link.rs b/examples/src/bin/dma_channel_link.rs
index 34162d931..361c9ebc7 100644
--- a/examples/src/bin/dma_channel_link.rs
+++ b/examples/src/bin/dma_channel_link.rs
@@ -18,7 +18,7 @@
18 18
19use embassy_executor::Spawner; 19use embassy_executor::Spawner;
20use embassy_mcxa::clocks::config::Div8; 20use embassy_mcxa::clocks::config::Div8;
21use embassy_mcxa::dma::{self, DmaCh0InterruptHandler, DmaCh1InterruptHandler, DmaCh2InterruptHandler, DmaChannel}; 21use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler, DmaCh2InterruptHandler, DmaChannel};
22use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; 22use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx};
23use embassy_mcxa::{bind_interrupts, pac}; 23use embassy_mcxa::{bind_interrupts, pac};
24use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; 24use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
@@ -85,8 +85,7 @@ async fn main(_spawner: Spawner) {
85 85
86 defmt::info!("DMA channel link example starting..."); 86 defmt::info!("DMA channel link example starting...");
87 87
88 // Ensure DMA is initialized (clock/reset/init handled automatically by HAL) 88 // DMA is initialized during hal::init() - no need to call ensure_init()
89 dma::ensure_init();
90 89
91 let pac_periphs = unsafe { pac::Peripherals::steal() }; 90 let pac_periphs = unsafe { pac::Peripherals::steal() };
92 let dma0 = &pac_periphs.dma0; 91 let dma0 = &pac_periphs.dma0;
diff --git a/examples/src/bin/dma_scatter_gather.rs b/examples/src/bin/dma_scatter_gather.rs
index b5ae00057..9844071b7 100644
--- a/examples/src/bin/dma_scatter_gather.rs
+++ b/examples/src/bin/dma_scatter_gather.rs
@@ -120,8 +120,7 @@ async fn main(_spawner: Spawner) {
120 120
121 defmt::info!("DMA scatter-gather transfer example starting..."); 121 defmt::info!("DMA scatter-gather transfer example starting...");
122 122
123 // Ensure DMA is initialized (clock/reset/init handled automatically by HAL) 123 // DMA is initialized during hal::init() - no need to call ensure_init()
124 dma::ensure_init();
125 124
126 // Enable DMA interrupt 125 // Enable DMA interrupt
127 unsafe { 126 unsafe {
diff --git a/examples/src/bin/lpuart_ring_buffer.rs b/examples/src/bin/lpuart_ring_buffer.rs
index 6cc14f1c7..0946bad03 100644
--- a/examples/src/bin/lpuart_ring_buffer.rs
+++ b/examples/src/bin/lpuart_ring_buffer.rs
@@ -1,18 +1,18 @@
1//! LPUART Ring Buffer DMA example for MCXA276. 1//! LPUART Ring Buffer DMA example for MCXA276.
2//! 2//!
3//! This example demonstrates using the new `RingBuffer` API for continuous 3//! This example demonstrates using the high-level `LpuartRxDma::setup_ring_buffer()`
4//! circular DMA reception from a UART peripheral. 4//! API for continuous circular DMA reception from a UART peripheral.
5//! 5//!
6//! # Features demonstrated: 6//! # Features demonstrated:
7//! - `setup_circular_read()` for continuous peripheral-to-memory DMA 7//! - `LpuartRxDma::setup_ring_buffer()` for continuous peripheral-to-memory DMA
8//! - `RingBuffer` for async reading of received data 8//! - `RingBuffer` for async reading of received data
9//! - Handling of potential overrun conditions 9//! - Handling of potential overrun conditions
10//! - Half-transfer and complete-transfer interrupts for timely wakeups 10//! - Half-transfer and complete-transfer interrupts for timely wakeups
11//! 11//!
12//! # How it works: 12//! # How it works:
13//! 1. Set up a circular DMA transfer from LPUART RX to a ring buffer 13//! 1. Create an `LpuartRxDma` driver with a DMA channel
14//! 2. DMA continuously writes received bytes into the buffer, wrapping around 14//! 2. Call `setup_ring_buffer()` which handles all low-level DMA configuration
15//! 3. Application asynchronously reads data as it arrives 15//! 3. Application asynchronously reads data as it arrives via `ring_buf.read()`
16//! 4. Both half-transfer and complete-transfer interrupts wake the reader 16//! 4. Both half-transfer and complete-transfer interrupts wake the reader
17 17
18#![no_std] 18#![no_std]
@@ -20,9 +20,9 @@
20 20
21use embassy_executor::Spawner; 21use embassy_executor::Spawner;
22use embassy_mcxa::clocks::config::Div8; 22use embassy_mcxa::clocks::config::Div8;
23use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler, DmaChannel, DMA_REQ_LPUART2_RX}; 23use embassy_mcxa::dma::{DmaCh0InterruptHandler, DmaCh1InterruptHandler};
24use embassy_mcxa::lpuart::{Blocking, Config, Lpuart, LpuartTx}; 24use embassy_mcxa::lpuart::{Config, LpuartDma, LpuartTxDma};
25use embassy_mcxa::{bind_interrupts, pac}; 25use embassy_mcxa::bind_interrupts;
26use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _}; 26use {defmt_rtt as _, embassy_mcxa as hal, panic_probe as _};
27 27
28// Bind DMA channel interrupts 28// Bind DMA channel interrupts
@@ -35,7 +35,10 @@ bind_interrupts!(struct Irqs {
35static mut RX_RING_BUFFER: [u8; 64] = [0; 64]; 35static mut RX_RING_BUFFER: [u8; 64] = [0; 64];
36 36
37/// Helper to write a byte as hex to UART 37/// Helper to write a byte as hex to UART
38fn write_hex(tx: &mut LpuartTx<'_, Blocking>, byte: u8) { 38fn write_hex<T: embassy_mcxa::lpuart::Instance, C: embassy_mcxa::dma::Channel>(
39 tx: &mut LpuartTxDma<'_, T, C>,
40 byte: u8,
41) {
39 const HEX: &[u8; 16] = b"0123456789ABCDEF"; 42 const HEX: &[u8; 16] = b"0123456789ABCDEF";
40 let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]]; 43 let buf = [HEX[(byte >> 4) as usize], HEX[(byte & 0x0F) as usize]];
41 tx.blocking_write(&buf).ok(); 44 tx.blocking_write(&buf).ok();
@@ -55,12 +58,6 @@ async fn main(_spawner: Spawner) {
55 58
56 defmt::info!("LPUART Ring Buffer DMA example starting..."); 59 defmt::info!("LPUART Ring Buffer DMA example starting...");
57 60
58 // Enable DMA interrupts (DMA clock/reset/init is handled automatically by HAL)
59 unsafe {
60 cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH0);
61 cortex_m::peripheral::NVIC::unmask(pac::Interrupt::DMA_CH1);
62 }
63
64 // Create UART configuration 61 // Create UART configuration
65 let config = Config { 62 let config = Config {
66 baudrate_bps: 115_200, 63 baudrate_bps: 115_200,
@@ -69,41 +66,27 @@ async fn main(_spawner: Spawner) {
69 ..Default::default() 66 ..Default::default()
70 }; 67 };
71 68
72 // Create blocking UART for TX (we'll use DMA for RX only) 69 // Create LPUART with DMA support for both TX and RX, then split
73 let lpuart = Lpuart::new_blocking(p.LPUART2, p.P2_2, p.P2_3, config).unwrap(); 70 // This is the proper Embassy pattern - create once, split into TX and RX
74 let (mut tx, _rx) = lpuart.split(); 71 let lpuart = LpuartDma::new(p.LPUART2, p.P2_2, p.P2_3, p.DMA_CH1, p.DMA_CH0, config).unwrap();
72 let (mut tx, rx) = lpuart.split();
75 73
76 tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap(); 74 tx.blocking_write(b"LPUART Ring Buffer DMA Example\r\n").unwrap();
77 tx.blocking_write(b"==============================\r\n\r\n").unwrap(); 75 tx.blocking_write(b"==============================\r\n\r\n").unwrap();
78 76
79 // Get LPUART2 RX data register address for DMA
80 let lpuart2 = unsafe { &*pac::Lpuart2::ptr() };
81 let rx_data_addr = lpuart2.data().as_ptr() as *const u8;
82
83 // Enable RX DMA request in LPUART
84 lpuart2.baud().modify(|_, w| w.rdmae().enabled());
85
86 // Create DMA channel for RX
87 let dma_ch_rx = DmaChannel::new(p.DMA_CH0);
88
89 // Configure the DMA mux for LPUART2 RX
90 unsafe {
91 dma_ch_rx.set_request_source(DMA_REQ_LPUART2_RX);
92 }
93
94 tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n") 77 tx.blocking_write(b"Setting up circular DMA for UART RX...\r\n")
95 .unwrap(); 78 .unwrap();
96 79
97 // Set up the ring buffer with circular DMA 80 // Set up the ring buffer with circular DMA
98 // This configures the DMA for continuous reception 81 // The HAL handles: DMA request source, RDMAE enable, circular transfer config, NVIC enable
99 let ring_buf = unsafe { 82 let ring_buf = unsafe {
100 let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER); 83 let buf = &mut *core::ptr::addr_of_mut!(RX_RING_BUFFER);
101 dma_ch_rx.setup_circular_read(rx_data_addr, buf) 84 rx.setup_ring_buffer(buf)
102 }; 85 };
103 86
104 // Enable DMA requests to start continuous reception 87 // Enable DMA requests to start continuous reception
105 unsafe { 88 unsafe {
106 dma_ch_rx.enable_request(); 89 rx.enable_dma_request();
107 } 90 }
108 91
109 tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n") 92 tx.blocking_write(b"Ring buffer ready! Type characters to see them echoed.\r\n")
diff --git a/src/dma.rs b/src/dma.rs
index 7bfc95752..8d82c254a 100644
--- a/src/dma.rs
+++ b/src/dma.rs
@@ -118,17 +118,14 @@ use crate::peripherals::DMA0;
118/// Static flag to track whether DMA has been initialized. 118/// Static flag to track whether DMA has been initialized.
119static DMA_INITIALIZED: AtomicBool = AtomicBool::new(false); 119static DMA_INITIALIZED: AtomicBool = AtomicBool::new(false);
120 120
121/// Ensure DMA is initialized (clock enabled, reset released, controller configured). 121/// Initialize DMA controller (clock enabled, reset released, controller configured).
122/// 122///
123/// This function is idempotent - it will only initialize DMA once, even if called 123/// This function is intended to be called during HAL initialization (`hal::init()`).
124/// multiple times. It is automatically called when creating a DmaChannel. 124/// It is idempotent - it will only initialize DMA once, even if called multiple times.
125/// 125///
126/// # Safety 126/// The function enables the DMA0 clock, releases reset, and configures the controller
127/// 127/// for normal operation with round-robin arbitration.
128/// This function accesses hardware registers and must be called from a context 128pub fn init() {
129/// where such access is safe (typically during system initialization or with
130/// interrupts properly managed).
131pub fn ensure_init() {
132 // Fast path: already initialized 129 // Fast path: already initialized
133 if DMA_INITIALIZED.load(Ordering::Acquire) { 130 if DMA_INITIALIZED.load(Ordering::Acquire) {
134 return; 131 return;
@@ -334,6 +331,16 @@ pub enum EnableInterrupt {
334} 331}
335 332
336// ============================================================================ 333// ============================================================================
334// DMA Constants
335// ============================================================================
336
337/// Maximum bytes per DMA transfer (eDMA4 CITER/BITER are 15-bit fields).
338///
339/// This is a hardware limitation of the eDMA4 controller. Transfers larger
340/// than this must be split into multiple DMA operations.
341pub const DMA_MAX_TRANSFER_SIZE: usize = 0x7FFF;
342
343// ============================================================================
337// DMA Request Source Constants 344// DMA Request Source Constants
338// ============================================================================ 345// ============================================================================
339 346
@@ -531,11 +538,9 @@ pub struct DmaChannel<C: Channel> {
531impl<C: Channel> DmaChannel<C> { 538impl<C: Channel> DmaChannel<C> {
532 /// Wrap a DMA channel token (takes ownership of the Peri wrapper). 539 /// Wrap a DMA channel token (takes ownership of the Peri wrapper).
533 /// 540 ///
534 /// This automatically initializes the DMA controller if not already done 541 /// Note: DMA is initialized during `hal::init()` via `dma::init()`.
535 /// (enables clock, releases reset, configures controller).
536 #[inline] 542 #[inline]
537 pub fn new(_ch: embassy_hal_internal::Peri<'_, C>) -> Self { 543 pub fn new(_ch: embassy_hal_internal::Peri<'_, C>) -> Self {
538 ensure_init();
539 Self { 544 Self {
540 _ch: core::marker::PhantomData, 545 _ch: core::marker::PhantomData,
541 } 546 }
@@ -543,10 +548,9 @@ impl<C: Channel> DmaChannel<C> {
543 548
544 /// Wrap a DMA channel token directly (for internal use). 549 /// Wrap a DMA channel token directly (for internal use).
545 /// 550 ///
546 /// This automatically initializes the DMA controller if not already done. 551 /// Note: DMA is initialized during `hal::init()` via `dma::init()`.
547 #[inline] 552 #[inline]
548 pub fn from_token(_ch: C) -> Self { 553 pub fn from_token(_ch: C) -> Self {
549 ensure_init();
550 Self { 554 Self {
551 _ch: core::marker::PhantomData, 555 _ch: core::marker::PhantomData,
552 } 556 }
@@ -2205,6 +2209,9 @@ impl<C: Channel> DmaChannel<C> {
2205 // Enable the channel request 2209 // Enable the channel request
2206 t.ch_csr().modify(|_, w| w.erq().enable()); 2210 t.ch_csr().modify(|_, w| w.erq().enable());
2207 2211
2212 // Enable NVIC interrupt for this channel so async wakeups work
2213 self.enable_interrupt();
2214
2208 RingBuffer::new(self.as_any(), buf) 2215 RingBuffer::new(self.as_any(), buf)
2209 } 2216 }
2210} 2217}
diff --git a/src/lib.rs b/src/lib.rs
index d3560e651..d721f53e6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -373,6 +373,9 @@ pub fn init(cfg: crate::config::Config) -> Peripherals {
373 crate::gpio::init(); 373 crate::gpio::init();
374 } 374 }
375 375
376 // Initialize DMA controller (clock, reset, configuration)
377 crate::dma::init();
378
376 // Initialize embassy-time global driver backed by OSTIMER0 379 // Initialize embassy-time global driver backed by OSTIMER0
377 #[cfg(feature = "time")] 380 #[cfg(feature = "time")]
378 crate::ostimer::time_driver::init(crate::config::Config::default().time_interrupt_priority, 1_000_000); 381 crate::ostimer::time_driver::init(crate::config::Config::default().time_interrupt_priority, 1_000_000);
diff --git a/src/lpuart/mod.rs b/src/lpuart/mod.rs
index 1f8904e9c..bcac1fdbd 100644
--- a/src/lpuart/mod.rs
+++ b/src/lpuart/mod.rs
@@ -18,7 +18,7 @@ pub mod buffered;
18// DMA INTEGRATION 18// DMA INTEGRATION
19// ============================================================================ 19// ============================================================================
20 20
21use crate::dma::{Channel as DmaChannelTrait, DmaChannel, EnableInterrupt}; 21use crate::dma::{Channel as DmaChannelTrait, DmaChannel, EnableInterrupt, RingBuffer, DMA_MAX_TRANSFER_SIZE};
22 22
23// ============================================================================ 23// ============================================================================
24// MISC 24// MISC
@@ -1056,9 +1056,6 @@ impl<'a> Lpuart<'a, Blocking> {
1056// ASYNC MODE IMPLEMENTATIONS (DMA-based) 1056// ASYNC MODE IMPLEMENTATIONS (DMA-based)
1057// ============================================================================ 1057// ============================================================================
1058 1058
1059/// Maximum bytes per DMA transfer (eDMA CITER/BITER are 15-bit fields).
1060const DMA_MAX_TRANSFER_SIZE: usize = 0x7FFF;
1061
1062/// Guard struct that ensures DMA is stopped if the async future is cancelled. 1059/// Guard struct that ensures DMA is stopped if the async future is cancelled.
1063/// 1060///
1064/// This implements the RAII pattern: if the future is dropped before completion 1061/// This implements the RAII pattern: if the future is dropped before completion
@@ -1357,6 +1354,68 @@ impl<'a, T: Instance, C: DmaChannelTrait> LpuartRxDma<'a, T, C> {
1357 } 1354 }
1358 Ok(()) 1355 Ok(())
1359 } 1356 }
1357
1358 /// Set up a ring buffer for continuous DMA reception.
1359 ///
1360 /// This configures the DMA channel for circular operation, enabling continuous
1361 /// reception of data without gaps. The DMA will continuously write received
1362 /// bytes into the buffer, wrapping around when it reaches the end.
1363 ///
1364 /// This method encapsulates all the low-level setup:
1365 /// - Configures the DMA request source for this LPUART instance
1366 /// - Enables the RX DMA request in the LPUART peripheral
1367 /// - Sets up the circular DMA transfer
1368 /// - Enables the NVIC interrupt for async wakeups
1369 ///
1370 /// # Arguments
1371 ///
1372 /// * `buf` - Destination buffer for received data (power-of-2 size is ideal for efficiency)
1373 ///
1374 /// # Returns
1375 ///
1376 /// A [`RingBuffer`] that can be used to asynchronously read received data.
1377 ///
1378 /// # Example
1379 ///
1380 /// ```no_run
1381 /// static mut RX_BUF: [u8; 64] = [0; 64];
1382 ///
1383 /// let rx = LpuartRxDma::new(p.LPUART2, p.P2_3, p.DMA_CH0, config).unwrap();
1384 /// let ring_buf = unsafe { rx.setup_ring_buffer(&mut RX_BUF) };
1385 ///
1386 /// // Read data as it arrives
1387 /// let mut buf = [0u8; 16];
1388 /// let n = ring_buf.read(&mut buf).await.unwrap();
1389 /// ```
1390 ///
1391 /// # Safety
1392 ///
1393 /// - The buffer must remain valid for the lifetime of the returned RingBuffer.
1394 /// - Only one RingBuffer should exist per LPUART RX channel at a time.
1395 /// - The caller must ensure the static buffer is not accessed elsewhere while
1396 /// the ring buffer is active.
1397 pub unsafe fn setup_ring_buffer<'b>(&self, buf: &'b mut [u8]) -> RingBuffer<'b, u8> {
1398 // Get the peripheral data register address
1399 let peri_addr = self.info.regs.data().as_ptr() as *const u8;
1400
1401 // Configure DMA request source for this LPUART instance
1402 self.rx_dma.set_request_source(T::RX_DMA_REQ);
1403
1404 // Enable RX DMA request in the LPUART peripheral
1405 self.info.regs.baud().modify(|_, w| w.rdmae().enabled());
1406
1407 // Set up circular DMA transfer (this also enables NVIC interrupt)
1408 self.rx_dma.setup_circular_read(peri_addr, buf)
1409 }
1410
1411 /// Enable the DMA channel request.
1412 ///
1413 /// Call this after `setup_ring_buffer()` to start continuous reception.
1414 /// This is separated from setup to allow for any additional configuration
1415 /// before starting the transfer.
1416 pub unsafe fn enable_dma_request(&self) {
1417 self.rx_dma.enable_request();
1418 }
1360} 1419}
1361 1420
1362impl<'a, T: Instance, TxC: DmaChannelTrait, RxC: DmaChannelTrait> LpuartDma<'a, T, TxC, RxC> { 1421impl<'a, T: Instance, TxC: DmaChannelTrait, RxC: DmaChannelTrait> LpuartDma<'a, T, TxC, RxC> {