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 `true` if 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 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 `true` if 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 if the slot is
385    /// CPU-owned, enabling zero-copy frame construction.
386    ///
387    /// After writing the frame, call [`TDesRing::commit`] to hand it to DMA.
388    pub fn available_buf(&mut self) -> Option<&mut [u8; MAX_FRAME_SIZE]> {
389        if self.has_capacity() {
390            let idx = self.index;
391            Some(self.buffers[idx].get_mut().into_inner())
392        } else {
393            None
394        }
395    }
396
397    /// Commits the frame written into the buffer returned by [`TDesRing::available_buf`].
398    ///
399    /// Sets the frame length, hands the descriptor to DMA, and advances the
400    /// ring index.  The caller must trigger a TX poll demand after this call
401    /// (see `EmacRegs::demand_tx_poll`).
402    pub fn commit(&mut self, len: usize) {
403        let idx = self.index;
404        let n = self.descriptors.len();
405
406        #[cfg(soc_internal_memory_cached)]
407        self.buffers[idx].get_mut().writeback();
408
409        let mut desc = self.descriptors[idx].get_mut();
410        desc.set_len_and_flags(len);
411        desc.set_owned_by(OwnedBy::Dma);
412
413        #[cfg(soc_internal_memory_cached)]
414        desc.writeback();
415
416        fence(Ordering::Release);
417        self.index = (idx + 1) % n;
418    }
419}
420
421/// Error returned by [`TDesRing::transmit`].
422#[derive(Clone, Copy, Debug, Eq, PartialEq)]
423pub enum TxError {
424    /// No CPU-owned descriptor is available right now.
425    RingFull,
426    /// Frame is larger than `MAX_FRAME_SIZE`.
427    FrameTooLarge,
428}
429
430// ── RX ring ─────────────────────────────────────────────────────────────────
431
432/// RX descriptor ring backed by references into `EthernetDmaStorage`.
433pub struct RDesRing<'a> {
434    descriptors: &'a mut [InternalMemory<RDes>],
435    buffers: &'a mut [InternalMemory<[u8; MAX_FRAME_SIZE]>],
436    index: usize,
437}
438
439impl<'a> RDesRing<'a> {
440    /// Creates a new RX ring from the given descriptor and buffer slices.
441    pub fn new(
442        descriptors: &'a mut [InternalMemory<RDes>],
443        buffers: &'a mut [InternalMemory<[u8; MAX_FRAME_SIZE]>],
444    ) -> Self {
445        assert!(!descriptors.is_empty());
446        assert_eq!(descriptors.len(), buffers.len());
447
448        let mut ring = Self {
449            descriptors,
450            buffers,
451            index: 0,
452        };
453        ring.reset();
454        ring
455    }
456
457    /// Rebuilds ring links and returns all descriptors to DMA ownership.
458    ///
459    /// Call once after `EMAC_DMA` soft-reset completes and before starting RX.
460    pub fn reset(&mut self) {
461        let n = self.descriptors.len();
462        for i in 0..n {
463            let next = self.descriptors[(i + 1) % n].as_ptr();
464            let buf_addr = self.buffers[i].as_ptr().cast::<u8>();
465
466            #[cfg(soc_internal_memory_cached)]
467            self.buffers[i].get_mut().invalidate();
468
469            let mut desc = self.descriptors[i].get_mut();
470            desc.rdes0.set(0);
471            desc.configure_buffer(MAX_FRAME_SIZE);
472            desc.set_buffer_addr(buf_addr);
473            desc.set_next_desc(next);
474            desc.set_owned_by(OwnedBy::Dma);
475            #[cfg(soc_internal_memory_cached)]
476            desc.writeback();
477        }
478        self.index = 0;
479        fence(Ordering::Release);
480    }
481
482    /// Returns the address of the first descriptor (used to program `EMAC_DMA.dmarxbaseaddr`).
483    pub fn base_ptr(&self) -> *const RDes {
484        self.descriptors[0].as_ptr()
485    }
486
487    /// Returns a mutable data slice if a frame is ready.
488    ///
489    /// Loops past error/incomplete/oversized frames, recycling them back to DMA
490    /// automatically. Returns `None` only when no CPU-owned descriptor remains.
491    /// Call [`RDesRing::pop`] after the returned slice is no longer needed, to
492    /// release the descriptor back to DMA.
493    pub fn receive(&mut self) -> Option<&mut [u8]> {
494        loop {
495            let idx = self.index;
496
497            // Inspect the descriptor. Returns `Some(len)` for a valid frame,
498            // `None` if the slot must be recycled. The descriptor borrow ends
499            // with this block so `recycle_current` can reborrow `self`.
500            let len = {
501                let desc = self.descriptors[idx].get_ref();
502                #[cfg(soc_internal_memory_cached)]
503                desc.invalidate();
504                fence(Ordering::Acquire);
505                if desc.owned_by() != OwnedBy::Cpu {
506                    return None;
507                }
508
509                let status = desc.rdes0.get();
510                let is_complete = desc.is_complete_frame();
511                let frame_len = desc.frame_len();
512
513                // On chips with cut-through RX the DMA can't auto-drop
514                // checksum-error frames, so reject them here based on the
515                // extended-status COE bits.
516                let checksum_bad = cfg_select! {
517                    esp32p4 => desc.checksum_error(),
518                    _ => false,
519                };
520
521                if status & RDES0_ES != 0 || !is_complete || checksum_bad {
522                    None
523                } else {
524                    // Strip the 4-byte FCS the GMAC appends to the frame length.
525                    let len = frame_len.saturating_sub(4);
526                    if (MIN_RX_FRAME_SIZE..=MAX_FRAME_SIZE).contains(&len) {
527                        Some(len)
528                    } else {
529                        None
530                    }
531                }
532            };
533
534            let Some(len) = len else {
535                self.recycle_current();
536                continue;
537            };
538
539            #[cfg(soc_internal_memory_cached)]
540            self.buffers[idx].get_mut().invalidate();
541            fence(Ordering::Acquire);
542            return Some(&mut self.buffers[idx].get_mut().into_inner()[..len]);
543        }
544    }
545
546    /// Releases the current RX descriptor back to DMA ownership.
547    ///
548    /// Must be called after every successful [`RDesRing::receive`] call.
549    pub fn pop(&mut self) {
550        self.recycle_current();
551    }
552
553    /// Returns `true` when the current descriptor has been returned by DMA.
554    pub fn has_packet(&self) -> bool {
555        let desc = self.descriptors[self.index].get_ref();
556        #[cfg(soc_internal_memory_cached)]
557        desc.invalidate();
558        fence(Ordering::Acquire);
559        desc.owned_by() == OwnedBy::Cpu
560    }
561
562    fn recycle_current(&mut self) {
563        let idx = self.index;
564        let n = self.descriptors.len();
565        let mut desc = self.descriptors[idx].get_mut();
566        desc.set_rdes0(RDES0_OWN);
567        #[cfg(soc_internal_memory_cached)]
568        desc.writeback();
569        fence(Ordering::Release);
570        self.index = (idx + 1) % n;
571    }
572}