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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ construction from the `SYS_CFG2` chip-id (Kestrel: PID-first):
only what an independent witness decoded — legacy OFDM + HT MCS0-7, BCC,
20/40 MHz on 2.4/5 GHz, plus long-preamble CCK on 2.4 GHz at 20 MHz, plus
10 MHz narrowband (5 MHz refused — `src/rtl8733b/CLAUDE.md`).
Everything the backend has not ported (TSF/beacons, hardware ACK, A-MPDU,
Everything the backend has not ported (TSF/beacons, A-MPDU, CCX/`tx.report`,
the flat-index and per-rate TX-power knobs) falls through to `IRtlDevice`'s
not-ported defaults rather than being faked, so read the base class before
assuming a cross-generation feature below applies here. `FastRetune` IS
Expand Down
54 changes: 51 additions & 3 deletions docs/rtl8733b.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,38 @@ silently or failing to initialise. Silence is the only outcome that would let a
refused knob look like a granted one; a hard failure would be a harsh answer to
a request a caller may be making only through an inherited environment.

## Hardware ARQ

Measured against an RTL8812AU peer on the validation unit.
Both directions of a reliable-unicast link now work on this die.

| knob | state | evidence |
| --- | --- | --- |
| `tx.ack_timeout_us` | honoured | REG_ACKTO read back at bring-up: `<unset>/128/33/200 -> 128/128/33/200` |
| `SetAckResponder` | ported, `ack_responder_ok = true` (normal-ACK singles; BlockAck untested) | 8733B as responder: armed 1736/1736 ACKed, retries_mean 0.00; re-armed on a different MAC 1736/1736; disarmed 0.00 with retries pinned at 12 |
| `tx.retry_limit` | live, `tx_retry_limit_ok = true` | airtime dose-response 0/3/12 -> 0.93/3.93/12.27 airings per frame (expected 1 + N), repeatable over a 0/3/12/0/12 ladder |
| `tx.report` (CCX) | **not** ported — firmware emits nothing | see Known gaps below |

Implementation detail, the register recipe and the full CCX bench narrative:
`src/rtl8733b/CLAUDE.md` "Hardware ARQ". The cross-generation ARQ matrix this
die now joins is `docs/scheduled-mac.md`.

The retry measurement could not use the 12/0/12 CCX A/B the Jaguars are judged
by, because this die has no `tx.report` to judge itself with; it counts airings
at a passive monitor instead (`tests/rtl8733b_retry_limit_onair.sh`), and takes
three dose levels rather than an on/off pair so that a straight line — not a
single delta that ambient conditions could fake — carries the claim. Its
counterparts: one physical unit and one peer generation, like every other
on-air claim in this section — note the document as a whole rests on two units,
but these ARQ cells ran on the `f72b` sample only; the monitor can only lose
airings, never invent them, so the measured ratio is a floor. Two different things account for the shortfalls, and only the smaller one is an
artefact: the `rx.txhit` readout quantizes to at most 99 airings (the event
fires on the first 10 hits then every 100th), which is <=0.066/frame at 1500
frames and covers the 0 and 3 arms entirely. It does NOT cover the 12 arm —
12.27 against 13 is 0.73/frame, about 1095 airings, an order of magnitude past
that bound. That residue is monitor loss (or genuinely fewer airings), which is
why the ratio is reported as a floor and not a point estimate.

## Known gaps and deferred validation

These results have **not** been claimed:
Expand Down Expand Up @@ -403,9 +435,25 @@ These results have **not** been claimed:
the normal NIC image.
- SGI remains disabled: a descriptor with its short-GI bit set submitted
successfully, but an independent RTL8812AU decoded both broadcast and
unicast probes as long GI. STBC and LDPC remain rejected. ACK/BlockAck
response, A-MPDU and throughput were not independently
validated and remain unadvertised or unsupported by this backend.
unicast probes as long GI. STBC and LDPC remain rejected. A-MPDU and
throughput were not independently validated and remain unadvertised or
unsupported by this backend. Normal-ACK response IS ported and measured
(Hardware ARQ above); BlockAck response to an aggregate remains untested, as
does A-MPDU itself.
- No CCX path, so no `tx.report` events — and unlike every other gap here the
cause is the firmware, not a missing port. Everything under the backend's
control was verified correct on air (descriptor SPE_RPT/SW_DEFINE set at the
vendor's own bit positions, C2H decoded at the vendor's dword2[28], bulk-IN
delivery as the vendor uses, and the fw-offload C2H format this firmware
speaks per its own dispatch), yet with an RX loop live and 3160 frames
received the firmware returned **zero** C2H packets in any format — with and
without net_type armed, peer ACKing and silent. The outstanding lead is the
halmac H2C queue + a MEDIA_STATUS_RPT registering the descriptor MACID; this
backend has no H2C transport at all. The knob warns at bring-up instead of
stamping descriptors that buy nothing. Consequence: an 8733BU can be either
end of a hardware-ARQ link but cannot see per-frame delivery, so detecting a
departed peer needs an application-level timeout
(`src/rtl8733b/CLAUDE.md` "Hardware ARQ").
- Fast retune is now ported and independently witnessed (intra-band,
same-width; `src/rtl8733b/CLAUDE.md` has the measured contract and its
counterparts): channel-state readback parity 7/7 hops, a 299/300 post-hop
Expand Down
17 changes: 14 additions & 3 deletions docs/scheduled-mac.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ scheduled MAC runs TX+RX anyway, so this is the relevant session shape.
| Jaguar2 8812BU | 0.91 / 2.1 (run-to-run 0.12–0.91) | 0.64 / 5.3 | yes (12) | 0.86 | 0 |
| Jaguar3 8822CU | 1.00 / 0.24 | 1.00 / 0.13 | yes (12) | 0.96 | 0 |

The RTL8733B is deliberately absent from this table: it is the one die that
runs closed-loop hardware ARQ with **no CCX report at all**, so none of these
columns can be filled for it as the soliciting TX. Its retry knob is measured
from the air instead (airings per submitted frame, 0/3/12 -> 0.93/3.93/12.27)
and its responder side is measured with this same harness pointed the other
way — `docs/rtl8733b.md` "Hardware ARQ".

The OFF-phase pin is set by `DEVOURER_TX_RETRY_LIMIT` (the matrix runs 12,
the value the descriptors used to hardcode) — the knob, not a descriptor
constant, is now the single source of truth for the retry limit on
Expand Down Expand Up @@ -251,7 +258,8 @@ nonzero limit for absolute numbers; 8821AU row re-measured ch6):
| 8821AU | 62% | 0% | works (94% closed-loop at retry 8) |
| 8812EU | 98% | 0% | works |
| 8812CU | 69% | 0% | works |
| 8852CU (Kestrel) | 0% | 0% | not implemented (SetAckResponder is J1/2/3-only) |
| 8733B | unmeasured | 0% | works (closed-loop 1736/1736 at retry 12; single-shot cell never run) |
| 8852CU (Kestrel) | 0% | 0% | not implemented on the AX generation |

Unmeasured for lack of plugged hardware: 8821CU / PCIe 8821CE (recipe-shared
with the 8822B; their `AdapterCaps.ack_responder_ok` stays false-as-unmeasured
Expand Down Expand Up @@ -300,8 +308,11 @@ carry this table per die.
2,048-bit window leaked 2,846 delivered frames out of coverage when a
stalled spsc-fat pool drained ~3 k frames in one receipt interval; the
8192 default clears that bench worst case ~2.7×).
2. **Closed-loop hardware ACK + autonomous retry is GO on Jaguar1 and
Jaguar3** (100% delivery, retries ≈ 0.2–0.3) including retargeting an
2. **Closed-loop hardware ACK + autonomous retry is GO on Jaguar1, Jaguar3 and
the RTL8733B** (100% delivery, retries ≈ 0.2–0.3; the 8733B closes the loop
as responder at 1736/1736 but has no CCX report of its own, so its TX-side
retry evidence is airtime rather than `tx.report` — `docs/rtl8733b.md`)
including retargeting an
arbitrary UE MAC mid-session (re-arm `SetAckResponder`, change the
descriptor RA — both fully dynamic). Requires a nonzero
`DEVOURER_TX_RETRY_LIMIT` — the hardware ARQ loop retransmits until ACK
Expand Down
23 changes: 23 additions & 0 deletions src/AckResponder.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,28 @@ inline void disable(RtlAdapter &dev) {
dev.rtw_write8(0x0102, static_cast<uint8_t>(nt & ~0x03u));
}

/* The MAC must be UNICAST: a station cannot ACK-target a group address, so an
* arm on one can never fire. Lives here rather than in each backend because
* the precondition is a property of the recipe, not of any one die. */
inline bool is_unicast(const uint8_t mac[6]) { return (mac[0] & 0x01u) == 0; }

/* Did the arm actually land? Reads back the gate (net_type) and the RA the ACK
* engine matches (MACID), composed exactly as enable() writes them — keeping
* the register map in ONE file, so a change to enable() cannot silently
* diverge from a copy of it somewhere else.
*
* NB the BSSID companion at 0x0618 is written but not checked: the ACK engine
* matches on MACID, and 0x0618 is programmed only because the proven AP recipe
* programs both. Verifying the two fields that gate the behaviour keeps this
* honest without asserting on one that does not. */
inline bool verify(RtlAdapter &dev, const uint8_t mac[6]) {
const uint32_t want_lo = (uint32_t)mac[0] | ((uint32_t)mac[1] << 8) |
((uint32_t)mac[2] << 16) | ((uint32_t)mac[3] << 24);
const uint16_t want_hi = (uint16_t)(mac[4] | (mac[5] << 8));
return (dev.rtw_read8(0x0102) & 0x03u) == 0x03u &&
dev.rtw_read<uint32_t>(0x0610) == want_lo &&
dev.rtw_read16(0x0614) == want_hi;
}

} /* namespace ack */
} /* namespace devourer */
9 changes: 7 additions & 2 deletions src/AdapterCaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,17 @@ struct AdapterCaps {
* an earlier "broken" verdict was a harness artifact: the responder's arm
* was never verified, so a silently dead responder read as on=0/off=0),
* 8822B, 8812C/8822C, 8812E/8822E (the 8811A rides the 8812 die path and
* inherits its row). False-as-unmeasured (the
* inherits its row), 8733B (1736/1736 frames ACKed at retries_mean 0.00,
* retarget-proof and disarm-proof — tests/ack_txreport_matrix.sh run with
* the 8733B as the responder). False-as-unmeasured (the
* vht_2g4_ok reading: unmeasured, not incapable): the 8821C — it shares
* the recipe but no 8821CU/CE cell has run. FALSE on Kestrel:
* SetAckResponder is not implemented on the AX generation.
* tx_retry_limit_ok: DEVOURER_TX_RETRY_LIMIT drives hardware autonomous
* retransmission (measured 12/0/12 A/B: 8821AU, 8812BU, 8822CU; Kestrel
* retransmission (measured 12/0/12 A/B: 8821AU, 8812BU, 8822CU; the 8733B
* by airtime dose-response instead, 0/3/12 -> 0.93/3.93/12.27 airings per
* frame, because that die has no CCX path to judge its own frames
* (tests/rtl8733b_retry_limit_onair.sh); Kestrel
* 8832CU witness-measured — the AX WD DATA_TXCNT_LMT field counts
* ATTEMPTS, folded +1 to the N-retries contract, limits {0,2,8} -> modal
* on-air copies {1,3,8-9}). FALSE on the 8814A die (the vendor
Expand Down
8 changes: 5 additions & 3 deletions src/DeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,8 @@ struct DeviceConfig {
/* env: DEVOURER_TX_RETRY_LIMIT — per-frame hardware retry limit (0..63;
* Kestrel ceiling 62 — its attempts-counting WD field folds +1). Maps to
* the TX descriptor DATA_RETRY_LIMIT / RTS_DATA_RTY_LMT field on the
* 11ac generations and wd_info DATA_TXCNT_LMT on Kestrel. 0 = no retries
* 11ac generations and the RTL8733B, and wd_info DATA_TXCNT_LMT on
* Kestrel. 0 = no retries
* (WFB default: FEC provides reliability, not MAC retries). On a busy
* half-duplex link retries flood the air and blind the receiver.
* Hardware-ARQ (SetAckResponder + unicast TA, docs/scheduled-mac.md)
Expand All @@ -234,8 +235,9 @@ struct DeviceConfig {
* per-bandwidth vendor value is 117 µs), so it also replaces the
* per-chip / per-bandwidth vendor defaults (which ranged 33..128 µs
* and made hardware-ARQ range silently die-dependent). The register:
* REG_ACKTO 0x640 on the 11ac generations, R_AX_RSP_CHK_SIG 0xCC00
* byte0 on Kestrel; the CTS window (REG_CTS2TO 0x641) is separate and
* REG_ACKTO 0x640 on the 11ac generations and the RTL8733B (which
* overwrites its HALMAC vendor default at bring-up), R_AX_RSP_CHK_SIG
* 0xCC00 byte0 on Kestrel; the CTS window (REG_CTS2TO 0x641) is separate and
* untouched. Sizing: ~6.7 µs x round-trip km + ~50 µs ACK flight and
* detection margin; a longer window is NOT free — every retry of a
* LOST frame waits the full window, measured (dead RA, retry 8, max
Expand Down
12 changes: 12 additions & 0 deletions src/jaguar1/RtlJaguarDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,18 @@ bool RtlJaguarDevice::send_packet(const uint8_t *packet, size_t length) {
}

bool RtlJaguarDevice::SetAckResponder(const devourer::MacAddr &mac) {
if (!devourer::ack::is_unicast(mac.data())) {
/* A station cannot ACK-target a group address, so this arm could never
* fire. Refusing beats returning true for a responder that will read as
* silently dead — the shape AdapterCaps.h records from the 8821AU
* episode. Only the precondition is enforced here: adopting the shared
* readback verify() too wants a bench cell per die, since a family whose
* 0x0102 does not read back would start refusing healthy arms. */
_logger->error("{}: ACK responder needs a UNICAST MAC (I/G set in "
"{:02x}) — not armed",
"Jaguar1", mac.bytes[0]);
return false;
}
/* Hardware ACK responder (src/AckResponder.h) — same register recipe as
* the HalMAC generations (0x610/0x618/0x102 are map-identical here). */
devourer::ack::enable(_device, mac.data());
Expand Down
12 changes: 12 additions & 0 deletions src/jaguar2/RtlJaguar2Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,18 @@ void RtlJaguar2Device::apply_replay_wseq() {
}

bool RtlJaguar2Device::SetAckResponder(const devourer::MacAddr &mac) {
if (!devourer::ack::is_unicast(mac.data())) {
/* A station cannot ACK-target a group address, so this arm could never
* fire. Refusing beats returning true for a responder that will read as
* silently dead — the shape AdapterCaps.h records from the 8821AU
* episode. Only the precondition is enforced here: adopting the shared
* readback verify() too wants a bench cell per die, since a family whose
* 0x0102 does not read back would start refusing healthy arms. */
_logger->error("{}: ACK responder needs a UNICAST MAC (I/G set in "
"{:02x}) — not armed",
"Jaguar2", mac.bytes[0]);
return false;
}
/* Hardware ACK responder (src/AckResponder.h): port identity + net_type so
* the MAC auto-ACKs unicast frames to `mac`. Same registers the proven
* StartBeacon/AP path programs, minus the beacon machinery. */
Expand Down
12 changes: 12 additions & 0 deletions src/jaguar3/RtlJaguar3Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2150,6 +2150,18 @@ void RtlJaguar3Device::WriteTsf(uint64_t tsf) {
}

bool RtlJaguar3Device::SetAckResponder(const devourer::MacAddr &mac) {
if (!devourer::ack::is_unicast(mac.data())) {
/* A station cannot ACK-target a group address, so this arm could never
* fire. Refusing beats returning true for a responder that will read as
* silently dead — the shape AdapterCaps.h records from the 8821AU
* episode. Only the precondition is enforced here: adopting the shared
* readback verify() too wants a bench cell per die, since a family whose
* 0x0102 does not read back would start refusing healthy arms. */
_logger->error("{}: ACK responder needs a UNICAST MAC (I/G set in "
"{:02x}) — not armed",
"Jaguar3", mac.bytes[0]);
return false;
}
/* Hardware ACK responder (src/AckResponder.h): port identity + net_type so
* the MAC auto-ACKs unicast frames to `mac`. Same registers the proven
* StartBeacon/AP path programs, minus the beacon machinery. Serialized on
Expand Down
Loading
Loading