Skip to main content

ove/async_net/
qemu_shm.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 QEMU MPS2-AN500 shared-memory Ethernet
8//! transport.
9//!
10//! Frames flow guest ↔ host through `/dev/shm/ove-net` via ARM
11//! semihosting; the host-side bridge (`config/scripts/qemu-net-bridge.py`)
12//! converts to/from a Linux TAP interface.
13//!
14//! The driver polls (no interrupt) — receive() returns frames already
15//! delivered to the SHM ring and registers the embassy-net waker so a
16//! background timer task can re-poll. For a typical app, spawn a small
17//! poller alongside the embassy-net Runner:
18//!
19//! ```ignore
20//! #[embassy_executor::task]
21//! async fn poll_task() {
22//!     loop {
23//!         embassy_time::Timer::after_millis(10).await;
24//!         ove::async_net::qemu_shm::wake_poll();
25//!     }
26//! }
27//! ```
28
29use ::core::ffi::{c_int, c_void};
30use ::core::sync::atomic::{AtomicBool, Ordering};
31use ::core::task::Context;
32
33use embassy_net_driver::{Capabilities, Driver, HardwareAddress, LinkState, RxToken, TxToken};
34use embassy_sync::waitqueue::AtomicWaker;
35
36/// Max Ethernet frame size — matches `NET_SHM_MTU` in the C header.
37pub const MTU: usize = 1518;
38
39// FFI surface from boards/qemu-mps2-an500/src/qemu_net_async.c.
40// Declared inline here rather than going through bindings_stub.rs
41// because the board crate is the consumer and the symbols are
42// always paired with the `async-net-qemu-shm` Cargo feature.
43unsafe extern "C" {
44    fn ove_qemu_net_async_init(mac: *const u8) -> c_int;
45    fn ove_qemu_net_async_tx(frame: *const c_void, len: u32) -> c_int;
46    fn ove_qemu_net_async_rx(buf: *mut c_void, buf_size: u32, out_len: *mut u32) -> c_int;
47    fn ove_qemu_net_async_link_up() -> c_int;
48}
49
50/// Waker the periodic poller signals so the embassy-net runner re-checks
51/// the RX ring. Single-waker is sufficient because embassy-net runs a
52/// single Runner task per Driver.
53static RX_WAKER: AtomicWaker = AtomicWaker::new();
54static INITED: AtomicBool = AtomicBool::new(false);
55
56/// Wake the embassy-net runner to re-poll the RX ring. Call this
57/// periodically (e.g. from a 10 ms timer task) to drive frame delivery.
58pub fn wake_poll() {
59    RX_WAKER.wake();
60}
61
62/// Driver state — owns a single RX frame buffer to keep storage small.
63/// The C side serialises ring access; a single in-flight frame at a
64/// time is fine for the SHM transport's throughput envelope.
65pub struct QemuShmDriver {
66    mac: [u8; 6],
67}
68
69impl QemuShmDriver {
70    /// Construct the driver and announce ourselves to the host bridge.
71    /// Safe to call once; subsequent calls reset ring positions.
72    pub fn new(mac: [u8; 6]) -> Self {
73        // SAFETY: ove_qemu_net_async_init only touches static C state.
74        let _ = unsafe { ove_qemu_net_async_init(mac.as_ptr()) };
75        INITED.store(true, Ordering::Release);
76        Self { mac }
77    }
78}
79
80/// RX token — wraps a stack-local frame buffer the runner reads from.
81pub struct QemuShmRx {
82    buf: [u8; MTU],
83    len: usize,
84}
85
86impl RxToken for QemuShmRx {
87    fn consume<R, F>(mut self, f: F) -> R
88    where
89        F: FnOnce(&mut [u8]) -> R,
90    {
91        f(&mut self.buf[..self.len])
92    }
93}
94
95/// TX token — owns a stack-local frame buffer the runner fills, then
96/// the consume() closure flushes to the SHM ring.
97pub struct QemuShmTx;
98
99impl TxToken for QemuShmTx {
100    fn consume<R, F>(self, len: usize, f: F) -> R
101    where
102        F: FnOnce(&mut [u8]) -> R,
103    {
104        let mut buf = [0u8; MTU];
105        let n = len.min(MTU);
106        let result = f(&mut buf[..n]);
107        // SAFETY: pointer + length are valid; the C side validates len.
108        unsafe {
109            ove_qemu_net_async_tx(buf.as_ptr() as *const c_void, n as u32);
110        }
111        result
112    }
113}
114
115impl Driver for QemuShmDriver {
116    type RxToken<'a>
117        = QemuShmRx
118    where
119        Self: 'a;
120    type TxToken<'a>
121        = QemuShmTx
122    where
123        Self: 'a;
124
125    fn receive(&mut self, cx: &mut Context<'_>) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
126        let mut rx = QemuShmRx {
127            buf: [0; MTU],
128            len: 0,
129        };
130        let mut out_len: u32 = 0;
131        // SAFETY: buf and out_len are owned by us, valid for the call.
132        let rc = unsafe {
133            ove_qemu_net_async_rx(
134                rx.buf.as_mut_ptr() as *mut c_void,
135                MTU as u32,
136                &mut out_len as *mut u32,
137            )
138        };
139        if rc == 0 && out_len > 0 {
140            rx.len = out_len as usize;
141            Some((rx, QemuShmTx))
142        } else {
143            RX_WAKER.register(cx.waker());
144            None
145        }
146    }
147
148    fn transmit(&mut self, _cx: &mut Context<'_>) -> Option<Self::TxToken<'_>> {
149        // SHM TX is always available (we drop on ring-full inside consume).
150        Some(QemuShmTx)
151    }
152
153    fn link_state(&mut self, _cx: &mut Context<'_>) -> LinkState {
154        // SAFETY: pure read of a C int.
155        let up = unsafe { ove_qemu_net_async_link_up() };
156        if up != 0 {
157            LinkState::Up
158        } else {
159            LinkState::Down
160        }
161    }
162
163    fn capabilities(&self) -> Capabilities {
164        // Medium is implied by hardware_address() returning Ethernet.
165        // MTU is Ethernet-frame-size (per embassy-net-driver 0.2 docs).
166        let mut caps = Capabilities::default();
167        caps.max_transmission_unit = 1514; // 1500 IP MTU + 14 Ethernet header
168        caps
169    }
170
171    fn hardware_address(&self) -> HardwareAddress {
172        HardwareAddress::Ethernet(self.mac)
173    }
174}