Skip to content
Open
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
11 changes: 7 additions & 4 deletions docs/aggregation.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,13 @@ numbers above came from.
arms the MAC's autonomous ACK engine while monitor RX/injection continue
unchanged: port identity (MACID/BSSID 0x610/0x618 = `mac`) + net_type (0x102
[1:0] = AP). No beacon machinery, no ADDBA session state, no CAM entry.
Which half of that pair is actually the gate is per-die: on the generations
the AP-mode work covered, net_type is; on the RTL8733B it is inert and the
engine matches MACID alone, so a disarm there must move the identity
(`src/AckResponder.h` retarget(), `src/AdapterCaps.h`).
Which half of that pair ends live response behavior is per-die. net_type
participates on the Jaguar generations covered by the AP-mode work, but a
reference RTL8812AU still answered on the old MACID after NoLink read back and
needed its pre-arm MACID restored (BSSID is restored too as defensive port
state, not as a claimed response gate). On RTL8733B net_type is wholly inert
and the engine matches MACID alone. See the per-die evidence in
`src/AdapterCaps.h` and the shared mechanics in `src/AckResponder.h`.

With a responder armed, a peer TXing unicast QoS-Data (normal ack-policy) to
`mac` runs a full hardware ARQ loop — SIFS-timed ACKs from the responder,
Expand Down
21 changes: 15 additions & 6 deletions docs/scheduled-mac.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,21 @@ SW_DEFINE tag-echo gap counting. An off verdict also requires at least 90% of
reported frames at the configured retry limit (`MIN_RETRY_PIN_RATE`); one
pinned outlier cannot stand in for the claimed retry-pinned distribution.

When the configured responder is an RTL8733B (`0bda:f72b` or `0bda:b733`),
the harness adds a fourth **disarmed** phase: arm MAC1, start a timer only
after the backend has completed bring-up and arming, clear it in the same live
process, then solicit MAC1 and expect the off verdict. That phase is
deliberately skipped for the default Jaguar1 responder; its disarm semantics
require separate backend-specific evidence.
When the configured responder has a measured backend-owned hook — RTL8733B
(`0bda:f72b` / `0bda:b733`) or the reference RTL8812AU (`0bda:8812`) — the
harness adds a fourth **disarmed** phase: arm MAC1, start a timer only after
that backend has completed bring-up and arming, clear it in the same live
process, then solicit MAC1 and expect the off verdict. Other responders are
skipped rather than using a generic timer whose ordering against `Init()` is
undefined.

The Jaguar1 disarm cell was measured on a reference `0bda:8812` responder
with a `0bda:c812` solicitor at channel 36/MCS3/retry limit 12. It reproduced
the gate-only failure and passed after restoring the captured pre-arm MACID;
`src/AdapterCaps.h` owns the exact counts and the same-address adversary. The
implementation also restores and readback-verifies BSSID as defensive
port-state cleanup; the ACK-rate result does not establish that BSSID affected
response behavior.

TX sessions run `DEVOURER_TX_WITH_RX=thread`: CCX reports arrive on the C2H
RX path, so J1/J2 TX-only sessions never see them (measured: J2 TX-only = 0
Expand Down
38 changes: 22 additions & 16 deletions examples/rx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1447,9 +1447,9 @@ int main(int argc, char **argv) {
.f("stage", "demo.create_device")
.f("ms", ms_since_start());
devourer::emit_adapter_caps(*g_ev, rtlDevice);
/* RTL8733B-only measurement hook. Scheduling belongs to the concrete
* backend so the delay starts after its verified arm/bring-up rather than
* racing Init from a generic side thread. Refuse other generations: a green
/* Backend-scoped measurement hook. Scheduling belongs to each measured
* concrete backend so the delay starts after its arm/bring-up rather than
* racing Init from a generic side thread. Refuse unmeasured paths: a green
* run that cleared a cold port before Init armed it is false evidence. */
if (const char *d = std::getenv("DEVOURER_ACK_DISARM_AFTER_MS")) {
const char *responder = std::getenv("DEVOURER_ACK_RESPONDER");
Expand All @@ -1471,22 +1471,28 @@ int main(int argc, char **argv) {
d);
return 1;
}
bool scheduled = false;
#if defined(DEVOURER_HAVE_JAGUAR1)
if (auto *jaguar1 = dynamic_cast<RtlJaguarDevice *>(rtlDevice))
scheduled = jaguar1->ScheduleAckResponderDisarmForTest(
static_cast<uint32_t>(ms));
#endif
#if defined(DEVOURER_HAVE_8733B)
auto *rtl8733b = dynamic_cast<Rtl8733bDevice *>(rtlDevice);
if (rtl8733b == nullptr) {
logger->error("DEVOURER_ACK_DISARM_AFTER_MS is RTL8733B-only: "
"refusing a race-prone measurement on {}",
devourer::generation_name(
rtlDevice->GetAdapterCaps().generation));
return 1;
if (auto *rtl8733b = dynamic_cast<Rtl8733bDevice *>(rtlDevice)) {
rtl8733b->ScheduleAckResponderDisarmForTest(
static_cast<uint32_t>(ms));
scheduled = true;
}
rtl8733b->ScheduleAckResponderDisarmForTest(
static_cast<uint32_t>(ms));
#else
logger->error("DEVOURER_ACK_DISARM_AFTER_MS requires an RTL8733B-enabled "
"build");
return 1;
#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::generation_name(
rtlDevice->GetAdapterCaps().generation));
return 1;
}
}
}
/* The BB-debug-port / queue-depth research helpers are Jaguar1-only, so
Expand Down
97 changes: 92 additions & 5 deletions src/AckResponder.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
* matches MACID while net_type reads NoLink, so MAC bring-up already permits
* responses to the adapter's own address and SetAckResponder retargets that
* match to the requested address. See the measured truth table in
* AdapterCaps.h and Rtl8733bDevice::ClearAckResponder.
* AdapterCaps.h and the RTL8733B/Jaguar1 clear implementations.
*
* This header carries that register recipe minus the beacon machinery. Which
* half controls a live disarm is a measured per-die property: use the shared
* gate-only clear only where net_type is sufficient; otherwise the backend
* must also move the identity off the responder address with retarget().
* must also move the identity off the responder address with retarget() or an
* exact captured-identity restore.
*
* On the adapter combinations exercised by tests/ampdu_ba_check.sh, the SAME
* gate also enables the hardware BlockAck responder. RTL8733B has its own
Expand Down Expand Up @@ -99,21 +100,21 @@ inline bool enable(RtlAdapter &dev, const uint8_t mac[6]) noexcept {
return false;
} catch (...) {
/* SetAckResponder is a bool contract. Its callers perform a verified
* rollback and report UNKNOWN state if transport reads remain unavailable. */
* rollback and diagnose whichever register readbacks remain unavailable. */
return false;
}
}

/* Shared gate-only clear for dies where net_type controls the responder.
* RTL8733B callers must additionally retarget MACID. */
* RTL8733B and Jaguar1/CHIP_8812 callers must additionally retarget MACID. */
inline bool disable(RtlAdapter &dev) {
const uint8_t nt = dev.rtw_read8(0x0102);
return dev.rtw_write8(0x0102, static_cast<uint8_t>(nt & ~0x03u));
}

/* Point the ACK-match identity at another address WITHOUT touching the gate.
*
* On the RTL8733B this is the ONLY thing that changes whether the port answers.
* On RTL8733B this is the ONLY thing that changes whether the port answers.
* net_type is inert there — measured, three cells, single-shot ACK rate at
* MCS3 with a Jaguar1 soliciting:
*
Expand Down Expand Up @@ -141,6 +142,11 @@ inline bool disable(RtlAdapter &dev) {
* (src/jaguar1/HalModule.cpp, EepromManager.h) — and a radio being disarmed may
* still be injecting. Zero would not remove the match either, only move it:
* 00:00:00:00:00:00 has the I/G bit clear, so is_unicast() accepts it.
* Jaguar1/CHIP_8812 has a distinct measured failure with the same remedy:
* NoLink verifies after a gate-only clear, but the old MACID continues
* answering until the backend restores its captured pre-arm port identity.
* AdapterCaps.h owns that before/after evidence; the result is not generalized
* to other Jaguar1 dies.
*
* Gate untouched on purpose: this is the identity half, so a caller composes it
* with disable() in whichever order its die needs, and no generation gets a
Expand Down Expand Up @@ -176,11 +182,92 @@ inline bool disable_verified(RtlAdapter &dev) noexcept {
}
}

/* Exact port-0 identity snapshot/restore for a backend that must return more
* than MACID to its pre-arm state. Keep the packed register representation so
* no byte-order conversion can diverge between capture and restore. */
struct PortIdentity {
uint32_t macid_lo = 0;
uint16_t macid_hi = 0;
uint32_t bssid_lo = 0;
uint16_t bssid_hi = 0;
};

inline bool snapshot_port_identity(RtlAdapter &dev, PortIdentity &out) noexcept {
try {
out = {.macid_lo = dev.rtw_read<uint32_t>(0x0610),
.macid_hi = dev.rtw_read16(0x0614),
.bssid_lo = dev.rtw_read<uint32_t>(0x0618),
.bssid_hi = dev.rtw_read16(0x061c)};
return true;
} catch (...) {
return false;
}
}

inline bool port_mac_is(const PortIdentity &identity,
const uint8_t mac[6]) noexcept {
return identity.macid_lo == macid_lo(mac) &&
identity.macid_hi == macid_hi(mac);
}

inline bool restore_port_identity(RtlAdapter &dev,
const PortIdentity &identity) noexcept {
/* Keep each operation exception-contained so all four halves are attempted
* even if a transport throws instead of returning false. */
auto write32 = [&dev](uint16_t reg, uint32_t value) noexcept {
try {
return dev.rtw_write<uint32_t>(reg, value);
} catch (...) {
return false;
}
};
auto write16 = [&dev](uint16_t reg, uint16_t value) noexcept {
try {
return dev.rtw_write16(reg, value);
} catch (...) {
return false;
}
};
const bool ml = write32(0x0610, identity.macid_lo);
const bool mh = write16(0x0614, identity.macid_hi);
const bool bl = write32(0x0618, identity.bssid_lo);
const bool bh = write16(0x061c, identity.bssid_hi);
return ml && mh && bl && bh;
}

inline bool port_identity_is(RtlAdapter &dev,
const PortIdentity &identity) noexcept {
try {
return dev.rtw_read<uint32_t>(0x0610) == identity.macid_lo &&
dev.rtw_read16(0x0614) == identity.macid_hi &&
dev.rtw_read<uint32_t>(0x0618) == identity.bssid_lo &&
dev.rtw_read16(0x061c) == identity.bssid_hi;
} catch (...) {
return false;
}
}

/* 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; }

/* A rollback target must be a usable station identity, not merely readable.
* An all-zero MAC has the I/G bit clear (so is_unicast accepts it), remains a
* matchable address, and also stops TX scheduling on the Jaguar1 8812 path. */
inline bool has_safe_restore_mac(const PortIdentity &identity) noexcept {
return (identity.macid_lo != 0 || identity.macid_hi != 0) &&
(identity.macid_lo & 0x01u) == 0;
}

/* Retargeting can only move a responder off its armed address when the restore
* identity differs. The caller owns both values and must reject equality on a
* die where the gate clear alone was measured insufficient. */
inline bool disarmable_by_retarget(const uint8_t responder[6],
const PortIdentity &restore) noexcept {
return !port_mac_is(restore, responder);
}

/* Did the requested arm recipe land? Reads back 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
Expand Down
15 changes: 12 additions & 3 deletions src/AdapterCaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,18 @@ struct AdapterCaps {
* tests/ack_txreport_matrix.sh run with the 8733B as the responder).
*
* The `on`, `retarget`, and legacy `off` rows establish arming, retargeting,
* and a never-armed control. Only the RTL8733B-scoped `disarmed` row arms and
* clears within one process; it supports the RTL8733B live-disarm claim only
* and establishes nothing about disarm on other dies.
* and a never-armed control. A backend-owned `disarmed` row supports only
* the live-disarm claim for the responder used in that run. On the reference
* RTL8812AU, that cell found that the old gate-only clear left 1946/1946
* soliciting reports ACKed. Restoring the captured pre-arm MACID produced
* 0/1052 ACKed with retries pinned at 12; the never-armed counterpart was

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three different count sets now describe the same claim: this header says 0/1052 and 0/1033, the PR body says 0/997 and 0/1077, and docs/scheduled-mac.md points here as the owner of "the exact counts". They are different runs and all agree, but a caps header comment is turning into a measurement log. Keep one set here (or point at the doc that carries the table) so the next edit does not have to reconcile them.

* 0/1033. A separate own-MAC adversary was 0/1074 never armed but 1120/1120
* after arm then same-address clear, so an arm equal to the captured MACID
* is refused. The implementation also restores and readback-verifies BSSID
* as port-state hygiene; the ACK-rate result does not attribute the
* behavioral change to BSSID. The implementation covers the shared
* CHIP_8812 path, but its 1T1R RTL8811AU cut was not separately measured;
* 8814A/8821A and the HalMAC generations do not inherit the result.
*
* On the 8733B the net_type gate is INERT and the engine matches MACID
* alone: at single-shot ACK rate a never-armed port answers on its own EFUSE
Expand Down
14 changes: 8 additions & 6 deletions src/IRtlDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,11 @@ class IRtlDevice {
* when unsupported or when arm/verification fails; false is not proof of
* passive state, so implementations log if rollback cannot be verified.
* Clear is a non-throwing best effort to return net_type to No Link — and,
* on a die whose engine does not consult net_type, to move the port identity
* off `mac` as well, which is the only thing that changes its behaviour
* there (see Rtl8733bDevice::disarm_ack_responder). Clear does not promise
* silence: a die that matches MACID alone answers for whatever address is
* left programmed, including the one MAC bring-up wrote. */
* where clearing net_type does not end the measured response behavior, to
* move the port identity off `mac` as well (see the RTL8733B and
* Jaguar1/CHIP_8812 backends). Clear does not promise silence: a die that
* matches MACID alone answers for whatever address is left programmed,
* including the one MAC bring-up wrote. */
virtual bool SetAckResponder(const devourer::MacAddr &mac) {
(void)mac;
return false;
Expand Down Expand Up @@ -426,7 +426,9 @@ class IRtlDevice {
* process does NOT silence it (bench-bitten: a killed probe's beacon kept
* airing and contaminated the next test's witness) — so any beaconing
* session that ends without a device power-cycle must call this. Idempotent;
* returns false when no beacon was active. */
* returns false when no beacon was active or shutdown could not be verified.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This base-contract sentence now promises a verified stop, but only Jaguar1 verifies: RtlJaguar2Device::StopBeacon and RtlJaguar3Device::StopBeacon still return true unconditionally after their writes. Either scope the sentence to Jaguar1 ("Jaguar1 additionally reads back the three stop controls and returns false if any is still active") or leave the base contract as it was.

* A failed verified stop must be retried (or followed by hardware shutdown)
* before a shared port is reused. */
virtual bool StopBeacon() { return false; }

/* Disable / restore the MAC carrier-sense gate that defers TX — both primary
Expand Down
66 changes: 66 additions & 0 deletions src/jaguar1/BeaconPort.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* Jaguar1 port-0 beacon control helpers.
*
* The beacon engine is autonomous once armed. Its logical port ownership may
* therefore be released only after all three hardware stop controls read back
* inactive, irrespective of the transport write return values. */
#ifndef DEVOURER_JAGUAR1_BEACON_PORT_H
#define DEVOURER_JAGUAR1_BEACON_PORT_H

#include <cstdint>

#include "RtlAdapter.h"

namespace devourer::jaguar1 {

struct BeaconStopResult {
bool transfers_ok = false;
bool function_off = false;
bool tx_stopped = false;
bool no_link = false;

bool verified() const noexcept {
return function_off && tx_stopped && no_link;
}
};

inline BeaconStopResult stop_port0_beacon_verified(RtlAdapter &dev) noexcept {
auto write8 = [&dev](uint16_t reg, uint8_t value) noexcept {
try {
return dev.rtw_write8(reg, value);
} catch (...) {
return false;
}
};
auto clear8 = [&dev](uint16_t reg, uint8_t mask) noexcept {
try {
const uint8_t value = dev.rtw_read8(reg);
return dev.rtw_write8(reg, static_cast<uint8_t>(value & ~mask));
} catch (...) {
return false;
}
};
auto bits_clear = [&dev](uint16_t reg, uint8_t mask) noexcept {
try {
return (dev.rtw_read8(reg) & mask) == 0;
} catch (...) {
return false;
}
};

/* Keep the operations independent: a failure stopping one control must not
* suppress the attempts to stop the other two. */
const bool function_transfer = write8(0x0550, 0x10);
const bool tx_transfer = clear8(0x0422, 0x40);
const bool link_transfer = clear8(0x0102, 0x03);

BeaconStopResult result;
result.transfers_ok = function_transfer && tx_transfer && link_transfer;
result.function_off = bits_clear(0x0550, 0x08);
result.tx_stopped = bits_clear(0x0422, 0x40);
result.no_link = bits_clear(0x0102, 0x03);
return result;
}

} // namespace devourer::jaguar1

#endif // DEVOURER_JAGUAR1_BEACON_PORT_H
Loading
Loading