Devourer's RTL8733B backend supports the Wi-Fi function of the 1T1R, dual-band RTL8731BU/RTL8733BU family. It is based on the vendor's HALMAC 87xx path rather than any Jaguar or Kestrel implementation.
The supported USB identities are:
0bda:f72b, the Wi-Fi-only identity used by the tested RTL8731BU module.0bda:b733, the combo-module Wi-Fi function listed by the vendor driver.
An opened device must also report RTL8733B chip ID 0x16; a matching PID is
not accepted as sufficient identity.
The backend advertises one spatial stream, 2.4 and 5 GHz, legacy OFDM and HT MCS0–7 at 20 or 40 MHz. It also accepts all four long-preamble CCK rates on 2.4 GHz at 20 MHz; CCK is rejected on 5 GHz and at 40 MHz. Raw HT TX is deliberately forced to BCC. SGI, STBC, LDPC, VHT, 80 MHz, CCK short preamble, Bluetooth/coexistence controls and narrowband are not advertised.
That conservative surface follows both the hardware results and the vendor
HAL. The pinned 2023 HAL declares 802.11b/g/n, one TX and RX spatial stream,
and only BW_CAP_20M | BW_CAP_40M. It sets TX-LDPC true but RX-LDPC false;
devourer still reports both false because independently witnessed raw HT TX
only became reliable after clearing the global HT/VHT LDPC enable bits. The
newer 2024 vendor tree has the same 1SS, HT and 20/40 MHz declarations, and its
Fix VHT flags commit removes the accidentally enabled VHT build flag.
Every claim below rests on one physical unit: a bare unbranded 1T1R
RTL8731BU module, 0bda:f72b, RTL8733B chip ID 0x16, cut D, on USB high
speed. Independent witness: an RTL8812AU in monitor mode. Nothing here was
measured on an RTL8733BU — that chip is recognized from the vendor ID table and
rides the same code path, but no 0bda:b733 combo module was available.
The following was exercised through the normal WiFiDriver / IRtlDevice
factory path:
- USB discovery and endpoint selection; card enable and disable; firmware download, checksum and ready handshakes; HALMAC/MAC, EFUSE, BB and RF setup.
- Stable physical EFUSE reads, permanent MAC extraction, firmware-health and thermal telemetry.
- Ambient monitor RX on channels 6 and 36, including descriptor aggregation, PHY status, CRC reporting and clean shutdown.
- Independently captured raw TX on 2.4 and 5 GHz: legacy OFDM and HT MCS0–7, 20 MHz and HT 40 MHz. BCC was used throughout. On 2.4 GHz, an RTL8812AU witness also decoded long-preamble 1, 2, 5.5 and 11 Mbps CCK with valid FCS.
- Repeated warm initialization, concurrent RX/TX, bounded sustained TX, teardown during active RX/TX, malformed-input rejection and post-test adapter-doctor checks.
Per-rate delivery, witnessed by an RTL8812AU (0bda:8812) in monitor mode on
channel 6, 300 submitted frames per rate. Counted per frame from the witness's
own RX events, so these are exact rather than sampled:
| requested | delivered | decoded as | FCS errors |
|---|---|---|---|
| 1 Mbps CCK | 298 / 300 | 1 Mbps | 0 |
| 2 Mbps CCK | 300 / 300 | 2 Mbps | 0 |
| 5.5 Mbps CCK | 300 / 300 | 5.5 Mbps | 0 |
| 11 Mbps CCK | 294 / 300 | 11 Mbps | 0 |
| 6 Mbps OFDM | 300 / 300 | 6 Mbps | 0 |
| HT MCS0 | 270 / 300 | MCS0 | 0 |
| HT MCS7 | 277 / 300 | MCS7 | 0 |
Every frame decoded at the rate it was requested at, and no captured frame carried an FCS error at any rate. These are single probes, so per this repo's own guidance they are worth about +/-3 points each.
The two HT readings sitting below the legacy ones are not a transmitter
effect, and the witness was qualified rather than assumed
(tests/ground_station_qualify.sh exists for exactly this question). Sweeping
the RTL8812AU witness across the HT ladder, 300 frames each:
| rate | delivered | witness RSSI |
|---|---|---|
| MCS0 | 270 / 300 | 57.8 |
| MCS1 | 300 / 300 | 65.0 |
| MCS3 | 300 / 300 | 65.3 |
| MCS5 | 300 / 300 | 65.2 |
| MCS6 | 296 / 300 | 65.1 |
| MCS7 | 277 / 300 | 60.3 |
The ladder is flat at 100% through MCS5, so the witness is nowhere near its modulation cliff and is fit to measure this rate range — a receiver measuring itself would fall off progressively at the top, not dip at both ends. MCS0 is the most robust rate in the set, so its being the lowest reading rules out a cliff at either end. Both low readings also came with 5-7 units less RSSI than the flat middle, which places the variation in the per-run link conditions rather than in modulation. Treat the whole HT column as one flat band around 90-100% until something moves it by more than the probe noise.
A mixed run alternating all eight rates (1/2/5.5/11 Mbps CCK, 6 Mbps OFDM,
MCS0/4/7) submitted 250 frames, crossing the CCK/OFDM rate-class boundary on
every frame. The witness decoded all eight rate_hw values on air with valid
FCS, every frame at 20 MHz with STBC and LDPC clear, matching the forced-BCC
contract.
RX aggregation is capped at 12 KiB (rtl8733b::kRxAggregateBytes8733b) because
the vendor default of 20 KiB exceeds one bulk-IN URB: a 60-second high-traffic
receive run had xHCI split such an aggregate, leaving a descriptor tail and its
body in separate completions. The RX loop floors its URB size at the same
constant, so DEVOURER_RX_URB_BYTES cannot reintroduce the straddle from the
other side. Five repeated high-traffic channel-36 runs with the cap in place
completed without malformed descriptors, aggregate mismatches or parser aborts.
A separate ten-cycle channel-6/channel-36 warm lifecycle run passed EFUSE,
firmware-ready, initialization, thermal, rollback and card-disable gates.
tests/rtl8733b_lifecycle_soak.sh makes that warm test reproducible and labels
it explicitly as distinct from a true VBUS cycle.
Thermal readings are telemetry, exactly as on every other generation:
GetThermalStatus() exposes the RF 0x42 meter against the EFUSE baseline,
InitWrite logs one bring-up snapshot, and DEVOURER_THERMAL_POLL_MS emits
thermal events at the caller's cadence. Nothing in the send path reads the
meter — a per-frame read would be several USB control transfers per frame, and
the meter is a PA-bias tracking index rather than a calibrated junction
temperature, so it is not sound input for a TX gate
(docs/warm-tx-degradation.md). During the mixed CCK/OFDM witness run the raw
code stayed at 32–33 against baseline 32 across 650 submitted frames; 636 were
captured with valid FCS across all five requested rates. A separate 1,000-frame
run alternating CCK and OFDM submitted 1,000/1,000; the witness captured 660
canonical frames across both rates, all with valid FCS, while the same 32–33
thermal range held. Both runs are rate-coverage and thermal evidence only —
neither says anything about TX-power tracking, and at these deltas (+0 to +1)
the two thermal-compensation curves are the same bits anyway.
On a unit whose EFUSE is in TSSI-offset PG mode — the tested RTL8731BU is —
closed-loop TSSI is the TX-power control, so it runs by default. It is not
optional there: the flat fallback index (kSafeTxAgcIndex8733b) is a
conservative bring-up value, and on air it runs cold enough that HT rates do
not survive the link. Measured with the witness, 300 frames submitted at MCS7
with the flat index: zero captured, twice. A unit whose EFUSE carries no TSSI
calibration has nothing to drive the loop and takes the flat path.
The chip keeps two thermal-compensation curves, one for CCK and one for
OFDM/HT. The table is chosen once per channel set from the configured TX mode
and then left alone, which is what the vendor driver does —
_halrf_tssi_set_tmeter_tbl_8733b is only ever called from full TSSI setup,
keyed on phydm_get_tx_rate at that instant, and never re-selected at runtime.
There is deliberately no runtime switch, and the reason is worth recording so nobody adds one back.
The two curves are bit-identical for thermal deltas 0..+17 and first differ
at +18 — the swing ramp starts there, and the words either side of the
baseline are zero in both tables. A five-minute max-duty MCS7 soak on the
validation unit plateaued at +8 after two minutes and stopped climbing,
less than half the delta needed for the tables to differ by a single entry. So
on this silicon a runtime switch would spend 84 ms — 136 USB register round
trips, inside send_packet, capping a mixed-rate stream near 11 fps — to
install a table bit-identical to the one already loaded.
An opt-in knob for it was written, measured on air and then deleted on that evidence. Room temperature agreed too: 300 frames of 1 Mbps CCK delivered 300/300 with the OFDM table loaded and 299/300 with the CCK table, zero FCS errors either way. OpenIPC/devourer#389 has the cost breakdown and a validated in-place alternative (13.8 ms, tracking left enabled) — if a future board does reach +18, that is the implementation to revisit, not the teardown/rebuild.
A related property of the closed loop, worth knowing before reading power numbers from a fast rate-switching run: TSSI needs settling time. Alternating CCK and OFDM at roughly 9 ms per frame leaves CCK airing about 5 dB above its settled level, and the same stream paced to 86 ms per frame lands on the settled value. That is the tracking loop converging, not a descriptor or table-programming effect.
SetCcaMode(false) — the universal carrier-sense-enabled default, and the
state this backend's MAC bring-up leaves programmed — succeeds as a no-op.
SetCcaMode(true) (DEVOURER_DIS_CCA) throws: the HALMAC 87xx carrier-sense
gate has not been located and measured here, and the backend refuses rather
than silently pretending. The refusal leaves the session running.
The construction-time form of the same knob — DeviceConfig::tuning::disable_cca,
which every other generation applies during bring-up — cannot be honoured
either, and bring-up emits one warning saying so rather than dropping it
silently or failing to initialise. Silence is the only outcome that would let a
refused knob look like a granted one; a hard failure would be a harsh answer to
a request a caller may be making only through an inherited environment.
These results have not been claimed:
- No SDR was available, so occupied bandwidth, spectral mask, EVM and absolute output power were not measured.
- The experimental 5/10 MHz sequence has register-readback and normal-path RX
evidence only. Narrowband TX and decode by an independent narrowband peer are
deferred;
AdapterCaps::narrowband_okremains false. - No physical
0bda:b733combo module was available. Its USB identity is taken from the vendor driver, but Bluetooth and coexistence are out of scope. - Secure-firmware variants were not exercised. The tested cut-D device used the normal NIC image.
- The test hub cannot switch VBUS, so repeated true power-off cold boots were not automated. Physical unplug/replug and repeated warm bring-up were tested.
- SGI remains disabled: a descriptor with its short-GI bit set submitted successfully, but an independent RTL8812AU decoded both broadcast and unicast probes as long GI. STBC and LDPC remain rejected. ACK/BlockAck response, A-MPDU, fast retune and throughput were not independently validated and remain unadvertised or unsupported by this backend.
- A vendor-kernel-driver cross-test was attempted with the 2024 source on Linux
6.14, but its module did not build: its Makefile omitted required include
paths, and after supplying the main include path compilation stopped on an
incompatible
MODULE_IMPORT_NSuse and a missingplatform_ops.h. No vendor module was loaded, so no vendor-driver parity result is claimed.
A second RTL8731BU f72b unit — the same chip the surviving DUT is, so it
would have covered the same README row — passed early identity, EFUSE and
firmware bring-up checks, then stopped enumerating and overheated while
attached to a custom power supply. It is excluded from all radio-validation
claims, and the RTL8731BU row therefore rests on a single surviving sample: a
one-unit result, not a population.
The experimental 5/10 MHz path ports the later workaround in
rtl8733bu-20230626: program the RF as 20 MHz, then apply the small-bandwidth
BB clock fields after the RF writes. Both 5 and 10 MHz register transitions
read back on the tested device. The vendor's published capability mask remains
20/40 MHz, however, and the workaround itself describes a no-RF-output failure
without its final writes. Consequently the code is retained for further work
but the public capability mask stays at 20/40 MHz until SDR occupied-bandwidth
and independent narrowband TX/RX tests are available.
Firmware and parameter tables are reproducibly extracted from
libc0607/rtl8733bu-20230626
commit 9e5f6845b4e3393207720fc1886668bc80b6af1c, pinned as the
reference/rtl8733bu-20230626 submodule (reference/README.md). That tree is
also the source of the halmac_reg2.h and power-sequence constants transcribed
into src/rtl8733b/, so both the generated artifacts and the hand-carried
constants are re-verifiable from a fresh checkout. The extraction tools pin the
source-file hashes, output sizes and content hashes:
git submodule update --init reference/rtl8733bu-20230626
python3 tools/extract_8733b_fw.py --check
python3 tools/extract_8733b_tables.py --checkBoth accept --input / --source-root to point at a tree elsewhere.
The implementation was also compared with
libc0607/rtl8733bu-20240806
commit 2ec19e154cffbc2abd98d43d59278dffa6e50d49; it was not used to silently
replace the firmware or tables already validated on the device. The review
covered the later active-RF-path TSSI selection (already matched here), SRRC
band-edge setup, WLAN/Bluetooth GPIO pull-up changes, power-saving and C2H/TX
tasklet fixes. None justified replacing the pinned firmware/tables or widening
the raw-monitor capability claims above.
The backend covers the USB-first HALMAC 87xx scope of OpenIPC/devourer#233. It does not adopt that issue's provisional VHT/80 MHz assumptions, because both vendor trees describe this silicon as HT-only and 20/40 MHz.