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}