esp_hal/ethernet/embassy_net.rs
1//! `embassy-net` driver integration for the EMAC Ethernet peripheral.
2//!
3//! This module provides an [`embassy_net_driver_02::Driver`] implementation
4//! for [`Ethernet`] operating in async mode, enabling the
5//! Ethernet peripheral to be used as a network interface with
6//! [`embassy-net`](https://crates.io/crates/embassy-net).
7//!
8//! # Usage
9//!
10//! After obtaining an `Ethernet<'_, Async, P>` instance, pass it directly to
11//! `embassy_net::new()` — the [`Driver`] impl is inherent on the type.
12
13use core::task::Context;
14
15use embassy_net_driver_02::{
16 Capabilities,
17 Checksum,
18 Driver,
19 HardwareAddress,
20 LinkState,
21 RxToken,
22 TxToken,
23};
24
25use super::{Ethernet, RX_WAKER, TX_WAKER, mac::EmacRegs};
26use crate::{
27 Async,
28 ethernet::{
29 dma::{RDesRing, TDesRing},
30 phy::Phy,
31 },
32};
33
34/// Maximum Ethernet frame size (header + payload, no FCS).
35const MTU: usize = 1514;
36
37// ── Token types ───────────────────────────────────────────────────────────────
38
39/// Received-frame token.
40///
41/// Holds a mutable borrow of the RX ring and a shared borrow of the MAC
42/// register block needed to resume the RX DMA after releasing the descriptor.
43/// The closure passed to [`consume`][RxToken::consume] receives a `&mut [u8]`
44/// pointing directly into the DMA RX buffer — no copy is performed.
45pub struct EthernetRxToken<'a, 'd> {
46 rx: &'a mut RDesRing<'d>,
47}
48
49/// Transmit token.
50///
51/// Holds a mutable borrow of the TX ring and a shared borrow of the MAC
52/// register block needed to trigger a TX poll after committing the frame.
53/// The closure passed to [`consume`][TxToken::consume] receives a `&mut [u8]`
54/// pointing directly into the DMA TX buffer — no copy is performed.
55pub struct EthernetTxToken<'a, 'd> {
56 tx: &'a mut TDesRing<'d>,
57}
58
59impl<'a, 'd> RxToken for EthernetRxToken<'a, 'd> {
60 fn consume<R, F>(self, f: F) -> R
61 where
62 F: FnOnce(&mut [u8]) -> R,
63 {
64 // receive() loops past error frames and returns a direct reference into
65 // the DMA buffer. The descriptor is still CPU-owned while f runs.
66 // NOTE: unwrap is safe — Driver::receive() verified a valid frame exists
67 // and we hold exclusive access to the ring via &'a mut.
68 let pkt = unwrap!(self.rx.receive(), "RX packet vanished");
69 let r = f(pkt);
70 // After f returns, the &mut [u8] borrow on the ring ends (NLL),
71 // so we can recycle the descriptor.
72 self.rx.pop();
73 // Poke the RX DMA in case it suspended waiting for a CPU-owned descriptor.
74 EmacRegs.demand_rx_poll();
75 r
76 }
77}
78
79impl<'a, 'd> TxToken for EthernetTxToken<'a, 'd> {
80 fn consume<R, F>(self, len: usize, f: F) -> R
81 where
82 F: FnOnce(&mut [u8]) -> R,
83 {
84 let capped = len.min(MTU);
85 // Get a direct mutable reference into the DMA TX buffer — no copy.
86 // NOTE: unwrap is safe — Driver::transmit/receive() verified capacity.
87 let buf = unwrap!(self.tx.available_buf(), "TX slot vanished");
88 let r = f(&mut buf[..capped]);
89 // After f returns the &mut borrow on buf ends, we can commit.
90 self.tx.commit(capped);
91 EmacRegs.demand_tx_poll();
92 r
93 }
94}
95
96// ── Driver impl ───────────────────────────────────────────────────────────────
97
98impl<'d, P: Phy> Driver for Ethernet<'d, Async, P> {
99 type RxToken<'a>
100 = EthernetRxToken<'a, 'd>
101 where
102 Self: 'a;
103
104 type TxToken<'a>
105 = EthernetTxToken<'a, 'd>
106 where
107 Self: 'a;
108
109 fn receive(&mut self, cx: &mut Context<'_>) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
110 RX_WAKER.register(cx.waker());
111 TX_WAKER.register(cx.waker());
112
113 // Check availability as plain booleans so the borrows end before we
114 // split &mut self into the two ring references for the tokens.
115 let rx_ready = self.rx.receive().is_some();
116 // Poke the RX DMA unconditionally: receive() may have recycled error
117 // frames back to DMA ownership without a poll-demand write, which
118 // would leave the GMAC RX channel suspended.
119 EmacRegs.demand_rx_poll();
120 let tx_ready = self.tx.available_buf().is_some();
121
122 if rx_ready && tx_ready {
123 Some((
124 EthernetRxToken { rx: &mut self.rx },
125 EthernetTxToken { tx: &mut self.tx },
126 ))
127 } else {
128 None
129 }
130 }
131
132 fn transmit(&mut self, cx: &mut Context<'_>) -> Option<Self::TxToken<'_>> {
133 TX_WAKER.register(cx.waker());
134 if self.tx.available_buf().is_some() {
135 Some(EthernetTxToken { tx: &mut self.tx })
136 } else {
137 None
138 }
139 }
140
141 fn link_state(&mut self, cx: &mut Context<'_>) -> LinkState {
142 let state = self.poll_link(Some(cx));
143 if state.up {
144 self.set_speed(state.speed);
145 self.set_duplex(state.duplex);
146 LinkState::Up
147 } else {
148 LinkState::Down
149 }
150 }
151
152 fn capabilities(&self) -> Capabilities {
153 let mut caps = Capabilities::default();
154 caps.max_transmission_unit = MTU;
155 caps.max_burst_size = Some(self.tx.len());
156 // Checksums are offloaded to hardware in both directions (RX COE + TX
157 // insertion via the descriptor CIC bits), so smoltcp does neither.
158 caps.checksum.ipv4 = Checksum::None;
159 caps.checksum.tcp = Checksum::None;
160 caps.checksum.udp = Checksum::None;
161 caps.checksum.icmpv4 = Checksum::None;
162 caps.checksum.icmpv6 = Checksum::None;
163 caps
164 }
165
166 fn hardware_address(&self) -> HardwareAddress {
167 HardwareAddress::Ethernet(self.mac_addr())
168 }
169}