Skip to content

Commit 4e896fd

Browse files
authored
kestrel: hardware ACK responder on the 8852C (#489)
## The problem On the 8852C, `SetAckResponder` refused: the firmware ignores the addr_cam H2C altogether. After it, neither the address CAM entry nor the BSSID CAM entry it carries is written, and nothing changed that: a byte-for-byte replay of the vendor Windows driver's association H2Cs (CMAC table, DMAC table, join_info, addr_cam), a join_info alone, and a 64-byte addr_cam. The same replay commits on the 8852B. ## The change - On the 8852C the arm writes the station entry from the host (`HalKestrel::write_addr_cam_sta`), the way the vendor's `mac_upd_addr_cam` does when the firmware is not running: VALID cleared first (a retarget rewrites a live entry), then the body, then VALID. No H2C is sent, and the clear is the host clear alone. The 8852B path is unchanged. - The address CAM stride is per die (`addr_cam_entry_addr`), as the vendor's `get_addr_cam_size` lays it out: 0x40 on the 8852B, 0x20 on the 8852C, 128 entries on both. `HalKestrel` used 0x40 on both before; it only mattered on the 8852C. - The responder's slot is per die (`ack_slot()`): 127 on the 8852B, as before, and 63 on the 8852C. `RegisterPeerSta` / `RegisterBeamformee` keep refusing it, and now also refuse an ADDR_CAM index past the 128-entry CAM. - The station entry encoding moves to `KestrelAddrCam.h`, shared by the 8852B's addr_cam H2C (the payload is byte-identical to before) and the 8852C's host write. A new headless selftest, `kestrel_addr_cam`, pins it against hardware read-backs: an entry the 8852B firmware committed, and one the host wrote on an 8852C. - The response-abort gate clear is mandatory on this die: its EDCCA reads its own floor as busy, and with the gates armed every ACK was dropped (0/2060, 0/2019). - The host write reports a failed register transfer (it stops before setting VALID again), and the arm on the 8852C is confirmed by reading the whole entry back, every dword the hardware backs (dword 5 reads `0xdeadbeef` on this die whatever is written), not only VALID and the address. - `ack_responder_ok` is true on the 8852B only (`== C8852B`) and false on the 8852C: a dead session reads its entry back like a good one, so the flag could not tell a caller the loop closes. `SetAckResponder` still arms the 8852C (below). - rxdemo's disarm message, `AdapterCaps.h` and `docs/kestrel-ack-responder.md` are updated. ## Measured TX50UH (RTL8832CU) as responder, TX20U Nano (RTL8852BU) soliciting and counting the ACKs it receives, ch161, MCS1, one attempt per frame, 10 s per cell, 3 runs per row. This matrix was taken with the entry at hardware entry 126 (the 0x40-stride layout); the re-measurement at entry 63 follows it. | row | ACKs | per run | |---|---|---| | armed at Init (rxdemo) | 13831/14041 | 99.3 / 96.6 / 99.5 | | armed at InitWrite (txdemo) | 9439/9639 (two live runs) | 98.9 / dead session / 97.0 | | retargeted (second MAC) | 14239/14408 | 98.9 / 99.0 / 98.6 | | old address after the retarget | 0/5993 | 0 / 0 / 0 | | never armed | 0/6062 | 0 / 0 / 0 | Arm A, retarget to B, clear, re-arm A in one session: correct in the live run (A 98.3%, then A 0% / B 99.0%, both 0% after the clear, A 99.0% after the re-arm); the other two runs were dead sessions. Injection delivery to a witness is unchanged armed vs unarmed. The long RX descriptor shows every frame addressed to the responder matching the host-written entry (macid 63). At hardware entry 63 (8 s per cell): live disarm with the solicitor running across it in one session, 3812/3837 then 0/1616 and 3846/3848 then 0/1623 (a third cell was a dead session); 15 armed sessions back to back, 9 answering 97.5-99.9% and 6 dead. 8852B regression on this code (0x40 stride, unchanged): 3 sessions 99.8-99.9%, live disarm across the solicitor 3842/3848 then 0/1773. Earlier, at the first revision: 99.5-99.8% armed and retargeted, 0 after a live disarm, the arm/retarget/clear/re-arm cycle correct, still on slot 127. Full MSVC build and `ctest` pass (79 tests on Windows). ## What this does not solve Some 8852C bring-ups answer no ACK at all: the responder arms, the entry reads back, the adapter airs its own frames, and it answers nothing for the whole session. How often varies, and no cause is established: 3 of 12 and 6 of 15 sessions here, and on the maintainer's bench 8 of 21 at entry 126 and 1 of 16 at entry 63. Dead sessions come interleaved with good ones, a VBUS cold cycle before every session lowered the rate but did not remove it, and the thermal meter stayed flat, so it is not read as thermal or uptime-related. In failed sessions the MAC configuration registers are identical to a good session's, and the response abort counters stay at 0 (the response is never attempted). What does differ is that the MAC classifies the soliciting frames as its own BSS's traffic (`R_AX_WMAC_NAV_UP_INFO` records intra-BSS NAV updates, `R_AX_PS_RXINFO` flags port-0 data). Ruled out by measurement: the response NAV checks, the BSSID CAM, the injector, the host's per-frame RX cost, and arm timing. The doc lists what was measured. I don't have the cause yet. One bench, one pair of dongles, near field.
1 parent 7b25f65 commit 4e896fd

11 files changed

Lines changed: 415 additions & 107 deletions

‎CMakeLists.txt‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -923,6 +923,16 @@ target_link_libraries(ToneMaskSelftest PRIVATE devourer)
923923

924924
add_test(NAME tone_mask_math COMMAND ToneMaskSelftest)
925925

926+
# Headless guard for the Kestrel station address-CAM encoder shared by the
927+
# 8852B's addr_cam H2C and the 8852C's host write; expected words are
928+
# hardware read-backs (src/kestrel/KestrelAddrCam.h).
929+
add_executable(KestrelAddrCamSelftest
930+
tests/kestrel_addr_cam_selftest.cpp
931+
)
932+
target_link_libraries(KestrelAddrCamSelftest PRIVATE devourer)
933+
934+
add_test(NAME kestrel_addr_cam COMMAND KestrelAddrCamSelftest)
935+
926936
# Headless guard for the radio contract: a radio implementing only IRadio's
927937
# pure-virtual core must compile without any Realtek type, its not-ported
928938
# defaults must hold, and it must not be an IRtlRadio. A Realtek-typed pure

‎docs/kestrel-ack-responder.md‎

Lines changed: 103 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# Hardware ACK responder on Kestrel (RTL8852BU)
1+
# Hardware ACK responder on Kestrel (RTL8852BU, RTL8832CU)
22

3-
How `SetAckResponder` arms the AX MAC's SIFS ACK on the 8852B, what was
4-
measured to be needed, and what is still open. The bench numbers are the
3+
How `SetAckResponder` arms the AX MAC's SIFS ACK on both Kestrel dies, what
4+
was measured to be needed, and what is still open. The bench numbers are the
55
`ack_responder_ok` row in `src/AdapterCaps.h`.
66

77
## Evidence behind the mechanism
@@ -26,32 +26,118 @@ was measured to get there.
2626
On ch149, shared with a neighbour AP, one of three runs without the clear
2727
dropped to 82.4% single-shot; three with it stayed at >= 99.6%. On a quiet
2828
ch161 it made no difference.
29-
- **Own macid.** The station sits at macid 127 / ADDR_CAM entry 127 (the last
30-
entry of the 0x7f search range), apart from the injection context at macid 0
31-
/ entry 0 and from the low slots peer registrations take (`RegisterPeerSta`
32-
callers use 1, 2, ...; it and `RegisterBeamformee` refuse 127). The
33-
firmware commits entry 127 in the same ~2.2 ms as entry 1 or 0. With the
34-
station at entry 1 during development, entry 0 read back byte-identical
35-
before the arm, after it and after the clear. Sharing entry 0 (the previous
36-
layout) had no measurable cost: a witness counted the same >= 2900 of 3000
37-
injected frames armed and unarmed, on both layouts. The separation is for
38-
the injection context's integrity, not a delivery fix.
29+
- **Own slot.** The slot itself is documented at `ack_slot()`. Measured: the
30+
8852B firmware commits entry 127 in the same ~2.2 ms as entry 1 or 0. The
31+
CAM has 128 entries on both dies, laid out at a per-die stride (the vendor's
32+
get_addr_cam_size): 0x40 on the 8852B, 0x20 on the 8852C. An earlier
33+
revision used the 8852B's stride on both, so its 8852C "entry 63" was
34+
hardware entry 126 and the 0xdeadbeef it read "from entry 64 up" was the
35+
end of the 128-entry CAM. Dword 5 of an 8852C entry reads 0xdeadbeef
36+
whatever is written (the other seven read back as written), so the host's
37+
read-back skips it. With the station at entry 1 during development, entry 0 read back
38+
byte-identical before the arm, after it and after the clear. Sharing entry 0
39+
(the previous layout) had no measurable cost: a witness counted the same >=
40+
2900 of 3000 injected frames armed and unarmed, on both layouts.
3941
- **Unexplained cell.** In about 55 armed cells across development (station at
4042
entry 1), one read the entry back committed (after 10 ms rather than ~2.2
4143
ms) and answered no ACKs; other slow commits were fine and it has not
42-
reproduced.
44+
reproduced. It has the shape of the 8852C bring-ups described below, much
45+
rarer.
4346
- **Clear needs the host.** Measured at entry 1: after `join_info(disconnect)`
4447
and a NO_LINK addr_cam, the entry stays VALID with the responder's address;
4548
a role REMOVE, or role CREATE plus NO_LINK, leaves it valid too. Clearing
4649
VALID from the host takes, and re-read for 2 s it stays clear. Because those
4750
H2Cs land asynchronously, the clear repeats until the entry has read back
4851
empty for 50 ms, and only that counts as disarmed. rxdemo's
4952
`DEVOURER_ACK_DISARM_AFTER_MS` exercises it live.
50-
- **8852C:** its firmware accepts both commands and leaves the entry invalid,
51-
so the arm refuses. The vendor's 8852C association sends far more before the
52-
addr_cam (BB/RF offload, port configuration); which of it the C needs is open.
53+
- **8852C:** see the next two sections.
5354
- Measured near field (two dongles, injector at -15 dB), single-shot, no CCX:
5455
the numbers are in `AdapterCaps.h`. The responder's own transmissions are
5556
not part of that matrix: with carrier sense off (this die's TX default) a
5657
responder that also sends unicast loses some ACKs to its own queued frames
5758
(the response scheduler abort counter at 0xCC18[23:16] moves).
59+
60+
## 8852C: the host writes the entry
61+
62+
The 8852C firmware ignores the addr_cam H2C altogether. After it, neither the
63+
address CAM entry nor the BSSID CAM entry it carries is written: the address
64+
CAM still holds the uninitialised SRAM it powered up with, and the BSSID CAM
65+
stays zero. Nothing changed that, including a byte-for-byte replay of the
66+
vendor Windows driver's four association H2Cs (CMAC table, DMAC table, a
67+
12-byte join_info, addr_cam), a 12-byte join_info alone, and a 64-byte
68+
addr_cam (the vendor Linux `struct fwcmd_addrcam_info`). The same replay
69+
commits on the 8852B. Both dies report non-secure (`is_sec_ic=0`), and the two
70+
ways of reading the window (per-dword address, or the vendor's page base plus
71+
offset) agree, so the read-back itself is not the issue.
72+
73+
The RMAC still answers from the CAM, so on this die the arm writes the entry
74+
from the host instead, the vendor's no-firmware path (`mac_upd_addr_cam`'s
75+
indirect SRAM write); what it writes and how is at
76+
`HalKestrel::write_addr_cam_sta`, and the entry encoding is shared with the
77+
8852B's H2C (`KestrelAddrCam.h`, pinned by a selftest against hardware
78+
read-backs). Measured on its own:
79+
80+
- **Only the address CAM entry matters.** Leaving out the join_info H2C, the
81+
addr_cam H2C or the BSSID CAM entry, alone or all together, kept the
82+
single-shot rate at 97.6-99.4%. Host-writing the BSSID CAM entry the vendor
83+
path also writes changed nothing measurable.
84+
- **The response-abort gates are mandatory here.** This die's EDCCA reads its
85+
own floor as busy (-67 dBm, `busy=1` at every tune), and with the gates
86+
armed the RMAC dropped every ACK: 0/2060 and 0/2019, against >= 97.6% with
87+
them cleared.
88+
- **The lookup hits the entry.** The long RX descriptor (dword 5:
89+
ADDR_CAM_VLD, addr_cam index, macid) shows every frame addressed to the
90+
responder matching the host-written entry (macid 63). That was measured on
91+
the earlier layout, where the index read 126: the entry the 0x40-stride
92+
write had landed in, counted at the 8852C's 0x20 stride.
93+
- **Clear** reads back empty and the soliciting peer then gets no ACK. On
94+
this die a 0 after a disarm only counts when the same session answered
95+
before it (a dead bring-up also reads 0), so the live-disarm row in
96+
`AdapterCaps.h` solicits across the disarm.
97+
98+
## 8852C: bring-ups that answer no ACKs
99+
100+
Some 8852C bring-ups answer no ACK at all: the responder arms, the entry reads
101+
back, and the adapter answers nothing for the whole session, where a good
102+
session answers >= 97.5%. How often varies and no cause is established. On
103+
the first bench it looked tied to uptime: over a day of back-to-back sessions
104+
it rose from about one in ten to three in four (10 of 83 overall), and after a
105+
long power-off the first 12 sessions answered 97.5-99.4% before 3 of the next
106+
12 came up dead. A second bench (two TX50UH, an 8812CU soliciting) does not
107+
support that reading: dead sessions came interleaved with good ones
108+
(dead/good/dead/good/dead in one series), one came on the second session after
109+
a 3 s VBUS cut, a VBUS cold cycle before every session still left 1 of 13
110+
dead, and the RF thermal meter sat at delta 2-3 throughout. Back on the first
111+
bench, with the entry at hardware entry 63, 6 of 15 sessions were dead, in
112+
runs (3 dead, 3 good, 2 dead, 4 good). It is a property of the bring-up, not
113+
of the arm: in one failed session three
114+
separate injector processes in a row all saw 0, and no session has ever
115+
recovered or failed partway. Measured in failed sessions:
116+
117+
- Every frame addressed to the responder still matches the entry in the RX
118+
descriptor (ADDR_CAM_VLD, macid 63), and the adapter airs its own frames
119+
normally: only the SIFS response is missing.
120+
- The response abort counters (0xCC14 NAV, 0xCC18 CCA/EDCCA/scheduler) stay at
121+
0: the hardware does not attempt the response and abort it.
122+
- Every MAC configuration register read (0xC000, 0xC300, 0xC400, 0xC600,
123+
0xC800, 0xCA00, 0xCC00, 0xCE00 and the BB path-com block at 0xD800) is
124+
identical to a good session's; only counters differ.
125+
- What does differ: `R_AX_WMAC_NAV_UP_INFO` (0xCC84) records intra-BSS NAV
126+
updates and `R_AX_PS_RXINFO` (0xCEA0) flags port-0 data, i.e. the MAC
127+
classifies the soliciting frames as its own BSS's traffic, which it does not
128+
in a good session.
129+
- Ruled out by measurement: the response NAV checks (`R_AX_RSP_CHK_SIG` bits
130+
19-21 cleared, still fails), the BSSID CAM (all zero in good and failed
131+
sessions alike; writing entry 1 valid, still fails), the injector, the
132+
host's per-frame RX cost, and the arm's timing relative to the RX loop.
133+
The slot is not ruled out: development tested hardware entry 126 only, and
134+
on a second bench entry 126 gave more dead sessions than entry 63 (8 of 21
135+
against 1 of 16), but entry 63 gave 6 of 15 on the first, so the slot does
136+
not explain it.
137+
138+
What decides it is not found. The 8852B shows the same shape far more rarely
139+
(about one bring-up in 55). Because a dead session reads back like a good one,
140+
`GetAdapterCaps` reports `ack_responder_ok` false on the 8852C (true on the
141+
8852B), while `SetAckResponder` still arms it. A caller that needs the ACK on
142+
the 8852C should treat a soliciting peer's retries as the tell and bring the
143+
adapter up again.

‎examples/rx/main.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,7 @@ int main(int argc, char **argv) {
17181718
logger->error(
17191719
"DEVOURER_ACK_DISARM_AFTER_MS is supported only by RTL8733B, "
17201720
"the Jaguar1/CHIP_8812 path (measured on RTL8812AU) and Kestrel "
1721-
"(measured on RTL8852BU); refusing {}",
1721+
"(measured on RTL8852BU and RTL8832CU); refusing {}",
17221722
devourer::generation_name(
17231723
rtlDevice->GetAdapterCaps().generation));
17241724
return 1;

‎src/AdapterCaps.h‎

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,26 @@ struct AdapterCaps {
197197
* ch161, 3 runs per row, responder on its own macid 127 / ADDR_CAM entry
198198
* 127: 14414/14422 armed at Init, 14376/14417 armed at InitWrite,
199199
* 14419/14432 retargeted, 0/6597 at the old address after the retarget,
200-
* 0/6575 never armed, 0/6561 after a live disarm; one cell in about 55
200+
* 0/6575 never armed, 0/6561 after a live disarm, re-run with the
201+
* solicitor across the disarm: 3842/3848 then 0/1773; one cell in about 55
201202
* across the development runs, at entry 1, read the entry back committed
202-
* and answered no ACKs, not reproduced since). The 8852C reads false: its
203-
* firmware does not commit the station entry, and the arm refuses on the
204-
* read-back.
203+
* and answered no ACKs, not reproduced since), 8852C (the same bench with
204+
* the roles swapped, the 8852B soliciting; the entry is host-written at
205+
* macid / ADDR_CAM entry 63, docs/kestrel-ack-responder.md. 3 runs per row
206+
* with the entry at hardware entry 126, an earlier layout that used the
207+
* 8852B's CAM stride: 13831/14041 armed at Init, 14239/14408 retargeted,
208+
* armed at InitWrite 98.9% and 97.0% in two runs with the third a dead
209+
* session, 0/5993 at the old address, 0/6062 never armed. At hardware
210+
* entry 63, the solicitor running across a live disarm in one session:
211+
* 3812/3837 then 0/1616, 3846/3848 then 0/1623 (a third cell was a dead
212+
* session and says nothing); 15 armed sessions back to back, 9 answering
213+
* 97.5-99.9% and 6 dead. Some bring-ups answer no ACK at all, at any
214+
* entry tried: 3 of 12 and 6 of 15 here, 8 of 21 and 1 of 16 on a second
215+
* bench, interleaved with good sessions). The 8852C is measured but reports
216+
* FALSE: a dead session reads its entry back like a good one, so the flag
217+
* could not tell a caller the loop closes; SetAckResponder still arms it,
218+
* and a caller that wants it there judges by the soliciting peer's
219+
* retries.
205220
*
206221
* The `on`, `retarget`, and legacy `off` rows establish arming, retargeting,
207222
* and a never-armed control. A backend-owned `disarmed` row supports only
@@ -226,8 +241,8 @@ struct AdapterCaps {
226241
* generation — the AP-mode work proved the gate where it was measured.
227242
* False-as-unmeasured (the
228243
* vht_2g4_ok reading: unmeasured, not incapable): the 8821C — it shares
229-
* the recipe but no 8821CU/CE cell has run. FALSE on Kestrel:
230-
* SetAckResponder is not implemented on the AX generation.
244+
* the recipe but no 8821CU/CE cell has run. FALSE on the Kestrel 8852C
245+
* (measured, see above).
231246
* tx_retry_limit_ok: DEVOURER_TX_RETRY_LIMIT drives hardware autonomous
232247
* retransmission (measured 12/0/12 A/B: 8821AU, 8812BU, 8822CU; the 8733B
233248
* by airtime dose-response instead, 0/3/12 -> 1.00/4.00/12.32–12.33 airings per

‎src/kestrel/HalKestrel.h‎

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "ChipVariant.h"
99
#include "KestrelFw.h"
10+
#include "KestrelAddrCam.h"
1011
#include "MacRegAx.h" /* mac_ax role enums */
1112
#include "RtlAdapter.h"
1213
#include "SelectedChannel.h" /* ChannelWidth_t */
@@ -276,8 +277,9 @@ class HalKestrel {
276277
* vendor's surrounding commands (role CLIENT, macid unpause, CMAC and DMAC
277278
* tables) were each dropped on the 8852B with no change in the commit or the
278279
* ACK rate, so they are not sent. The commit is asynchronous; callers verify
279-
* with addr_cam_holds(). The 8852C firmware accepts both commands and does
280-
* not commit. */
280+
* with addr_cam_holds(). The 8852C firmware ignores the addr_cam H2C
281+
* altogether (neither CAM entry is written), so that die takes
282+
* write_addr_cam_sta() instead. */
281283
bool register_infra_sta(const uint8_t own[6], const uint8_t bssid[6],
282284
uint8_t macid, uint8_t addr_cam_idx,
283285
uint8_t bssid_cam_idx, uint8_t band = 0,
@@ -309,15 +311,72 @@ class HalKestrel {
309311
return ok;
310312
}
311313

314+
/* Internal address of address-CAM entry `idx`. The stride is per die, as
315+
* the vendor's get_addr_cam_size + mac_upd_addr_cam lay it out (entry N at
316+
* N * len): the LONG entry (0x40) on the 8852B, the SHORT one (0x20) on the
317+
* 8852C, 128 entries on both (ADDRCAM_NUM). */
318+
uint32_t addr_cam_entry_addr(uint8_t idx) const {
319+
const uint32_t stride = _variant == ChipVariant::C8852C
320+
? reg::ADDR_CAM_ENT_SHORT_SIZE
321+
: reg::ADDR_CAM_ENT_LONG_SIZE;
322+
return reg::ADDR_CAM_MEM_BASE + idx * stride;
323+
}
324+
312325
/* Write one dword of the MAC's internal table SRAM (the write half of
313-
* read_mac_mem32). */
314-
void write_mac_mem32(uint32_t internal_addr, uint32_t value) {
315-
_device.rtw_write32(reg::R_AX_FILTER_MODEL_ADDR, internal_addr);
316-
_device.rtw_write32_wide(reg::R_AX_INDIR_ACCESS_ENTRY, value);
326+
* read_mac_mem32). False when either register transfer failed. */
327+
bool write_mac_mem32(uint32_t internal_addr, uint32_t value) {
328+
return _device.rtw_write32(reg::R_AX_FILTER_MODEL_ADDR, internal_addr) &&
329+
_device.rtw_write32_wide(reg::R_AX_INDIR_ACCESS_ENTRY, value);
317330
}
318331
/* Clear VALID on address-CAM entry `idx`. */
319-
void clear_addr_cam_entry(uint8_t idx) {
320-
write_mac_mem32(reg::ADDR_CAM_MEM_BASE + idx * reg::ADDR_CAM_ENT_LONG_SIZE, 0u);
332+
bool clear_addr_cam_entry(uint8_t idx) {
333+
return write_mac_mem32(addr_cam_entry_addr(idx), 0u);
334+
}
335+
336+
/* Write the station entry register_infra_sta asks the firmware for straight
337+
* into the physical address CAM -- the vendor's no-firmware path
338+
* (mac_upd_addr_cam's indirect SRAM write), for the 8852C, whose firmware
339+
* ignores the addr_cam H2C. The same SHORT entry the 8852B firmware commits:
340+
* VALID, net_type INFRA, the SMA/TMA hashes (XOR of the address bytes),
341+
* `bssid_cam_idx`, SMA=own, TMA=bssid, `macid` plus the vendor's bit 14.
342+
* Only the address CAM: the vendor path also writes the BSSID CAM entry,
343+
* but host-writing it changed nothing measurable (the response rate, and
344+
* the bring-ups that answer no ACKs, both stayed). Synchronous. False on
345+
* the first failed transfer, VALID then not set again (a failure after the
346+
* VALID-off write leaves the entry invalid, not half-written and live);
347+
* callers verify with addr_cam_sta_holds(). */
348+
bool write_addr_cam_sta(uint8_t idx, const uint8_t own[6],
349+
const uint8_t bssid[6], uint8_t macid,
350+
uint8_t bssid_cam_idx) {
351+
const auto w = addr_cam_sta_entry(own, bssid, reg::MAC_AX_NET_TYPE_INFRA,
352+
bssid_cam_idx, macid);
353+
const uint32_t base = addr_cam_entry_addr(idx);
354+
/* VALID off first (a retarget rewrites a live entry), then the body, then
355+
* VALID last, so the RMAC never matches a half-written entry. */
356+
if (!write_mac_mem32(base, 0u))
357+
return false;
358+
for (int i = 7; i >= 1; --i)
359+
if (!write_mac_mem32(base + 4 * i, w[i]))
360+
return false;
361+
return write_mac_mem32(base, w[0]);
362+
}
363+
364+
/* Whether the physical address-CAM entry `idx` reads back as exactly the
365+
* station entry write_addr_cam_sta writes for these arguments -- the check
366+
* for the host-written path, where nothing but the read says the whole
367+
* entry landed (addr_cam_holds only checks VALID + SMA). Every dword but
368+
* dword 5, which the 8852C does not back: it reads 0xdeadbeef whatever was
369+
* written (measured; the other seven read back as written). */
370+
bool addr_cam_sta_holds(uint8_t idx, const uint8_t own[6],
371+
const uint8_t bssid[6], uint8_t macid,
372+
uint8_t bssid_cam_idx) {
373+
const auto w = addr_cam_sta_entry(own, bssid, reg::MAC_AX_NET_TYPE_INFRA,
374+
bssid_cam_idx, macid);
375+
const uint32_t base = addr_cam_entry_addr(idx);
376+
for (int i = 0; i < 8; ++i)
377+
if (i != 5 && read_mac_mem32(base + 4 * i) != w[i])
378+
return false;
379+
return true;
321380
}
322381

323382
/* One dword of the MAC's internal table SRAM (rtw89 mac_mem_read): the
@@ -334,7 +393,7 @@ class HalKestrel {
334393
* the firmware to write. Layout (rtw89 cam.h): word0 bit0 = VALID, then
335394
* SMA[0..3] in word2 and SMA[4..5] in the low half of word3. */
336395
bool addr_cam_holds(uint8_t idx, const uint8_t sma[6]) {
337-
const uint32_t base = reg::ADDR_CAM_MEM_BASE + idx * reg::ADDR_CAM_ENT_LONG_SIZE;
396+
const uint32_t base = addr_cam_entry_addr(idx);
338397
if (!(read_mac_mem32(base) & 0x1u))
339398
return false;
340399
const uint32_t w2 = read_mac_mem32(base + 8);

0 commit comments

Comments
 (0)