ove/async_net/mod.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//! Async networking via embassy-net.
8//!
9//! Provides an `embassy_net::Stack` backed by a per-board Ethernet
10//! Driver impl. Once you've spawned the runner task, open async TCP /
11//! UDP / DNS sockets via the standard `embassy_net::tcp::TcpSocket` /
12//! `UdpSocket` types.
13//!
14//! ## Build gating
15//!
16//! Two layers must be enabled:
17//!
18//! 1. **C side** — `CONFIG_OVE_ASYNC_NET=y` in your `ove_config.h`
19//! (set via `defconfig`, `menuconfig`, or an app's `app.yaml`).
20//! Implies `CONFIG_OVE_ASYNC=y`. **Mutually exclusive with
21//! `CONFIG_OVE_NET=y`** — both want the same MAC, only one IP stack
22//! can own the descriptor ring at runtime. The build system enforces
23//! this; menuconfig won't let you turn both on.
24//!
25//! 2. **Cargo side** — `async-net` feature on the `ove` crate, plus a
26//! transport feature for your board:
27//!
28//! | Board | Feature |
29//! |------------------------|--------------------------|
30//! | qemu-mps2-an500 | `async-net-qemu-shm` |
31//! | stm32f746g-discovery | `async-net-stm32f7-eth` |
32//!
33//! Apps that want to build for several boards from one `Cargo.toml`
34//! can enable all transports and pick the right Driver per-board
35//! using `#[cfg(board_<name>)]` (emitted by the shared
36//! `apps/rust/ove_build_common.rs` build script).
37//!
38//! ## Usage
39//!
40//! ```ignore
41//! use embassy_executor::Spawner;
42//! use embassy_net::{Config, Runner, Stack};
43//! use embassy_time::{Duration, Timer};
44//! use static_cell::StaticCell;
45//!
46//! use ove::async_net::{Stm32f7EthDriver, StackResources};
47//!
48//! #[embassy_executor::task]
49//! async fn net_task(mut runner: Runner<'static, Stm32f7EthDriver>) -> ! {
50//! runner.run().await
51//! }
52//!
53//! #[ove::main]
54//! async fn app(spawner: Spawner) {
55//! let driver = Stm32f7EthDriver::new([0x02, 0, 0, 0xBE, 0xEF, 0x01]);
56//! let config = Config::dhcpv4(Default::default());
57//! static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new();
58//! let resources = RESOURCES.init(StackResources::new());
59//! let (stack, runner) = embassy_net::new(driver, config, resources, 0xdead_beef);
60//! spawner.must_spawn(net_task(runner));
61//! // ... open sockets on `stack` ...
62//! }
63//! ```
64//!
65//! A full demo with TCP heartbeats lives at
66//! `apps/rust/heap/example_async_net/` (and its zero-heap twin under
67//! `apps/rust/zeroheap/`). Hardware-verified on STM32F746G-Discovery
68//! talking to a Pi over RMII.
69//!
70//! ## Comparison with the blocking [`crate::net`] stack
71//!
72//! Both stacks ship in the same `ove` crate; they share the [`crate::net::Address`]
73//! / [`crate::Error`] types so utility code can be reused. The split is at the
74//! socket / TCP/IP layer:
75//!
76//! | Aspect | [`crate::net`] (blocking, lwIP/Zephyr/NuttX/POSIX) | [`crate::async_net`] (embassy-net) |
77//! |-----------------------|----------------------------------------------------|------------------------------------------------|
78//! | Concurrency model | One thread per socket loop | Many tasks on one executor |
79//! | RAM footprint | ~30-50 KB lwIP heap + per-thread stacks | ~3 KB `StackResources<3>` + smoltcp socket bufs |
80//! | TCP throughput | Higher (lwIP zero-copy `pbuf` chains) | Lower (smoltcp copies into TX/RX bufs) |
81//! | Latency on RX | Driven by lwIP's tcpip_thread cadence | ISR-woken on STM32; polled on QEMU SHM |
82//! | TLS | [`crate::net_tls`] (mbedTLS, in-tree) | `embedded-tls` (crates.io) |
83//! | HTTP client | [`crate::net_http`] (in-tree C) | `reqwless` (crates.io) |
84//! | MQTT client | [`crate::net_mqtt`] (in-tree C) | `rust-mqtt` (crates.io) |
85//! | Time sync | [`crate::net_sntp`] (in-tree C) | `sntpc` (crates.io, no_std mode) |
86//! | HTTP server | [`crate::net_httpd`] (in-tree C, REST + WS) | `picoserve` (crates.io) |
87//! | Cross-RTOS coverage | All 4 backends | FreeRTOS + Zephyr + NuttX on supported boards |
88//!
89//! See [`crate::net`]'s module docs for the inverse decision guide.
90//!
91//! ## Pairing with community async crates
92//!
93//! Once the embassy-net Stack is up, these crates.io libraries Just Work
94//! on top:
95//!
96//! ### TLS — [`embedded-tls`]
97//!
98//! ```toml
99//! embedded-tls = { version = "0.17", default-features = false }
100//! ```
101//!
102//! ```ignore
103//! use embedded_tls::{Aes128GcmSha256, TlsConfig, TlsConnection, TlsContext};
104//!
105//! let mut tcp = TcpSocket::new(&stack, &mut rx_buf, &mut tx_buf);
106//! tcp.connect(endpoint).await?;
107//! let config = TlsConfig::new().with_server_name("example.com");
108//! let mut tls = TlsConnection::<_, Aes128GcmSha256>::new(tcp, &mut read_record, &mut write_record);
109//! tls.open(TlsContext::new(&config, UnsecureProvider::new::<Aes128GcmSha256>(rng))).await?;
110//! ```
111//!
112//! No mbedTLS in the binary. ~50 KB code, smaller than [`crate::net_tls`].
113//!
114//! ### HTTP client — [`reqwless`]
115//!
116//! ```toml
117//! reqwless = { version = "0.13", default-features = false, features = ["embedded-tls"] }
118//! ```
119//!
120//! ```ignore
121//! use reqwless::client::HttpClient;
122//! use reqwless::request::Method;
123//!
124//! let mut tls_buf = [0u8; 16640];
125//! let mut client = HttpClient::new_with_tls(&stack, &dns, TlsConfig::new(seed, &mut tls_buf, ..));
126//! let mut rx_buf = [0u8; 4096];
127//! let mut req = client.request(Method::GET, "https://example.com/").await?;
128//! let resp = req.send(&mut rx_buf).await?;
129//! ```
130//!
131//! Pairs with `embedded-tls` for HTTPS; works against plain HTTP without
132//! it. ~30 KB code added on top of TLS.
133//!
134//! ### MQTT — [`rust-mqtt`]
135//!
136//! ```toml
137//! rust-mqtt = { version = "0.3", default-features = false }
138//! ```
139//!
140//! ```ignore
141//! use rust_mqtt::client::client::MqttClient;
142//! use rust_mqtt::client::client_config::ClientConfig;
143//!
144//! let mut tcp = TcpSocket::new(&stack, &mut rx_buf, &mut tx_buf);
145//! tcp.connect(broker).await?;
146//! let mut config = ClientConfig::new(MqttVersion::MQTTv5, CountingRng(seed));
147//! config.add_client_id("overtos-rust");
148//! let mut client = MqttClient::new(tcp, &mut write_buf, 80, &mut recv_buf, 80, config);
149//! client.connect_to_broker().await?;
150//! client.subscribe_to_topic("device/+/status").await?;
151//! ```
152//!
153//! Supports MQTT 3.1.1 + 5.0; QoS 0/1/2.
154//!
155//! ### SNTP — [`sntpc`]
156//!
157//! ```toml
158//! sntpc = { version = "0.4", default-features = false, features = ["embassy-socket"] }
159//! ```
160//!
161//! Single-shot NTP query with millisecond accuracy; no separate `_init`
162//! step like [`crate::net_sntp`].
163//!
164//! ### HTTP server — [`picoserve`]
165//!
166//! ```toml
167//! picoserve = { version = "0.18", features = ["embassy"] }
168//! ```
169//!
170//! Async REST router with macro-driven path matching, JSON via `serde`,
171//! WebSocket upgrade. Replaces [`crate::net_httpd`] when going async.
172//!
173//! ## Performance notes
174//!
175//! - On **STM32F7 ETH** the driver runs ISR-woken — `ETH_IRQHandler`
176//! fires `HAL_ETH_RxCpltCallback` which wakes the embassy-net runner's
177//! `AtomicWaker`. No polling cadence needed for RX. The fallback poll
178//! timer (in the example app) ticks at 500 ms for slow link-state
179//! monitoring.
180//! - On **QEMU MPS2-AN500 SHM** there's no interrupt; the driver
181//! polls every ~10 ms via a small timer task. RX bandwidth is gated
182//! by semihosting overhead (~1 ms per syscall), so ~100 Kbit/s ceiling.
183//! Adequate for development; for high throughput, use real hardware.
184//!
185//! ## Memory budget on STM32F7
186//!
187//! Rough costs for a typical app (DHCP + 1 TCP socket + 1 UDP socket):
188//!
189//! - `StackResources<3>` — 2.7 KB (in `.bss` via `static_cell::StaticCell`)
190//! - smoltcp socket TX+RX bufs — 4 KB (caller-provided)
191//! - ETH DMA descriptors + buffers — 6 KB (`stm32f7_eth_async.c` statics)
192//! - Tasks (3) — 3 × 4 KB stacks = 12 KB (FreeRTOS heap)
193//!
194//! Total ~25 KB. Compare with the blocking lwIP path at ~50 KB.
195
196#[cfg(has_async_net)]
197pub mod qemu_shm;
198#[cfg(has_async_net)]
199pub mod stm32f7_eth;
200
201#[cfg(has_async_net)]
202pub use qemu_shm::QemuShmDriver;
203#[cfg(has_async_net)]
204pub use stm32f7_eth::Stm32f7EthDriver;
205
206/// Re-export of [`embassy_net::Stack`].
207pub use embassy_net::Stack;
208/// Re-export of [`embassy_net::StackResources`].
209pub use embassy_net::StackResources;