Skip to content

radio_esp32::net

The smoltcp link layer for an associated Wi-Fi station.

Phase 6 of doc/plan-radio.md, issue #68. smoltcp is the TCP/IP stack — Rust, no_std, and heapless: it never allocates on its own, working instead from buffers we hand it. That suits ground rule 3, where only this tier allocates and the kernel stays static; every buffer here comes from the radio heap ([kernel::heap]).

The device seam — the boundary smoltcp’s [phy::Device] trait sits on. An associated station moves raw Ethernet frames two ways:

  • Transmit hands a frame straight to the blob’s esp_wifi_internal_tx, the same raw-L2 path the 4-way handshake sends EAPOL through ([crate::supplicant]). smoltcp writes the frame into the token’s buffer; [TxToken::consume] passes it down.
  • Receive drains an [RxQueue] the MAC’s receive callback fills. Each frame the blob delivers is copied into a free ring slot on the driver’s task; [phy::Device::receive] hands the oldest one to smoltcp.

6.2/6.3/6.4: the stack reaches the internet. The [RxQueue] and [WifiDevice] carry traffic both ways; [rx_trampoline] is the MAC’s receive callback filling the ring. [obtain_dhcp_lease] runs DHCP to a lease, and [dns_tcp_probe] takes it further — one interface running DHCP, then a DNS resolution, then a TCP connection with a byte exchange. Validated on an ESP32-DevKitC against a real AP: a DHCP lease, example.com resolved, a TCP connection to it, and an HTTP/1.1 200 OK read back, all within a second of association. The ring behaviour is host-tested besides.

Still to come for #68: 6.5 TLS, deferred by the plan.

struct Lease {
pub ip: [u8; 4],
pub prefix_len: u8,
pub router: Option<[u8; 4]>,
pub dns: Option<[u8; 4]>,
}

A DHCP lease, in plain bytes so the caller needs no smoltcp types.

Implements: Clone, Copy, Debug

struct RxQueue { /* private fields */ }

A single-producer/single-consumer ring of received frames.

The MAC receive callback is the producer, on the driver’s task; the interface poll is the consumer, on the network task. A full ring drops the incoming frame rather than blocking the MAC — TCP recovers from a loss, a wedged receive path does not. The [Spinlock] is held only for the index arithmetic and the copy, never across a call out.

Implements: Default

struct WifiDevice<'q> { /* private fields */ }

smoltcp’s view of the associated station: an Ethernet device whose transmit goes to the blob and whose receive drains an [RxQueue].

Zero-copy is not worth it here — the blob owns the receive buffer only for the duration of its callback, so a frame is copied into the ring on arrival regardless. The device borrows the queue rather than owning it: the queue is a static the receive callback also names.

Implements: Device

struct WifiRxToken { /* private fields */ }

A received frame, owned by the token for the length of the consume call.

Implements: RxToken

struct WifiTxToken;

The right to transmit one frame: smoltcp fills the buffer, and consume hands it to the blob.

Implements: TxToken

const MTU: usize;

Largest Ethernet II frame smoltcp will build or accept, headers included and FCS excluded (the MAC appends and strips the FCS). 14-byte header + 1500 MTU.

const RX_SLOTS: usize;

How many received frames the queue holds before it drops the oldest. Sized to ride out a poll interval’s worth of arrivals without stalling the MAC; 16 × [MTU] is ~24 KiB out of the radio heap.