Skip to main content

esp_emac/
embassy.rs

1// SPDX-License-Identifier: GPL-2.0-or-later OR Apache-2.0
2// Copyright (c) Viacheslav Bocharov <v@baodeep.com> and JetHome (r)
3
4//! Native embassy-net driver for the ESP32 EMAC.
5//!
6//! Wraps [`crate::Emac`] directly (no `ph_esp32_mac::EmbassyEmac` proxy).
7//! [`EmacDriverState`] holds the wakers, link cache, and ISR counters
8//! and is intended to live in `static` storage so the EMAC ISR can
9//! reach it.
10//!
11//! # Lifetime alignment
12//!
13//! [`Emac`] and [`EmacDriverState`] play different roles, with
14//! different uniqueness requirements:
15//!
16//! - [`Emac`] drives a single hardware peripheral. The ESP32 has
17//!   exactly one built-in EMAC and `Emac::init` touches global MMIO,
18//!   so at most one initialized `Emac` instance can be active on a
19//!   running device. Place it in `static mut` storage and take the
20//!   `&'static mut` once at bring-up via
21//!   `unsafe { &mut *core::ptr::addr_of_mut!(EMAC) }`. `Emac::new`
22//!   is a `const fn`, so the value lives in BSS — no runtime stack
23//!   temporary, deterministic on cold boot. (See the *Usage* section
24//!   below for why a `StaticCell::init(EmacDefault::new(..))`
25//!   wrapper is *not* recommended at the default ring sizing.)
26//! - [`EmacDriverState`] is **not** a strict singleton. Multiple
27//!   instances are fine — host-side tests construct one per test, and
28//!   sequential re-initialization with a fresh state on the same
29//!   peripheral is allowed. The constraint is alignment: the
30//!   `EmacDriverState` whose [`handle_emac_interrupt`] runs from the
31//!   ISR must be the same instance you pass to [`EmacDriver::new`]
32//!   alongside the `Emac`. If those two references disagree, RX/TX
33//!   wakers fire against the wrong state and the stack stalls.
34//!
35//! [`EmacDriver`] then ties the pair together. The borrow checker
36//! enforces that **at most one** driver holds the `&'d mut Emac<...>`
37//! at any given moment. Sequential reuse is allowed — once the borrow
38//! ends (driver dropped, scope exited) the same `Emac` can be paired
39//! with a fresh driver again, which is what the unit tests in this
40//! module already exercise.
41//!
42//! # Recovery from task respawn
43//!
44//! `Emac::init` is a one-shot: if the task that owns the
45//! `&'static mut EmacDefault` panics and is respawned by the executor,
46//! the static EMAC retains state from the previous run. Calling
47//! `init` a second time returns [`EmacError::AlreadyInitialized`] and
48//! does nothing. The reborrowed `&'static mut` is still valid — the
49//! peripheral is still configured — but the DMA engine may have
50//! stopped mid-operation (descriptors marked owned by the engine,
51//! TX FIFO partially drained), and the driver state in
52//! [`EmacDriverState`] no longer matches the in-flight wakers from
53//! the previous run.
54//!
55//! Recovery sequence in the respawned task:
56//!
57//! ```ignore
58//! use esp_hal::delay::Delay;
59//!
60//! // Reborrow — same EMAC, still post-init from prior run.
61//! let emac = unsafe { &mut *core::ptr::addr_of_mut!(EMAC) };
62//! // Tear down the running engine and clear DMA state. `stop()`
63//! // takes a `&mut impl DelayNs` for the TX-FIFO flush poll.
64//! // It is idempotent on `Initialized` (returns Ok(())) and rejects
65//! // an `Uninitialized` driver with `EmacError::NotInitialized` —
66//! // neither matters here because the prior task left the EMAC in
67//! // `Running`. `Err(EmacError::TxFlushTimeout)` means teardown
68//! // still completed (state is back at `Initialized`); the warning
69//! // is recoverable, so swallow it.
70//! let mut delay = Delay::new();
71//! let _ = emac.stop(&mut delay);
72//! // Restart fresh. The peripheral keeps its already-programmed
73//! // pins, clocks, and MAC address — only the DMA rings need to
74//! // come back up.
75//! emac.start()?;
76//! ```
77//!
78//! Do **not** call `init()` a second time hoping it will reset the
79//! peripheral — it won't, and the error swallows silently in code
80//! that ignores the `Result`. Use the explicit `stop()` + `start()`
81//! cycle above.
82//!
83//! [`handle_emac_interrupt`]: EmacDriverState::handle_emac_interrupt
84//! [`EmacError::AlreadyInitialized`]: crate::EmacError::AlreadyInitialized
85//!
86//! # Usage
87//!
88//! The driver is non-functional until the EMAC ISR services
89//! `DMASTATUS` and wakes the RX/TX tasks. The ISR body must call
90//! [`EmacDriverState::handle_emac_interrupt`] (or the lower-level
91//! pair [`crate::Emac::handle_interrupt`] +
92//! [`EmacDriverState::on_interrupt_status`]) — without that, RX and
93//! TX block forever in `Driver::receive` / `Driver::transmit` waiting
94//! on wakers that nothing pokes.
95//!
96//! ```ignore
97//! use esp_emac::{
98//!     EmacConfig, RmiiClockConfig, RmiiPins, ClkGpio, XtalFreq,
99//!     EmacDefault,
100//!     embassy::{EmacDefaultDriver, EmacDriverState},
101//! };
102//! use esp_hal::interrupt::{InterruptHandler, Priority};
103//!
104//! // `EmacDefault::new` is a `const fn`, so the EMAC value is built at
105//! // compile time and lives in BSS — zero runtime stack involvement on
106//! // boot. The default ring sizing is currently 10 RX / 10 TX /
107//! // 1600-byte buffers (~32 KiB), sourced from `DEFAULT_RX` /
108//! // `DEFAULT_TX` / `DEFAULT_BUF`. A `StaticCell::init(EmacDefault::new(..))`
109//! // pattern would risk landing that 32 KiB on the caller's stack
110//! // before the move into static storage; the `static mut` form is
111//! // smaller, deterministic and avoids that hazard.
112//! static mut EMAC: EmacDefault = EmacDefault::new(EmacConfig {
113//!     clock: RmiiClockConfig::InternalApll {
114//!         gpio: ClkGpio::Gpio17,
115//!         xtal: XtalFreq::Mhz40,
116//!     },
117//!     pins: RmiiPins { mdc: 23, mdio: 18 },
118//! });
119//! static EMAC_STATE: EmacDriverState = EmacDriverState::new();
120//!
121//! // 1. ISR — must service DMASTATUS and wake stack tasks. The
122//! //    `EMAC_STATE` it touches has to be the same instance the
123//! //    driver is paired with below.
124//! #[esp_hal::handler(priority = Priority::Priority1)]
125//! fn emac_isr() {
126//!     EMAC_STATE.handle_emac_interrupt();
127//! }
128//!
129//! // 2. Bring-up + driver wiring.
130//! # fn example() -> Result<(), esp_emac::EmacError> {
131//! # let mut delay = esp_hal::delay::Delay::new();
132//! // SAFETY: `EMAC` is touched only here — single owner — so no aliasing.
133//! let emac = unsafe { &mut *core::ptr::addr_of_mut!(EMAC) };
134//! emac.set_mac_address([0x00, 0x70, 0x07, 0x24, 0x3B, 0x87]);
135//! emac.init(&mut delay)?;
136//! // ... PHY init + link wait + set_speed/set_duplex omitted ...
137//! emac.bind_interrupt(InterruptHandler::new(emac_isr, Priority::Priority1));
138//! emac.start()?;
139//! // `EmacDefaultDriver` is a type alias whose inherent `new` is the
140//! // same `EmacDriver::new` constructor — keeps the call site free of
141//! // the const-generic ceremony (currently `<10, 10, 1600>`, sourced
142//! // from `DEFAULT_RX` / `DEFAULT_TX` / `DEFAULT_BUF`).
143//! let driver = EmacDefaultDriver::new(emac, &EMAC_STATE);
144//! // Hand `driver` to embassy_net::new() / Stack.
145//! # Ok(()) }
146//! ```
147
148use core::cell::Cell;
149use core::marker::PhantomData;
150use core::sync::atomic::{AtomicU32, Ordering};
151use core::task::Context;
152
153use critical_section::Mutex;
154use embassy_net_driver::{
155    Capabilities, Checksum, ChecksumCapabilities, Driver, HardwareAddress, LinkState, RxToken,
156    TxToken,
157};
158use embassy_sync::waitqueue::AtomicWaker;
159
160use crate::emac::{Emac, DEFAULT_BUF, DEFAULT_RX, DEFAULT_TX, SMALL_RX, SMALL_TX};
161use crate::interrupt::InterruptStatus;
162
163/// Diagnostic snapshot of the `Driver::receive` / `Driver::transmit`
164/// path: how many times embassy-net asked for a token, how many of
165/// those calls actually had a frame, and how many frames the tokens
166/// failed to push to / pull from the EMAC.
167#[derive(Debug, Clone, Copy, Default)]
168pub struct DriverCounters {
169    /// Calls to `Driver::receive`.
170    pub rx_calls: u32,
171    /// Calls that returned a non-empty token pair (frame available).
172    pub rx_some: u32,
173    /// Frames silently dropped in `EmacRxToken::consume` because the
174    /// underlying `Emac::receive` returned `Err(_)` or `Ok(None)` after
175    /// the driver had already handed out a token. Indicates either an
176    /// errored frame (CRC, oversize) or a race where another path
177    /// consumed the descriptor first.
178    pub rx_dropped: u32,
179    /// Calls to `Driver::transmit`.
180    pub tx_calls: u32,
181    /// Calls that returned a token (TX path was ready).
182    pub tx_some: u32,
183    /// Frames silently dropped in `EmacTxToken::consume` because
184    /// `Emac::transmit` returned `Err(_)` after the driver had already
185    /// handed out a token. Typical cause: descriptor ring exhausted
186    /// between the readiness check and the actual push.
187    pub tx_dropped: u32,
188}
189
190/// Diagnostic snapshot of the ISR counters.
191#[derive(Debug, Clone, Copy, Default)]
192pub struct IrqCounters {
193    /// Total number of times the ISR ran.
194    pub total: u32,
195    /// `RI` (rx_complete) flag observed.
196    pub ri: u32,
197    /// `RU` (rx_buf_unavailable) flag observed.
198    pub ru: u32,
199    /// `TI` (tx_complete) flag observed.
200    pub ti: u32,
201    /// `TU` (tx_buf_unavailable) flag observed.
202    pub tu: u32,
203    /// `ERI` (early receive) flag observed.
204    pub eri: u32,
205    /// At least one error flag observed (UNF/OVF/FBI).
206    pub err: u32,
207    /// Last raw `DMASTATUS` snapshot taken in the ISR (before W1C).
208    pub last_dmastat: u32,
209}
210
211/// Maximum frame size for stack-allocated copy buffers (Ethernet MTU + headers).
212const MAX_FRAME_SIZE: usize = 1600;
213
214/// Standard Ethernet MTU (IP MTU + L2 header). Upper bound on the value
215/// the driver advertises to embassy-net — see
216/// `EmacDriver::effective_mtu` for the per-instance value, which caps
217/// this against the physical TX ring capacity.
218const ETH_MTU: usize = 1514;
219
220// =============================================================================
221// Driver state
222// =============================================================================
223
224/// Shared state for the embassy-net driver.
225///
226/// Holds the RX, TX, and link wakers plus the cached link state. Place
227/// in a `static` so it can be reached from the EMAC ISR.
228pub struct EmacDriverState {
229    rx_waker: AtomicWaker,
230    tx_waker: AtomicWaker,
231    link_waker: AtomicWaker,
232    link_state: Mutex<Cell<LinkState>>,
233    /// Diagnostic counters — incremented in the ISR. Used by the dev-log
234    /// hypotheses H6/H7.
235    irq_count: AtomicU32,
236    irq_ri: AtomicU32,
237    irq_ru: AtomicU32,
238    irq_ti: AtomicU32,
239    irq_tu: AtomicU32,
240    irq_eri: AtomicU32,
241    irq_err: AtomicU32,
242    /// Last observed raw DMASTAT (snapshot taken in ISR, before W1C).
243    last_dmastat: AtomicU32,
244    /// Counters bumped by [`EmacDriver::receive`] / [`EmacDriver::transmit`]
245    /// to see how often embassy-net actually pulls data.
246    drv_rx_calls: AtomicU32,
247    drv_rx_some: AtomicU32,
248    drv_rx_dropped: AtomicU32,
249    drv_tx_calls: AtomicU32,
250    drv_tx_some: AtomicU32,
251    drv_tx_dropped: AtomicU32,
252}
253
254impl Default for EmacDriverState {
255    fn default() -> Self {
256        Self::new()
257    }
258}
259
260impl EmacDriverState {
261    /// Create a new state with link initially down.
262    pub const fn new() -> Self {
263        Self {
264            rx_waker: AtomicWaker::new(),
265            tx_waker: AtomicWaker::new(),
266            link_waker: AtomicWaker::new(),
267            link_state: Mutex::new(Cell::new(LinkState::Down)),
268            irq_count: AtomicU32::new(0),
269            irq_ri: AtomicU32::new(0),
270            irq_ru: AtomicU32::new(0),
271            irq_ti: AtomicU32::new(0),
272            irq_tu: AtomicU32::new(0),
273            irq_eri: AtomicU32::new(0),
274            irq_err: AtomicU32::new(0),
275            last_dmastat: AtomicU32::new(0),
276            drv_rx_calls: AtomicU32::new(0),
277            drv_rx_some: AtomicU32::new(0),
278            drv_rx_dropped: AtomicU32::new(0),
279            drv_tx_calls: AtomicU32::new(0),
280            drv_tx_some: AtomicU32::new(0),
281            drv_tx_dropped: AtomicU32::new(0),
282        }
283    }
284
285    /// Diagnostic counters from the ISR.
286    pub fn irq_counters(&self) -> IrqCounters {
287        IrqCounters {
288            total: self.irq_count.load(Ordering::Relaxed),
289            ri: self.irq_ri.load(Ordering::Relaxed),
290            ru: self.irq_ru.load(Ordering::Relaxed),
291            ti: self.irq_ti.load(Ordering::Relaxed),
292            tu: self.irq_tu.load(Ordering::Relaxed),
293            eri: self.irq_eri.load(Ordering::Relaxed),
294            err: self.irq_err.load(Ordering::Relaxed),
295            last_dmastat: self.last_dmastat.load(Ordering::Relaxed),
296        }
297    }
298
299    /// Diagnostic counters from `Driver::receive` / `Driver::transmit`
300    /// and the matching tokens.
301    pub fn driver_counters(&self) -> DriverCounters {
302        DriverCounters {
303            rx_calls: self.drv_rx_calls.load(Ordering::Relaxed),
304            rx_some: self.drv_rx_some.load(Ordering::Relaxed),
305            rx_dropped: self.drv_rx_dropped.load(Ordering::Relaxed),
306            tx_calls: self.drv_tx_calls.load(Ordering::Relaxed),
307            tx_some: self.drv_tx_some.load(Ordering::Relaxed),
308            tx_dropped: self.drv_tx_dropped.load(Ordering::Relaxed),
309        }
310    }
311
312    /// Read the cached link state.
313    pub fn link_state(&self) -> LinkState {
314        critical_section::with(|cs| self.link_state.borrow(cs).get())
315    }
316
317    /// Update the cached link state and wake stack tasks.
318    pub fn set_link_state(&self, state: LinkState) {
319        critical_section::with(|cs| self.link_state.borrow(cs).set(state));
320        self.link_waker.wake();
321    }
322
323    /// Mark the link as up and wake the stack.
324    pub fn set_link_up(&self) {
325        self.set_link_state(LinkState::Up);
326    }
327
328    /// Mark the link as down and wake the stack.
329    pub fn set_link_down(&self) {
330        self.set_link_state(LinkState::Down);
331    }
332
333    /// Wake RX/TX tasks based on a snapshot of the DMA interrupt status.
334    pub fn on_interrupt_status(&self, status: InterruptStatus) {
335        if status.rx_complete || status.rx_buf_unavailable {
336            self.rx_waker.wake();
337        }
338        if status.tx_complete || status.tx_buf_unavailable {
339            self.tx_waker.wake();
340        }
341        if status.has_error() {
342            self.rx_waker.wake();
343            self.tx_waker.wake();
344        }
345    }
346
347    /// Read the DMA status register, clear the interrupts, and wake
348    /// any tasks waiting on RX or TX.
349    ///
350    /// Does **not** wake `link_waker` — link state isn't reflected in
351    /// `DMASTATUS` and is updated separately by whatever PHY-polling
352    /// task calls [`set_link_up`](Self::set_link_up) /
353    /// [`set_link_down`](Self::set_link_down). That path takes care
354    /// of waking link-state observers itself.
355    ///
356    /// Intended to be called from the EMAC ISR. Touches only memory-
357    /// mapped EMAC registers and the embedded wakers, so there is no
358    /// aliasing concern with the [`EmacDriver`] holding a raw pointer
359    /// to the [`Emac`] state.
360    pub fn handle_emac_interrupt(&self) {
361        let dmastat = crate::regs::dma::BASE + crate::regs::dma::DMASTATUS;
362        // SAFETY: DMASTATUS is a known-valid 32-bit memory-mapped register.
363        let raw = unsafe { core::ptr::read_volatile(dmastat as *const u32) };
364        let status = InterruptStatus::from_raw(raw);
365        // Write-1-to-clear using the raw snapshot, masked against
366        // `ALL_INTERRUPTS` so only the W1C interrupt bits are written
367        // back. This still catches every asserted W1C bit — including
368        // ones outside `InterruptStatus` such as `ERI` (bit 14), `ETI`
369        // (bit 10), `RWT` (bit 9), `TJT` (bit 3) — but excludes the
370        // read-only fields (`RS`/`TS`/`EB`/`MMC`/`PMT`/`TTI`) so we
371        // never write garbage at addresses the hardware doesn't expect.
372        // Round-tripping through `to_raw()` would silently drop those
373        // bits and risk an interrupt storm.
374        // SAFETY: same address; masked write hits only W1C bits.
375        unsafe {
376            core::ptr::write_volatile(
377                dmastat as *mut u32,
378                raw & crate::regs::dma::status::ALL_INTERRUPTS,
379            )
380        };
381
382        self.irq_count.fetch_add(1, Ordering::Relaxed);
383        self.last_dmastat.store(raw, Ordering::Relaxed);
384        if status.rx_complete {
385            self.irq_ri.fetch_add(1, Ordering::Relaxed);
386        }
387        if status.rx_buf_unavailable {
388            self.irq_ru.fetch_add(1, Ordering::Relaxed);
389        }
390        if status.tx_complete {
391            self.irq_ti.fetch_add(1, Ordering::Relaxed);
392        }
393        if status.tx_buf_unavailable {
394            self.irq_tu.fetch_add(1, Ordering::Relaxed);
395        }
396        // Early Receive Interrupt (ERI, bit 14 of DMASTATUS — distinct
397        // from ETI, the Early Transmit Interrupt at bit 10) isn't
398        // surfaced through `InterruptStatus`, so check the raw flag
399        // against the canonical `regs::dma::status::ERI` constant
400        // rather than a magic shift.
401        if (raw & crate::regs::dma::status::ERI) != 0 {
402            self.irq_eri.fetch_add(1, Ordering::Relaxed);
403        }
404        if status.has_error() {
405            self.irq_err.fetch_add(1, Ordering::Relaxed);
406        }
407
408        self.on_interrupt_status(status);
409    }
410}
411
412// =============================================================================
413// Driver wrapper
414// =============================================================================
415
416/// embassy-net driver for the ESP32 EMAC.
417///
418/// The driver holds a raw pointer to a previously-initialized
419/// [`Emac`] together with a reference to a shared [`EmacDriverState`].
420///
421/// # Concurrent ownership
422///
423/// At most **one** `EmacDriver` can hold the `&'d mut Emac<...>` at a
424/// time. The borrow checker enforces that through the `&'d mut`
425/// argument to [`Self::new`] — concurrent aliasing is impossible.
426/// Sequential reuse is fine: once a driver is dropped, the same
427/// `Emac` can be paired with a fresh driver again. The unit tests in
428/// this module exercise that pattern.
429///
430/// The companion [`EmacDriverState`] is **not** a strict singleton —
431/// see the module-level *Lifetime alignment* section. The constraint
432/// is that whichever instance the EMAC ISR's
433/// [`EmacDriverState::handle_emac_interrupt`] runs against must be
434/// the same one passed here as `state`.
435///
436/// For the default ring sizing the
437/// [`EmacDefaultDriver<'d>`](EmacDefaultDriver) alias removes the need
438/// to repeat the const generics in `embassy_executor::task` signatures.
439///
440/// # Safety
441///
442/// The pointer is dereferenced in `Driver` impl methods. The lifetime
443/// `'d` ensures the underlying `Emac` outlives the driver, but the raw
444/// pointer means **mutable aliasing** would be unsound. The
445/// at-most-one-concurrent-driver invariant above is what keeps that
446/// aliasing impossible in practice.
447pub struct EmacDriver<'d, const RX: usize, const TX: usize, const BUF: usize> {
448    emac: *mut Emac<RX, TX, BUF>,
449    state: &'d EmacDriverState,
450    _marker: PhantomData<&'d mut Emac<RX, TX, BUF>>,
451}
452
453// SAFETY contract for `unsafe impl Send`:
454//
455// `EmacDriver` carries a `*mut Emac<...>` (raw pointers are not auto-Send),
456// but the manual impl is sound under the following invariants — break any
457// one of them and the impl becomes unsound, so revisit it together with
458// any change that touches `Emac`'s field layout or the ISR data path:
459//
460// 1. Single ownership. Exactly one `EmacDriver` exists per `Emac`
461//    instance for the lifetime of `'d`. `EmacDriver::new` consumes a
462//    `&'d mut Emac<...>`, which the borrow checker enforces as long
463//    as the pointer isn't laundered through other unsafe code.
464// 2. ISR-side access through `EmacDriverState` only touches MMIO
465//    (`DMASTATUS`) and `AtomicU32` counters — *not* the `Emac` struct
466//    behind the raw pointer. So the ISR is not a concurrent reader
467//    of the data the `Driver` impl mutates.
468// 3. The pointee `Emac<RX, TX, BUF>` is itself `Send`. The raw pointer
469//    hides auto-trait inference, so without an explicit bound a
470//    future `Cell<X>` / `Rc<X>` / `MutexGuard<'_, X>` inside `Emac`
471//    would silently leave this impl claiming `Send`. The
472//    `where Emac<RX, TX, BUF>: Send` clause below promotes that
473//    invariant from documentation to a compile-time check: such a
474//    refactor will fail to compile here instead of producing
475//    unsound `EmacDriver: Send`.
476unsafe impl<const RX: usize, const TX: usize, const BUF: usize> Send for EmacDriver<'_, RX, TX, BUF> where
477    Emac<RX, TX, BUF>: Send
478{
479}
480
481impl<'d, const RX: usize, const TX: usize, const BUF: usize> EmacDriver<'d, RX, TX, BUF> {
482    /// Create a new embassy-net driver.
483    ///
484    /// `emac` must be already initialized and started; `state` must be
485    /// the same instance whose [`on_interrupt_status`] is called from
486    /// the EMAC ISR.
487    pub fn new(emac: &'d mut Emac<RX, TX, BUF>, state: &'d EmacDriverState) -> Self {
488        Self {
489            emac: emac as *mut _,
490            state,
491            _marker: PhantomData,
492        }
493    }
494
495    /// Borrow the shared state.
496    pub fn state(&self) -> &EmacDriverState {
497        self.state
498    }
499
500    /// Effective MTU advertised to embassy-net and used as the
501    /// readiness threshold in `Driver::transmit`.
502    ///
503    /// Capped by the physical TX ring capacity (`TX * BUF`) so the
504    /// driver never advertises — and never gates on — a frame size
505    /// the engine couldn't actually push. On normal rings (e.g.
506    /// `TX=10, BUF=1600`) this returns the standard Ethernet MTU
507    /// of `1514`. On undersized rings (`TX * BUF < 1514`) it shrinks
508    /// to `TX * BUF`, so small frames still flow even though full-MTU
509    /// frames are physically impossible.
510    pub const fn effective_mtu() -> usize {
511        let ring_capacity = TX * BUF;
512        if ring_capacity < ETH_MTU {
513            ring_capacity
514        } else {
515            ETH_MTU
516        }
517    }
518}
519
520// =============================================================================
521// Convenience type aliases
522// =============================================================================
523
524/// Driver for the [`crate::EmacDefault`] ring sizing.
525///
526/// Sourced from the same [`DEFAULT_RX`] / [`DEFAULT_TX`] /
527/// [`DEFAULT_BUF`] constants as `EmacDefault`, so the two aliases
528/// stay paired even if the canonical sizing is retuned. The
529/// `embassy_executor::task` signature for the `net_task` runner can
530/// then read `Runner<'static, EmacDefaultDriver<'static>>` instead
531/// of repeating the const generics at every call site.
532pub type EmacDefaultDriver<'d> = EmacDriver<'d, DEFAULT_RX, DEFAULT_TX, DEFAULT_BUF>;
533
534/// Driver for the [`crate::EmacSmall`] ring sizing.
535///
536/// See [`EmacDefaultDriver`] for the rationale.
537pub type EmacSmallDriver<'d> = EmacDriver<'d, SMALL_RX, SMALL_TX, DEFAULT_BUF>;
538
539// =============================================================================
540// RX / TX tokens
541// =============================================================================
542
543/// embassy-net RX token — copies one received frame on `consume`.
544pub struct EmacRxToken<'a, const RX: usize, const TX: usize, const BUF: usize> {
545    emac: *mut Emac<RX, TX, BUF>,
546    state: &'a EmacDriverState,
547    _marker: PhantomData<&'a mut Emac<RX, TX, BUF>>,
548}
549
550impl<const RX: usize, const TX: usize, const BUF: usize> RxToken for EmacRxToken<'_, RX, TX, BUF> {
551    fn consume<R, F>(self, f: F) -> R
552    where
553        F: FnOnce(&mut [u8]) -> R,
554    {
555        let mut buffer = [0u8; MAX_FRAME_SIZE];
556        // SAFETY: `EmacDriver` guarantees the pointer is valid for the
557        // lifetime tracked by `'a`; tokens are consumed synchronously by
558        // the embassy stack.
559        let emac = unsafe { &mut *self.emac };
560        match emac.receive(&mut buffer) {
561            Ok(Some(n)) => f(&mut buffer[..n]),
562            // No frame after we already handed out a token — either an
563            // error path (FrameError, BufferTooSmall: descriptor was
564            // recycled by the engine) or a race where another caller
565            // consumed it. Bump `rx_dropped` so the drop is observable
566            // and pass an empty slice to satisfy the `RxToken` contract.
567            Ok(None) | Err(_) => {
568                self.state.drv_rx_dropped.fetch_add(1, Ordering::Relaxed);
569                f(&mut [])
570            }
571        }
572    }
573}
574
575/// embassy-net TX token — submits one frame on `consume`.
576pub struct EmacTxToken<'a, const RX: usize, const TX: usize, const BUF: usize> {
577    emac: *mut Emac<RX, TX, BUF>,
578    state: &'a EmacDriverState,
579    _marker: PhantomData<&'a mut Emac<RX, TX, BUF>>,
580}
581
582impl<const RX: usize, const TX: usize, const BUF: usize> TxToken for EmacTxToken<'_, RX, TX, BUF> {
583    fn consume<R, F>(self, len: usize, f: F) -> R
584    where
585        F: FnOnce(&mut [u8]) -> R,
586    {
587        let len = len.min(MAX_FRAME_SIZE);
588        let mut buffer = [0u8; MAX_FRAME_SIZE];
589        let result = f(&mut buffer[..len]);
590        // SAFETY: see `EmacRxToken::consume`.
591        let emac = unsafe { &mut *self.emac };
592        if emac.transmit(&buffer[..len]).is_err() {
593            // `embassy-net-driver`'s `TxToken::consume` has no fallible
594            // return, so a failed push silently drops the frame. Bump
595            // `tx_dropped` for diagnostics.
596            self.state.drv_tx_dropped.fetch_add(1, Ordering::Relaxed);
597        }
598        result
599    }
600}
601
602// =============================================================================
603// Driver trait
604// =============================================================================
605
606impl<const RX: usize, const TX: usize, const BUF: usize> Driver for EmacDriver<'_, RX, TX, BUF> {
607    type RxToken<'a>
608        = EmacRxToken<'a, RX, TX, BUF>
609    where
610        Self: 'a;
611    type TxToken<'a>
612        = EmacTxToken<'a, RX, TX, BUF>
613    where
614        Self: 'a;
615
616    fn receive(&mut self, cx: &mut Context<'_>) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
617        self.state.drv_rx_calls.fetch_add(1, Ordering::Relaxed);
618        // SAFETY: see `EmacDriver` doc.
619        let emac = unsafe { &mut *self.emac };
620
621        if !emac.rx_available() {
622            self.state.rx_waker.register(cx.waker());
623            if !emac.rx_available() {
624                return None;
625            }
626        }
627
628        self.state.drv_rx_some.fetch_add(1, Ordering::Relaxed);
629        Some((
630            EmacRxToken {
631                emac: self.emac,
632                state: self.state,
633                _marker: PhantomData,
634            },
635            EmacTxToken {
636                emac: self.emac,
637                state: self.state,
638                _marker: PhantomData,
639            },
640        ))
641    }
642
643    fn transmit(&mut self, cx: &mut Context<'_>) -> Option<Self::TxToken<'_>> {
644        self.state.drv_tx_calls.fetch_add(1, Ordering::Relaxed);
645        // SAFETY: see `EmacDriver` doc.
646        let emac = unsafe { &mut *self.emac };
647
648        // Gate on capacity for a worst-case MTU-sized frame, not just
649        // "≥ 1 free descriptor". A frame larger than `BUF` consumes
650        // `len.div_ceil(BUF)` descriptors, so on rings where `BUF < MTU`
651        // a single-descriptor readiness check would let the driver hand
652        // out a token that `EmacTxToken::consume` then can't actually
653        // push, silently dropping the frame.
654        //
655        // `effective_mtu()` is capped by `TX * BUF`, so on undersized
656        // rings we still gate on something the engine can transmit
657        // (smaller frames will fit) instead of permanently returning
658        // `None` for a 1514-byte target the ring can never hold.
659        let mtu = Self::effective_mtu();
660        if !emac.can_transmit(mtu) {
661            self.state.tx_waker.register(cx.waker());
662            if !emac.can_transmit(mtu) {
663                return None;
664            }
665        }
666
667        self.state.drv_tx_some.fetch_add(1, Ordering::Relaxed);
668        Some(EmacTxToken {
669            emac: self.emac,
670            state: self.state,
671            _marker: PhantomData,
672        })
673    }
674
675    fn link_state(&mut self, cx: &mut Context<'_>) -> LinkState {
676        self.state.link_waker.register(cx.waker());
677        self.state.link_state()
678    }
679
680    fn capabilities(&self) -> Capabilities {
681        let mut caps = Capabilities::default();
682        // Advertise the value the driver can actually deliver (capped
683        // by ring capacity), not a fixed Ethernet MTU.
684        caps.max_transmission_unit = Self::effective_mtu();
685        caps.max_burst_size = Some(1);
686
687        // Hardware checksum offload is enabled on this driver:
688        //
689        // TX: TDES0.CIC = 0b11 instructs the GMAC to insert IPv4 header
690        //     and TCP/UDP/ICMP (with pseudo-header) checksums on every
691        //     outgoing frame. smoltcp must NOT compute them in software.
692        //
693        // RX: GMACCONFIG.IPC = 1 enables hardware checksum verification.
694        //     With DMAOPERATION.DT = 0 (default), the DMA automatically
695        //     drops frames whose IP/TCP/UDP checksums fail before the CPU
696        //     descriptor ring sees them. Frames that reach our `receive()`
697        //     path have already passed HW verification; smoltcp need not
698        //     re-verify.
699        //
700        // Tell smoltcp to skip both TX computation and RX verification for
701        // the four offloaded protocols. `Checksum::None` means "hardware
702        // handles it, don't touch".
703        let mut cs = ChecksumCapabilities::default();
704        cs.ipv4 = Checksum::None;
705        cs.tcp = Checksum::None;
706        cs.udp = Checksum::None;
707        cs.icmpv4 = Checksum::None;
708        caps.checksum = cs;
709        caps
710    }
711
712    fn hardware_address(&self) -> HardwareAddress {
713        // SAFETY: see `EmacDriver` doc.
714        let emac = unsafe { &*self.emac };
715        HardwareAddress::Ethernet(emac.mac_address())
716    }
717}
718
719// =============================================================================
720// Tests
721// =============================================================================
722
723#[cfg(test)]
724mod tests {
725    use super::*;
726
727    #[test]
728    fn state_starts_link_down() {
729        let s = EmacDriverState::new();
730        assert!(matches!(s.link_state(), LinkState::Down));
731    }
732
733    #[test]
734    fn state_link_set_up_then_down() {
735        let s = EmacDriverState::new();
736        s.set_link_up();
737        assert!(matches!(s.link_state(), LinkState::Up));
738        s.set_link_down();
739        assert!(matches!(s.link_state(), LinkState::Down));
740    }
741
742    #[test]
743    fn state_static_compatible() {
744        static STATE: EmacDriverState = EmacDriverState::new();
745        assert!(matches!(STATE.link_state(), LinkState::Down));
746    }
747
748    // ── Driver wrapper (host-side static behaviour) ──────────────
749
750    fn test_emac() -> Emac<10, 10, 1600> {
751        use crate::config::{ClkGpio, EmacConfig, RmiiClockConfig, RmiiPins, XtalFreq};
752
753        Emac::new(EmacConfig {
754            clock: RmiiClockConfig::InternalApll {
755                gpio: ClkGpio::Gpio17,
756                xtal: XtalFreq::Mhz40,
757            },
758            pins: RmiiPins { mdc: 23, mdio: 18 },
759        })
760    }
761
762    #[test]
763    fn driver_capabilities_advertise_mtu_and_burst() {
764        let mut emac = test_emac();
765        let state = EmacDriverState::new();
766        let driver = EmacDriver::new(&mut emac, &state);
767
768        let caps = driver.capabilities();
769        // 10 × 1600 ring fits a full Ethernet frame, so `effective_mtu`
770        // collapses to the standard ETH_MTU.
771        assert_eq!(caps.max_transmission_unit, ETH_MTU);
772        assert_eq!(caps.max_transmission_unit, 1514);
773        // Single-frame burst — the driver hands out one TX token at a
774        // time, so the stack should not pipeline more than one frame.
775        assert_eq!(caps.max_burst_size, Some(1));
776    }
777
778    #[test]
779    fn driver_capabilities_checksum_offloaded() {
780        // Hardware checksum offload: smoltcp must not compute or verify any
781        // of the four protocols. Checksum::None means "hardware handles it".
782        let mut emac = test_emac();
783        let state = EmacDriverState::new();
784        let driver = EmacDriver::new(&mut emac, &state);
785        let caps = driver.capabilities();
786        assert!(
787            matches!(caps.checksum.ipv4, Checksum::None),
788            "IPv4 checksum must be offloaded to hardware"
789        );
790        assert!(
791            matches!(caps.checksum.tcp, Checksum::None),
792            "TCP checksum must be offloaded to hardware"
793        );
794        assert!(
795            matches!(caps.checksum.udp, Checksum::None),
796            "UDP checksum must be offloaded to hardware"
797        );
798        assert!(
799            matches!(caps.checksum.icmpv4, Checksum::None),
800            "ICMPv4 checksum must be offloaded to hardware"
801        );
802    }
803
804    #[test]
805    fn effective_mtu_caps_to_ring_capacity() {
806        // Standard configuration: ring is plenty large, full ETH MTU.
807        assert_eq!(EmacDriver::<10, 10, 1600>::effective_mtu(), ETH_MTU);
808        assert_eq!(EmacDriver::<4, 4, 1600>::effective_mtu(), ETH_MTU);
809        // Undersized ring: `TX * BUF = 1024` < 1514. We must NOT advertise
810        // 1514 — the engine can't transmit that. Capped to ring capacity.
811        assert_eq!(EmacDriver::<2, 2, 512>::effective_mtu(), 1024);
812        // Edge: exactly equal to ETH_MTU.
813        assert_eq!(EmacDriver::<1, 1, 1514>::effective_mtu(), ETH_MTU);
814        // One byte short.
815        assert_eq!(EmacDriver::<1, 1, 1513>::effective_mtu(), 1513);
816    }
817
818    #[test]
819    fn driver_hardware_address_reflects_cached_mac() {
820        let mut emac = test_emac();
821        // Before any `set_mac_address`, the cached value is the zero
822        // address — the bring-up code is expected to programme one
823        // before `init` reaches the address-filter step.
824        {
825            let state = EmacDriverState::new();
826            let driver = EmacDriver::new(&mut emac, &state);
827            let HardwareAddress::Ethernet(mac) = driver.hardware_address() else {
828                panic!("expected Ethernet hardware address");
829            };
830            assert_eq!(mac, [0u8; 6]);
831        }
832
833        // Cache a MAC; the driver should reflect it on the next read.
834        let custom = [0xF0, 0x57, 0x8D, 0x01, 0x04, 0xE3];
835        emac.set_mac_address(custom);
836
837        let state = EmacDriverState::new();
838        let driver = EmacDriver::new(&mut emac, &state);
839        let HardwareAddress::Ethernet(mac) = driver.hardware_address() else {
840            panic!("expected Ethernet hardware address");
841        };
842        assert_eq!(mac, custom);
843    }
844}