Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 54 additions & 80 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,46 +21,29 @@ a failed FW-boot poll means dead TX while RX still works), and
**RTL8821AU** (1T1R AC + BT combo). These share one HAL (`src/HalModule`,
`src/RtlJaguarDevice`) with chip-family branches.

**Jaguar3 (`rtl8822c` PHY generation).** A second, self-contained HAL under
`src/jaguar3/` for the **RTL8812CU / RTL8822CU** (`0bda:c812` etc.), built to
reach **narrowband 5/10 MHz** (a baseband underclock the Jaguar-1 silicon
physically lacks). It ports Realtek's vendor bring-up from source — power-on,
HalMAC firmware download, MAC/BB/RF init, the halrf calibration steps needed
for TX (3-wire RF + DACK + beamforming init), then RX and on-air TX at
20 MHz plus the 5/10 MHz narrowband re-clock. Validated on RTL8812CU
hardware. RTL8812EU/8822EU (the `rtl8822e` MAC/DLFW fork) are out of scope.

RX and narrowband are SDR-confirmed. **Sustained continuous TX is confirmed
across the full 5 GHz band** — UNII-1 (ch36–48), UNII-2/DFS (ch100/120/144),
and UNII-3 (ch149) — SDR-validated flat at ~93% channel duty / ~60 Mbps
(MCS7/20) with zero bulk-OUT failures. The halrf thermal chain is ported —
`pwr_track` (swing-table TX-power compensation) and `do_lck` (synth re-lock on
drift) in `Halrf8822cIqk`; the remaining unported calibration is per-channel DPK
(`rtw8822c_do_dpk`).

Sustained 5 GHz TX is kept alive by a **coex runtime thread**
(`RtlJaguar3Device::coex_runtime_loop`, started in `InitWrite`) that ports the
rtw88 watchdog's coex path — it drains the firmware C2H reports off bulk-IN (so
the on-chip C2H buffer never fills) and every ~2 s re-applies the 5 GHz coex
decision (`Hal8822c::coex_run_5g`, a port of `rtw_coex_action_wl_under5g`:
GNT_BT→HW-PTA + GNT_WL→SW-high, antenna owner = WL, the WL-wins PTA table) plus
the FW heartbeats (`fw_update_wl_phy_info`, `fw_set_pwr_mode_active`,
`fw_coex_query_bt_info`). Without it the combo chip's coex firmware silences the
antenna after ~50 s; with it, 5 GHz TX runs indefinitely (SDR-validated to ~3 min
across UNII-1/2/3).

TX power: by default (no `DEVOURER_TX_PWR`) the chip transmits at its
**efuse-calibrated power**, flat and sustained — the firmware reads efuse itself
and holds that level (same firmware + efuse the kernel uses, so the sustained
power matches the kernel's). `DEVOURER_TX_PWR=0xNN` writes a flat TXAGC reference
that overdrives the PA for ~60 s until the FW's calibration reverts it to the
efuse level; it's a debug/SDR-visibility knob, not for sustained use.

NOT 8821AU's family confusion: it IS Jaguar wave 1 (CHIP_8821 = 7 in
Realtek's HalVerDef, shares the enum with CHIP_8812), not Jaguar2 as
sometimes claimed. The **Jaguar2 (8812BU, 8822BU/BE, ...) and Kestrel
(11ax) families are out of scope** — they share the "AU"/"BU" branding
but the baseband and HAL differ enough to need their own driver.
**Jaguar3.** A second, self-contained HAL under `src/jaguar3/` covering two PHY
generations that share one core: **rtl8822c** (RTL8812CU / RTL8822CU, `0bda:c812`)
and **rtl8822e** (RTL8812EU / RTL8822EU, `0bda:a81a`). Bring-up is ported from
Realtek's vendor source — power-on, HalMAC firmware download, MAC/BB/RF init,
halrf calibration (DACK, IQK, TXGAPK, thermal tracking), RX, and on-air TX at
20 MHz plus the **5/10 MHz narrowband** re-clock the Jaguar-1 silicon lacks.
Per-generation PHY/RF tables and calibration sit behind strategy interfaces
(`Jaguar3PhyTables`, `Jaguar3Calibration`); the flow and table-walker are shared.

Sustained 5 GHz TX needs the **coex runtime thread**
(`RtlJaguar3Device::coex_runtime_loop`, started in `InitWrite`): it drains the
firmware C2H reports off bulk-IN and every ~2 s re-applies the WiFi-only coex
decision (GNT_WL, antenna owner = WL), the FW heartbeats, and thermal TX-power
tracking. Without it the combo chip's coex firmware silences the antenna.

TX power: by default the chip transmits at its efuse-calibrated per-path,
per-channel level (the rtl8822e TXAGC references match the kernel driver's).
`DEVOURER_TX_PWR=0xNN` forces a flat TXAGC reference — a debug/SDR-visibility
knob, not for sustained use.

**RTL8821AU is Jaguar wave 1** (CHIP_8821 = 7, shares the enum with CHIP_8812),
not Jaguar2. The **Jaguar2 (8812BU, 8822BU/BE, …) and Kestrel (11ax) families are
out of scope** — same "AU"/"BU" branding, different baseband/HAL.

## Build

Expand Down Expand Up @@ -261,11 +244,12 @@ to the factory. `demo/main.cpp` is the canonical boilerplate.

**Chip identity is resolved at construction** from SYS_CFG bits + USB PID.
`CreateRtlDevice` returns an `IRtlDevice` (`Init` / `InitWrite` / `send_packet`)
and dispatches on chip generation: Jaguar-1 PIDs construct `RtlJaguarDevice`,
Jaguar3 PIDs (`0bda:c812`, …) construct `RtlJaguar3Device`. Each orchestrator
then drives bring-up, RX, and TX through its own HAL. `RtlJaguarDevice` was
previously named `Rtl8812aDevice` — a deprecated alias still exists for one
release cycle.
and dispatches on chip generation: Jaguar-1 PIDs construct `RtlJaguarDevice`;
Jaguar3 PIDs construct `RtlJaguar3Device`, with the generation (`rtl8822c` vs
`rtl8822e`) selected from the `SYS_CFG2` chip-id (`0x13` → C8822C, `0x17` →
C8822E). Each orchestrator drives bring-up, RX, and TX through its own HAL.
`RtlJaguarDevice` was previously named `Rtl8812aDevice` — a deprecated alias
still exists for one release cycle.

Module layout in `src/`:

Expand All @@ -289,30 +273,30 @@ Module layout in `src/`:
`send_packet` **must** begin with a radiotap header; rate / MCS / VHT /
STBC / LDPC / SGI / bandwidth are read from it.

The Jaguar3 HAL is a parallel, self-contained set under `src/jaguar3/`:
The Jaguar3 HAL is a parallel, self-contained set under `src/jaguar3/`. The core
uses generation-neutral names; per-generation behaviour is behind two strategy
interfaces selected by `ChipVariant`:

- `RtlJaguar3Device` — orchestrator (Init / InitWrite / send_packet / Stop).
- `Hal8822c` — bring-up: power-on/off PWR_SEQ, MAC/USB config, BB/AGC/RF table
apply, `config_phydm_parameter_init` (3-wire RF), `bf_init` (beamforming),
and `enable_tx_path`. Calls into the calibration and FW modules.
- `Halmac8822cFw` — verbatim-ported HalMAC firmware download/boot (the
`rtl8822c` analogue of the 8814 3081 path).
- `Halrf8822cIqk` — ported halrf calibration: IQK plus the DAC DC cal (DACK,
`dac_calibrate`) the TX path requires.
- `RadioManagement8822c` — channel / bandwidth / TX power, including the
`0x9b0`/`0x9b4` baseband-divider recipe for 5/10 MHz narrowband and the
RF18 channel-tune encoding.
- `FrameParser8822c.h` / `PhyTableLoader8822c` — 8822C TX/RX descriptors and
the table walker for the `rtl8822c` phydm tables.
- `HalJaguar3` — bring-up: power-on/off PWR_SEQ, MAC/USB config, BB/AGC/RF table
apply, `config_phydm_parameter_init` (3-wire RF), `bf_init`, `enable_tx_path`,
efuse access (incl. the 8822e OTP burst-mode / 2-byte-header decode), RFE pins.
- `HalmacJaguar3Fw` / `HalmacJaguar3MacInit` — HalMAC firmware download + MAC init.
- `Jaguar3Calibration` (interface) → `Halrf8822c` / `Halrf8822e`: halrf DACK, IQK,
TXGAPK, thermal TX-power tracking, coex/antenna control.
- `Jaguar3PhyTables` (interface) → `Phy8822cTables` / `Phy8822eTables`.
- `RadioManagementJaguar3` — channel / bandwidth / per-path TX power, the
`0x9b0`/`0x9b4` narrowband divider recipe, and the RF18 channel-tune encoding.
- `FrameParserJaguar3.h` / `PhyTableLoaderJaguar3` — TX/RX descriptors and the
phydm table walker (shared across both generations).

`hal/` holds vendor headers and tables ported from Realtek's tree. The
8814-specific BB/AGC/RF tables under `hal/phydm/rtl8814a/Hal8814_PhyTables.{c,h}`
are **generated** from the upstream aircrack-ng/rtl8814au source by
`tools/extract_8814a_phy_tables.py` — edit the generator, not the output.
The runtime parser for these tables is `src/PhyTableLoader`, not the upstream
phydm parser. The Jaguar3 tables under `hal/phydm/rtl8822c/` are likewise
generated by `tools/extract_8822c_phy_tables.py` and walked by
`src/jaguar3/PhyTableLoader8822c`.
8814-specific tables under `hal/phydm/rtl8814a/Hal8814_PhyTables.{c,h}` are
**generated** from the upstream aircrack-ng/rtl8814au source by
`tools/extract_8814a_phy_tables.py`; the Jaguar3 tables under
`hal/phydm/rtl8822{c,e}/` by `tools/extract_8822c_phy_tables.py`. Edit the
generators, not the output. The runtime parser is `src/PhyTableLoader` (Jaguar1)
/ `src/jaguar3/PhyTableLoaderJaguar3`, not the upstream phydm parser.

## Hardware gotchas

Expand All @@ -324,21 +308,11 @@ generated by `tools/extract_8822c_phy_tables.py` and walked by
- **rmmod/sysfs-unbind actively de-inits the chip** (RF off, MAC DMA off).
After detaching a kernel driver, expect to re-init from cold, not warm.
`DEVOURER_SKIP_RESET=1` only helps when firmware state is still intact.
- **USB Vbus sag on bus-powered hub chains**: 5 GHz TX draws far more PA current
than 2.4 GHz. Fed through a deep bus-powered hub chain the rail can brown out
the PA. Symptom: frames submit fine (`rc` ok, 0 send-fails) but on-air power
collapses — SDR duty near the noise floor, or fully dark — *intermittently*, and
often on every plugged adapter at once, while 2.4 GHz keeps working. Recovers on
a `uhubctl` power-cycle of the hub tree (the most deeply-nested / highest-PA
adapter may need its own dedicated port cycle). **Do not mis-diagnose it** as a
per-chip dead PA, a 5 GHz code gate, a BT-coex/antenna issue, or an EFUSE
TX-power bug — every one of those was chased and refuted; it was the rail.
Defences: (1) keep a known-good control adapter and re-check it *each session* —
a sagging control silently makes the bench look like per-chip hardware death;
(2) measure TX as on-air **Mbps via SDR duty × PHY rate**, never monitor-sniffer
frame counts — a sensitive receiver decodes weak frames and masks a power
collapse; (3) don't trust a "fix validated" off a single reading on an unstable
rail. Durable fix: powered USB hub / direct root ports.
- **Bench measurement**: measure TX as on-air **Mbps via SDR duty × PHY rate**
(`tests/bench_onair.py`), not monitor-sniffer frame counts — a sensitive
receiver decodes weak frames and masks a real drop. Keep a known-good control
adapter and re-check it each session; take one clean SDR read per session (a
second back-to-back `sdr_duty` read can fail to reacquire and report ~0).

## TX path

Expand Down
45 changes: 30 additions & 15 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ add_library(WiFiDriver
hal/hal8821a_fw.h
hal/hal8822c_fw.c
hal/hal8822c_fw.h
hal/hal8822e_fw.c
hal/hal8822e_fw.h
hal/hal_com_reg.h
hal/rtl8812a_hal.h
hal/rtl8812a_recv.h
Expand All @@ -50,13 +52,19 @@ add_library(WiFiDriver

# RTL8822C (Jaguar3) phydm-format BB/AGC/RF tables. Generated from the
# rtl88x2cu tree by tools/extract_8822c_phy_tables.py. These use the newer
# "halbb" conditional encoding — walked by src/jaguar3/PhyTableLoader8822c,
# "halbb" conditional encoding — walked by src/jaguar3/PhyTableLoaderJaguar3,
# NOT src/PhyTableLoader (which handles the Jaguar1 check_positive format).
hal/phydm/rtl8822c/Hal8822c_PhyTables.c
hal/phydm/rtl8822c/Hal8822c_PhyTables.h
hal/phydm/rtl8822c/Hal8822c_IqkNctl.c
hal/phydm/rtl8822c/Hal8822c_IqkNctl.h

# RTL8822E (Jaguar3 EU) phydm-format BB/AGC/RF tables. Generated from the
# rtl88x2eu tree by `tools/extract_8822c_phy_tables.py --chip 8822e`. Same
# halbb encoding as 8822c (one walker), different DATA.
hal/phydm/rtl8822e/Hal8822e_PhyTables.c
hal/phydm/rtl8822e/Hal8822e_PhyTables.h

src/ieee80211_radiotap.h
src/BbDbgportReader.cpp
src/BbDbgportReader.h
Expand Down Expand Up @@ -100,22 +108,28 @@ add_library(WiFiDriver
src/registry_priv.h

# Jaguar3 (RTL8822CU / RTL8812EU / RTL8822EU) port.
src/jaguar3/Jaguar3PhyTables.h
src/jaguar3/Jaguar3Calibration.h
src/jaguar3/Phy8822cTables.cpp
src/jaguar3/Phy8822eTables.cpp
src/jaguar3/RtlJaguar3Device.cpp
src/jaguar3/RtlJaguar3Device.h
src/jaguar3/Hal8822c.cpp
src/jaguar3/Hal8822c.h
src/jaguar3/Halmac8822cFw.cpp
src/jaguar3/Halmac8822cFw.h
src/jaguar3/Halmac8822cMacInit.cpp
src/jaguar3/Halmac8822cMacInit.h
src/jaguar3/Halrf8822cIqk.cpp
src/jaguar3/Halrf8822cIqk.h
src/jaguar3/Halmac8822cRegs.h
src/jaguar3/RadioManagement8822c.cpp
src/jaguar3/RadioManagement8822c.h
src/jaguar3/FrameParser8822c.h
src/jaguar3/PhyTableLoader8822c.cpp
src/jaguar3/PhyTableLoader8822c.h
src/jaguar3/HalJaguar3.cpp
src/jaguar3/HalJaguar3.h
src/jaguar3/HalmacJaguar3Fw.cpp
src/jaguar3/HalmacJaguar3Fw.h
src/jaguar3/HalmacJaguar3MacInit.cpp
src/jaguar3/HalmacJaguar3MacInit.h
src/jaguar3/Halrf8822c.cpp
src/jaguar3/Halrf8822c.h
src/jaguar3/Halrf8822e.cpp
src/jaguar3/Halrf8822e.h
src/jaguar3/HalmacJaguar3Regs.h
src/jaguar3/RadioManagementJaguar3.cpp
src/jaguar3/RadioManagementJaguar3.h
src/jaguar3/FrameParserJaguar3.h
src/jaguar3/PhyTableLoaderJaguar3.cpp
src/jaguar3/PhyTableLoaderJaguar3.h
)

target_compile_features(WiFiDriver PUBLIC cxx_std_20)
Expand All @@ -126,6 +140,7 @@ target_link_libraries(WiFiDriver PUBLIC PkgConfig::libusb)
target_include_directories(WiFiDriver PUBLIC hal)
target_include_directories(WiFiDriver PUBLIC hal/phydm/rtl8814a)
target_include_directories(WiFiDriver PUBLIC hal/phydm/rtl8822c)
target_include_directories(WiFiDriver PUBLIC hal/phydm/rtl8822e)
target_include_directories(WiFiDriver PUBLIC src)

add_executable(WiFiDriverDemo
Expand Down
49 changes: 23 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ Devourer is a userspace re-implementation of Realtek's RTL88xxAU Wi-Fi
driver, speaking to the chip directly through libusb. It covers two chip
generations: the first-generation **Jaguar** 802.11ac family (RTL8812AU,
RTL8814AU, and RTL8821AU shipping on every band, RTL8811AU via the 8812
code path) and the second-generation **Jaguar3** `rtl8822c` parts
(RTL8812CU / RTL8822CU), which additionally reach **5/10 MHz narrowband**
operation the Jaguar-1 silicon physically can't. No kernel module, no
code path) and the second-generation **Jaguar3** parts — `rtl8822c`
(RTL8812CU / RTL8822CU) and `rtl8822e` (RTL8812EU / RTL8822EU) — which
additionally reach **5/10 MHz narrowband** operation the Jaguar-1 silicon
physically can't. No kernel module, no
`rtl8812au` DKMS tree — just a C++20 static library (`WiFiDriver`) plus two
demo executables for RX and TX. It is the OpenIPC project's driver of choice
for long-range video links built on top of cheap Realtek 11ac USB radios.
Expand All @@ -23,31 +24,28 @@ register-table layout, firmware-download plumbing, and
family; chip-specific EEPROM handling, firmware blobs, and RF tables are
layered on top.

It also targets the second-generation **Jaguar3** (`rtl8822c` PHY
generation) parts — **RTL8812CU** and **RTL8822CU** (`0bda:c812`,
`0bda:c82c`, …) — through a self-contained HAL under `src/jaguar3/`,
dispatched at the factory by USB PID. These add **5/10 MHz narrowband**
operation (a baseband underclock the Jaguar-1 silicon lacks) and ship the
HalMAC firmware-download path, MAC/BB/RF init, and the halrf calibration
the TX path needs. Bring-up is ported from Realtek's vendor source.

Band cells show **devourer on-air TX throughput** (Mbps, HT MCS7, 20 MHz),
measured by USRP channel-occupancy (`tests/bench_onair.py`); devourer matches
wfb-ng on the `svpcom/rtl8812au` driver at parity — see
[`docs/wfb-ng-tuning.md`](docs/wfb-ng-tuning.md). The 8812AU is the fully-benchmarked
reference. `†` = transmits on air, but the on-air rate is **USB-power-bound** on
this bench and not reproducibly benchmarkable (5 GHz TX is current-hungry; needs a
powered USB hub / direct root port — see _USB Vbus sag_ in Hardware gotchas); the
bracketed figure is the best clean reading observed.
It also targets the second-generation **Jaguar3** parts through a
self-contained HAL under `src/jaguar3/`, dispatched at the factory from the
`SYS_CFG2` chip-id and USB PID: the `rtl8822c` generation (**RTL8812CU** /
**RTL8822CU**, `0bda:c812`, `0bda:c82c`) and the `rtl8822e` generation
(**RTL8812EU** / **RTL8822EU**, `0bda:a81a`). These add **5/10 MHz narrowband**
operation the Jaguar-1 silicon lacks. Bring-up is ported from Realtek's vendor
source.

Band cells are **devourer on-air TX throughput** (Mbps, HT MCS7, 20 MHz) via
USRP channel-occupancy (`tests/bench_onair.py`). `†` = on-air but the reading
varies run-to-run (bracketed = best clean reading).

| Part | RF / streams | 2.4 GHz (ch6) | UNII-1 (ch36) | UNII-2/3 (ch149) | Notes |
| ----------------------------- | ----------------- | ------------- | ------------- | ---------------- | ------------------------------------------- |
| **RTL8812AU** | 2T2R | 56 | 52 | 52 | VID/PID `0bda:8812`; reference part — solid on every band |
| **RTL8811AU** | 1T1R | mirrors 8812 | mirrors 8812 | mirrors 8812 | 1T1R cut of 8812 silicon; rides the 8812 code path with `RFType=RF_TYPE_1T1R` from `REG_SYS_CFG` bit 27. Not separately benchmarked (no working unit on the bench) |
| **RTL8814AU** | 4T4R, 3-SS max | 65 | †(32) | †(32) | VID/PID `0bda:8813`; 2-SS effective on USB-2. 2.4 GHz saturates the channel; 5 GHz reached 32 Mbps in good moments but sags otherwise on this bench — power-bound, not a chip limit |
| **RTL8821AU** | 1T1R AC + BT | 54 | 32 | 28 | OEM-rebadged as TP-Link Archer T2U Plus (`2357:0120`). 1T1R; 5 GHz SDR-measured and reproducible here |
| **RTL8812AU** | 2T2R | 56 | 52 | 52 | `0bda:8812`; reference part |
| **RTL8811AU** | 1T1R | mirrors 8812 | mirrors 8812 | mirrors 8812 | 1T1R cut of 8812 silicon; rides the 8812 code path (`RFType=RF_TYPE_1T1R` from `REG_SYS_CFG` bit 27). Not benchmarked |
| **RTL8814AU** | 4T4R, 3-SS max | 65 | †(32) | †(32) | `0bda:8813`; 2-SS effective on USB-2 |
| **RTL8821AU** | 1T1R AC + BT | 54 | 32 | 28 | TP-Link Archer T2U Plus (`2357:0120`) |
| **RTL8812CU** | 2T2R | 65 | 60 | 60 | LB-LINK WDN1300H (`0bda:c812`) |
| **RTL8822CU** | 2T2R + BT | — | — | — | no unit on the bench (`0bda:c82c`) |
| **RTL8822CU** | 2T2R + BT | — | — | — | not benchmarked (`0bda:c82c`) |
| **RTL8812EU** | 2T2R | 8 | 51 | 47 | LB-LINK BL-M8812EU2 (`0bda:a81a`); bare 5 GHz FPV module |
| **RTL8822EU** | 2T2R + BT | — | — | — | not benchmarked (`rtl8822e`) |

The **`Jaguar2`** / **`Jaguar+`** family (8812BU, 8822**B**U/BE, etc.) and
the later **`Kestrel`** 11ax generation are **out of scope**: they share
Expand All @@ -56,8 +54,7 @@ that they would need their own driver. Two naming traps worth calling out:
RTL8821AU is Jaguar wave 1 (CHIP_8821 = 7 in Realtek's HalVerDef, shares
the enum with CHIP_8812), **not** Jaguar2; and the in-scope RTL8822**C**U
(Jaguar3, `rtl8822c`) is a different chip from the out-of-scope RTL8822**B**U
(Jaguar2) despite the shared "8822" number. The `rtl8822e` parts
(RTL8812EU / RTL8822EU) are also out of scope.
(Jaguar2) despite the shared "8822" number.

> Heads up — some Realtek USB sticks ship in "ZeroCD" mode and enumerate first
> as a USB mass-storage device exposing the Windows driver installer
Expand Down
Loading
Loading