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
18 changes: 16 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,25 @@ add_library(WiFiDriver
hal/Hal8812PhyReg.h
hal/Hal8812PwrSeq.c
hal/Hal8812PwrSeq.h
hal/Hal8814PwrSeq.c
hal/Hal8814PwrSeq.h
hal/basic_types.h
hal/hal8812a_fw.c
hal/hal8812a_fw.h
hal/hal8814a_fw.c
hal/hal8814a_fw.h
hal/hal_com_reg.h
hal/rtl8812a_hal.h
hal/rtl8812a_recv.h
hal/rtl8812a_spec.h

# RTL8814AU phydm-format register tables. The .c file is generated from the
# aircrack-ng/rtl8814au tree by tools/extract_8814a_phy_tables.py; the
# loader at src/PhyTableLoader walks the same conditional encoding the
# upstream phydm parser does, without pulling in phydm itself.
hal/phydm/rtl8814a/Hal8814_PhyTables.c
hal/phydm/rtl8814a/Hal8814_PhyTables.h

src/ieee80211_radiotap.h
src/EepromManager.cpp
src/EepromManager.h
Expand All @@ -36,11 +47,13 @@ add_library(WiFiDriver
src/HalModule.cpp
src/HalModule.h
src/ParsedRadioPacket.cpp
src/PhyTableLoader.cpp
src/PhyTableLoader.h
src/RadioManagementModule.cpp
src/RadioManagementModule.h
src/Radiotap.c
src/Rtl8812aDevice.cpp
src/Rtl8812aDevice.h
src/RtlJaguarDevice.cpp
src/RtlJaguarDevice.h
src/RtlUsbAdapter.cpp
src/RtlUsbAdapter.h
src/SelectedChannel.h
Expand All @@ -55,6 +68,7 @@ target_compile_features(WiFiDriver PUBLIC cxx_std_20)
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 src)

add_executable(WiFiDriverDemo
Expand Down
142 changes: 137 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,148 @@
# devourer
The RTL8812AU driver that simply devours its competitors

## Installing
The Realtek 11ac driver that simply devours its competitors.

### Windows
Devourer is a userspace re-implementation of Realtek's RTL88xxAU Wi-Fi
driver (Jaguar family: RTL8812AU shipping, RTL8814AU RX-only, RTL8811AU
WIP), speaking to the chip directly through libusb. 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.

## Hardware landscape

Devourer targets **RTL8812AU**, **RTL8811AU**, and **RTL8814AU** — all
members of Realtek's first-generation 802.11ac silicon family, internally
codenamed **"Jaguar"**. The HAL, register-table layout, firmware-download
plumbing, and `SET_TX_DESC_*_8812` macros in `src/FrameParser.h` are shared
across the family; chip-specific EEPROM handling, firmware blobs, and RF
tables are layered on top.

| Part | RF / streams | Status | Notes |
| -------------- | --------------- | ------------- | ------------------------------------------- |
| **RTL8812AU** | 2T2R | Supported | VID/PID `0bda:8812`; reference part |
| **RTL8811AU** | 1T1R | WIP | Pin/feature-reduced cut of 8812AU |
| **RTL8814AU** | 4T4R, 3-SS max | RX supported | VID/PID `0bda:8813`; 2-SS effective on USB-2; TX path not yet validated |
| **RTL8821AU** | 1T1R + BT | Not supported | Same Jaguar PHY combined with Bluetooth |

Successor families (`Jaguar2` / `Jaguar+` — 8812BU, 8822BU/BE, etc., and the
later `Kestrel` 11ax generation) are **out of scope**: they share the Realtek
"AU" branding but the baseband and HAL differ enough that they would need
their own driver.

> Heads up — some Realtek USB sticks ship in "ZeroCD" mode and enumerate first
> as a USB mass-storage device exposing the Windows driver installer
> (`0bda:1a2b` is the canonical offender), then re-enumerate as the NIC after
> a mode switch. If `libusb_open_device_with_vid_pid(ctx, 0x0bda, 0x8812)`
> returns NULL, check `lsusb` — you may need `usb_modeswitch` to flip it
> first.

## Building

Toolchain: CMake ≥ 3.15, a C++20 compiler, and libusb-1.0.

You can download and install libraries using the vcpkg dependency manager:
### Linux / macOS

libusb is located via `pkg-config`:

```sh
# Debian/Ubuntu
sudo apt install build-essential cmake pkg-config libusb-1.0-0-dev
# macOS (Homebrew)
brew install cmake pkg-config libusb

cmake -S . -B build
cmake --build build -j
```

### Windows

Dependencies come from vcpkg. Set `VCPKG_ROOT` so the CMake toolchain file at
`$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake` resolves:

```powershell
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg integrate install
.\vcpkg install libusb spdlog
.\vcpkg install libusb
```

Then the same `cmake -S . -B build && cmake --build build` from the project
root.

### Build artifacts

- `WiFiDriver` — static library; link this from your application.
- `WiFiDriverDemo` — RX example built from `demo/main.cpp`. Walks every
Realtek device under VID `0bda` and tries to open it; sets monitor mode
on channel 36 / 20 MHz, runs the read loop.
- `WiFiDriverTxDemo` — TX example built from `txdemo/main.cpp`. Takes a USB
fd as `argv[1]` (the Termux-on-Android pattern using
`libusb_wrap_sys_device`), forks RX into a child, and TX-loops a hardcoded
beacon in the parent.

### Demo env vars

- `DEVOURER_PID=0xNNNN` — restrict the device-open loop to a single PID
(e.g. `0x8813` for RTL8814AU).
- `DEVOURER_CHANNEL=N` — override the demo's monitor channel (e.g. `6`
for 2.4 GHz, `36` for 5 GHz).
- `DEVOURER_SKIP_RESET=1` — skip `libusb_reset_device` before claim. Useful
when picking up a chip whose firmware is already running (e.g. after
unbinding a kernel driver that left fw state intact).
- `DEVOURER_FORCE_TXPWR=1` — force the per-rate TX-power loop to run during
channel switch. Skipped by default in 8814 monitor mode: the loop issues
~300 vendor control transfers and the resulting per-rate indices are
unused for RX-only operation.

## Using the library

The caller owns libusb: you must `libusb_init`, open the device, detach any
kernel driver, and `libusb_claim_interface(handle, 0)` **before** handing
the handle to `WiFiDriver::CreateRtlDevice`. The factory is intentionally
thin — see `demo/main.cpp` for the full boilerplate. A minimal RX path:

```cpp
auto logger = std::make_shared<Logger>();
WiFiDriver driver(logger);
auto dev = driver.CreateRtlDevice(handle); // handle is already claimed
dev->Init(packetProcessor, SelectedChannel{
.Channel = 36,
.ChannelOffset = 0,
.ChannelWidth = CHANNEL_WIDTH_20,
});
```

`packetProcessor` is your `void(const Packet&)` callback. `Init` runs the
RX loop until `should_stop` is set, then returns. For TX, use `InitWrite`
on a channel followed by `send_packet(buffer, len)` where the buffer begins
with a radiotap header (the iterator in `src/Radiotap.c` extracts
rate/MCS/VHT/STBC/LDPC/SGI/bandwidth from it).

## Project layout

```
hal/ Vendor headers and tables ported from Realtek's tree
Hal8812PhyReg.h, hal8812a_fw.[ch], rtl8812a_spec.h
Hal8814PhyReg.h, hal8814a_fw.[ch], Hal8814PwrSeq.[ch]
rtl8814a/Hal8814_PhyTables.[ch] (8814 BB/AGC/RF tables)
src/ Driver implementation
WiFiDriver thin factory
RtlJaguarDevice orchestrator (RX + TX entry points)
HalModule chip bring-up / power sequencing
RadioManagementModule channel, bandwidth, TX power, up to 4 RF paths
EepromManager EFUSE / EEPROM read + autoload state
FirmwareManager chip-specific firmware download
PhyTableLoader applies chip-cut-conditional BB/AGC tables
RtlUsbAdapter libusb wrapper (vendor + bulk transfers)
FrameParser RX parsing, TX descriptor layout
Radiotap.c radiotap header iterator
demo/ RX example
txdemo/ TX example (Android / Termux pattern)
```

## License

GPL-2.0. See [LICENSE](LICENSE).
75 changes: 68 additions & 7 deletions demo/main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <cassert>
#include <cstdlib>
#include <memory>

#include <libusb.h>
Expand All @@ -8,9 +9,26 @@
#include "WiFiDriver.h"

#define USB_VENDOR_ID 0x0bda
#define USB_PRODUCT_ID 0x8812

static void packetProcessor(const Packet &packet) {}
/* Known USB product IDs for the Realtek Jaguar 802.11ac family driven by this
* library: RTL8812AU (2T2R), RTL8811AU (1T1R cut), and RTL8814AU (4T4R RF /
* 3-SS baseband). */
static constexpr uint16_t kRealtekProductIds[] = {
0x8812, /* RTL8812AU (also seen on some 8811AU boards) */
0x0811, /* RTL8811AU */
0xa811, /* RTL8811AU */
0xb811, /* RTL8811AU/8821AU variants */
0x8813, /* RTL8814AU (Realtek demoboard PID, used by CF-938AC/CF-960AC) */
};

static int g_rx_count = 0;
static void packetProcessor(const Packet &packet) {
++g_rx_count;
if (g_rx_count <= 10 || g_rx_count % 100 == 0) {
printf("<devourer>RX pkt #%d (len=%zu)\n", g_rx_count, packet.Data.size());
fflush(stdout);
}
}

int main() {
libusb_context *ctx;
Expand All @@ -25,11 +43,26 @@ int main() {

libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);

libusb_device_handle *dev_handle =
libusb_open_device_with_vid_pid(ctx, USB_VENDOR_ID, USB_PRODUCT_ID);
/* DEVOURER_PID env var (hex, e.g. "0x8813") restricts the open loop to a
* single PID. Useful when multiple Realtek adapters are plugged. */
const char *pid_env = std::getenv("DEVOURER_PID");
uint16_t target_pid = 0;
if (pid_env != nullptr) {
target_pid = static_cast<uint16_t>(std::strtoul(pid_env, nullptr, 0));
logger->info("DEVOURER_PID={:04x} (limiting to this PID)", target_pid);
}
libusb_device_handle *dev_handle = nullptr;
for (uint16_t pid : kRealtekProductIds) {
if (target_pid != 0 && pid != target_pid) continue;
dev_handle = libusb_open_device_with_vid_pid(ctx, USB_VENDOR_ID, pid);
if (dev_handle != NULL) {
logger->info("Opened Realtek device {:04x}:{:04x}", USB_VENDOR_ID, pid);
break;
}
}
if (dev_handle == NULL) {
logger->error("Cannot find device {:04x}:{:04x}", USB_VENDOR_ID,
USB_PRODUCT_ID);
logger->error("Cannot find any supported Realtek device under VID {:04x}",
USB_VENDOR_ID);
libusb_exit(ctx);
return 1;
}
Expand All @@ -39,13 +72,41 @@ int main() {
rc = libusb_detach_kernel_driver(dev_handle, 0); // detach driver
}

/* Skip USB reset if DEVOURER_SKIP_RESET=1. Used when picking up a chip
* with firmware already running (e.g. after a patched-rtw88 sysfs unbind):
* USB reset would clobber fw state and force us to re-run fwdl. */
if (!std::getenv("DEVOURER_SKIP_RESET")) {
libusb_reset_device(dev_handle);
} else {
logger->info("DEVOURER_SKIP_RESET set — skipping libusb_reset_device");
}
rc = libusb_claim_interface(dev_handle, 0);
assert(rc == 0);

/* Probe MCUFWDL immediately after claim, before any other chip access.
* Useful for diagnosing post-rtw88-unbind takeover: shows whether chip
* state has transitioned away from rtw88's 0x0060e078 (fw running). */
if (std::getenv("DEVOURER_PROBE_MCUFWDL")) {
uint32_t mcufwdl = 0;
for (int i = 0; i < 5; ++i) {
int got = libusb_control_transfer(dev_handle, 0xC0, 5, 0x0080, 0,
reinterpret_cast<uint8_t *>(&mcufwdl),
4, 500);
logger->info("PROBE MCUFWDL[{}]: rc={} val=0x{:08x}", i, got, mcufwdl);
}
}

WiFiDriver wifi_driver(logger);
auto rtlDevice = wifi_driver.CreateRtlDevice(dev_handle);
/* Default channel 36 (5 GHz) for the 8812 reference. Override with
* DEVOURER_CHANNEL=N env var (e.g. DEVOURER_CHANNEL=6 for busy 2.4 GHz). */
int channel = 36;
if (const char *ch_env = std::getenv("DEVOURER_CHANNEL")) {
channel = std::atoi(ch_env);
logger->info("DEVOURER_CHANNEL set — tuning to channel {}", channel);
}
rtlDevice->Init(packetProcessor, SelectedChannel{
.Channel = 36,
.Channel = static_cast<uint8_t>(channel),
.ChannelOffset = 0,
.ChannelWidth = CHANNEL_WIDTH_20,
});
Expand Down
Loading
Loading