Repository navigation
kestrel: 80 MHz on the 8852C and 8852B — RX width, TX sub-carrier, fast same-block hop - #494
Conversation
The 8852C RX descriptor widens the bandwidth field to three bits, dword1 [31:29] (rtw89 AX_RXD_BW_v1_MASK, selected for RTL8852C in rtw89_core_query_rxdesc); the 8852B keeps [31:30]. parse_rx_8852b read [31:30] on both dies, so on the 8852C every width came out halved: an 80 MHz PPDU reported bw=1 (40 MHz) and a 40 MHz one bw=0 (20 MHz). The die is told apart by the 16-byte drv_info unit the parser already takes. - Measured on the 8852C (TX50UH) on ch149 at 80 MHz, an iPhone sending a 437 MB file: before, every frame read 40 MHz while the delivered rate (~45 MB/s at VHT 2SS MCS7-9 in ~46-MPDU A-MPDUs) needed more than the whole channel's airtime at 40 MHz; after, the same transfer reads 80 MHz on all 309k data frames. - Only RxAtrib.bw changes: the library copies it to the caller and branches on it nowhere; rxdemo's rx events report it, so on the 8852C their "bw" now reads 2 for 80 MHz where it read 1. The field can carry the 3-bit codes above 3 that the wider layout allows.
set_channel tuned the BB and RF to a wide channel but never told the
MAC: R_AX_WMAC_RFMOD (0xC010) stayed at 20 MHz and
R_AX_TX_SUB_CARRIER_VALUE (0xC088) at the zeros cmac_com_init writes.
A PPDU narrower than the channel -- a 20 MHz frame on an 80 MHz channel
-- then airs at TXSC 0, the block center, where nothing listening on
the primary hears it. mac_set_bw_txsc() now writes both after every
tune, as rtw89 does in rtw8852c_set_channel_mac: RFMOD 0/1/2/3 for
20/40/80/160, and the whole sub-carrier register from the 20/40/80 MHz
indices of a line-for-line port of rtw89_phy_get_txsc (primary vs
center), zero at 20 MHz.
The existing 40/80/160 MHz TX validation injected PPDUs as wide as the
channel, which do not use the sub-carrier index, so it could not see
this.
- Measured on the 8852C (TX50UH) tuned to ch149 at 80 MHz (center 155),
sending 20 MHz frames to an iPhone listening on 149: without the
writes the iPhone never discovered us (its frames reached us fine);
with them it did and transfers ran.
- On-air A/B on both dies: txdemo (500 6M canonical beacons, EDCCA gate
off) from a DUT tuned wide, rxdemo at 20 MHz on the primary on the
other adapter, counting rx.txhit. Before / after, hits of 500:
DUT channel/width primary TXSC before after
8852C 149/40 (151) lower 0x002 0 400+
8852C 153/40 (151) upper 0x001 0 400+
8852C 149/80 (155) idx 4 0x0a4 0 400+
8852C 161/80 (155) idx 3 0x093 0 400+
8852C 36/160 (50) idx 8 0xec8 0 500
8852B 149/40, 153/40, 149/80, 161/80 0 400+ each
both 149/20 control 0x000 400+ 400+
(rxdemo reports every 100th hit, so 400+ is 400-499.) The TXSC
values are what rtw89_phy_get_txsc gives for each primary/center.
- Band 0 only, like the rest of this HAL. 20 MHz writes RFMOD 0 and
zero indices, the values it had before.
A FastRetune at 80 MHz always fell back to the full SetMonitorChannel
(BB bandwidth config, RF tune, and on the 8852C the IQK): ~213 ms a hop
on the 8852C, ~145 ms on the 8852B. A hop between two channels of the
same 80 MHz block (149 <-> 161, both in 149-161 / center 155) moves no
RF at all, only which 20 MHz is primary. fast_set_primary_80() rewrites
just what halbb_ctrl_bw_ch_8852{b,c} derives from the primary at 80 MHz
-- the [11:8] sub-band index (0x4978 on the C, 0x49c4 on the B) and the
5 MHz edge mask (halbb_5m_mask_8852{b,c}) -- plus the MAC TX
sub-carrier, then resets the BB. FastRetune takes that path when the
width is 80 MHz, the band is 5 GHz and the target is in the current
block; everything else is unchanged.
- Hop time: median 2.2 ms on the 8852C (max 2.5 ms over 115 hops) vs
~213 ms full; median 18 ms on the 8852B vs ~145 ms full (its USB
register round-trips are the slower ones, as for its 20 MHz hop).
- iPhone over AWDL, schedule alternating 149/161, on the 8852C: full
path 206 hops, ~44 s deaf in a 49 s transfer (49.5 MB, ~1 MB/s);
fast path, nothing else changed, 50.8 MB in 7.5 s (~6.8 MB/s).
- txdemo alternating 149/161 at 80 MHz (50 frames a dwell, 5 rounds,
250 frames on 161), rxdemo at 20 MHz on 161: 200+ of 250 on both
dies, with DEVOURER_HOP_FAST=1 and with the full path alike (rxdemo
reports every 100th hit).
- Register parity against the full path, per die: BB 0x0000-0xFFFF (RF
direct window included) and CMAC 0xC000-0xCFFF dumped after 3 full
tunes of each primary and after 12 fast hops, for the pairs 149/161,
149/153 and 157/161 (both 5 MHz mask branches). Every register the
full path sets per primary matched on all 36 hops of each die. The
other differences either also differ between two full tunes of the
same channel (SW-SI counters and records, RX counters, AFE status
outputs, the NCTL/IQK engine status the skipped IQK leaves from the
previous tune) or showed on one hop of six (a few CMAC status words
undocumented in rtw89); a write the fast path missed would show on
every hop.
- The fast hop skips the IQK the full path re-runs and relies on the
RF center being unchanged; MAC retries stayed at 2-4% over the iPhone
sessions (one adapter, one peer, near field).
- 80 MHz only, 5 GHz only. At 40 MHz FastRetune keeps the tune's
primary offset, so a hop moves the block (center = channel +/- 2) and
there is no same-block case to shortcut. The 5 MHz mask uses the
0x4 value of both dies; the 8852D variant's 0x5 is not handled.
PR Summary by QodoFix Kestrel wide-channel RX, primary-channel TX, and 80 MHz hops
AI Description
Diagram
High-Level Assessment
Files changed (7)
|
Code Review by Qodo
1.
|
FastSetBandwidth took the lean 20 <-> 5/10 MHz toggle whenever the target was narrow, whatever the current width. From a 40/80/160 MHz channel that path only rewrites the BB small-BW field: the RF stays on the block center at the wide RF bw, and with set_channel now programming the MAC width (RFMOD + TX sub-carrier) the MAC stays wide too, and no later FastRetune repairs either. The lean path now needs both ends narrow; leaving a wide channel takes the full SetMonitorChannel, as entering one already did. - Measured with FastSetBandwidth(20) from 149/80, compared with a full SetMonitorChannel(149/20) over BB 0x0000-0xFFFF and CMAC 0xC000-0xCFFF: before, RFMOD 2 vs 0, TXSC 0x0a4 vs 0, RF18 0x1059b (ch 155, 80 MHz) vs 0x10d95 (ch 149, 20 MHz), 43 registers differing on the 8852C and 36 on the 8852B; after, RFMOD, TXSC and RF18 match on both dies. The 3 (C) / 7 (B) registers still differing are the ones that differ between any two full tunes of a channel, which is what the comparison now is. - The RF part predates the MAC writes: the lean path never re-tuned.
fast_set_bw wrote RF_BW and the small-BW field at 0x49C0/0x49C4, the 8852B's addresses (halbb_ctrl_bw_8852b), on both dies. halbb_ctrl_bw_8852c keeps them at 0x4974[31:30] and 0x4978[13:12], so on the 8852C the lean narrowband toggle never changed the bandwidth: the BB stayed at the previous width while the device recorded the new one. The ACI-detect bits (0x4738/0x4AA4[16]) are shared and unchanged. - Measured with FastSetBandwidth(10), (5), then (20) from a full 149/20 tune, each compared with full SetMonitorChannel(149/10|5|20) tunes: before, 8852C 0x4978 read 0x0003 after the 10 MHz toggle where a full tune leaves 0x2003, and 0x2003 after the 5 MHz one where it leaves 0x1003; after, 0x4978 matches on every step. The 8852B, whose addresses were right, is unchanged.
josephnef
left a comment
There was a problem hiding this comment.
Reviewed hunk by hunk against the vendored halbb and the vendor mac_ax, and reproduced the on-air A/B on an independent pair of TX50UH (8832CU) units. No correctness bugs found. Approving, with three notes below.
Source cross-check (all match):
phy_get_txscis line-for-linehalbb_get_txsc(every cbw/dbw branch incl. the 0xff case, whichfield()masks to 4 bits).fast_set_primary_80's index formula and the 5 MHz mask writes (0x3f=0x4, [12], [8], [6], 0x4450[24] / 0x4440[31]) matchhalbb_ctrl_bw_ch_8852{b,c}+halbb_5m_mask_8852{b,c}for the 80 MHz case;block()inFastRetuneis exactlyset_channel's 5 GHz 80 MHz center formula.fast_set_bw's 8852C addresses (0x4974 RF_BW, 0x4978 small-BW) matchhalbb_ctrl_bw_8852c. That commit fixes a real pre-existing bug (the C was being written at the B's 0x49C0/0x49C4) and is not in the PR description — worth a line there, since the body still says three commits and the branch has five.AX_RXD_BW_v1_{SH,MSK}(29 / 0x7) is in both vendorrxdesc.hfiles.
On-air, two 8832CU on this bench (both USB 2.0, bus 3 TX / bus 9 RX), 500 canonical 6M/20 beacons per cell, DEVOURER_CCA_GATES=0,1, witness at 20 MHz on the primary:
| cell | txsc logged | master | this branch |
|---|---|---|---|
| 149/20 control (start / end) | — | 408 / 404 | 324 / 500 |
| 149/40 (center 151) | 0x002 | 0 | 329 |
| 153/40 upper (center 151) | 0x001 | 0 | 316 |
| 149/80 (center 155) | 0x0a4 | 0 | 308 |
| 161/80 (center 155) | 0x093 | 0 | 297 |
| 36/160 (center 50) | 0xec8 | 0 | 345 |
| hop 149/161 at 80, 50/dwell, witness on 161 | both | 0 | 202 |
149/80, VHT1SS_MCS3/80, witness at 80 — decoded bw |
0x0a4 | 66 hits, bw=1 | 58 hits, bw=2 |
The wide cells sit inside the control's own 324..500 band, so they are at parity with 20 MHz here as in your table. Hop switch_us over 10 dwells: master (full path) median 588 ms on this host, this branch median 3.2 ms (max 3.4). 8852B was not on the bench, so the B-side numbers are yours alone.
Notes (none blocking):
mac_set_bw_txsclogs atinfoon every call, andfast_set_primary_80calls it per hop — the hop cell above wrote 10 lines for 10 hops. At the slot cadence this hop is for (DEVOURER_HOP_SLOT_MS10 ms) that is ~100 flushed stderr lines/s at the demos' default level, where the existingfast_retunelogs atdebug. Suggestdebug, or log only from theset_channelcall site.- The vendor
mac_set_bwwritesR_AX_TXRATE_CHK(0xC628, mask 0x13:BAND_MODEon 2.4 GHz;CHECK_CCK_EN | RTS_LIMIT_IN_OFDM6on 5/6 GHz) in the same step as RFMOD + TXSC, and nothing insrc/kestrel/writes it. Pre-existing, no measured failure; the plausible consumer is the MAC-chosen response rate of the hardware ACK responder. Either port it alongside or note it as out of scope in the CLAUDE.md paragraph. - Same-block detection relies on
_channel.Channeltracking; a caller that callsSetMonitorChannelwith a channel outside the 80 MHz plan (e.g. 165/80) getsblock()= 165, center 171, consistent withset_channel's own extrapolation — fine, just noting it was checked.
In short
On the Kestrel chips, an 80 MHz channel did not really work for talking to another device. Three separate problems, one per commit (the first is 8852C-only, the other two apply to the 8852C and the 8852B alike):
Found and tested with an iPhone sending files over AWDL (AirDrop) to a TP-Link TX50UH. Before, the iPhone could not see the adapter at 80 MHz at all. With fix 2 it could, but transfers stalled at every 149/161 hop (49.5 MB in 49 s); with fix 3, 50.8 MB took 7.5 s with nothing else changed. (With further work on the AirDrop side, outside devourer, a 437 MB file now arrives in about 8 s on these three commits.)
Fix 2 was also checked on air on both chips with two adapters, one sending and one listening on the main channel: on master, none of the frames sent at 40, 80 or 160 MHz arrived (0 of 500 in every case, on the 8852C and the 8852B); with the fix, 400+ of 500 in every case, the same as the 20 MHz control.
Fix 3 was checked the same way on both chips: comparing every register against the slow path, and on air (frames sent after a fast hop arrive on the new main channel just like after a slow one). It applies at 80 MHz on 5 GHz; at 40 MHz there is no same-block hop to shortcut (a hop there moves the block), and every other hop keeps the existing path.
Technical detail
1.
kestrel: read the 8852C RX bandwidth from dword1 [31:29]The 8852C RX descriptor widens the bandwidth field to dword1 [31:29] (rtw89
AX_RXD_BW_v1_MASK, chosen for RTL8852C inrtw89_core_query_rxdesc); the 8852B keeps [31:30].parse_rx_8852bread [31:30] on both dies, so on the 8852C 80 MHz read as 1 and 40 MHz as 0. The die is told apart by the 16-byte drv_info unit the parser already receives.Only
RxAtrib.bwchanges. The library branches on it nowhere; rxdemo's rx events report it, so on the 8852C theirbwnow reads 2 for 80 MHz where it read 1.Measured: an iPhone sending a 437 MB file on ch149 at 80 MHz. Before, every frame read 40 MHz while the delivered rate (~45 MB/s at VHT 2SS MCS7-9 in ~46-MPDU A-MPDUs) needed more airtime than a 40 MHz channel has; after, all 309k data frames read 80 MHz.
2.
kestrel: program RFMOD and the TX sub-carrier for 40/80/160 MHzset_channeltuned the BB and RF to a wide channel but left the MAC at 20 MHz:R_AX_WMAC_RFMOD(0xC010) unwritten andR_AX_TX_SUB_CARRIER_VALUE(0xC088) at the zeroscmac_com_initwrites. A PPDU narrower than the channel then airs at sub-carrier index 0, the block center.mac_set_bw_txsc()now writes both after every tune, asrtw8852c_set_channel_macdoes: RFMOD 0/1/2/3 for 20/40/80/160, and the whole sub-carrier register from a line-for-line port ofrtw89_phy_get_txsc. Primary 149 / center 155 gives TXSC20 4 and TXSC40 10 (0x0a4), primary 161 gives 0x093, as rtw89 computes.The existing 40/80/160 MHz TX validation injected PPDUs as wide as the channel, which do not use the sub-carrier index, so it could not see this.
Measured on the TX50UH tuned to 149/80: without the writes the iPhone, listening on 149, never discovered the adapter (its own frames reached us fine); with them it did, and transfers ran.
On-air A/B on both dies, no iPhone: txdemo (500 canonical 6M beacons,
DEVOURER_CCA_GATES=0,1) from a DUT tuned wide withDEVOURER_HOP_BW, rxdemo on the other adapter at 20 MHz on the primary, countingrx.txhit. Same txdemo source at upstream master (before) and at this branch (after); TX50UH (8852C) and TX20U Nano (8852B), swapping roles.rxdemo reports
rx.txhitfor the first 10 hits and then every 100th, so 400+ means 400-499 of 500. The TXSC values are whatrtw89_phy_get_txscgives for each primary and center. Band 0 only, like the rest of the HAL; 20 MHz writes RFMOD 0 and a zero register, the values it had before.3.
kestrel: fast retune within one 80 MHz blockA
FastRetuneat 80 MHz fell back to the fullSetMonitorChannel(BB bandwidth config, RF tune, and on the 8852C the IQK). For a target in the same 80 MHz block on 5 GHz,FastRetunenow callsfast_set_primary_80(), which rewrites only whathalbb_ctrl_bw_ch_8852{b,c}derives from the primary at 80 MHz plus the MAC sub-carrier, then resets the BB. The registers are each die's own: the [11:8] sub-band index at 0x4978 (C) / 0x49c4 (B), and thehalbb_5m_mask_8852{b,c}edge mask at 0x4c4c/0x4d10/0x4450[24] (C) / 0x46f8/0x47b8/0x4440[31] (B). Everything else keeps its existing path.Register parity against the full path, on each die: BB 0x0000-0xFFFF (RF direct window included) and CMAC 0xC000-0xCFFF dumped after 3 full tunes of each primary and after 12 fast hops, for the pairs 149/161, 149/153 and 157/161 (both branches of the 5 MHz mask). Every register the full path sets per primary matched on all 36 hops of each die. On the 8852C:
On the 8852B the same pattern at its own addresses (0x49c4 = 0x4403/0x4203/0x4103/0x4303, the mask at 0x46f8/0x47b8/0x4440, plus their 0x69c4/0x6440 mirrors and 0x1b44, which the full path also moves per primary), and MAC 0xC088 as above.
The other differences either also differ between two full tunes of the same channel (SW-SI counters and records at 0x0370/0x0378/0x174c-0x1764, RX counters 0x1a14-0x1a80/0x1b10-0x1b34/0x3c94, the AFE status outputs 0x1cb0/0x3cb0, and NCTL/IQK engine status at 0xb1fc/0xbff4/0xbffc that the skipped IQK leaves from the previous tune), or showed on one hop of six (CMAC 0xC5B0/0xC5B4 on the C, 0xCCF8/0xCEF0 on the B, undocumented in rtw89). A write the fast path missed would show on every hop.
Limits: 80 MHz and 5 GHz only. At 40 MHz
FastRetunekeeps the tune's primary offset, so a hop moves the block (center = channel +/- 2) and there is no same-block case; making it flip the offset instead would change whatFastRetunedoes for existing 40 MHz hoppers. The 5 MHz mask uses the 0x4 value of both dies; the 8852D variant's 0x5 is not handled. The fast hop skips the IQK the full path re-runs; MAC retries stayed at 2-4% over the iPhone sessions (one adapter, one peer, near field), and the shortcut relies on the RF center not moving.Docs
src/kestrel/CLAUDE.md: the MAC side of the width in the validated surface, the RX bandwidth field in the 8852C/8852B divergences, and the 80 MHz same-block hop in the FastRetune section.docs/frequency-hopping.md: a paragraph on the 80 MHz shortcut.Build
Each commit builds on its own (MSVC, target
devourer).