Skip to content

rtl8733b: runtime TX-power knobs — SetTxPowerOffsetQdb / GetTxPowerCaps are unwired, and a consumer cannot tell #1

Description

@snokvist

Follow-up to the RTL8733B bring-up (upstream PR OpenIPC/devourer#388). Investigate here on the fork, PR upstream once measured.

docs/rtl8733b-upstream-plan.md deliberately deferred this — Stage 8's audit says "Narrowband, SGI, LDPC, STBC, VHT/HE, ACK responder, beaconing, runtime TX-power knobs, energy sensing, and advanced scheduled-MAC features are not claimed", and the scope note lists "generalized TX-power controls … remain later gates". So this is planned work, not an oversight. What is new is a consumer-side cost that argues for doing it, and for one small change even if the actuator itself waits.

What a consumer hits

Rtl8733bDevice overrides none of the IRtlDevice runtime-power family — not SetTxPowerOffsetQdb, GetTxPowerCaps, SetTxPowerIndexOverride, GetTxPowerState or ReApplyTxPower. Only jaguar1/2/3 and kestrel do. Every call therefore lands on src/IRtlDevice.h:134, body (void)qdb; return 0;.

Measured 2026-08-14 on a CV610 board (0bda:f72b) transmitting 5805/20, received by an RTL8812AU:

commanded offset ground RSSI (mean of 5) ground EVM
−24 qdb −28.0 −22
−48 qdb −28.4 −22
−96 qdb −26.8 −22
−24 qdb −27.6 −22

18 dB commanded, nothing aired. A matched positive control on the same receiver at the same bench geometry minutes earlier — an 8822EU over the identical rungs — moved 5.6 dB of air for a 6 dB command and then railed with saturated_low. So the receiver reads a working actuator fine at that range; the 8733BU simply has none.

That part is expected. The problem is that return 0 is indistinguishable from a successful zero-offset apply, and the consumer's own reported state came out as:

{"override_active":true,"qdb":-24,"applied_qdb":0,
 "saturated_low":false,"saturated_high":false}

Both the value and the saturation flags read like a healthy actuator with travel remaining.

Two pieces, and the cheap one is worth doing first

1. GetTxPowerCaps() — one override, no RF work. The base class already returns supported=false, so a caller can discriminate today; but a caller reading it gets an all-zero struct that says nothing about which knob is missing. An explicit 8733B override returning {supported=false} alongside the honest per-knob bits makes the refusal deliberate and greppable, matches how the other four backends state their answer, and costs nothing. This alone would have let the consumer refuse rather than report success.

2. The actuator itself. The silicon and this backend already have what the API needs:

  • Phy8733b::set_flat_tx_power(index) writes the TXAGC refs at BB 0x4308 and the per-rate diffs at 0x3a00..0x3a10, and read_txagc_state() reads them back — a verified write path already exists.
  • On a TSSI-offset PG unit, configure_tx_power drives enable_tssi_tracking(..., kSafeTssiTargetQdbm8733b) with a fixed 64 qdBm = 16 dBm target. That is an absolute qdBm knob the closed loop already honours.

So SetTxPowerOffsetQdb(qdb) maps naturally onto re-running the TSSI target at kSafeTssiTargetQdbm8733b + qdb, clamped, with the flat-index path as the fallback for a non-TSSI unit. TxPowerCaps already reserves index_max = 0 for exactly this shape — the comment reads "0 = dBm model" — so no new capability vocabulary is needed.

Two things to measure rather than assume, both flagged by docs/rtl8733b.md:

  • Settling. The doc records the loop needing time: alternating CCK/OFDM at ~9 ms per frame left CCK ~5 dB above its settled level, and 86 ms pacing landed on it. Any offset sweep has to pace or it will misread its own steps.
  • The floor is real. kSafeTxAgcIndex8733b was measured too cold to carry HT at all (MCS7, 300/300 submitted, 0 captured, twice). Negative offsets have somewhere unusable to go, so saturated_low needs to mean something concrete here rather than being wired to false.

Suggested order

  1. Override GetTxPowerCaps() with the honest false set — mergeable on its own, no hardware.
  2. Implement SetTxPowerOffsetQdb on the TSSI-target path; report saturated_low/high from the clamp.
  3. Wire GetTxPowerState() from read_txagc_state() plus the TSSI target, so the state snapshot is chip truth rather than a shadow.
  4. Measure a paced offset sweep against a witness receiver; only then flip supported=true.
  5. PR upstream, citing the bench evidence.

Consumer-side evidence and the full measurement write-up: snokvist/waybeam-link #188, issue #185.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions