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