Skip to content
Draft
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
27 changes: 27 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ add_library(WiFiDriver
hal/hal8814a_fw.h
hal/hal8821a_fw.c
hal/hal8821a_fw.h
hal/hal8822c_fw.c
hal/hal8822c_fw.h
hal/hal_com_reg.h
hal/rtl8812a_hal.h
hal/rtl8812a_recv.h
Expand All @@ -43,6 +45,13 @@ add_library(WiFiDriver
hal/phydm/rtl8814a/Hal8814_PhyTables.c
hal/phydm/rtl8814a/Hal8814_PhyTables.h

# 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,
# NOT src/PhyTableLoader (which handles the Jaguar1 check_positive format).
hal/phydm/rtl8822c/Hal8822c_PhyTables.c
hal/phydm/rtl8822c/Hal8822c_PhyTables.h

src/ieee80211_radiotap.h
src/BbDbgportReader.cpp
src/BbDbgportReader.h
Expand Down Expand Up @@ -76,9 +85,26 @@ add_library(WiFiDriver
src/RtlUsbAdapter.cpp
src/RtlUsbAdapter.h
src/SelectedChannel.h
src/IRtlDevice.h
src/WiFiDriver.cpp
src/WiFiDriver.h
src/registry_priv.h

# Jaguar3 (RTL8822CU / RTL8812EU / RTL8822EU) port — WORK IN PROGRESS.
# Scaffold only; bring-up lands in milestones (docs/jaguar3-bringup.md).
src/jaguar3/Jaguar3Common.h
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/Halmac8822cRegs.h
src/jaguar3/RadioManagement8822c.cpp
src/jaguar3/RadioManagement8822c.h
src/jaguar3/FrameParser8822c.h
src/jaguar3/PhyTableLoader8822c.cpp
src/jaguar3/PhyTableLoader8822c.h
)

target_compile_features(WiFiDriver PUBLIC cxx_std_20)
Expand All @@ -88,6 +114,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 src)

add_executable(WiFiDriverDemo
Expand Down
12 changes: 9 additions & 3 deletions demo/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ static constexpr uint16_t kRealtekProductIds[] = {
0xa811, /* RTL8811AU */
0xb811, /* RTL8811AU/8821AU variants */
0x8813, /* RTL8814AU (Realtek demoboard PID, used by CF-938AC/CF-960AC) */
0xc82c, /* RTL8822CU (Jaguar3) — WIP */
0xc82e, /* RTL8822CU (Jaguar3) — WIP */
0xc812, /* RTL8822CU WiFi-only (Jaguar3) — WIP */
};

static int g_rx_count = 0;
Expand Down Expand Up @@ -361,13 +364,16 @@ int main() {
WiFiDriver wifi_driver(logger);
auto rtlDevice = wifi_driver.CreateRtlDevice(dev_handle);
logger->info("init-timing: demo.create_device = {} ms", ms_since_start());
g_rtl_device = rtlDevice.get();
/* The BB-debug-port / queue-depth research helpers are Jaguar1-only, so they
* live on RtlJaguarDevice rather than the IRtlDevice interface. dynamic_cast
* yields nullptr for a Jaguar3 device, which disables those helpers cleanly. */
g_rtl_device = dynamic_cast<RtlJaguarDevice *>(rtlDevice.get());
std::atomic<bool> qd_emitter_stop{false};
std::thread qd_emitter;
if (g_qd_poll_ms > 0) {
if (g_qd_poll_ms > 0 && g_rtl_device != nullptr) {
logger->info("DEVOURER_QUEUE_POLL_MS={} — starting queue-depth poller",
g_qd_poll_ms);
rtlDevice->start_queue_depth_poller(g_qd_poll_ms);
g_rtl_device->start_queue_depth_poller(g_qd_poll_ms);
/* Self-driven emitter — ticks at the poll cadence so the queue snapshot
* surfaces even when the RX hook is sparse (e.g. broken-RX 8814 cells).
* Idempotent w.r.t. the poller; just reads the atomic snapshot. */
Expand Down
99 changes: 99 additions & 0 deletions docs/jaguar3-bringup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Jaguar3 (RTL8822CU / RTL8812EU / RTL8822EU) bring-up runbook

Status: **M0 scaffold landed; M1+ require hardware.** This document is the
working plan for adding the Realtek "Jaguar3" 802.11ac USB family to devourer.
The motivation is FPV **narrowband (5/10 MHz) TX**, which is achievable on
Jaguar3 but not on the Jaguar1 chips devourer already supports (Jaguar1's
baseband has no clock divider; Jaguar3's does — regs `0x9b0`/`0x9b4`).

## Why a whole new family (not a branch in the existing code)

Jaguar3 differs from Jaguar1 (8812AU/8814AU/8821AU) at every layer:

- **Baseband:** new IP generation, `0x9bx` register map (vs Jaguar1 `0x8xx`).
- **MAC:** new multi-port design.
- **TX/RX descriptor:** different field map (`SET_TX_DESC_*_8822C`), not the
Jaguar1 `SET_TX_DESC_*_8812` 40-byte layout.
- **Bring-up:** routed through Realtek's **HalMAC** library (firmware download,
power-on, queue/page init) — devourer has none of it.
- **phydm:** newer generation; channel/bandwidth is **procedural**
(`config_phydm_switch_channel_8822c` / `..._switch_bandwidth_8822c`) rather
than the Jaguar1 `phy_PostSetBwMode8812` register writes.

So Jaguar3 is a second device implementation behind the shared `IRtlDevice`
interface, reusing only the thin generic substrate.

## Architecture

- `src/IRtlDevice.h` — the device contract (`Init` / `InitWrite` /
`SetMonitorChannel` / `SetTxPower` / `send_packet` / `GetSelectedChannel`).
- `RtlJaguarDevice` (Jaguar1) and `RtlJaguar3Device` (Jaguar3, `src/jaguar3/`)
both implement it.
- `WiFiDriver::CreateRtlDevice` dispatches on chip family.

**Reused as-is:** `RtlUsbAdapter` (vendor-control register I/O, bulk transfers,
EFUSE byte reads, endpoint discovery), `Radiotap.c` + `RadiotapBuilder`.

**NOT reused — corrected during M1:** `src/PhyTableLoader` walks the Jaguar1
`check_positive` encoding (opcodes `0x8/0x9/0xA/0xB`, with `0x80000000` /
`0x40000000` condition halves). The Jaguar3 halhwimg tables use a *different*,
newer **"halbb"** encoding (a `>>28 == 0xf` headline section selected by
{cut, rfe} priority, then `PARA_IF/ELSE_IF/ELSE/END/CHK` body opcodes). So
Jaguar3 gets its own walker, `src/jaguar3/PhyTableLoader8822c`, a faithful port
of `odm_read_and_config_mp_8822c_*`. Also: there is **no phydm MAC table** on
Jaguar3 — MAC register init is done by HalMAC.

**New (`src/jaguar3/`):** `RtlJaguar3Device` (orchestrator), `Hal8822c`
(power/queue/tables), `Halmac8822cFw` (verbatim HalMAC DLFW port),
`RadioManagement8822c` (channel/BW/power + narrowband recipe), `FrameParser8822c`
(8822C descriptors). Plus `tools/extract_8822c_phy_tables.py` (BB/AGC/RF tables).

### Chip-family detection (important caveat)

PID matching is reliable only for RTL8822CU (`0xC82C` / `0xC82E` / `0xC812`).
The **8812EU/8822EU defaults are ambiguous and an 8812EU can enumerate as
`0x8812` — the same PID as the Jaguar1 RTL8812AU.** Distinguishing those needs
the SYS_CFG / HalVerDef chip-id read (**M1**). For now, force the family with
`DEVOURER_FAMILY=jaguar3` (or `jaguar1`).

## Milestones

| # | Goal | Needs HW | Gate / how to verify |
|---|------|----------|----------------------|
| M0 | Scaffold: interface, factory dispatch, skeleton modules, this runbook | no | `cmake --build` green; Jaguar3 stub throws "not yet implemented (Mn)" |
| M1 | **DONE (table pipeline).** Vendor `rtl8822c` BB+RF sources; `extract_8822c_phy_tables.py` → `Hal8822c_PhyTables.{c,h}` (agc_tab/phy_reg/phy_reg_pg/radioa/radiob); halbb walker `PhyTableLoader8822c`; `Hal8822c::apply_bb_rf_agc_tables` applies BB+AGC | no | build green; walker unit-checked vs real tables (phy_reg→1289 writes, first `0x1d0c=0x00410000`; agc→450) |
| M2 | **Chip-version decode DONE** (`Hal8822c::read_chip_version`, port of rtl8822c_ops.c: SYS_CFG1 0xF0 → cut/vendor/RF/test → `_phy_ctx.cut_version`; bit-math unit-tested). NIC fw blob **vendored** (`hal/hal8822c_fw.c`, 200624B) + `download_default_firmware`. **Remaining (HW):** hand-rolled power-on (`Hal8822cPwrSeq`), EFUSE read → `_phy_ctx.rfe_type`, RF-table apply via `odm_set_rf_reg` | partial (decode+blob done) | MAC alive; register canaries match kernel `rtl88x2eu` via usbmon diff |
| M3 | **HalMAC DLFW** state machine ported end-to-end (`Halmac8822cFw` + `Halmac8822cRegs.h`): download_firmware/start_dlfw/dlfw_to_mem/iddma/check_chksum/dlfw_end_flow/wlan_cpu_en/pltfm_reset, **plus `send_fw_page`** (dl_rsvd_page bracket + 8822C TX desc + checksum + bulk-OUT + download-OK poll). Header-parse VALIDATED vs real NIC fw (200624B → exact). **Remaining:** vendor the fw blob + M2-supplied `_rsvd_boundary`/HIQ-EP | code complete (parse validated) | on HW: FW_INIT_RDY (`REG_MCUFW_CTRL==0xC078`) — the hard gate (cf. 8814 3081 boot) |
| M4 | BB/RF/AGC apply + 20 MHz channel set → **RX first** | yes | first RX frame decoded |
| M5 | **TX path DONE (sw).** `FrameParser8822c.h`: 48-byte TX + 24-byte RX field macros + `cal_txdesc_chksum_8822c` + `fill_data_tx_desc_8822c`; `RtlJaguar3Device::send_packet` parses radiotap (legacy/HT/VHT, mirrors Jaguar1) → desc → bulk-OUT. Unit-tested (14 TX fields + checksum; RX parse). **Remaining:** on-air (QSEL/endpoint confirm) | code complete (unit-tested) | sniffer/SDR sees the frame |
| M6 | **Narrowband** `CHANNEL_WIDTH_5/10` via `0x9b0`/`0x9b4` | yes + SDR | SDR shows ~½ (10 MHz) / ~¼ (5 MHz) occupied BW |
| M7 | regress.py integration, docs, PR | — | — |

## Narrowband recipe (M6 payoff — already known)

From `config_phydm_switch_bandwidth_8822c` (Jaguar3). Set as a channel/PHY
state; the radiotap header stays 20 MHz, so **only an SDR confirms it**.

| field | reg | 5 MHz | 10 MHz | 20 MHz |
|-------|-----|-------|--------|--------|
| small BW | `0x9b0[7:6]` | 0x1 | 0x2 | 0x0 |
| DAC clk | `0x9b4[10:8]` | 0x4 (120M) | 0x6 (240M) | 0x7 (480M) |
| ADC clk | `0x9b4[22:20]` | 0x4 (40M) | 0x5 (80M) | 0x6 (160M) |
| RX DFIR | `0x810[13:4]` | 0x2ab | 0x2ab | 0x19b |

Constants live in `src/jaguar3/RadioManagement8822c.h`. Caveats: 5 MHz has known
DAC mirror/leakage (10 MHz is the reliable target); TX power can't be changed
while in narrowband (set at 20 MHz, then switch width).

## Bring-up method (M2–M5)

Same approach that landed the 8814: capture the kernel `rtl88x2eu` driver doing
the same operation with `usbmon`, capture devourer, diff the vendor-control
register writes, and converge. RX before TX. Validate narrowband on the SDR (the
regress.py sniffer/radiotap matrix cannot see it — RX reports 20 MHz).

## Reference drivers

`libc0607/OpenHD rtl88x2eu` and `rtl88x2cu` — `hal/rtl8822c|rtl8822e/*`,
`hal/phydm/rtl8822c/phydm_hal_api8822c.c`, `hal/halmac/halmac_88xx/*`,
`hal/hal_halmac.c`.
12 changes: 11 additions & 1 deletion hal/HalVerDef.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ typedef enum tag_HAL_IC_Type_Definition {
CHIP_8188F = 12,
CHIP_8822B = 13,
CHIP_8723D = 14,
CHIP_8821C = 15
CHIP_8821C = 15,
/* Jaguar3 family added by devourer (not in upstream HalVerDef). 8822C and
* 8822E share the rtl8822c phydm generation; 8812EU enumerates under the
* 8822E HAL. See docs/jaguar3-bringup.md. */
CHIP_8822C = 16,
CHIP_8822E = 17
} HAL_IC_TYPE_E;

/* HAL_CHIP_TYPE_E */
Expand Down Expand Up @@ -127,6 +132,11 @@ typedef struct tag_HAL_VERSION {
#define IS_8703B_SERIES(version) ((GET_CVID_IC_TYPE(version) == CHIP_8703B) ? TRUE : FALSE)
#define IS_8822B_SERIES(version) ((GET_CVID_IC_TYPE(version) == CHIP_8822B) ? TRUE : FALSE)
#define IS_8821C_SERIES(version) ((GET_CVID_IC_TYPE(version) == CHIP_8821C) ? TRUE : FALSE)
#define IS_8822C_SERIES(version) ((GET_CVID_IC_TYPE(version) == CHIP_8822C) ? TRUE : FALSE)
#define IS_8822E_SERIES(version) ((GET_CVID_IC_TYPE(version) == CHIP_8822E) ? TRUE : FALSE)
/* Jaguar3 = the 8822C/8822E baseband+MAC generation (shared rtl8822c phydm).
* Used by the WiFiDriver factory to dispatch to RtlJaguar3Device. */
#define IS_JAGUAR3(version) ((IS_8822C_SERIES(version) || IS_8822E_SERIES(version)) ? TRUE : FALSE)
#define IS_8723D_SERIES(version)\
((GET_CVID_IC_TYPE(version) == CHIP_8723D) ? TRUE : FALSE)
/* HAL_CHIP_TYPE_E */
Expand Down
Loading
Loading