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.
0x4318 stayed at 7 (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_table that writes 0x4380 + 0x4200..0x423c in place and readback-verifies, skipping the disable/enable pair.
- Gate it behind a
DeviceConfig field 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/txdemo
500 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.
Summary
On the RTL8733B backend, switching the TSSI thermal table between the CCK and OFDM/HT variants costs 84 ms.
select_tssi_rate_tableruns insidesend_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_tableearly-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:f72bcut D, USB high speed) with an instrumented build counting every BB register access and timing each stage. 60 consecutive switches, steady state:enable_tssi_trackingdisable_tssi_trackingprepare_tssi_thermalThere are no sleeps in the path — it is 136 USB control transfers.
build/reglaton 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 byrestore_tssi_analogand written straight back byapply_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_tablesays 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.cin the pinnedreference/rtl8733bu-20230626) writesR_0x4380and the 16 dwords at0x4200..0x423cdirectly and touches no enable bit.Caveat, stated plainly: the vendor only ever calls it from the full
_halrf_tssi_8733bsetup sequence, so there is no vendor precedent for a runtime swap in either direction. The vendor picks a table once at TSSI setup from whateverphydm_get_tx_ratereturns and leaves it.The hardware accepts an in-place rewrite
Throwaway build, writing the table with tracking live and reading it back:
0x4318stayed at7(tracking enabled) across every swap.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
select_tssi_rate_tablethat writes0x4380+0x4200..0x423cin place and readback-verifies, skipping the disable/enable pair.DeviceConfigfield until validated, defaulting to the current conservative path.Separately:
write_tssi_anaparis 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
500 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
masterthe per-switchrtl8733b.tssi_thermal_tablelog 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.