TX power: per-rate diff table on every generation (#332) - #347
Merged
Conversation
SetTxPowerRateDiffs was wired for the 8822E only; every other generation returned false. That was an artificial gap — Jaguar1 and Jaguar2 already compute a per-rate TXAGC index in software, the Jaguar3 diff-table block is shared by both dies (the routine devourer ports is rtw8822c_set_write_tx_power _ref), and Kestrel already rewrites its BB power target between frames. One contract everywhere: the caller table REPLACES the calibrated per-rate shape — rate level = anchor + its diff (quantized to the family step) + the runtime offset, clamped to the rail, where the anchor is the level the chip's own default walk produces for HT MCS7. A zero table is therefore a no-op at MCS7 and flattens every other rate onto it; rates the struct does not describe (MCS8+, VHT/HE, 2SS+) sit at the anchor. Diffs are not regulatory-clamped, the same operator's call the offset and flat-override knobs already make. Shared surface stays small: rate_diff_steps / rate_diff_qdb_for_rate / clamp_rate_diffs in src/TxPower, each HAL keeping its own register write. The rail, the saturation plumbing and the anchor derivation are family-specific, so no "anchor + diff + offset + clamp" composer is worth the abstraction. Per generation: - Jaguar1: one branch in ComputeTxPowerIndex, which every per-rate index already funnels through, so the 8812A/8821A register fanout and the 8814A packed port both work unchanged and the table is sticky by construction. A per-apply anchor cache keeps a 4-path 8814A walk at 4 EFUSE lookups. - Jaguar2: write_txagc_diffs over the 0x1d00/0x1d80 block, anchored on the HT 1SS section index taken pre-offset and pre-rail (clamping first would rebuild the shape off a saturated base). The table joins the SetMonitorChannel / FastRetune re-apply gates, without which a channel change silently reverts to the calibrated shape. An unprogrammed EFUSE has no anchor: that path warns and leaves TXAGC alone rather than flattening to zero. - Jaguar3: the 8822E-only compile guard around apply_rate_diffs was vestigial, and the 8822C reaches the same refs + 0x3a00 table with its flat reference as the anchor. Its no-table path is unchanged, so the SDR-validated CU power is bit-identical. - Kestrel: no per-rate TXAGC table exists under the fixed-dBm model, so the diff for the frame's own rate folds into the target in send_packet, reusing the existing write-on-change guard. Stored as a flat atomic array rather than the struct so the TX path reads one entry without a lock. Two consequences are documented rather than papered over: HE/VHT frames carry no diff, and the target is global, so a hardware-timed beacon airing between host frames inherits the last frame's level. GetTxPowerCaps().rate_diffs_hw_table separates that software fold from a hardware table. The knob was unreachable from txdemo, i.e. unusable from the TX path it exists for; DEVOURER_TX_RATE_DIFFS maps it, range-checking before the int8_t cast that would otherwise wrap a boost request into a cut. Validation. Register/shadow: txpwr_rate_diffs_regcheck.sh now walks every DUT, gated on the device's own caps advertisement instead of a PID list, asserting the delta in family steps and the readback class each part can actually back — a write-only family proves state management, not radiated power, and the script says so. On-air: txpwr_rate_diffs_onair.sh trims MCS0 only and checks 6M does not move, which is what separates a per-rate lever from a global one. Measured against a ground-station RSSI reference (an SDR front end hard-limits at this geometry), -32 qdB on MCS0: 8812AU -8.0, 8821AU -7.0 at 2.4 GHz, 8814AU -8.0, 8822BU -9.0, 8822CU -8.0, 8822EU -9.0, 8852BU -7.0, 8832CU -8.0 dB against -8.0 nominal, 6M unmoved on all eight. rate_diffs_measured stays false on the 8822E, whose TSSI-reshaped transfer means its cell can only assert sign — the same split step_measured already makes. No-regression: txpwr_offset_regcheck.sh 42/42 with canary parity byte-identical to master on all three 11ac generations, including the Jaguar2 per-section TXAGC log parity cell that covers the lambda split here. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #332.
SetTxPowerRateDiffswas wired for the 8822E only; every other generation returnedfalse. That was an artificial gap — Jaguar1 and Jaguar2 already compute a per-rate TXAGC index in software, the Jaguar3 diff-table block is shared by both dies (the routine devourer ports isrtw8822c_set_write_tx_power_ref), and Kestrel already rewrites its BB power target between frames.The contract, everywhere
So a zero table is a no-op at MCS7 and flattens every other rate onto it; rates the struct doesn't describe (MCS8+, VHT/HE, 2SS+) sit at the anchor. Diffs are not regulatory-clamped — the same operator's call the other two knobs make. Resolution is the family step, so 0.5 dB on Jaguar1/Jaguar2: an odd qdB rounds, and the setters log the applied step count.
Per generation
ComputeTxPowerIndex, which every per-rate index already funnels through — the 8812A/8821A fanout and the 8814A packed port work unchanged, and the table is sticky by construction. Per-apply anchor cache keeps a 4-path 8814A walk at 4 EFUSE lookups.write_txagc_diffsover0x1d00/0x1d80, anchored on the HT 1SS index taken pre-offset and pre-rail. Added to theSetMonitorChannel/FastRetunere-apply gates — without that a channel change silently reverts to the calibrated shape.apply_rate_diffswas vestigial; the 8822C reaches the same refs +0x3a00table with its flat reference as the anchor. Its no-table path is untouched, so the SDR-validated CU power is bit-identical.send_packet, reusing the existing write-on-change guard. Flat atomic array (not the struct) so the TX path reads one entry lock-free.GetTxPowerCaps().rate_diffs_hw_tableseparates Kestrel's software fold from a hardware table — different cost and different reach. Two Kestrel consequences are documented rather than papered over: HE/VHT frames carry no diff, and the target is global, so a hardware-timed beacon airing between host frames inherits the last frame's level.Also: the knob was unreachable from
txdemo, i.e. unusable from the TX path it exists for.DEVOURER_TX_RATE_DIFFSmaps it, range-checking before theint8_tcast that would otherwise wrap a boost request into a cut.Validation
Register/shadow —
tests/txpwr_rate_diffs_regcheck.shnow walks every DUT, gated on the device's own caps advertisement instead of a PID list, asserting the delta in family steps and the readback class each part can actually back. A write-only family proves state management, not radiated power, and the script says so. 50/50 across 8 DUTs.On-air —
tests/txpwr_rate_diffs_onair.shtrims MCS0 only and checks 6M does not move, which is what separates a per-rate lever from a global one. Ground-station RSSI reference (an SDR front end hard-limits at this geometry). −32 qdB on MCS0, −8.0 dB nominal:Plus a register-exact check on the 8812AU:
BB 0xc2cbyte 0 goes0x16→0x0E(anchor 22 → 14, exactly −8 steps) with MCS1..3 untouched.rate_diffs_measuredstays false on the 8822E, whose TSSI-reshaped transfer means its cell can only assert sign — the same splitstep_measuredalready makes.No-regression —
tests/txpwr_offset_regcheck.sh42/42 with canary parity byte-identical to master on all three 11ac generations, including the Jaguar2 per-section TXAGC log parity cell that covers the lambda split here.ctest47/47; all 7 per-chip subset build configs green.🤖 Generated with Claude Code