Skip to main content

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}