Skip to main content

esp_hal/ethernet/
dma.rs

1//! EMAC DMA descriptor rings.
2//!
3//! Implements the chained-ring descriptor layout for the Synopsys DesignWare
4//! GMAC as found on ESP32 and ESP32-P4. The driver always uses the **enhanced
5//! 32-byte descriptor format** (`ALT_DESC_SIZE = 1` in `EMAC_DMA.dmabusmode`).
6
7use core::sync::atomic::{Ordering, fence};
8
9use crate::{dma::aligned::InternalMemory, reg_access::VolatileCell};
10
11/// Maximum frame size supported per DMA buffer (1518 + FCS + rounding).
12pub const MAX_FRAME_SIZE: usize = 1524;
13/// Minimum accepted RX frame length.
14pub const MIN_RX_FRAME_SIZE: usize = 14;
15
16// ── TDES bits ──────────────────────────────────────────────────────────────
17
18/// TX descriptor ownership bit: 1 = owned by DMA.
19pub const TDES0_OWN: u32 = 1 << 31;
20/// TX interrupt-on-completion.
21pub const TDES0_IC: u32 = 1 << 30;
22/// TX last-segment flag.
23pub const TDES0_LS: u32 = 1 << 29;
24/// TX first-segment flag.
25pub const TDES0_FS: u32 = 1 << 28;
26/// TX checksum insertion control: 3 = IP Header checksum and payload checksum calculation
27/// and insertion are enabled, and pseudo-header checksum is calculated in hardware.
28pub const TDES0_CIC_FULL: u32 = 3 << 22;
29/// TX second-address-chained mode (next descriptor pointer in TDES3).
30pub const TDES0_CHAINED: u32 = 1 << 20;
31
32// ── RDES bits ──────────────────────────────────────────────────────────────
33
34/// RX descriptor ownership bit: 1 = owned by DMA.
35pub const RDES0_OWN: u32 = 1 << 31;
36/// RX frame length field shift inside RDES0.
37pub const RDES0_FL_SHIFT: u32 = 16;
38/// RX frame length field mask inside RDES0.
39pub const RDES0_FL_MASK: u32 = 0x3fff << RDES0_FL_SHIFT;
40/// RX error-summary bit.
41pub const RDES0_ES: u32 = 1 << 15;
42/// RX first-segment flag.
43pub const RDES0_FS: u32 = 1 << 9;
44/// RX last-segment flag.
45pub const RDES0_LS: u32 = 1 << 8;
46
47/// RX buffer-1 size field mask in RDES1.
48pub const RDES1_BUF1_SIZE_MASK: u32 = 0x1fff;
49/// RX second-address-chained bit.
50pub const RDES1_CHAINED: u32 = 1 << 14;
51
52// ── RDES4 extended-status bits (Type-2 checksum offload) ─────────────────────
53//
54// Only consumed on chips whose RX FIFO runs in cut-through mode and therefore
55// cannot rely on the DMA to auto-drop checksum-error frames (see `dma_start`).
56
57cfg_select! {
58    esp32p4 => {
59        /// RDES0 extended-status-available bit: RDES4 holds valid COE status.
60        pub const RDES0_ESA: u32 = 1 << 0;
61        /// RDES4: IP header checksum error.
62        pub const RDES4_IP_HEADER_ERROR: u32 = 1 << 3;
63        /// RDES4: IP payload (TCP/UDP/ICMP) checksum error.
64        pub const RDES4_IP_PAYLOAD_ERROR: u32 = 1 << 4;
65        /// RDES4: IP checksum offload engine was bypassed (checksum not verified).
66        pub const RDES4_IP_CHECKSUM_BYPASSED: u32 = 1 << 5;
67        /// RDES4: IPv4 packet received.
68        pub const RDES4_IPV4_PACKET: u32 = 1 << 6;
69        /// RDES4: IPv6 packet received.
70        pub const RDES4_IPV6_PACKET: u32 = 1 << 7;
71    }
72    _ => {}
73}
74
75// ── Descriptor types ────────────────────────────────────────────────────────
76
77/// Current descriptor owner.
78#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub enum OwnedBy {
80    /// The CPU owns the descriptor.
81    Cpu,
82    /// The DMA engine owns the descriptor.
83    Dma,
84}
85
86/// TX DMA descriptor (enhanced 32-byte format, `ALT_DESC_SIZE = 1`).
87///
88/// Layout matches the Synopsys DesignWare GMAC databook for enhanced mode.
89/// Words 4–7 are reserved for hardware use (TX timestamp, etc.).
90#[repr(C)]
91pub struct TDes {
92    pub(super) tdes0: VolatileCell<u32>,
93    pub(super) tdes1: VolatileCell<u32>,
94    pub(super) buf_addr: VolatileCell<u32>,
95    pub(super) next_desc: VolatileCell<u32>,
96    // Enhanced words — set to zero; hardware may write TX timestamp here.
97    _tdes4: VolatileCell<u32>,
98    _tdes5: VolatileCell<u32>,
99    _tdes6: VolatileCell<u32>,
100    _tdes7: VolatileCell<u32>,
101}
102
103impl TDes {
104    /// Zero-initialized descriptor, suitable for `static` initializers.
105    pub const fn new_zeroed() -> Self {
106        Self {
107            tdes0: VolatileCell::new(0),
108            tdes1: VolatileCell::new(0),
109            buf_addr: VolatileCell::new(0),
110            next_desc: VolatileCell::new(0),
111            _tdes4: VolatileCell::new(0),
112            _tdes5: VolatileCell::new(0),
113            _tdes6: VolatileCell::new(0),
114            _tdes7: VolatileCell::new(0),
115        }
116    }
117
118    /// Current ownership of this descriptor.
119    pub fn owned_by(&self) -> OwnedBy {
120        if self.tdes0.get() & TDES0_OWN != 0 {
121            OwnedBy::Dma
122        } else {
123            OwnedBy::Cpu
124        }
125    }
126
127    /// Sets the ownership bit.
128    pub fn set_owned_by(&mut self, owner: OwnedBy) {
129        let v = self.tdes0.get();
130        self.tdes0.set(match owner {
131            OwnedBy::Cpu => v & !TDES0_OWN,
132            OwnedBy::Dma => v | TDES0_OWN,
133        });
134    }
135
136    fn set_chained(&mut self) {
137        self.tdes0.set(self.tdes0.get() | TDES0_CHAINED);
138    }
139
140    fn set_len_and_flags(&mut self, len: usize) {
141        self.tdes1.set(len as u32 & RDES1_BUF1_SIZE_MASK);
142        self.tdes0.set(
143            (self.tdes0.get() & TDES0_CHAINED) | TDES0_FS | TDES0_LS | TDES0_IC | TDES0_CIC_FULL,
144        );
145    }
146
147    fn set_buffer_addr(&mut self, addr: *const u8) {
148        self.buf_addr.set(addr as u32);
149    }
150
151    fn set_next_desc(&mut self, addr: *const TDes) {
152        self.next_desc.set(addr as u32);
153    }
154}
155
156/// RX DMA descriptor (enhanced 32-byte format, `ALT_DESC_SIZE = 1`).
157///
158/// Words 4–7 are reserved for hardware use (RX timestamp, VLAN, etc.).
159#[repr(C)]
160pub struct RDes {
161    pub(super) rdes0: VolatileCell<u32>,
162    pub(super) rdes1: VolatileCell<u32>,
163    pub(super) buf_addr: VolatileCell<u32>,
164    pub(super) next_desc: VolatileCell<u32>,
165    // Extended status; the GMAC writes IP checksum-offload results here.
166    #[cfg_attr(
167        not(esp32p4),
168        allow(dead_code, reason = "only read for P4 RX checksum offload")
169    )]
170    rdes4: VolatileCell<u32>,
171    _rdes5: VolatileCell<u32>,
172    _rdes6: VolatileCell<u32>,
173    _rdes7: VolatileCell<u32>,
174}
175
176impl RDes {
177    /// Zero-initialized descriptor, suitable for `static` initializers.
178    pub const fn new_zeroed() -> Self {
179        Self {
180            rdes0: VolatileCell::new(0),
181            rdes1: VolatileCell::new(0),
182            buf_addr: VolatileCell::new(0),
183            next_desc: VolatileCell::new(0),
184            rdes4: VolatileCell::new(0),
185            _rdes5: VolatileCell::new(0),
186            _rdes6: VolatileCell::new(0),
187            _rdes7: VolatileCell::new(0),
188        }
189    }
190
191    /// Current ownership of this descriptor.
192    pub fn owned_by(&self) -> OwnedBy {
193        if self.rdes0.get() & RDES0_OWN != 0 {
194            OwnedBy::Dma
195        } else {
196            OwnedBy::Cpu
197        }
198    }
199
200    fn set_rdes0(&mut self, value: u32) {
201        self.rdes0.set(value);
202    }
203
204    fn set_owned_by(&mut self, owner: OwnedBy) {
205        let v = self.rdes0.get();
206        self.set_rdes0(match owner {
207            OwnedBy::Cpu => v & !RDES0_OWN,
208            OwnedBy::Dma => v | RDES0_OWN,
209        });
210    }
211
212    fn is_complete_frame(&self) -> bool {
213        let s = self.rdes0.get();
214        s & RDES0_FS != 0 && s & RDES0_LS != 0
215    }
216
217    fn frame_len(&self) -> usize {
218        ((self.rdes0.get() & RDES0_FL_MASK) >> RDES0_FL_SHIFT) as usize
219    }
220
221    /// Returns whether the hardware IP checksum-offload engine flagged a
222    /// header or payload checksum error for this frame.
223    ///
224    /// These results live in the extended-status word (RDES4) rather than in
225    /// `RDES0_ES`, so they must be inspected explicitly. Only meaningful on the
226    /// last descriptor of a frame. Non-IP frames (e.g. ARP) and frames whose
227    /// checksum the engine bypassed never report an error.
228    #[cfg(esp32p4)]
229    fn checksum_error(&self) -> bool {
230        // The extended status is only valid when RDES0[0] (ESA) is set.
231        if self.rdes0.get() & RDES0_ESA == 0 {
232            return false;
233        }
234
235        let ext = self.rdes4.get();
236
237        // The COE status bits only apply to IPv4/IPv6 frames.
238        let is_ip = ext & (RDES4_IPV4_PACKET | RDES4_IPV6_PACKET) != 0;
239        // If the engine bypassed the checksum, there is nothing to reject.
240        let bypassed = ext & RDES4_IP_CHECKSUM_BYPASSED != 0;
241
242        is_ip && !bypassed && ext & (RDES4_IP_HEADER_ERROR | RDES4_IP_PAYLOAD_ERROR) != 0
243    }
244
245    fn configure_buffer(&mut self, size: usize) {
246        self.rdes1.set(
247            (self.rdes1.get() & !RDES1_BUF1_SIZE_MASK)
248                | (size as u32 & RDES1_BUF1_SIZE_MASK)
249                | RDES1_CHAINED,
250        );
251    }
252
253    fn set_buffer_addr(&mut self, addr: *const u8) {
254        self.buf_addr.set(addr as u32);
255    }
256
257    fn set_next_desc(&mut self, addr: *const RDes) {
258        self.next_desc.set(addr as u32);
259    }
260}
261
262// ── Static DMA storage ──────────────────────────────────────────────────────
263
264/// Static backing storage for all DMA descriptor rings and packet buffers.
265///
266/// `TX` is the number of transmit slots; `RX` is the number of receive slots.
267/// Pass a mutable reference to [`Ethernet::new`][super::Ethernet::new].
268pub struct EthernetDmaStorage<const RX: usize, const TX: usize> {
269    pub(super) rx_descs: [InternalMemory<RDes>; RX],
270    pub(super) tx_descs: [InternalMemory<TDes>; TX],
271    pub(super) rx_bufs: [InternalMemory<[u8; MAX_FRAME_SIZE]>; RX],
272    pub(super) tx_bufs: [InternalMemory<[u8; MAX_FRAME_SIZE]>; TX],
273}
274
275impl<const RX: usize, const TX: usize> Default for EthernetDmaStorage<RX, TX> {
276    fn default() -> Self {
277        Self::new()
278    }
279}
280
281impl<const RX: usize, const TX: usize> EthernetDmaStorage<RX, TX> {
282    /// Creates a new zero-initialized storage block, suitable for `static` placement.
283    pub const fn new() -> Self {
284        Self {
285            rx_descs: [const { InternalMemory::new(RDes::new_zeroed()) }; RX],
286            tx_descs: [const { InternalMemory::new(TDes::new_zeroed()) }; TX],
287            rx_bufs: [const { InternalMemory::new([0u8; MAX_FRAME_SIZE]) }; RX],
288            tx_bufs: [const { InternalMemory::new([0u8; MAX_FRAME_SIZE]) }; TX],
289        }
290    }
291}
292
293// SAFETY: `EthernetDmaStorage` is only accessed through the driver, which
294// enforces exclusive access via `&mut` borrows.
295unsafe impl<const RX: usize, const TX: usize> Send for EthernetDmaStorage<RX, TX> {}
296unsafe impl<const RX: usize, const TX: usize> Sync for EthernetDmaStorage<RX, TX> {}
297
298// ── TX ring ─────────────────────────────────────────────────────────────────
299
300/// TX descriptor ring backed by references into `EthernetDmaStorage`.
301pub struct TDesRing<'a> {
302    descriptors: &'a mut [InternalMemory<TDes>],
303    buffers: &'a mut [InternalMemory<[u8; MAX_FRAME_SIZE]>],
304    index: usize,
305}
306
307impl<'a> TDesRing<'a> {
308    /// Creates a new TX ring from the given descriptor and buffer slices.
309    pub fn new(
310        descriptors: &'a mut [InternalMemory<TDes>],
311        buffers: &'a mut [InternalMemory<[u8; MAX_FRAME_SIZE]>],
312    ) -> Self {
313        assert!(!descriptors.is_empty());
314        assert_eq!(descriptors.len(), buffers.len());
315
316        let mut ring = Self {
317            descriptors,
318            buffers,
319            index: 0,
320        };
321        ring.reset();
322        ring
323    }
324
325    pub(crate) fn len(&self) -> usize {
326        self.descriptors.len()
327    }
328
329    /// Rebuilds ring links and returns all descriptors to CPU ownership.
330    ///
331    /// Call once after `EMAC_DMA` soft-reset completes and before starting TX.
332    pub fn reset(&mut self) {
333        let n = self.descriptors.len();
334        for i in 0..n {
335            let next = self.descriptors[(i + 1) % n].as_ptr();
336            let buf_addr = self.buffers[i].as_ptr().cast::<u8>();
337
338            let mut desc = self.descriptors[i].get_mut();
339            desc.tdes0.set(0);
340            desc.tdes1.set(0);
341            desc.set_chained();
342            desc.set_buffer_addr(buf_addr);
343            desc.set_next_desc(next);
344            desc.set_owned_by(OwnedBy::Cpu);
345            #[cfg(soc_internal_memory_cached)]
346            self.descriptors[i].get_mut().writeback();
347        }
348        self.index = 0;
349        fence(Ordering::Release);
350    }
351
352    /// Returns the address of the first descriptor (used to program `EMAC_DMA.dmatxbaseaddr`).
353    pub fn base_ptr(&self) -> *const TDes {
354        self.descriptors[0].as_ptr()
355    }
356
357    /// Copies `frame` into the next available TX buffer and hands it to DMA.
358    ///
359    /// Returns `Err(DescriptorError::RingFull)` if no CPU-owned slot is available.
360    /// and `Err(DescriptorError::FrameTooLarge)` if the frame exceeds [`MAX_FRAME_SIZE`].
361    pub fn transmit(&mut self, frame: &[u8]) -> Result<(), TxError> {
362        if frame.len() > MAX_FRAME_SIZE {
363            return Err(TxError::FrameTooLarge);
364        }
365
366        if let Some(buf) = self.available_buf() {
367            buf[..frame.len()].copy_from_slice(frame);
368            self.commit(frame.len());
369            Ok(())
370        } else {
371            Err(TxError::RingFull)
372        }
373    }
374
375    /// Returns whether the current slot is CPU-owned (ready to accept a frame).
376    pub fn has_capacity(&self) -> bool {
377        let desc = self.descriptors[self.index].get_ref();
378        #[cfg(soc_internal_memory_cached)]
379        desc.invalidate();
380        fence(Ordering::Acquire);
381        desc.owned_by() == OwnedBy::Cpu
382    }
383
384    /// Returns a mutable reference to the current TX DMA buffer when the slot is
385    /// CPU-owned, which enables zero-copy frame construction.
386    ///
387    /// After writing the frame, call [`TDesRing::commit`] to hand it to DMA.
388    /// Returns `None` when the slot is not CPU-owned.
389    pub fn available_buf(&mut self) -> Option<&mut [u8; MAX_FRAME_SIZE]> {
390        if self.has_capacity() {
391            let idx = self.index;
392            Some(self.buffers[idx].get_mut().into_inner())
393        } else {
394            None
395        }
396    }
397
398    /// Commits the frame written into the buffer returned by [`TDesRing::available_buf`].
399    ///
400    /// Sets the frame length, hands the descriptor to DMA, and advances the
401    /// ring index.  The caller must trigger a TX poll demand after this call
402    /// (see `EmacRegs::demand_tx_poll`).
403    pub fn commit(&mut self, len: usize) {
404        let idx = self.index;
405        let n = self.descriptors.len();
406
407        #[cfg(soc_internal_memory_cached)]
408        self.buffers[idx].get_mut().writeback();
409
410        let mut desc = self.descriptors[idx].get_mut();
411        desc.set_len_and_flags(len);
412        desc.set_owned_by(OwnedBy::Dma);
413
414        #[cfg(soc_internal_memory_cached)]
415        desc.writeback();
416
417        fence(Ordering::Release);
418        self.index = (idx + 1) % n;
419    }
420}
421
422/// Error returned by [`TDesRing::transmit`].
423#[derive(Clone, Copy, Debug, Eq, PartialEq)]
424pub enum TxError {
425    /// No CPU-owned descriptor is available right now.
426    RingFull,
427    /// Frame is larger than `MAX_FRAME_SIZE`.
428    FrameTooLarge,
429}
430
431// ── RX ring ─────────────────────────────────────────────────────────────────
432
433/// RX descriptor ring backed by references into `EthernetDmaStorage`.
434pub struct RDesRing<'a> {
435    descriptors: &'a mut [InternalMemory<RDes>],
436    buffers: &'a mut [InternalMemory<[u8; MAX_FRAME_SIZE]>],
437    index: usize,
438}
439
440impl<'a> RDesRing<'a> {
441    /// Creates a new RX ring from the given descriptor and buffer slices.
442    pub fn new(
443        descriptors: &'a mut [InternalMemory<RDes>],
444        buffers: &'a mut [InternalMemory<[u8; MAX_FRAME_SIZE]>],
445    ) -> Self {
446        assert!(!descriptors.is_empty());
447        assert_eq!(descriptors.len(), buffers.len());
448
449        let mut ring = Self {
450            descriptors,
451            buffers,
452            index: 0,
453        };
454        ring.reset();
455        ring
456    }
457
458    /// Rebuilds ring links and returns all descriptors to DMA ownership.
459    ///
460    /// Call once after `EMAC_DMA` soft-reset completes and before starting RX.
461    pub fn reset(&mut self) {
462        let n = self.descriptors.len();
463        for i in 0..n {
464            let next = self.descriptors[(i + 1) % n].as_ptr();
465            let buf_addr = self.buffers[i].as_ptr().cast::<u8>();
466
467            #[cfg(soc_internal_memory_cached)]
468            self.buffers[i].get_mut().invalidate();
469
470            let mut desc = self.descriptors[i].get_mut();
471            desc.rdes0.set(0);
472            desc.configure_buffer(MAX_FRAME_SIZE);
473            desc.set_buffer_addr(buf_addr);
474            desc.set_next_desc(next);
475            desc.set_owned_by(OwnedBy::Dma);
476            #[cfg(soc_internal_memory_cached)]
477            desc.writeback();
478        }
479        self.index = 0;
480        fence(Ordering::Release);
481    }
482
483    /// Returns the address of the first descriptor (used to program `EMAC_DMA.dmarxbaseaddr`).
484    pub fn base_ptr(&self) -> *const RDes {
485        self.descriptors[0].as_ptr()
486    }
487
488    /// Returns a mutable data slice for a ready frame, or `None` when no frame is ready.
489    ///
490    /// Loops past error/incomplete/oversized frames, recycling them back to DMA
491    /// automatically. Returns `None` only when no CPU-owned descriptor remains.
492    /// Call [`RDesRing::pop`] after the returned slice is no longer needed, to
493    /// release the descriptor back to DMA.
494    pub fn receive(&mut self) -> Option<&mut [u8]> {
495        loop {
496            let idx = self.index;
497
498            // Inspect the descriptor. Returns `Some(len)` for a valid frame,
499            // `None` if the slot must be recycled. The descriptor borrow ends
500            // with this block so `recycle_current` can reborrow `self`.
501            let len = {
502                let desc = self.descriptors[idx].get_ref();
503                #[cfg(soc_internal_memory_cached)]
504                desc.invalidate();
505                fence(Ordering::Acquire);
506                if desc.owned_by() != OwnedBy::Cpu {
507                    return None;
508                }
509
510                let status = desc.rdes0.get();
511                let is_complete = desc.is_complete_frame();
512                let frame_len = desc.frame_len();
513
514                // On chips with cut-through RX the DMA can't auto-drop
515                // checksum-error frames, so reject them here based on the
516                // extended-status COE bits.
517                let checksum_bad = cfg_select! {
518                    esp32p4 => desc.checksum_error(),
519                    _ => false,
520                };
521
522                if status & RDES0_ES != 0 || !is_complete || checksum_bad {
523                    None
524                } else {
525                    // Strip the 4-byte FCS the GMAC appends to the frame length.
526                    let len = frame_len.saturating_sub(4);
527                    if (MIN_RX_FRAME_SIZE..=MAX_FRAME_SIZE).contains(&len) {
528                        Some(len)
529                    } else {
530                        None
531                    }
532                }
533            };
534
535            let Some(len) = len else {
536                self.recycle_current();
537                continue;
538            };
539
540            #[cfg(soc_internal_memory_cached)]
541            self.buffers[idx].get_mut().invalidate();
542            fence(Ordering::Acquire);
543            return Some(&mut self.buffers[idx].get_mut().into_inner()[..len]);
544        }
545    }
546
547    /// Releases the current RX descriptor back to DMA ownership.
548    ///
549    /// Must be called after every successful [`RDesRing::receive`] call.
550    pub fn pop(&mut self) {
551        self.recycle_current();
552    }
553
554    /// Returns `true` when the current descriptor has been returned by DMA.
555    pub fn has_packet(&self) -> bool {
556        let desc = self.descriptors[self.index].get_ref();
557        #[cfg(soc_internal_memory_cached)]
558        desc.invalidate();
559        fence(Ordering::Acquire);
560        desc.owned_by() == OwnedBy::Cpu
561    }
562
563    fn recycle_current(&mut self) {
564        let idx = self.index;
565        let n = self.descriptors.len();
566        let mut desc = self.descriptors[idx].get_mut();
567        desc.set_rdes0(RDES0_OWN);
568        #[cfg(soc_internal_memory_cached)]
569        desc.writeback();
570        fence(Ordering::Release);
571        self.index = (idx + 1) % n;
572    }
573}