English | 中文
A portable RTOS WiFi stack: the 802.11 core, the Linux SoftMAC drivers and a supplicant, running on an RTOS kernel through a single abstraction.
Plug a commodity WiFi card into an RTOS and use it. That is the point. An off-the-shelf USB WiFi dongle - the same one you would plug into a PC - with no vendor SDK and no vendor blob: the mainline Linux driver, taken unchanged, running on ThreadX or FreeRTOS.
An open-source port of the mainline Linux SoftMAC drivers to an RTOS. The rtw88 and rtw89 drivers are the mainline Linux drivers for a large family of Realtek WiFi chips; iwlwifi (the mvm generation) is Intel's mainline driver for the Wireless-AC family. To our knowledge these are the first open-source ports of this driver set to an RTOS: the driver bytes are unchanged, and the RTOS-facing interface is provided underneath them.
The end goal is a camera whose data streams over WiFi for long, unattended runs
- a wireless IP camera on an RTOS. Both halves are in the tree: the wireless side is brought up on hardware, and the USB camera line captures and streams frames on the same board.
Open-source 802.11 support on RTOS kernels has, in practice, been tied to vendor SDKs and binary blobs, without the reusable, vendored-and-ported stack that BSD and Linux enjoy. Contributing that missing piece - a portable 802.11 stack with per-OS ports that other RTOS projects can adopt - is the point of this project.
The stack is deliberately not tied to one board. The components, the ports and the contract tests must not assume an MMU, 64-bit or a large memory, so the same code can run on a small MCU and on a 64-bit application processor. Two kernels are supported (ThreadX and FreeRTOS) behind one CMSIS-RTOS2 abstraction, and the abstraction is enforced: the components must build and link on both, and a gate rejects a foreign kernel call in a component.
Three USB WiFi lines, one SDIO line, one PCIe line and the board's own gigabit Ethernet are brought up on the reference board, and the USB camera line streams frames on the same board. "Full chain" means the card is enumerated, its firmware loaded, it scans, it joins a WPA2 network, it gets an address over DHCP, it answers ping, and TCP data flows. It does not mean the throughput is good.
| Line | Chip | Board state |
|---|---|---|
rtl8xxxu |
RTL8188EUS | Full chain. |
rtw88 |
RTL8821CU | Full chain, including a boot-time CD-ROM to WLAN mode switch. |
rtw89 |
RTL8851BU | Experimental: full chain reached, join intermittent, link poor. |
| SDIO | RTL8189FTV | Full chain, over the SDIO bus rather than USB: the vendor driver carries its own MLME and talks to the port's cfg80211 and netdev faces. It also runs as an open access point: a station associates to the board, and both directions measure (see Throughput). |
iwlwifi |
Intel Wireless-AC 7260 | Full chain, over PCIe message interrupts (GICv3 ITS): the driver routes station management and data frames through its transmit queues, and the port owns the two delivery contexts a threaded interrupt needs. |
| GMAC (wired) | RK3568 MAC + RTL8211F PHY | Full chain: DHCP, ping and a single TCP stream in both directions against a Raspberry Pi peer over the on-board RJ45 - the closest line to Linux on this board (see Throughput). |
| UVC | Logitech Pro 9000 | Camera enumerated, opened and streaming MJPEG 640x480 over its isochronous endpoint (see The camera line). |
The board, the connectors and pin/PHY multiplexing behind these lines, and which port each line is wired to, are documented per board under docs/boards/.
Single TCP stream, measured at the receiver, against the same access point (channel 1). The Linux column is the same card under Linux on the same board, from a clean upstream build. Channel width drifts across boots on this access point, so the figures below name the width they were taken at, and the RTOS and Linux cells of a row are the same width.
| Line | RTOS, down (host to board) | RTOS, up (board to host) | Linux, same card, same channel |
|---|---|---|---|
| RTL8188EUS | 4.00 / 4.66 Mbit/s (HT40) | not measured | 42.6 / 43.9 / 45.3 Mbit/s |
| RTL8821CU | 3.65 Mbit/s | not measured | 63.7 / 66.0 / 68.2 Mbit/s |
| RTL8851BU | 136 / 173 Kbit/s | not measured | 23.9 / 25.3 / 26.5 Mbit/s |
| RTL8189FTV (SDIO) | 18.1 (HT20) / 20.5 (HT40) Mbit/s | 15.1-16.8 (HT20) Mbit/s | 31.4 down / 30.7 up (HT20), 44.2 / 50.5 (HT40) |
| Intel Wireless-AC 7260 (PCIe) | 0.24 Mbit/s | not measured | 167 Mbit/s |
All lines are measured against the same access point, BSSID
14:a3:2f:18:07:2c. Each Linux cell is min/avg/max over three 30 s runs (the
7260's is a single 20 s run). The RTL8189FTV line is the one that has been
worked on: its receive path was measured first and two levers were found and
adopted - a pump cadence that keeps serving a busy card (+45%) and the card's
own receive-aggregation timeout byte (+42% at HT20) - which moved it from one
fourth of Linux to about 1/1.7 (down) and 1/1.9 (up), the closest of the
wireless lines. Its reverse direction was measured for the first time on the same rig
(board as the iperf client), so the "not measured" cells above are per-line
facts, not a blanket one. Its access point mode, brought up later, measures
19.8-20.2 Mbit/s down and 18.3 Mbit/s up with a station associated to the board
itself. The other lines' gaps are as recorded: one tenth (8188EUS), one
eighteenth (8821CU), one hundred-and-seventieth (8851BU), one
seven-hundredth (7260).
The gap to Linux is still large on most lines, and it is the current open problem. It is stated here rather than hidden: the value of the port so far is in the structure, not in the numbers. See docs/known-limitations.md.
The board's own gigabit MAC (the RK3568 GMAC behind an RTL8211F PHY), measured as a single TCP stream between the board and a Raspberry Pi peer over the on-board RJ45. Same board under Linux, same peer: 940 Mbit/s up / 941 Mbit/s down. UDP through the same driver measures 955 down / 957 up, which is what the wire path itself carries at this frame size.
| Direction | RTOS, four sheets in one boot (2026-10-20) | Linux, same board and peer |
|---|---|---|
| Up (board to peer) | 894.5 - 919.2, median 900.0 Mbit/s | 940 Mbit/s |
| Down (peer to board) | 684.0 - 760.6, median 742.4 Mbit/s | 941 Mbit/s |
The uplink sits at 95.7% of the Linux build and is the first figure on this board at or above the line's own 900 Mbit/s acceptance bar; the downlink is at 79% and is the open half. Two changes carried it here. An interrupt census classified every interrupt entry by its raw status word and retired a wrong hypothesis with it: the "odd" interrupt family that timing had charged 5% of the core measured 0.1-0.2%, while the entry cost that matters is 1.9 us overall. And the C library now comes from the toolchain's newlib instead of the image's own copy of it - the hand-written 1514-byte memcpy moved one 8-byte word per loop iteration and cost 1.5 us per frame, about 13% of the single core at line rate, where newlib's size-branched LDP/STP implementation moves 16 bytes per instruction. The same swap is still to be applied to the other board images.
The USB camera (UVC) line is the second headline milestone, and it streams on the reference board. A Logitech Pro 9000 on the board's USB 2.0 host: the camera is enumerated, the probe/commit exchange runs, an alternate setting is selected, and MJPEG 640x480 frames come out of an isochronous URB ring and are assembled by payload-header boundaries - 267 to 275 frames per 20-second window over cold boots, about 13.5 fps and 1.9 Mbit/s. The same camera and board under Linux gave the ground truth in the dim scene it was captured in (4.69 fps, 372 kbit/s); the acceptance floor is two thirds of that, and the stream runs several times above it.
Two pieces of the vendored USB stack needed filling in, and each travels as a patch to it, written to be sendable upstream: the high-speed isochronous transfer engine the stack declares but does not define, and the running-stream half of its video class - the streaming engine (the alternate-setting choice from the device's own answer, payload-header frame assembly, the frame pool) is not in the open-source tier. See patches/cherryusb/, and docs/known-limitations.md for what is measured and what is not.
app/ entry per OS and flavour
os/ RTOS ports and the CMSIS-RTOS2 abstraction
arch/ board/ CPU layer and per-board BSP
components/ drivers and protocol stacks
net80211/ the 802.11 core (vendored)
linuxkpi/ the Linux driver-facing interface
skbuff/ tlsf/ ... supporting units
ports/ first-party glue for the imported units
net80211/ osal / bus / net port + the driver bridge
cherryusb/ fsl_sdmmc/ bus backends (USB host, SDIO host)
lwip/ the network stack backend
third-party/ pinned upstream trees (Linux drivers, RTOS kernels)
patches/ diffs replayed onto the vendored trees at build time
tests/host/ tests/board/ the two test tiers
The wireless core is the authority and the driver aligns to it. The bridge presents the contract the Linux driver was written against; it is not relaxed to accommodate a particular chip. Where a chip needs something the interface normally supplies, the driver is given that state and the chip's own logic does the rest. See docs/net80211.md.
The build is driven from the repository root:
make -C freewificam mock # host mock suites (Gate1; seconds to minutes)
make -C freewificam gates # static gates (local knowledge, build shape)
make -C freewificam board-images # build every board imageA single suite: freewificam/tools/mock/run.sh <suite> (--list shows the
registry). The board tier is driven by freewificam/tools/gates/run-gate2.sh.
The air, from outside the board, is measured by freewificam/tools/prober/
(monitor, station, access point and traffic roles on a Linux host with a
monitor-capable USB card; see its README).
The two tiers and what they guarantee are described in
docs/bus-and-threading.md and
CONTRIBUTING.md.
This stack stands on other people's work and says so: the net80211 core (NetBSD lineage), CherryUSB, CherrySH, lwIP, the ThreadX and FreeRTOS kernels, CMSIS, and the mainline Linux drivers. Each is vendored or imported with its license recorded in LICENSES.md.
See CONTRIBUTING.md.
First-party code is BSD-3-Clause (see LICENSE). Every vendored tree, imported unit and binary blob keeps its upstream license, recorded component by component in LICENSES.md, including the distribution obligations for the one copyleft unit in the tree.