Repository navigation
Description
Summary
On the RTL8733B backend, switching the TSSI thermal table between the CCK and OFDM/HT variants costs 84 ms. select_tssi_rate_table runs inside send_packet, so a stream that alternates CCK and OFDM rates is capped at ~11.5 fps.
This is latent, not a live regression. select_tssi_rate_table early-returns when the rate class is unchanged, so a single-rate stream — the normal FPV/video case — pays nothing. It predates #388 and is not caused by it.
Where the 84 ms goes
Measured on the DUT (bare unbranded RTL8731BU module, 0bda:f72b cut D, USB high speed) with an instrumented build counting every BB register access and timing each stage. 60 consecutive switches, steady state:
| stage | ms | reads | writes | ops |
|---|---|---|---|---|
enable_tssi_tracking |
44.99 | 35 | 35 | 70 |
disable_tssi_tracking |
25.77 | 13 | 34 | 47 |
prepare_tssi_thermal |
13.50 | 2 | 17 | 19 |
| total | 84.26 | 50 | 86 | 136 |
There are no sleeps in the path — it is 136 USB control transfers. build/reglat on the same adapter measures a register round trip at mean 375 us / p99 407 us, and the mixed read/write traffic here averages ~620 us/op.
The structural cause: to change 16 BB dwords, the backend tears down and rebuilds the entire TSSI front-end. 32 of the 136 ops are the 16-command ANAPAR sequence (write_tssi_anapar), written out by restore_tssi_analog and written straight back by apply_tssi_analog. The channel and band do not change across a rate-table switch, so that analog state is restored only to be re-applied microseconds later.
The premise looks wrong
Rtl8733bDevice::select_tssi_rate_table says the table "cannot be changed while closed-loop tracking is enabled". The vendor's own writer disagrees: _halrf_tssi_set_tmeter_tbl_8733b (hal/phydm/halrf/rtl8733b/halrf_tssi_8733b.c in the pinned reference/rtl8733bu-20230626) writes R_0x4380 and the 16 dwords at 0x4200..0x423c directly and touches no enable bit.
Caveat, stated plainly: the vendor only ever calls it from the full _halrf_tssi_8733b setup sequence, so there is no vendor precedent for a runtime swap in either direction. The vendor picks a table once at TSSI setup from whatever phydm_get_tx_rate returns and leaves it.
The hardware accepts an in-place rewrite
Throwaway build, writing the table with tracking live and reading it back:
- 101/101 swaps readback-verified across all 16 words against the rate-specific plan.
0x4318stayed at7(tracking enabled) across every swap.- 13.77 ms / 20 ops, against 84.26 ms / 136 ops — a 6.1x reduction.
The two plans genuinely differ (8 of the 16 words), so the readback was tracking a real change rather than a no-op.
Why this is not already a PR
TSSI is closed-loop PA control. Register readback proves the write lands; it does not prove the tracking loop behaves correctly across a live table change. Validating that needs an independent witness and ideally an SDR — neither was available — and the failure mode of getting it wrong is transmitting at an uncalibrated power setting.
Suggested work
- Add a table-swap-only path to
select_tssi_rate_tablethat writes0x4380+0x4200..0x423cin place and readback-verifies, skipping the disable/enable pair. - Gate it behind a
DeviceConfigfield until validated, defaulting to the current conservative path. - Validate with a witness at both rate classes across repeated swaps, and an SDR power check either side of a swap to confirm the closed loop settles to the same output power as the teardown path.
- If it holds, make it the default and drop the comment claiming the table cannot be changed while tracking is enabled.
Separately: write_tssi_anapar is 16 dwords written twice per switch. If the in-place path lands, that cost disappears with it; if it does not, the restore/re-apply pair is worth revisiting on its own.
Reproducing
# alternating CCK/OFDM so every frame forces a table switch
sudo DEVOURER_PID=0xf72b DEVOURER_CHANNEL=6 DEVOURER_TX_RATE=1M \
DEVOURER_TX_MCS_SWEEP="1M,6M,2M,MCS0,5.5M,MCS4,11M,MCS7" \
DEVOURER_TX_MCS_STEP_MS=17 DEVOURER_TX_GAP_US=2000 \
DEVOURER_TX_FRAMES=500 ./build/txdemo500 frames takes ~45 s (~87 ms/frame). The single-rate control — same command without DEVOURER_TX_MCS_SWEEP — takes ~3.5 s (~2.6 ms/frame).
Note that on master the per-switch rtl8733b.tssi_thermal_table log cannot show whether the swap took effect: it prints words 0 and 15, which are identical in both tables. #388 changes it to report the words that differ plus a fold over all 16.
Activity
docs: record the witnessed RTL8733B delivery results
snokvist commentedon Aug 13, 2026
Validated on air — the in-place path produces the same output power
The blocker in the issue body was "needs an independent witness and ideally an SDR". A witness is now available (RTL8812AU
0bda:8812, monitor mode, channel 6), so here is the A/B.Method: one binary, the in-place path selected by env var, so the only difference is the code path. Alternating
1M,6Mso every frame forces a table switch, 200 frames per arm, arms interleaved to cancel drift. Per-frame RSSI from the witness (DEVOURER_RX_DUMP_ALL=1, filtered to the injected frames), which is a relative power proxy, not calibrated power.First pass — a 5 dB discrepancy on CCK:
Reproducible, same direction both reps, CCK only. Delivery was ~100% in all four arms with zero FCS errors, so nothing was being lost — but ~5 dB is a lot of transmit power to move, and it is exactly the failure this issue said register readback could not rule out.
It is not the path — it is settling time. The two arms do not just differ in code, they differ in cadence: 86 ms/frame versus 9 ms/frame. Re-running the in-place path paced to the same 86 ms frame period:
CCK matches the teardown path to within 0.04 on one rep and exactly on the other. So given equal settling time the two paths land on the same output power, and the earlier gap was the TSSI closed loop not having converged at 9 ms/frame.
That is a useful finding in its own right, and it is a property of the loop rather than of the swap method — the current path simply cannot switch fast enough to expose it. Any implementation should document it, and anyone reading power off a fast rate-switching run should know about it.
Where that leaves the issue
Evidence now in hand for the in-place path:
0x4318steady at7.Still missing, and still needing an SDR:
So I would not make it the default yet. Implementing it behind a
DeviceConfigfield defaulting to the current conservative path — step 2 of the plan in the issue body — is now justified by evidence, and steps 3 and 4 stay blocked on SDR access.Not closing this: the change is not implemented, and the SDR checks remain outstanding.
rtl8733b: keep the send path free of register I/O, warn on dis_cca
josephnef commentedon Aug 13, 2026
A fix for this exists but is not pushed yet — reported on #388 after the merge.
Root cause is upstream of the cost:
_halrf_tssi_set_tmeter_tbl_8733bis only ever reachable from full TSSI setup in the vendor tree, keyed onphydm_get_tx_rateat that instant, and is never re-selected at runtime. The 84 ms was paying for a mechanism that does not exist upstream. Choosing the table once per channel set and leaving it alone takes an alternating CCK/OFDM stream from 19.69 s to 2.82 s per 200 frames, with 200 switches becoming 0.Two things to carry into the PR rather than lose here:
Leaving open until the change lands.
snokvist commentedon Aug 13, 2026
#396 removes the cost from the default path: the thermal table is now chosen once per channel set instead of on every rate-class crossing, matching what the vendor actually does. Alternating CCK/OFDM drops from 19.69 s to 2.82 s per 200 frames, 200 switches to 0.
Two things that survive this and are worth keeping the record straight on, since the PR closes the issue:
The 84 ms is still real on the opt-in path.
DEVOURER_TSSI_RATE_TABLE=1pays exactly the measured cost. If that knob survives the heat-soak question in #390, the validated in-place alternative in the comment above (13.8 ms / 20 ops, 101/101 readback-verified, equal output power at equal pacing) is what makes it affordable. If the knob is deleted instead, the in-place work goes with it.Channel changes are untouched by #396 and still pay.
SetMonitorChanneltears the loop down and runs a fullconfigure_tx_powerrebuild, which is a superset of the table swap, not the same thing — the in-place trick does not help there because it is a full TSSI re-setup, not a 16-dword rewrite. Nobody has measured that path; the 84 ms figure here is the table switch only, so do not quote it for retune cost.Happy for this to close on merge, but if you would rather keep it open scoped to the channel-change path, that is a defensible read too.
rtl8733b: delete the TSSI rate-table knob — it cannot do anything
rtl8733b: keep the send path free of register I/O, scope the TSSI rat…
rtl8733b: port FastRetune — intra-band hop with TSSI tracking kept li…
rtl8733b: runtime TX-power offset on the TSSI target (#399)