Skip to main content

ove/async_net/
stm32f7_eth.rs

1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! embassy-net driver for the STM32F7 ETH MAC + LAN8742A PHY.
8//!
9//! Same shape as [`super::qemu_shm::QemuShmDriver`] — the differences
10//! are all behind the C entry points (`ove_stm32f7_eth_async_*` vs
11//! `ove_qemu_net_async_*`). For now the driver polls; spawn a poller
12//! task alongside the embassy-net Runner:
13//!
14//! ```ignore
15//! #[embassy_executor::task]
16//! async fn poll_task() {
17//!     loop {
18//!         embassy_time::Timer::after_millis(2).await;
19//!         ove::async_net::stm32f7_eth::wake_poll();
20//!     }
21//! }
22//! ```
23//!
24//! ISR-backed wake (via `HAL_ETH_IRQHandler`) is a future optimisation
25//! — the STM32F7 ETH IRQ exists but stock oveRTOS doesn't enable it
26//! (the lwIP backend also polls).
27
28use ::core::ffi::c_void;
29use ::core::sync::atomic::{AtomicBool, Ordering};
30use ::core::task::Context;
31
32use embassy_net_driver::{Capabilities, Driver, HardwareAddress, LinkState, RxToken, TxToken};
33use embassy_sync::waitqueue::AtomicWaker;
34
35/// Max Ethernet frame size (Ethernet header + IP MTU + CRC slack).
36pub const MTU: usize = 1518;
37
38unsafe extern "C" {
39    fn ove_stm32f7_eth_async_init(mac: *const u8) -> ::core::ffi::c_int;
40    fn ove_stm32f7_eth_async_tx(frame: *const c_void, len: u32) -> ::core::ffi::c_int;
41    fn ove_stm32f7_eth_async_rx(
42        buf: *mut c_void,
43        buf_size: u32,
44        out_len: *mut u32,
45    ) -> ::core::ffi::c_int;
46    fn ove_stm32f7_eth_async_link_up() -> ::core::ffi::c_int;
47    fn ove_stm32f7_eth_async_set_notify(
48        cb: Option<unsafe extern "C" fn(*mut c_void)>,
49        user_data: *mut c_void,
50    );
51}
52
53/// ISR trampoline: the ETH IRQ → HAL_ETH_RxCpltCallback fires this,
54/// which wakes the embassy-net runner. We pass `&RX_WAKER` as user_data;
55/// dereferencing is sound because it's a 'static.
56unsafe extern "C" fn isr_notify(_ud: *mut c_void) {
57    RX_WAKER.wake();
58}
59
60static RX_WAKER: AtomicWaker = AtomicWaker::new();
61static INITED: AtomicBool = AtomicBool::new(false);
62
63/// Wake the embassy-net runner to re-poll the ETH MAC.
64///
65/// Since the driver wakes itself from `ETH_IRQHandler` on RX/TX
66/// completion, calling this is only useful as a slow link-state poll
67/// (every few hundred ms — enough to notice cable unplugs). It is
68/// idempotent and safe from any context.
69pub fn wake_poll() {
70    RX_WAKER.wake();
71}
72
73/// STM32F7 ETH MAC driver.
74pub struct Stm32f7EthDriver {
75    mac: [u8; 6],
76}
77
78impl Stm32f7EthDriver {
79    /// Initialise the MAC + PHY and return a driver handle.
80    /// Blocks for up to ~5 s waiting for PHY autoneg.
81    pub fn new(mac: [u8; 6]) -> Self {
82        // SAFETY: C init touches static MAC state only; idempotent on
83        // re-init (resets ring positions).
84        let _ = unsafe { ove_stm32f7_eth_async_init(mac.as_ptr()) };
85        // SAFETY: passing a function pointer + null user_data (the
86        // trampoline reads the 'static RX_WAKER directly).
87        unsafe {
88            ove_stm32f7_eth_async_set_notify(Some(isr_notify), ::core::ptr::null_mut());
89        }
90        INITED.store(true, Ordering::Release);
91        Self { mac }
92    }
93}
94
95pub struct Stm32f7EthRx {
96    buf: [u8; MTU],
97    len: usize,
98}
99
100impl RxToken for Stm32f7EthRx {
101    fn consume<R, F>(mut self, f: F) -> R
102    where
103        F: FnOnce(&mut [u8]) -> R,
104    {
105        f(&mut self.buf[..self.len])
106    }
107}
108
109pub struct Stm32f7EthTx;
110
111impl TxToken for Stm32f7EthTx {
112    fn consume<R, F>(self, len: usize, f: F) -> R
113    where
114        F: FnOnce(&mut [u8]) -> R,
115    {
116        let mut buf = [0u8; MTU];
117        let n = len.min(MTU);
118        let result = f(&mut buf[..n]);
119        // SAFETY: pointer + length are valid; HAL_ETH_Transmit copies
120        // the data into the descriptor before returning.
121        unsafe {
122            ove_stm32f7_eth_async_tx(buf.as_ptr() as *const c_void, n as u32);
123        }
124        result
125    }
126}
127
128impl Driver for Stm32f7EthDriver {
129    type RxToken<'a>
130        = Stm32f7EthRx
131    where
132        Self: 'a;
133    type TxToken<'a>
134        = Stm32f7EthTx
135    where
136        Self: 'a;
137
138    fn receive(&mut self, cx: &mut Context<'_>) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
139        let mut rx = Stm32f7EthRx {
140            buf: [0; MTU],
141            len: 0,
142        };
143        let mut out_len: u32 = 0;
144        let rc = unsafe {
145            ove_stm32f7_eth_async_rx(
146                rx.buf.as_mut_ptr() as *mut c_void,
147                MTU as u32,
148                &mut out_len as *mut u32,
149            )
150        };
151        if rc == 0 && out_len > 0 {
152            rx.len = out_len as usize;
153            Some((rx, Stm32f7EthTx))
154        } else {
155            RX_WAKER.register(cx.waker());
156            None
157        }
158    }
159
160    fn transmit(&mut self, _cx: &mut Context<'_>) -> Option<Self::TxToken<'_>> {
161        Some(Stm32f7EthTx)
162    }
163
164    fn link_state(&mut self, _cx: &mut Context<'_>) -> LinkState {
165        // SAFETY: pure PHY register read.
166        let up = unsafe { ove_stm32f7_eth_async_link_up() };
167        if up != 0 {
168            LinkState::Up
169        } else {
170            LinkState::Down
171        }
172    }
173
174    fn capabilities(&self) -> Capabilities {
175        let mut caps = Capabilities::default();
176        caps.max_transmission_unit = 1514;
177        caps
178    }
179
180    fn hardware_address(&self) -> HardwareAddress {
181        HardwareAddress::Ethernet(self.mac)
182    }
183}