Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions docs/kestrel-ack-responder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Hardware ACK responder on Kestrel (RTL8852BU)

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

## Evidence behind the mechanism

What `SetAckResponder` / `ClearAckResponder` do and verify is documented once,
on their declaration in `src/kestrel/RtlKestrelDevice.h`. This page keeps what
was measured to get there.

- **Why it used to measure 0%.** `add_self_sta`'s NO_LINK entry is accepted
over H2C and never committed: the physical entry, read through
`FILTER_MODEL_ADDR`/`INDIR_ACCESS_ENTRY` at 0x18850000, stays valid=0.
- **What commits it** was captured from the vendor Windows driver over
USBPcap: `join_info(INFRA, connect)` THEN the station addr_cam (SHORT
entry, BSSID CAM at index 1). Without the join the entry never commits. The
vendor's surrounding commands (role CLIENT, macid unpause, CMAC/DMAC tables,
H2C REC/DONE_ACK) were each dropped with no change in commit or ACK rate,
so `register_infra_sta` sends only the two.
- **The commit is asynchronous.** Typically read back ~2.2 ms after the H2Cs
(a single read takes about that long over USB), once 19.5 ms. A one-shot
read-back refused healthy arms, so `SetAckResponder` polls for up to 100 ms.
- **Response-abort gates.** The arm clears `R_AX_TRXPTCL_RESP_0` bits 23-28.
On ch149, shared with a neighbour AP, one of three runs without the clear
dropped to 82.4% single-shot; three with it stayed at >= 99.6%. On a quiet
ch161 it made no difference.
- **Clear** is exercised live by rxdemo's `DEVOURER_ACK_DISARM_AFTER_MS`.
- **8852C:** its firmware accepts both commands and leaves the entry invalid,
so the arm refuses. The vendor's 8852C association sends far more before the
addr_cam (BB/RF offload, port configuration); which of it the C needs is open.
- Measured near field (two dongles, injector at -15 dB), single-shot, no CCX:
the numbers are in `AdapterCaps.h`. The responder's own transmissions are
not part of that matrix: with carrier sense off (this die's TX default) a
responder that also sends unicast loses some ACKs to its own queued frames
(the response scheduler abort counter at 0xCC18[23:16] moves).
15 changes: 12 additions & 3 deletions examples/rx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
#if defined(DEVOURER_HAVE_8733B)
#include "rtl8733b/Rtl8733bDevice.h"
#endif
#if defined(DEVOURER_HAVE_KESTREL)
#include "kestrel/RtlKestrelDevice.h"
#endif
#include "RtlAdapter.h"
#include "SignalStop.h"
#include "UsbOpen.h"
Expand Down Expand Up @@ -1704,12 +1707,18 @@ int main(int argc, char **argv) {
static_cast<uint32_t>(ms));
scheduled = true;
}
#endif
#if defined(DEVOURER_HAVE_KESTREL)
if (auto *kestrel = dynamic_cast<RtlKestrelDevice *>(rtlDevice)) {
kestrel->ScheduleAckResponderDisarmForTest(static_cast<uint32_t>(ms));
scheduled = true;
}
#endif
if (!scheduled) {
logger->error(
"DEVOURER_ACK_DISARM_AFTER_MS is supported only by RTL8733B "
"and the Jaguar1/CHIP_8812 path (measured on RTL8812AU); "
"refusing {}",
"DEVOURER_ACK_DISARM_AFTER_MS is supported only by RTL8733B, "
"the Jaguar1/CHIP_8812 path (measured on RTL8812AU) and Kestrel "
"(measured on RTL8852BU); refusing {}",
devourer::generation_name(
rtlDevice->GetAdapterCaps().generation));
return 1;
Expand Down
9 changes: 8 additions & 1 deletion src/AdapterCaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,14 @@ struct AdapterCaps {
* 8822B, 8812C/8822C, 8812E/8822E (the 8811A rides the 8812 die path and
* inherits its row), 8733B (1725/1725 frames ACKed at retries_mean 0.00,
* and retarget-proof: re-armed on a different MAC, 1728/1728 —
* tests/ack_txreport_matrix.sh run with the 8733B as the responder).
* tests/ack_txreport_matrix.sh run with the 8733B as the responder),
* 8852B (Kestrel has no CCX report, so the soliciting TX is an 8832CU
* counting the ACK frames it receives, one attempt per frame, MCS1 on
* ch161, 3 runs per row: 14230/14430 armed at Init, 14391/14431 armed at
* InitWrite, 14347/14425 retargeted, 0/6440 at the old address after the
* retarget, 0/6194 never armed, 0/6603 after a live disarm). The 8852C
* reads false: its firmware does not commit the station entry, and the
* arm refuses on the read-back.
*
* The `on`, `retarget`, and legacy `off` rows establish arming, retargeting,
* and a never-armed control. A backend-owned `disarmed` row supports only
Expand Down
50 changes: 50 additions & 0 deletions src/kestrel/HalKestrel.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <array>
#include <cstdint>
#include <cstring>

#include "ChipVariant.h"
#include "KestrelFw.h"
Expand Down Expand Up @@ -265,6 +266,55 @@ class HalKestrel {
return ok;
}

/* Register `own` as an associated INFRA station of `bssid` -- what makes the
* firmware COMMIT the physical ADDR_CAM entry, which add_self_sta's NO_LINK
* entry never gets (accepted over H2C, reads back valid=0). Two commands, in
* the order the vendor Windows driver sends them on a real association
* (captured over USBPcap): join_info(INFRA, connect), THEN the station
* addr_cam (SMA=own, TMA=bssid, SHORT entry, BSSID CAM at index 1). The
* join is the step that matters: without it the entry never commits. The
* vendor's surrounding commands (role CLIENT, macid unpause, CMAC and DMAC
* tables) were each dropped on the 8852B with no change in the commit or the
* ACK rate, so they are not sent. The commit is asynchronous; callers verify
* with addr_cam_holds(). The 8852C firmware accepts both commands and does
* not commit. */
bool register_infra_sta(const uint8_t own[6], const uint8_t bssid[6],
uint8_t macid = 0, uint8_t band = 0, uint8_t port = 0) {
bool ok = _fw.fw_join_info(macid, reg::MAC_AX_NET_TYPE_INFRA,
reg::MAC_AX_SELF_ROLE_CLIENT,
reg::MAC_AX_WIFI_ROLE_STATION, port, band,
/*connect=*/true);
ok = _fw.fw_upd_addr_cam_sta(macid, own, bssid, reg::MAC_AX_NET_TYPE_INFRA,
/*addr_cam_idx=*/0, /*bssid_cam_idx=*/1) &&
ok;
return ok;
}

/* One dword of the MAC's internal table SRAM (rtw89 mac_mem_read): the
* internal address goes to R_AX_FILTER_MODEL_ADDR, the data comes back
* through the R_AX_INDIR_ACCESS_ENTRY window (0x40000, a 32-bit-address
* access). */
uint32_t read_mac_mem32(uint32_t internal_addr) {
_device.rtw_write32(reg::R_AX_FILTER_MODEL_ADDR, internal_addr);
return _device.rtw_read32_wide(reg::R_AX_INDIR_ACCESS_ENTRY);
}

/* Whether the PHYSICAL address-CAM entry `idx` is valid and carries `sma` --
* what the RMAC actually matches A1 against, as opposed to what an H2C asked
* the firmware to write. Layout (rtw89 cam.h): word0 bit0 = VALID, then
* SMA[0..3] in word2 and SMA[4..5] in the low half of word3. */
bool addr_cam_holds(uint8_t idx, const uint8_t sma[6]) {
const uint32_t base = reg::ADDR_CAM_MEM_BASE + idx * reg::ADDR_CAM_ENT_LONG_SIZE;
if (!(read_mac_mem32(base) & 0x1u))
return false;
const uint32_t w2 = read_mac_mem32(base + 8);
const uint32_t w3 = read_mac_mem32(base + 12);
const uint8_t got[6] = {static_cast<uint8_t>(w2), static_cast<uint8_t>(w2 >> 8),
static_cast<uint8_t>(w2 >> 16), static_cast<uint8_t>(w2 >> 24),
static_cast<uint8_t>(w3), static_cast<uint8_t>(w3 >> 8)};
return std::memcmp(got, sma, 6) == 0;
}

/* Register an associated peer STA (an AP-mode client we schedule UL for):
* the same role + ADDR_CAM + CMAC-control chain as add_self_sta but for a
* distinct macid and net_type=AP, so the fw tracks the peer and a Trigger's
Expand Down
68 changes: 68 additions & 0 deletions src/kestrel/KestrelFw.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,29 @@ bool KestrelFw::fw_role_maintain(uint8_t macid, uint8_t self_role,
return ok;
}

bool KestrelFw::fw_join_info(uint8_t macid, uint8_t net_type, uint8_t self_role,
uint8_t wifi_role, uint8_t port, uint8_t band,
bool connect) {
/* One dword; OP is "disconnect", so connect sends OP=0. The HE/UL-OFDMA
* fields (WMM/TGR/ISHESTA/DLBW/TF_MAC_PAD/DL_T_PE) stay 0. */
uint8_t content[4] = {0};
const uint32_t op = connect ? 0u : 1u;
const uint32_t w0 = (static_cast<uint32_t>(macid) & 0xff) | (op << 8) |
((static_cast<uint32_t>(band) & 0x1) << 9) |
((static_cast<uint32_t>(port) & 0x7) << 21) |
((static_cast<uint32_t>(net_type) & 0x3) << 24) |
((static_cast<uint32_t>(wifi_role) & 0xf) << 26) |
((static_cast<uint32_t>(self_role) & 0x3) << 30);
put_le32(content + 0, w0);
bool ok = send_h2c_cmd(r::FWCMD_H2C_CAT_MAC, r::FWCMD_H2C_CL_MEDIA_RPT,
r::FWCMD_H2C_FUNC_JOININFO, content, sizeof(content));
_logger->info("Kestrel: join_info (macid={} op={} net_type={} self={} wifi={} "
"port={} band={}) -> {}",
macid, op, net_type, self_role, wifi_role, port, band,
ok ? "sent" : "FAILED");
return ok;
}

bool KestrelFw::fw_upd_addr_cam(uint8_t macid, const uint8_t self_mac[6],
uint8_t net_type, uint8_t addr_cam_idx,
uint8_t bssid_cam_idx) {
Expand Down Expand Up @@ -475,6 +498,51 @@ bool KestrelFw::fw_upd_addr_cam(uint8_t macid, const uint8_t self_mac[6],
return ok;
}

bool KestrelFw::fw_upd_addr_cam_sta(uint8_t macid, const uint8_t sma[6],
const uint8_t tma[6], uint8_t net_type,
uint8_t addr_cam_idx, uint8_t bssid_cam_idx) {
/* Field layout as fw_upd_addr_cam (rtw89 cam.h), with the station values the
* vendor driver uses: SHORT length, separate SMA/TMA hashes, the BSSID CAM
* index in dword3, macid plus the vendor's bit 14 in dword8, and an embedded
* valid BSSID CAM entry carrying tma. */
uint8_t sma_hash = 0, tma_hash = 0;
for (int i = 0; i < 6; i++) {
sma_hash ^= sma[i];
tma_hash ^= tma[i];
}
uint8_t c[60] = {0};
auto sw = [](uint32_t v, uint32_t msk, uint32_t sh) {
return (v & msk) << sh;
};
put_le32(c + 4, sw(addr_cam_idx, 0xff, 0) | sw(0, 0xff, 8) |
sw(r::ADDR_CAM_ENT_SHORT_SIZE, 0xff, 16));
put_le32(c + 8, 0x1u | sw(net_type, 0x3, 1) | sw(r::MAC_AX_NO_MSK, 0x3, 14) |
sw(sma_hash, 0xff, 16) | sw(tma_hash, 0xff, 24));
put_le32(c + 12, sw(bssid_cam_idx, 0x3f, 0));
put_le32(c + 16, sw(sma[0], 0xff, 0) | sw(sma[1], 0xff, 8) |
sw(sma[2], 0xff, 16) | sw(sma[3], 0xff, 24));
put_le32(c + 20, sw(sma[4], 0xff, 0) | sw(sma[5], 0xff, 8) |
sw(tma[0], 0xff, 16) | sw(tma[1], 0xff, 24));
put_le32(c + 24, sw(tma[2], 0xff, 0) | sw(tma[3], 0xff, 8) |
sw(tma[4], 0xff, 16) | sw(tma[5], 0xff, 24));
put_le32(c + 32, sw(macid, 0xff, 0) | (1u << 14));
put_le32(c + 48, sw(bssid_cam_idx, 0xff, 0) | sw(0, 0xff, 8) |
sw(r::BSSID_CAM_ENT_SIZE, 0xff, 16));
put_le32(c + 52, 0x1u | sw(r::MAC_AX_NO_MSK, 0x3f, 2) | sw(0, 0x3f, 8) |
sw(tma[0], 0xff, 16) | sw(tma[1], 0xff, 24));
put_le32(c + 56, sw(tma[2], 0xff, 0) | sw(tma[3], 0xff, 8) |
sw(tma[4], 0xff, 16) | sw(tma[5], 0xff, 24));
bool ok = send_h2c_cmd(r::FWCMD_H2C_CAT_MAC, r::FWCMD_H2C_CL_ADDR_CAM_UPDATE,
r::FWCMD_H2C_FUNC_ADDRCAM_INFO, c, sizeof(c));
_logger->info("Kestrel: upd_addr_cam_sta (macid={} net_type={} a_idx={} "
"b_idx={} sma={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x} "
"tma={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}) -> {}",
macid, net_type, addr_cam_idx, bssid_cam_idx, sma[0], sma[1],
sma[2], sma[3], sma[4], sma[5], tma[0], tma[1], tma[2], tma[3],
tma[4], tma[5], ok ? "sent" : "FAILED");
return ok;
}

bool KestrelFw::fw_upd_cctl_basic(uint8_t macid, uint8_t addr_cam_idx,
uint16_t datarate, uint8_t ntx_path_en,
uint8_t path_map_a, bool bmc) {
Expand Down
14 changes: 14 additions & 0 deletions src/kestrel/KestrelFw.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ class KestrelFw {
bool fw_role_maintain(uint8_t macid, uint8_t self_role, uint8_t wifi_role,
uint8_t upd_mode, uint8_t band, uint8_t port);

/* rtw89_fw_h2c_join_info: H2C cat=MAC, class=MEDIA_RPT, func=JOININFO --
* the firmware's "station connected" op (`connect` sends OP=0). Without it
* the firmware accepts a station's addr_cam H2C and does not commit it.
* Roles are mac_ax_self_role / mac_ax_wifi_role, net_type mac_ax_net_type. */
bool fw_join_info(uint8_t macid, uint8_t net_type, uint8_t self_role,
uint8_t wifi_role, uint8_t port, uint8_t band, bool connect);

/* mac_upd_addr_cam (addr_cam.c): H2C cat=MAC, class=ADDR_CAM_UPDATE,
* func=ADDRCAM_INFO — programs the address-CAM entry (BSSID+SMA+TMA+macid+
* net_type) for `macid`. `self_mac` is the 6-byte transmitter address (our
Expand All @@ -76,6 +83,13 @@ class KestrelFw {
bool fw_upd_addr_cam(uint8_t macid, const uint8_t self_mac[6], uint8_t net_type,
uint8_t addr_cam_idx, uint8_t bssid_cam_idx);

/* The station form of the addr_cam, as the vendor Windows driver writes it on
* an association: SMA = our own address, TMA = the AP's BSSID (different),
* the SHORT entry length, and the BSSID CAM entry embedded with BSSID = tma. */
bool fw_upd_addr_cam_sta(uint8_t macid, const uint8_t sma[6],
const uint8_t tma[6], uint8_t net_type,
uint8_t addr_cam_idx, uint8_t bssid_cam_idx);

/* mac_upd_cctl_info (tblupd.c): H2C cat=MAC, class=FR_EXCHG, func=CCTLINFO_UD
* — the per-MACID CMAC control table. Sets the default TX rate, the TX
* antenna path enable (ntx_path_en — without it a frame has NO antenna and
Expand Down
22 changes: 22 additions & 0 deletions src/kestrel/MacRegAx.h
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,12 @@ constexpr uint8_t USR_TX_RPT_MODE_LAST_PKT = 2;
* | WIFI_ROLE[16:13] | BAND[18:17] | PORT[21:19]. */
constexpr uint8_t FWCMD_H2C_CL_MEDIA_RPT = 0x8;
constexpr uint8_t FWCMD_H2C_FUNC_FWROLE_MAINTAIN = 0x4;
/* JOININFO (same MEDIA_RPT class, func 0): the fw "STA connected" op. One dword
* w0 = MACID[7:0] | OP[8] (0=connect,1=disconnect) | BAND[9] | WMM[11:10] |
* TGR[12] | ISHESTA[13] | DLBW[15:14] | TF_MAC_PAD[17:16] | DL_T_PE[20:18] |
* PORT_ID[23:21] | NET_TYPE[25:24] | WIFI_ROLE[29:26] | SELF_ROLE[31:30]
* (rtw89 fw.c rtw89_fw_h2c_join_info, fw.h RTW89_H2C_JOININFO_W0_*). */
constexpr uint8_t FWCMD_H2C_FUNC_JOININFO = 0x0;
constexpr uint8_t H2C_FWROLE_SELF_ROLE_SH = 8; /* mask 0x3 */
constexpr uint8_t H2C_FWROLE_UPD_MODE_SH = 10; /* mask 0x7 */
constexpr uint8_t H2C_FWROLE_WIFI_ROLE_SH = 13; /* mask 0xf */
Expand Down Expand Up @@ -679,6 +685,11 @@ constexpr uint8_t FWCMD_H2C_FUNC_DCTLINFO_UD = 0x1;

/* addr_cam entry geometry (addr_cam.h): 8852B uses the LONG entry (0x40). */
constexpr uint8_t ADDR_CAM_ENT_LONG_SIZE = 0x40;
/* The entry length the vendor driver writes for an associated station. */
constexpr uint8_t ADDR_CAM_ENT_SHORT_SIZE = 0x20;
/* ADDR_CAM in the MAC table SRAM (rtw89 ADDR_CAM_BASE_ADDR), read through the
* FILTER_MODEL_ADDR / INDIR_ACCESS_ENTRY window. */
constexpr uint32_t ADDR_CAM_MEM_BASE = 0x18850000;
constexpr uint8_t BSSID_CAM_ENT_SIZE = 0x08;
/* mac_ax_addr_msk_sel (mac_def.h). */
constexpr uint8_t MAC_AX_NO_MSK = 0;
Expand Down Expand Up @@ -1567,6 +1578,17 @@ constexpr uint32_t WMAC_SPEC_SIFS_CCK = 0xA;
constexpr uint8_t B_AX_WMAC_SPEC_SIFS_OFDM_SH = 8;
constexpr uint32_t B_AX_WMAC_SPEC_SIFS_OFDM_MSK = 0xff;
constexpr uint32_t WMAC_SPEC_SIFS_OFDM_52B = 0x11;
/* Response (ACK/CTS/BA) abort gates in the same register (rtw89 reg.h): the
* RMAC drops a SIFS response when an armed check reads the medium busy. */
constexpr uint32_t B_AX_RSP_CHK_CCA = 1u << 23;
constexpr uint32_t B_AX_RSP_CHK_EDCCA = 1u << 24;
constexpr uint32_t B_AX_RSP_CHK_BTCCA = 1u << 25;
constexpr uint32_t B_AX_RSP_CHK_SEC_CCA_20 = 1u << 26;
constexpr uint32_t B_AX_RSP_CHK_SEC_CCA_40 = 1u << 27;
constexpr uint32_t B_AX_RSP_CHK_SEC_CCA_80 = 1u << 28;
constexpr uint32_t B_AX_RSP_CHK_ALL =
B_AX_RSP_CHK_CCA | B_AX_RSP_CHK_EDCCA | B_AX_RSP_CHK_BTCCA |
B_AX_RSP_CHK_SEC_CCA_20 | B_AX_RSP_CHK_SEC_CCA_40 | B_AX_RSP_CHK_SEC_CCA_80;
constexpr uint16_t R_AX_RXTRIG_TEST_USER_2 = 0xCCB0;
constexpr uint32_t B_AX_RXTRIG_FCSCHK_EN = 1u << 20;
constexpr uint16_t R_AX_TRXPTCL_RRSR_CTL_0 = 0xCC08;
Expand Down
Loading
Loading