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 let tx_ready = self.tx.available_buf().is_some();
117
118 if rx_ready && tx_ready {
119 Some((
120 EthernetRxToken { rx: &mut self.rx },
121 EthernetTxToken { tx: &mut self.tx },
122 ))
123 } else {
124 None
125 }
126 }
127
128 fn transmit(&mut self, cx: &mut Context<'_>) -> Option<Self::TxToken<'_>> {
129 TX_WAKER.register(cx.waker());
130 if self.tx.available_buf().is_some() {
131 Some(EthernetTxToken { tx: &mut self.tx })
132 } else {
133 None
134 }
135 }
136
137 /// Polls the PHY and tracks the DMA interrupt state with the link.
138 ///
139 /// DMA interrupts are only enabled while the link is up: with no link there
140 /// is no RX clock, and error interrupts would otherwise fire continuously.
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 super::bind_eth_isr();
147 EmacRegs.dma_enable_interrupts(true);
148 LinkState::Up
149 } else {
150 EmacRegs.dma_disable_interrupts();
151 LinkState::Down
152 }
153 }
154
155 fn capabilities(&self) -> Capabilities {
156 let mut caps = Capabilities::default();
157 caps.max_transmission_unit = MTU;
158 caps.max_burst_size = Some(self.tx.len());
159 // `Checksum` names the direction(s) computed in software. Hardware
160 // offload is advertised so the stack skips that side.
161 const RX_COE: bool = property!("ethernet.rx_checksum_offload");
162 const TX_COE: bool = property!("ethernet.tx_checksum_offload");
163 let checksum = match (RX_COE, TX_COE) {
164 (true, true) => Checksum::None,
165 (true, false) => Checksum::Tx,
166 (false, true) => Checksum::Rx,
167 (false, false) => Checksum::Both,
168 };
169 caps.checksum.ipv4 = checksum;
170 caps.checksum.tcp = checksum;
171 caps.checksum.udp = checksum;
172 caps.checksum.icmpv4 = checksum;
173 caps.checksum.icmpv6 = checksum;
174 caps
175 }
176
177 fn hardware_address(&self) -> HardwareAddress {
178 HardwareAddress::Ethernet(self.mac_addr())
179 }
180}