Skip to content

Latest commit

 

History

History
424 lines (360 loc) · 23.3 KB

File metadata and controls

424 lines (360 loc) · 23.3 KB

RTL8731BU / RTL8733BU support

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.

Advertised capability

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.

Hardware validation

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.

USB TX aggregation is ported and is the one host-side lever that matters on a small SoC. send_packets packs up to three [txdesc][frame] blocks into one bulk-OUT URB (DEVOURER_TX_USB_AGG, default 0 = off = byte-identical), the HalMAC 3-descriptor rule that MAC init had already programmed as BLK_DESC_NUM in DWBCN0_CTRL[7:4]. The block count rides the first descriptor's dword7[31:24] and is written before the checksum, which folds over byte 0x1f.

The cost it removes is host CPU, not air time. One bulk submission measured ~248 µs of CPU on the CV610 craft against ~22 µs on x86 — the same transfer, an 11× ratio — and a profile attributes ~87% of it to the kernel USB submit/completion path, with the per-byte term below noise on ARM. Packing 3:1 therefore removes two submissions in three: on the craft, 248 → 148 µs per frame and 43.0% → 26.7% of one core at ~1750 fps, frame rate unchanged (1733 → 1800). On x86 the same A/B reads 21.5 → 10.6 µs.

The failure mode this was checked against is the 8822BU's: wrong packing makes the TXDMA re-air block 1 agg_num times, which frame counts cannot see. Each frame was therefore stamped (DEVOURER_TX_QOS_DATA) and the stamps counted at the witness (DEVOURER_RX_PCTR): 31552 receptions, 31551 distinct counters, ratio 1.00 where re-airing would read 3.00. Note GetTxStats().submitted counts URBs rather than frames, so an aggregated session reports about a third — accounting, not throughput.

The boundary shim is verified separately, because a fixed-payload sweep never reaches it. When a packed total lands on an exact bulk-MPS multiple the planner inserts the 8-byte first-block PKT_OFFSET reserve (the sync bulk path has no ZLP), and on the 8822BU a reserved first block is precisely what made the TXDMA re-air block 1. For three blocks at USB HS that happens when the MPDU length is ≡ 472 mod 512 — measured exactly, urb_bytes 1544 / 3080 / 4616 at payloads 472 / 984 / 1496, all n x 512 + 8. Stamped at the witness with the shim engaged: 15027 shim URBs, 44032 receptions, 44032 distinct, ratio 1.00 against 1.00 for the no-shim control. This part's block walker does account the reserve.

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.

TX power control

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 runtime lever

Because the loop is the power control, the runtime knob is the loop's target. IRtlDevice::SetTxPowerOffsetQdb shifts every per-rate target that survives the safe ceiling — cap first, then shift, so the calibrated per-rate spread comes through the move intact — and writes it as the five packed dwords at 0x3a00..0x3a10, in place, with tracking left enabled. That is the same shape fast_retune uses for its per-channel rewrite (#389); the alternative, a TSSI disable/re-enable pair, costs ~165 ms and buys nothing here.

The capability report is the dBm model — index_max = 0, one qdB per step — over [-128, +127]. Offset 0 is kSafeTssiTargetQdbm8733b (16 dBm), a first-light clip sitting at or below this part's factory targets (18–20 dBm at 2.4 GHz, 16–19 at 5 GHz).

That range is asymmetric, and only one end is a hardware limit. The 0x3a00..0x3a10 bytes hold a signed int8 offset from the 64 qdBm anchor, so the field itself spans [−128, +127] — targets from −16 dBm to +47.75 dBm.

Both ends are that field, and neither is re-clamped at something softer — the same clamped-only-at-the-hardware-rail answer Jaguar1 (±126) and Jaguar3 (±127) give. Where the chip stops responding is measured and written down below rather than enforced in the setter, because on this part both ends stop responding well inside the field and at points only a bench can find.

The positive half is deliberately not re-clamped at the factory table. src/TxPower.h states for every family that headroom above the generated table is the operator's call, and a per-unit EFUSE trimmed too cold is exactly what a bench calibration exists to correct — clamping here would make this the one backend where a measured operating point cannot be commanded. What that costs is measured below.

The offset is sticky: a full SetMonitorChannel re-folds it against the new channel's targets, and FastRetune carries it into the hop's in-place rewrite.

The flat-index and per-rate-diff knobs are not ported. The flat index has no measured dB-per-step slope on this part and, as above, cannot carry HT.

Measured on air. Two independent 6-point passes at ch36 against an RTL8812AU witness (chip-RSSI ground station — the SDR saturates at this range, see tests/txpwr_offset_onair.sh): the lever is monotone and worth 14.2 / 14.8 dB of received power for the full 16 dB of command, overall slope 0.222 / 0.231 dB per qdB against the 0.25 nominal. Its adversarial counterpart, in the same breath: the step is not constant. The bottom 12 qdB delivered 0.125 and 0.126 dB/qdB — the only structure that reproduced exactly across both passes — while everything above −52 qdB ran 0.233..0.242 and the mid-range scattered 0.219..0.252 between passes. Near the floor the loop returns about half the dB commanded, and saturated_low does not warn about it (that clamp fires only at −64). TxPowerCaps::step_measured therefore stays false, the same call the 8822E gets for the same reason: a controller should calibrate its own dB-per-qdB, or close the loop on the ground's RSSI.

The control that makes those numbers readable: the pre-change binary, in the same session and geometry, measured flat — 0.3 dB across the same 64 qdB of command, because it had no actuator to move. One unit, one witness, near-field geometry, integer-quantised RSSI, no SDR. Harnesses: tests/rtl8733b_txpwr_regcheck.sh (registers, including that null control), tests/txpwr_offset_onair.sh (slope), tests/rtl8733b_txpwr_selftest.cpp (the offset math, in ctest).

The backoff floor is about −96 qdB, not the 0 dBm target

An earlier cut of this work clamped the offset at −64 qdB, reasoning that a target below 0 dBm was meaningless. Sweeping past it says otherwise (MCS0, ch36):

offset target witness RSSI witness EVM
−32 8.0 dBm 69.40 −60.8
−64 0.0 dBm 62.07 −59.3
−80 −4.0 dBm 54.80 −54.0
−96 −8.0 dBm 52.96 −53.1
−112 −12.0 dBm 52.97 −52.8
−128 −16.0 dBm 52.99 −52.8

Power keeps falling past the 0 dBm target — another 7.3 dB between it and −4 dBm — with no sign wrap, and pins from about −96 qdB, where three successive rungs agree within 0.03 dB. EVM softens from −59 to −53 across the whole extension and the link stays decodable throughout. So usable travel is ~23 dB below the clip rather than the ~16 dB the first cut allowed, which matters for near-field bench work and for a link that wants to sit quiet.

Overdrive: about 3 dB, and then the PA compresses

Sweeping the other way — up from the clip, MCS0 at ch36, with the witness reporting EVM beside RSSI:

offset witness RSSI witness EVM witness SNR
0 75.4 −62.0 62.0
+16 (top of the PG table) 78.2 −50.2 63.7
+32 84.1 −18.0 57.9
+48 83.7 −18.0 58.1
+64 83.8 −18.0 58.2

The +16 rung is real gain, +2.8 dB, though the constellation has already given up 12 dB of EVM to get it. The +32 rung is not gain: the witness hears 8.7 dB more energy while EVM collapses to −18, and +48 and +64 change nothing at all — RSSI and EVM both pinned. That is the PA in hard compression, and SNR never saw it (58..64 throughout), which is the failure mode docs/bench-testing-near-field.md exists to warn about: strong RSSI plus poor EVM means back power off, the opposite of the weak-link response.

Two things follow. The vendor's PG table lands about where this part stops being linear, so +16 qdB is the edge of usable overdrive even though the API allows +127 — a caller sweeping for its own operating point should watch EVM, not RSSI, and stop where EVM turns. And the negative half is clean by comparison: EVM sits flat at −58..−61 across all 16 dB of backoff.

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.

Known gaps and deferred validation

These results have not been claimed:

  • No SDR was available, so occupied bandwidth, spectral mask, EVM and absolute output power were not measured. The runtime power lever's slope is a relative witness-RSSI measurement for the same reason: it says the lever moves ~14 dB, not what any rung radiates in dBm.
  • The TX-power offset's dB-per-qdB is not constant across its advertised range (0.125 in the bottom 12 qdB vs 0.233..0.242 above −52), so step_measured stays false and a controller must calibrate its own slope. Raising kSafeTssiTargetQdbm8733b above 16 dBm is deferred — that wants a conducted measurement, not a witness receiver.
  • 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_ok remains false.
  • No physical 0bda:b733 combo 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 and throughput were not independently validated and remain unadvertised or unsupported by this backend.
  • Fast retune is now ported and independently witnessed (intra-band, same-width; src/rtl8733b/CLAUDE.md has the measured contract and its counterparts): channel-state readback parity 7/7 hops, a 299/300 post-hop burst decoded by an RTL8812AU witness, ~55 ms call / ~10 ms p50 radio-live on this unit. The channel-bucketed TSSI-DE offsets are rewritten in place across the hop with tracking live (the first cut skipped them — caught in review): DE readback parity 22/22 across ch1→ch13 hops, tracking-enable field held at 7 throughout, settle p50 unchanged at 10.4 ms. Radiated power across a hop remains SDR-gated like every other RF-domain quantity here.
  • 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_NS use and a missing platform_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.

Narrowband status

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.

Vendor-source provenance

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 --check

Both 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.