esp_emac/embassy_net.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, ChecksumCapabilities, Driver, HardwareAddress, LinkState, RxToken, TxToken,
156};
157// `Checksum` is only referenced by the capabilities self-test that asserts
158// every protocol is computed in software (HW offload disabled — see
159// `Driver::capabilities` comment).
160#[cfg(test)]
161use embassy_net_driver::Checksum;
162use embassy_sync::waitqueue::AtomicWaker;
163
164use crate::emac::{
165 Emac, BENCH_RX, BENCH_TX, DEFAULT_BUF, DEFAULT_RX, DEFAULT_TX, SMALL_RX, SMALL_TX,
166};
167#[cfg(feature = "instrumentation")]
168use crate::instrumentation::{histogram_bucket, now_us, HISTOGRAM_BUCKETS, IRQ_TIMESTAMP_NONE};
169use crate::interrupt::InterruptStatus;
170
171/// Diagnostic snapshot of the `Driver::receive` / `Driver::transmit`
172/// path: how many times embassy-net asked for a token, how many of
173/// those calls actually had a frame, and how many frames the tokens
174/// failed to push to / pull from the EMAC.
175#[derive(Debug, Clone, Copy, Default)]
176pub struct DriverCounters {
177 /// Calls to `Driver::receive`.
178 pub rx_calls: u32,
179 /// Calls that returned a non-empty token pair (frame available).
180 pub rx_some: u32,
181 /// Frames silently dropped in `EmacRxToken::consume` because the
182 /// underlying `Emac::receive` returned `Err(_)` or `Ok(None)` after
183 /// the driver had already handed out a token. Indicates either an
184 /// errored frame (CRC, oversize) or a race where another path
185 /// consumed the descriptor first.
186 pub rx_dropped: u32,
187 /// Calls to `Driver::transmit`.
188 pub tx_calls: u32,
189 /// Calls that returned a token (TX path was ready).
190 pub tx_some: u32,
191 /// Frames silently dropped in `EmacTxToken::consume` because
192 /// `Emac::transmit` returned `Err(_)` after the driver had already
193 /// handed out a token. Typical cause: descriptor ring exhausted
194 /// between the readiness check and the actual push.
195 pub tx_dropped: u32,
196}
197
198/// Diagnostic snapshot of the ISR counters.
199#[derive(Debug, Clone, Copy, Default)]
200pub struct IrqCounters {
201 /// Total number of times the ISR ran.
202 pub total: u32,
203 /// `RI` (rx_complete) flag observed.
204 pub ri: u32,
205 /// `RU` (rx_buf_unavailable) flag observed.
206 pub ru: u32,
207 /// `TI` (tx_complete) flag observed.
208 pub ti: u32,
209 /// `TU` (tx_buf_unavailable) flag observed.
210 pub tu: u32,
211 /// `ERI` (early receive) flag observed.
212 pub eri: u32,
213 /// At least one error flag observed (UNF/OVF/FBI).
214 pub err: u32,
215 /// Last raw `DMASTATUS` snapshot taken in the ISR (before W1C).
216 pub last_dmastat: u32,
217}
218
219/// Maximum frame size for stack-allocated copy buffers (Ethernet MTU + headers).
220const MAX_FRAME_SIZE: usize = 1600;
221
222/// Standard Ethernet MTU (IP MTU + L2 header). Upper bound on the value
223/// the driver advertises to embassy-net — see
224/// `EmacDriver::effective_mtu` for the per-instance value, which caps
225/// this against the physical TX ring capacity.
226const ETH_MTU: usize = 1514;
227
228// =============================================================================
229// Driver state
230// =============================================================================
231
232/// Shared state for the embassy-net driver.
233///
234/// Holds the RX, TX, and link wakers plus the cached link state. Place
235/// in a `static` so it can be reached from the EMAC ISR.
236pub struct EmacDriverState {
237 rx_waker: AtomicWaker,
238 tx_waker: AtomicWaker,
239 link_waker: AtomicWaker,
240 link_state: Mutex<Cell<LinkState>>,
241 /// Diagnostic counters — incremented in the ISR. Used by the dev-log
242 /// hypotheses H6/H7.
243 irq_count: AtomicU32,
244 irq_ri: AtomicU32,
245 irq_ru: AtomicU32,
246 irq_ti: AtomicU32,
247 irq_tu: AtomicU32,
248 irq_eri: AtomicU32,
249 irq_err: AtomicU32,
250 /// Last observed raw DMASTAT (snapshot taken in ISR, before W1C).
251 last_dmastat: AtomicU32,
252 /// Counters bumped by [`EmacDriver::receive`] / [`EmacDriver::transmit`]
253 /// to see how often embassy-net actually pulls data. `pub(crate)` so
254 /// the [`crate::instrumentation`] snapshot/reset path can read them
255 /// directly under the `instrumentation` feature.
256 pub(crate) drv_rx_calls: AtomicU32,
257 pub(crate) drv_rx_some: AtomicU32,
258 pub(crate) drv_rx_dropped: AtomicU32,
259 pub(crate) drv_tx_calls: AtomicU32,
260 pub(crate) drv_tx_some: AtomicU32,
261 pub(crate) drv_tx_dropped: AtomicU32,
262 // ── Instrumentation fields (feature `instrumentation`) ──────────
263 //
264 // Raw byte counters in `AtomicU32`. Xtensa LX6 has no `AtomicU64`,
265 // so the counter wraps every 2³² bytes (≈ 4 GB; ≈ 340 s at sustained
266 // 100BASE-TX line rate). Callers running longer than that should
267 // snapshot-and-reset periodically. All `pub(crate)` so the
268 // snapshot/reset code in `crate::instrumentation` can touch them.
269 /// Total bytes received through `EmacRxToken::consume`.
270 #[cfg(feature = "instrumentation")]
271 pub(crate) drv_rx_bytes: AtomicU32,
272 /// Total bytes transmitted through `EmacTxToken::consume`.
273 #[cfg(feature = "instrumentation")]
274 pub(crate) drv_tx_bytes: AtomicU32,
275 /// Sticky accumulator of `DMAMISSEDFR[15:0]` rolled forward across
276 /// the clear-on-read register.
277 #[cfg(feature = "instrumentation")]
278 pub(crate) dma_missed_frames: AtomicU32,
279 /// Sticky accumulator of `DMAMISSEDFR[31:16]`.
280 #[cfg(feature = "instrumentation")]
281 pub(crate) dma_fifo_overflow: AtomicU32,
282 /// Microsecond timestamp (`now_us()`) of the most recent RX
283 /// interrupt that observed `RI` (rx_complete) and had not yet been
284 /// consumed by a paired `RxToken`. `IRQ_TIMESTAMP_NONE` means "no
285 /// pending IRQ", set whenever an RxToken consumes a frame.
286 #[cfg(feature = "instrumentation")]
287 pub(crate) last_rx_irq_us: AtomicU32,
288 /// Latency histogram for `rx_complete` IRQ → RxToken `consume`
289 /// **return** (includes user-closure time — see the matching
290 /// field on `EmacInstrumentation` for the full semantics).
291 /// Bucket boundaries [`crate::instrumentation::HISTOGRAM_UPPER_US`].
292 #[cfg(feature = "instrumentation")]
293 pub(crate) rx_irq_to_token_us: [AtomicU32; HISTOGRAM_BUCKETS],
294 /// TX-token-start→`Emac::transmit`-completion latency histogram.
295 /// Bucket boundaries [`crate::instrumentation::HISTOGRAM_UPPER_US`].
296 #[cfg(feature = "instrumentation")]
297 pub(crate) tx_token_to_dma_us: [AtomicU32; HISTOGRAM_BUCKETS],
298}
299
300impl Default for EmacDriverState {
301 fn default() -> Self {
302 Self::new()
303 }
304}
305
306impl EmacDriverState {
307 /// Create a new state with link initially down.
308 pub const fn new() -> Self {
309 Self {
310 rx_waker: AtomicWaker::new(),
311 tx_waker: AtomicWaker::new(),
312 link_waker: AtomicWaker::new(),
313 link_state: Mutex::new(Cell::new(LinkState::Down)),
314 irq_count: AtomicU32::new(0),
315 irq_ri: AtomicU32::new(0),
316 irq_ru: AtomicU32::new(0),
317 irq_ti: AtomicU32::new(0),
318 irq_tu: AtomicU32::new(0),
319 irq_eri: AtomicU32::new(0),
320 irq_err: AtomicU32::new(0),
321 last_dmastat: AtomicU32::new(0),
322 drv_rx_calls: AtomicU32::new(0),
323 drv_rx_some: AtomicU32::new(0),
324 drv_rx_dropped: AtomicU32::new(0),
325 drv_tx_calls: AtomicU32::new(0),
326 drv_tx_some: AtomicU32::new(0),
327 drv_tx_dropped: AtomicU32::new(0),
328 #[cfg(feature = "instrumentation")]
329 drv_rx_bytes: AtomicU32::new(0),
330 #[cfg(feature = "instrumentation")]
331 drv_tx_bytes: AtomicU32::new(0),
332 #[cfg(feature = "instrumentation")]
333 dma_missed_frames: AtomicU32::new(0),
334 #[cfg(feature = "instrumentation")]
335 dma_fifo_overflow: AtomicU32::new(0),
336 #[cfg(feature = "instrumentation")]
337 last_rx_irq_us: AtomicU32::new(IRQ_TIMESTAMP_NONE),
338 #[cfg(feature = "instrumentation")]
339 rx_irq_to_token_us: [const { AtomicU32::new(0) }; HISTOGRAM_BUCKETS],
340 #[cfg(feature = "instrumentation")]
341 tx_token_to_dma_us: [const { AtomicU32::new(0) }; HISTOGRAM_BUCKETS],
342 }
343 }
344
345 /// Diagnostic counters from the ISR.
346 pub fn irq_counters(&self) -> IrqCounters {
347 IrqCounters {
348 total: self.irq_count.load(Ordering::Relaxed),
349 ri: self.irq_ri.load(Ordering::Relaxed),
350 ru: self.irq_ru.load(Ordering::Relaxed),
351 ti: self.irq_ti.load(Ordering::Relaxed),
352 tu: self.irq_tu.load(Ordering::Relaxed),
353 eri: self.irq_eri.load(Ordering::Relaxed),
354 err: self.irq_err.load(Ordering::Relaxed),
355 last_dmastat: self.last_dmastat.load(Ordering::Relaxed),
356 }
357 }
358
359 /// Diagnostic counters from `Driver::receive` / `Driver::transmit`
360 /// and the matching tokens.
361 pub fn driver_counters(&self) -> DriverCounters {
362 DriverCounters {
363 rx_calls: self.drv_rx_calls.load(Ordering::Relaxed),
364 rx_some: self.drv_rx_some.load(Ordering::Relaxed),
365 rx_dropped: self.drv_rx_dropped.load(Ordering::Relaxed),
366 tx_calls: self.drv_tx_calls.load(Ordering::Relaxed),
367 tx_some: self.drv_tx_some.load(Ordering::Relaxed),
368 tx_dropped: self.drv_tx_dropped.load(Ordering::Relaxed),
369 }
370 }
371
372 /// Read the cached link state.
373 pub fn link_state(&self) -> LinkState {
374 critical_section::with(|cs| self.link_state.borrow(cs).get())
375 }
376
377 /// Update the cached link state and wake stack tasks.
378 pub fn set_link_state(&self, state: LinkState) {
379 critical_section::with(|cs| self.link_state.borrow(cs).set(state));
380 self.link_waker.wake();
381 }
382
383 /// Mark the link as up and wake the stack.
384 pub fn set_link_up(&self) {
385 self.set_link_state(LinkState::Up);
386 }
387
388 /// Mark the link as down and wake the stack.
389 pub fn set_link_down(&self) {
390 self.set_link_state(LinkState::Down);
391 }
392
393 /// Wake RX/TX tasks based on a snapshot of the DMA interrupt status.
394 pub fn on_interrupt_status(&self, status: InterruptStatus) {
395 if status.rx_complete || status.rx_buf_unavailable {
396 self.rx_waker.wake();
397 }
398 if status.tx_complete || status.tx_buf_unavailable {
399 self.tx_waker.wake();
400 }
401 if status.has_error() {
402 self.rx_waker.wake();
403 self.tx_waker.wake();
404 }
405 }
406
407 /// Read the DMA status register, clear the interrupts, and wake
408 /// any tasks waiting on RX or TX.
409 ///
410 /// Does **not** wake `link_waker` — link state isn't reflected in
411 /// `DMASTATUS` and is updated separately by whatever PHY-polling
412 /// task calls [`set_link_up`](Self::set_link_up) /
413 /// [`set_link_down`](Self::set_link_down). That path takes care
414 /// of waking link-state observers itself.
415 ///
416 /// Intended to be called from the EMAC ISR. Touches only memory-
417 /// mapped EMAC registers and the embedded wakers, so there is no
418 /// aliasing concern with the [`EmacDriver`] holding a raw pointer
419 /// to the [`Emac`] state.
420 pub fn handle_emac_interrupt(&self) {
421 let dmastat = crate::regs::dma::BASE + crate::regs::dma::DMASTATUS;
422 // SAFETY: DMASTATUS is a known-valid 32-bit memory-mapped register.
423 let raw = unsafe { core::ptr::read_volatile(dmastat as *const u32) };
424 let status = InterruptStatus::from_raw(raw);
425 // Write-1-to-clear using the raw snapshot, masked against
426 // `ALL_INTERRUPTS` so only the W1C interrupt bits are written
427 // back. This still catches every asserted W1C bit — including
428 // ones outside `InterruptStatus` such as `ERI` (bit 14), `ETI`
429 // (bit 10), `RWT` (bit 9), `TJT` (bit 3) — but excludes the
430 // read-only fields (`RS`/`TS`/`EB`/`MMC`/`PMT`/`TTI`) so we
431 // never write garbage at addresses the hardware doesn't expect.
432 // Round-tripping through `to_raw()` would silently drop those
433 // bits and risk an interrupt storm.
434 // SAFETY: same address; masked write hits only W1C bits.
435 unsafe {
436 core::ptr::write_volatile(
437 dmastat as *mut u32,
438 raw & crate::regs::dma::status::ALL_INTERRUPTS,
439 )
440 };
441
442 self.irq_count.fetch_add(1, Ordering::Relaxed);
443 self.last_dmastat.store(raw, Ordering::Relaxed);
444 if status.rx_complete {
445 self.irq_ri.fetch_add(1, Ordering::Relaxed);
446 // Instrumentation: record IRQ timestamp so the paired
447 // RxToken can compute the IRQ→token latency once embassy-net
448 // schedules the consumer. CAS-style "only set if currently
449 // NONE" to avoid clobbering a still-pending measurement —
450 // if multiple frames arrive back-to-back, the first frame's
451 // latency stays accurate and subsequent measurements collapse
452 // into the next IRQ's timestamp, which is the conservative
453 // bound we want.
454 #[cfg(feature = "instrumentation")]
455 {
456 let _ = self.last_rx_irq_us.compare_exchange(
457 IRQ_TIMESTAMP_NONE,
458 now_us(),
459 Ordering::Relaxed,
460 Ordering::Relaxed,
461 );
462 }
463 }
464 if status.rx_buf_unavailable {
465 self.irq_ru.fetch_add(1, Ordering::Relaxed);
466 }
467 if status.tx_complete {
468 self.irq_ti.fetch_add(1, Ordering::Relaxed);
469 }
470 if status.tx_buf_unavailable {
471 self.irq_tu.fetch_add(1, Ordering::Relaxed);
472 }
473 // Early Receive Interrupt (ERI, bit 14 of DMASTATUS — distinct
474 // from ETI, the Early Transmit Interrupt at bit 10) isn't
475 // surfaced through `InterruptStatus`, so check the raw flag
476 // against the canonical `regs::dma::status::ERI` constant
477 // rather than a magic shift.
478 if (raw & crate::regs::dma::status::ERI) != 0 {
479 self.irq_eri.fetch_add(1, Ordering::Relaxed);
480 }
481 if status.has_error() {
482 self.irq_err.fetch_add(1, Ordering::Relaxed);
483 }
484
485 self.on_interrupt_status(status);
486 }
487}
488
489// =============================================================================
490// Driver wrapper
491// =============================================================================
492
493/// embassy-net driver for the ESP32 EMAC.
494///
495/// The driver holds a raw pointer to a previously-initialized
496/// [`Emac`] together with a reference to a shared [`EmacDriverState`].
497///
498/// # Concurrent ownership
499///
500/// At most **one** `EmacDriver` can hold the `&'d mut Emac<...>` at a
501/// time. The borrow checker enforces that through the `&'d mut`
502/// argument to [`Self::new`] — concurrent aliasing is impossible.
503/// Sequential reuse is fine: once a driver is dropped, the same
504/// `Emac` can be paired with a fresh driver again. The unit tests in
505/// this module exercise that pattern.
506///
507/// The companion [`EmacDriverState`] is **not** a strict singleton —
508/// see the module-level *Lifetime alignment* section. The constraint
509/// is that whichever instance the EMAC ISR's
510/// [`EmacDriverState::handle_emac_interrupt`] runs against must be
511/// the same one passed here as `state`.
512///
513/// For the default ring sizing the
514/// [`EmacDefaultDriver<'d>`](EmacDefaultDriver) alias removes the need
515/// to repeat the const generics in `embassy_executor::task` signatures.
516///
517/// # Safety
518///
519/// The pointer is dereferenced in `Driver` impl methods. The lifetime
520/// `'d` ensures the underlying `Emac` outlives the driver, but the raw
521/// pointer means **mutable aliasing** would be unsound. The
522/// at-most-one-concurrent-driver invariant above is what keeps that
523/// aliasing impossible in practice.
524pub struct EmacDriver<'d, const RX: usize, const TX: usize, const BUF: usize> {
525 emac: *mut Emac<RX, TX, BUF>,
526 state: &'d EmacDriverState,
527 _marker: PhantomData<&'d mut Emac<RX, TX, BUF>>,
528}
529
530// SAFETY contract for `unsafe impl Send`:
531//
532// `EmacDriver` carries a `*mut Emac<...>` (raw pointers are not auto-Send),
533// but the manual impl is sound under the following invariants — break any
534// one of them and the impl becomes unsound, so revisit it together with
535// any change that touches `Emac`'s field layout or the ISR data path:
536//
537// 1. Single ownership. Exactly one `EmacDriver` exists per `Emac`
538// instance for the lifetime of `'d`. `EmacDriver::new` consumes a
539// `&'d mut Emac<...>`, which the borrow checker enforces as long
540// as the pointer isn't laundered through other unsafe code.
541// 2. ISR-side access through `EmacDriverState` only touches MMIO
542// (`DMASTATUS`) and `AtomicU32` counters — *not* the `Emac` struct
543// behind the raw pointer. So the ISR is not a concurrent reader
544// of the data the `Driver` impl mutates.
545// 3. The pointee `Emac<RX, TX, BUF>` is itself `Send`. The raw pointer
546// hides auto-trait inference, so without an explicit bound a
547// future `Cell<X>` / `Rc<X>` / `MutexGuard<'_, X>` inside `Emac`
548// would silently leave this impl claiming `Send`. The
549// `where Emac<RX, TX, BUF>: Send` clause below promotes that
550// invariant from documentation to a compile-time check: such a
551// refactor will fail to compile here instead of producing
552// unsound `EmacDriver: Send`.
553unsafe impl<const RX: usize, const TX: usize, const BUF: usize> Send for EmacDriver<'_, RX, TX, BUF> where
554 Emac<RX, TX, BUF>: Send
555{
556}
557
558impl<'d, const RX: usize, const TX: usize, const BUF: usize> EmacDriver<'d, RX, TX, BUF> {
559 /// Create a new embassy-net driver.
560 ///
561 /// `emac` must be already initialized and started; `state` must be
562 /// the same instance whose [`on_interrupt_status`] is called from
563 /// the EMAC ISR.
564 pub fn new(emac: &'d mut Emac<RX, TX, BUF>, state: &'d EmacDriverState) -> Self {
565 Self {
566 emac: emac as *mut _,
567 state,
568 _marker: PhantomData,
569 }
570 }
571
572 /// Borrow the shared state.
573 pub fn state(&self) -> &EmacDriverState {
574 self.state
575 }
576
577 /// Effective MTU advertised to embassy-net and used as the
578 /// readiness threshold in `Driver::transmit`.
579 ///
580 /// Capped by the physical TX ring capacity (`TX * BUF`) so the
581 /// driver never advertises — and never gates on — a frame size
582 /// the engine couldn't actually push. On normal rings (e.g.
583 /// `TX=10, BUF=1600`) this returns the standard Ethernet MTU
584 /// of `1514`. On undersized rings (`TX * BUF < 1514`) it shrinks
585 /// to `TX * BUF`, so small frames still flow even though full-MTU
586 /// frames are physically impossible.
587 pub const fn effective_mtu() -> usize {
588 let ring_capacity = TX * BUF;
589 if ring_capacity < ETH_MTU {
590 ring_capacity
591 } else {
592 ETH_MTU
593 }
594 }
595}
596
597// =============================================================================
598// Convenience type aliases
599// =============================================================================
600
601/// Driver for the [`crate::EmacDefault`] ring sizing.
602///
603/// Sourced from the same [`DEFAULT_RX`] / [`DEFAULT_TX`] /
604/// [`DEFAULT_BUF`] constants as `EmacDefault`, so the two aliases
605/// stay paired even if the canonical sizing is retuned. The
606/// `embassy_executor::task` signature for the `net_task` runner can
607/// then read `Runner<'static, EmacDefaultDriver<'static>>` instead
608/// of repeating the const generics at every call site.
609pub type EmacDefaultDriver<'d> = EmacDriver<'d, DEFAULT_RX, DEFAULT_TX, DEFAULT_BUF>;
610
611/// Driver for the [`crate::EmacSmall`] ring sizing.
612///
613/// See [`EmacDefaultDriver`] for the rationale.
614pub type EmacSmallDriver<'d> = EmacDriver<'d, SMALL_RX, SMALL_TX, DEFAULT_BUF>;
615
616/// Driver for the [`crate::EmacBench`] ring sizing.
617///
618/// See [`EmacDefaultDriver`] for the rationale. Use when callers need
619/// the deeper 32/16 descriptor depth — e.g. when `EmacDefaultDriver`
620/// reports `drv_rx_dropped` events under sustained high packet rates.
621pub type EmacBenchDriver<'d> = EmacDriver<'d, BENCH_RX, BENCH_TX, DEFAULT_BUF>;
622
623// =============================================================================
624// RX / TX tokens
625// =============================================================================
626
627/// embassy-net RX token — copies one received frame on `consume`.
628pub struct EmacRxToken<'a, const RX: usize, const TX: usize, const BUF: usize> {
629 emac: *mut Emac<RX, TX, BUF>,
630 state: &'a EmacDriverState,
631 _marker: PhantomData<&'a mut Emac<RX, TX, BUF>>,
632}
633
634impl<const RX: usize, const TX: usize, const BUF: usize> RxToken for EmacRxToken<'_, RX, TX, BUF> {
635 fn consume<R, F>(self, f: F) -> R
636 where
637 F: FnOnce(&mut [u8]) -> R,
638 {
639 // Instrumentation: latch the IRQ→token latency for this frame.
640 // The ISR set `last_rx_irq_us` to `now_us()` (sentinel-CAS),
641 // so reading it here gives the wait between hardware completion
642 // and embassy-net's actual descriptor pull. Restore the sentinel
643 // so the next RX IRQ records a fresh timestamp.
644 #[cfg(feature = "instrumentation")]
645 let irq_us = self
646 .state
647 .last_rx_irq_us
648 .swap(IRQ_TIMESTAMP_NONE, Ordering::Relaxed);
649
650 let mut buffer = [0u8; MAX_FRAME_SIZE];
651 // SAFETY: `EmacDriver` guarantees the pointer is valid for the
652 // lifetime tracked by `'a`; tokens are consumed synchronously by
653 // the embassy stack.
654 let emac = unsafe { &mut *self.emac };
655 let res = match emac.receive(&mut buffer) {
656 Ok(Some(n)) => {
657 #[cfg(feature = "instrumentation")]
658 self.state
659 .drv_rx_bytes
660 .fetch_add(n as u32, Ordering::Relaxed);
661 f(&mut buffer[..n])
662 }
663 // No frame after we already handed out a token — either an
664 // error path (FrameError, BufferTooSmall: descriptor was
665 // recycled by the engine) or a race where another caller
666 // consumed it. Bump `rx_dropped` so the drop is observable
667 // and pass an empty slice to satisfy the `RxToken` contract.
668 Ok(None) | Err(_) => {
669 self.state.drv_rx_dropped.fetch_add(1, Ordering::Relaxed);
670 f(&mut [])
671 }
672 };
673
674 // Bucket the IRQ→token latency *after* the user closure returns
675 // so the histogram captures the full path the receiver took.
676 #[cfg(feature = "instrumentation")]
677 if irq_us != IRQ_TIMESTAMP_NONE {
678 let d_us = now_us().wrapping_sub(irq_us);
679 let bucket = histogram_bucket(d_us);
680 self.state.rx_irq_to_token_us[bucket].fetch_add(1, Ordering::Relaxed);
681 }
682
683 res
684 }
685}
686
687/// embassy-net TX token — submits one frame on `consume`.
688pub struct EmacTxToken<'a, const RX: usize, const TX: usize, const BUF: usize> {
689 emac: *mut Emac<RX, TX, BUF>,
690 state: &'a EmacDriverState,
691 _marker: PhantomData<&'a mut Emac<RX, TX, BUF>>,
692}
693
694impl<const RX: usize, const TX: usize, const BUF: usize> TxToken for EmacTxToken<'_, RX, TX, BUF> {
695 fn consume<R, F>(self, len: usize, f: F) -> R
696 where
697 F: FnOnce(&mut [u8]) -> R,
698 {
699 let len = len.min(MAX_FRAME_SIZE);
700 let mut buffer = [0u8; MAX_FRAME_SIZE];
701 let result = f(&mut buffer[..len]);
702
703 // Instrumentation: timestamp right before the engine push so
704 // the histogram captures `Emac::transmit` (which includes the
705 // internal `copy_from_slice` to the DMA buffer + descriptor
706 // arming + `tx_poll_demand`). The user-provided closure ran
707 // above, so its cost stays out of this measurement — what we
708 // actually want is the EMAC-side latency.
709 #[cfg(feature = "instrumentation")]
710 let tx_start_us = now_us();
711
712 // SAFETY: see `EmacRxToken::consume`.
713 let emac = unsafe { &mut *self.emac };
714 let push = emac.transmit(&buffer[..len]);
715
716 #[cfg(feature = "instrumentation")]
717 {
718 let d_us = now_us().wrapping_sub(tx_start_us);
719 let bucket = histogram_bucket(d_us);
720 self.state.tx_token_to_dma_us[bucket].fetch_add(1, Ordering::Relaxed);
721 }
722
723 if push.is_err() {
724 // `embassy-net-driver`'s `TxToken::consume` has no fallible
725 // return, so a failed push silently drops the frame. Bump
726 // `tx_dropped` for diagnostics.
727 self.state.drv_tx_dropped.fetch_add(1, Ordering::Relaxed);
728 } else {
729 #[cfg(feature = "instrumentation")]
730 self.state
731 .drv_tx_bytes
732 .fetch_add(len as u32, Ordering::Relaxed);
733 }
734 result
735 }
736}
737
738// =============================================================================
739// Driver trait
740// =============================================================================
741
742impl<const RX: usize, const TX: usize, const BUF: usize> Driver for EmacDriver<'_, RX, TX, BUF> {
743 type RxToken<'a>
744 = EmacRxToken<'a, RX, TX, BUF>
745 where
746 Self: 'a;
747 type TxToken<'a>
748 = EmacTxToken<'a, RX, TX, BUF>
749 where
750 Self: 'a;
751
752 fn receive(&mut self, cx: &mut Context<'_>) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
753 self.state.drv_rx_calls.fetch_add(1, Ordering::Relaxed);
754 // SAFETY: see `EmacDriver` doc.
755 let emac = unsafe { &mut *self.emac };
756
757 if !emac.rx_available() {
758 self.state.rx_waker.register(cx.waker());
759 if !emac.rx_available() {
760 return None;
761 }
762 }
763
764 self.state.drv_rx_some.fetch_add(1, Ordering::Relaxed);
765 Some((
766 EmacRxToken {
767 emac: self.emac,
768 state: self.state,
769 _marker: PhantomData,
770 },
771 EmacTxToken {
772 emac: self.emac,
773 state: self.state,
774 _marker: PhantomData,
775 },
776 ))
777 }
778
779 fn transmit(&mut self, cx: &mut Context<'_>) -> Option<Self::TxToken<'_>> {
780 self.state.drv_tx_calls.fetch_add(1, Ordering::Relaxed);
781 // SAFETY: see `EmacDriver` doc.
782 let emac = unsafe { &mut *self.emac };
783
784 // Gate on capacity for a worst-case MTU-sized frame, not just
785 // "≥ 1 free descriptor". A frame larger than `BUF` consumes
786 // `len.div_ceil(BUF)` descriptors, so on rings where `BUF < MTU`
787 // a single-descriptor readiness check would let the driver hand
788 // out a token that `EmacTxToken::consume` then can't actually
789 // push, silently dropping the frame.
790 //
791 // `effective_mtu()` is capped by `TX * BUF`, so on undersized
792 // rings we still gate on something the engine can transmit
793 // (smaller frames will fit) instead of permanently returning
794 // `None` for a 1514-byte target the ring can never hold.
795 let mtu = Self::effective_mtu();
796 if !emac.can_transmit(mtu) {
797 self.state.tx_waker.register(cx.waker());
798 if !emac.can_transmit(mtu) {
799 return None;
800 }
801 }
802
803 self.state.drv_tx_some.fetch_add(1, Ordering::Relaxed);
804 Some(EmacTxToken {
805 emac: self.emac,
806 state: self.state,
807 _marker: PhantomData,
808 })
809 }
810
811 fn link_state(&mut self, cx: &mut Context<'_>) -> LinkState {
812 self.state.link_waker.register(cx.waker());
813 self.state.link_state()
814 }
815
816 fn capabilities(&self) -> Capabilities {
817 let mut caps = Capabilities::default();
818 // Advertise the value the driver can actually deliver (capped
819 // by ring capacity), not a fixed Ethernet MTU.
820 caps.max_transmission_unit = Self::effective_mtu();
821 caps.max_burst_size = Some(1);
822
823 // Hardware checksum offload is **disabled** in both directions on
824 // this driver because the ESP32 GMAC checksum engine is unreliable
825 // on at least rev v3.1 silicon:
826 // * TX insertion (`TDES0.CIC=0b11`) emitted wrong TCP/UDP
827 // checksums, breaking sustained TCP flow after the first
828 // MSS-sized segment (see `TxDescriptor::prepare`).
829 // * RX verification (`GMACCONFIG.IPC=1`) symmetrically marked
830 // valid incoming TCP/UDP frames as checksum-errored, causing
831 // the DMA to drop them at `DMAOPERATION.DT=0` before they
832 // reached the CPU. The empirical signature was iperf2 downlink
833 // collapsing to 0 Mbps while uplink kept working.
834 // Both `TDES0.CIC` and `GMACCONFIG.IPC` are therefore left at 0
835 // (see `mac_init` in `emac.rs`), and this driver advertises
836 // `ChecksumCapabilities::default()` so smoltcp computes and
837 // verifies IPv4/TCP/UDP/ICMP checksums in software.
838 caps.checksum = ChecksumCapabilities::default();
839 caps
840 }
841
842 fn hardware_address(&self) -> HardwareAddress {
843 // SAFETY: see `EmacDriver` doc.
844 let emac = unsafe { &*self.emac };
845 HardwareAddress::Ethernet(emac.mac_address())
846 }
847}
848
849// =============================================================================
850// Tests
851// =============================================================================
852
853#[cfg(test)]
854mod tests {
855 use super::*;
856
857 #[test]
858 fn state_starts_link_down() {
859 let s = EmacDriverState::new();
860 assert!(matches!(s.link_state(), LinkState::Down));
861 }
862
863 #[test]
864 fn state_link_set_up_then_down() {
865 let s = EmacDriverState::new();
866 s.set_link_up();
867 assert!(matches!(s.link_state(), LinkState::Up));
868 s.set_link_down();
869 assert!(matches!(s.link_state(), LinkState::Down));
870 }
871
872 #[test]
873 fn state_static_compatible() {
874 static STATE: EmacDriverState = EmacDriverState::new();
875 assert!(matches!(STATE.link_state(), LinkState::Down));
876 }
877
878 // ── Driver wrapper (host-side static behaviour) ──────────────
879
880 fn test_emac() -> Emac<10, 10, 1600> {
881 use crate::config::{ClkGpio, EmacConfig, RmiiClockConfig, RmiiPins, XtalFreq};
882
883 Emac::new(EmacConfig {
884 clock: RmiiClockConfig::InternalApll {
885 gpio: ClkGpio::Gpio17,
886 xtal: XtalFreq::Mhz40,
887 },
888 pins: RmiiPins { mdc: 23, mdio: 18 },
889 })
890 }
891
892 #[test]
893 fn driver_capabilities_advertise_mtu_and_burst() {
894 let mut emac = test_emac();
895 let state = EmacDriverState::new();
896 let driver = EmacDriver::new(&mut emac, &state);
897
898 let caps = driver.capabilities();
899 // 10 × 1600 ring fits a full Ethernet frame, so `effective_mtu`
900 // collapses to the standard ETH_MTU.
901 assert_eq!(caps.max_transmission_unit, ETH_MTU);
902 assert_eq!(caps.max_transmission_unit, 1514);
903 // Single-frame burst — the driver hands out one TX token at a
904 // time, so the stack should not pipeline more than one frame.
905 assert_eq!(caps.max_burst_size, Some(1));
906 }
907
908 #[test]
909 fn driver_capabilities_checksum_software() {
910 // HW checksum offload is disabled (broken on ESP32 rev v3.1 — see
911 // `TxDescriptor::prepare` and `Driver::capabilities` comments).
912 // smoltcp must compute IPv4/TCP/UDP/ICMP checksums in software,
913 // which corresponds to `ChecksumCapabilities::default()`. Every
914 // protocol must be in the `Both` (or equivalent enabled) state.
915 let mut emac = test_emac();
916 let state = EmacDriverState::new();
917 let driver = EmacDriver::new(&mut emac, &state);
918 let caps = driver.capabilities();
919 assert!(
920 !matches!(caps.checksum.ipv4, Checksum::None),
921 "IPv4 checksum must be computed by smoltcp (HW offload disabled)"
922 );
923 assert!(
924 !matches!(caps.checksum.tcp, Checksum::None),
925 "TCP checksum must be computed by smoltcp (HW offload disabled)"
926 );
927 assert!(
928 !matches!(caps.checksum.udp, Checksum::None),
929 "UDP checksum must be computed by smoltcp (HW offload disabled)"
930 );
931 assert!(
932 !matches!(caps.checksum.icmpv4, Checksum::None),
933 "ICMPv4 checksum must be computed by smoltcp (HW offload disabled)"
934 );
935 }
936
937 #[test]
938 fn effective_mtu_caps_to_ring_capacity() {
939 // Standard configuration: ring is plenty large, full ETH MTU.
940 assert_eq!(EmacDriver::<10, 10, 1600>::effective_mtu(), ETH_MTU);
941 assert_eq!(EmacDriver::<4, 4, 1600>::effective_mtu(), ETH_MTU);
942 // Undersized ring: `TX * BUF = 1024` < 1514. We must NOT advertise
943 // 1514 — the engine can't transmit that. Capped to ring capacity.
944 assert_eq!(EmacDriver::<2, 2, 512>::effective_mtu(), 1024);
945 // Edge: exactly equal to ETH_MTU.
946 assert_eq!(EmacDriver::<1, 1, 1514>::effective_mtu(), ETH_MTU);
947 // One byte short.
948 assert_eq!(EmacDriver::<1, 1, 1513>::effective_mtu(), 1513);
949 }
950
951 #[test]
952 fn driver_hardware_address_reflects_cached_mac() {
953 let mut emac = test_emac();
954 // Before any `set_mac_address`, the cached value is the zero
955 // address — the bring-up code is expected to programme one
956 // before `init` reaches the address-filter step.
957 {
958 let state = EmacDriverState::new();
959 let driver = EmacDriver::new(&mut emac, &state);
960 let HardwareAddress::Ethernet(mac) = driver.hardware_address() else {
961 panic!("expected Ethernet hardware address");
962 };
963 assert_eq!(mac, [0u8; 6]);
964 }
965
966 // Cache a MAC; the driver should reflect it on the next read.
967 let custom = [0xF0, 0x57, 0x8D, 0x01, 0x04, 0xE3];
968 emac.set_mac_address(custom);
969
970 let state = EmacDriverState::new();
971 let driver = EmacDriver::new(&mut emac, &state);
972 let HardwareAddress::Ethernet(mac) = driver.hardware_address() else {
973 panic!("expected Ethernet hardware address");
974 };
975 assert_eq!(mac, custom);
976 }
977}