diff --git a/src/jaguar1/HalModule.cpp b/src/jaguar1/HalModule.cpp index 8386dfc..f96d845 100644 --- a/src/jaguar1/HalModule.cpp +++ b/src/jaguar1/HalModule.cpp @@ -662,6 +662,18 @@ bool HalModule::rtl8812au_hal_init(uint8_t init_channel) { void HalModule::rtw_hal_deinit() { _logger->info("Jaguar1: clean de-init (stop TRX + card-disable)"); + /* Stop the phydm watchdog FIRST, before any of the writes below. Its + * thread does periodic BB reads/writes (FA counters, DIG on 0xc50/0xe50, + * the EDCCA thresholds), and everything after this point is tearing the + * chip down underneath it — MAC engines off, RX FIFO off, then the + * card-disable power sequence. Left running it races register access + * against power-off, which is the teardown-ordering hazard this driver + * takes seriously everywhere else. Stop() is idempotent (it + * compare-exchanges _running), so the destructor's own Stop() after this + * is a no-op, and a chip torn down twice is still safe. Null whenever + * tuning.phydm_watchdog did not build one, which is the default. */ + if (_phydmWatchdog) + _phydmWatchdog->Stop(); /* Halt the MAC engines before pulling power out from under them, so the * sequence isn't racing DMA that is still moving frames. Mirrors * HalJaguar3::rtw_hal_deinit. */ diff --git a/tests/cca_gates_probe.cpp b/tests/cca_gates_probe.cpp index 495d021..94167b3 100644 --- a/tests/cca_gates_probe.cpp +++ b/tests/cca_gates_probe.cpp @@ -11,6 +11,15 @@ * * sudo build/CcaGatesProbe --pid 0xc812 --channel 36 * sudo build/CcaGatesProbe --pid 0x8812 --channel 36 --hold 12 + * sudo build/CcaGatesProbe --pid 0x0120 --vid 0x2357 --phydm-watchdog + * + * --phydm-watchdog builds Jaguar1's optional phydm thread. Its EDCCA + * tracker is what SetCcaGates has to stop, and it does not exist without + * it, so the tracker cell needs this on Jaguar1 (Jaguar3 ignores it). + * A test lever, not a hint at a better default: that thread is opt-in + * because its periodic BB traffic shares the libusb queue with the TX bulk + * path and costs throughput (see HalModule). This probe never transmits, + * so it pays none of that. * * --hold N keeps each state applied for N seconds so an external peek can * sample it. Exit 0 = every step behaved; 4 = not a Realtek radio; 5 = the @@ -56,7 +65,7 @@ void report(const char *tag, bool ret, bool primary, bool edcca) { int main(int argc, char **argv) { uint16_t vid = 0x0bda, pid = 0xc812; - int channel = 36, retune = 0, fast_retune = 0, hold = 0; + int channel = 36, retune = 0, fast_retune = 0, hold = 0, phydm_wd = 0; for (int i = 1; i < argc; i++) { if (!std::strcmp(argv[i], "--vid") && i + 1 < argc) vid = (uint16_t)std::strtoul(argv[++i], nullptr, 0); @@ -70,6 +79,8 @@ int main(int argc, char **argv) { fast_retune = std::atoi(argv[++i]); else if (!std::strcmp(argv[i], "--hold") && i + 1 < argc) hold = std::atoi(argv[++i]); + else if (!std::strcmp(argv[i], "--phydm-watchdog")) + phydm_wd = 1; } auto logger = std::make_shared(); @@ -95,6 +106,13 @@ int main(int argc, char **argv) { session.adopt_lock(lock); devourer::DeviceConfig cfg; + /* Jaguar1's EDCCA tracker only EXISTS when the phydm watchdog is built — + * HalModule constructs it solely under tuning.phydm_watchdog, which is off + * by default. Without this the tracker cell has nothing to catch tracking, + * and reports "no tracker running" on a backend whose tracking path is + * simply not instantiated. Jaguar3 ignores the field (its phydm runtime + * rides the RX/coex thread), so passing it there costs nothing. */ + cfg.tuning.phydm_watchdog = phydm_wd != 0; WiFiDriver driver(logger); std::unique_ptr owned = driver.CreateRadio(handle, ctx, lock, cfg); if (!owned) { @@ -114,8 +132,9 @@ int main(int argc, char **argv) { * pokes the wrong one reports "no tracker running" instead of failing — * a false negative on exactly the arm the split exists to serve. Caps are * resolved at construction, so this is readable before bring-up. */ - std::printf("GATES-GEN %s\n", - devourer::generation_name(dev->GetAdapterCaps().generation)); + std::printf("GATES-GEN %s phydm_watchdog=%d\n", + devourer::generation_name(dev->GetAdapterCaps().generation), + phydm_wd); std::fflush(stdout); /* Pre-bring-up: both calls must refuse, and the refusal must not write the diff --git a/tests/cca_gates_regcheck.sh b/tests/cca_gates_regcheck.sh index 4928355..61ccaeb 100755 --- a/tests/cca_gates_regcheck.sh +++ b/tests/cca_gates_regcheck.sh @@ -41,7 +41,18 @@ # poking the other family's register reports "no tracker" and # passes a broken tracker silently. Skipped where no tracker runs # in the default arm, or where the generation has no known -# threshold register. +# threshold register. Runs the probe with --phydm-watchdog: +# Jaguar1's tracker IS the optional phydm thread, off by default, +# so without it this cell measures a backend whose tracking path +# was never built and calls that "no tracker". +# DEPENDS ON DIG BEING IN MOTION. The tracker is write-on-change +# (l2h != _edcca_last_l2h), so it only rewrites the marker while +# DIG is still walking IGI. Each arm restarts the probe, so DIG +# restarts with it and is walking during the sample window; if it +# has converged instead, the default arm reads as "no tracker" +# and the EDCCA-off arm SKIPs. That degrades to no verdict rather +# than a false one, but a SKIP here means the cell could not +# create the condition, not that the tracker behaved. # retune the state survives SetMonitorChannel and FastRetune, within a # band and across a band change. Jaguar3 re-asserts by design; # Jaguar1 merely is not clobbered (see src/IRtlRadio.h) — so this @@ -51,6 +62,12 @@ # # Usage: sudo -v && tests/cca_gates_regcheck.sh # every plugged part # PIDS=0xc812 sudo -v && tests/cca_gates_regcheck.sh +# VID=0x2357 PIDS=0x0120 tests/cca_gates_regcheck.sh # non-Realtek VID +# +# VID applies to the register peeks as well as the probe, so an adapter that +# enumerates under a vendor's own VID (TP-Link 0x2357, and most retail parts) +# is checkable — addressing chipstate by PID alone silently looked for it +# under 0x0bda and failed every register cell. set -u ROOT="$(cd "$(dirname "$0")/.." && pwd)" OUT="${CCA_GATES_OUT:-/tmp/devourer-cca-gates}" @@ -100,7 +117,7 @@ done # dead probe, i.e. the default arm would pass on no evidence. peek32() { # $1=pid $2=addr local bytes n - bytes=$(sudo -n "$BUILD/chipstate" --pid "$1" --no-claim \ + bytes=$(sudo -n "$BUILD/chipstate" --vid "$VID" --pid "$1" --no-claim \ --peek "$(printf '0x%x-0x%x' "$2" $(( $2 + 3 )))" 2>&1 | sed -n 's/^0x[0-9a-fA-F]\{4\}://p' | tr -s ' ' '\n' | grep -E '^[0-9a-f]{2}$' | head -4) @@ -109,7 +126,7 @@ peek32() { # $1=pid $2=addr printf '%s\n' "$bytes" | awk '{b[NR]=strtonum("0x"$1)} END{printf "%u\n", b[1]+b[2]*256+b[3]*65536+b[4]*16777216}' } -poke32() { sudo -n "$BUILD/chipstate" --pid "$1" --no-claim \ +poke32() { sudo -n "$BUILD/chipstate" --vid "$VID" --pid "$1" --no-claim \ --poke "$(printf '0x%x=0x%x:4' "$2" "$3")" >/dev/null 2>&1; } bit() { echo $(( ( $1 >> $2 ) & 1 )); } @@ -185,7 +202,7 @@ for pid in $PIDS; do [ $rc -eq 0 ] && pass "$pid api: probe walk clean" \ || fail "$pid api: probe reported failures (see $log)" - gen=$(sed -n 's/^GATES-GEN //p' "$log" | head -1) + gen=$(sed -n 's/^GATES-GEN //p' "$log" | head -1 | cut -d' ' -f1) note "$pid generation: ${gen:-unknown}" # --- regs + cntdown --------------------------------------------------- @@ -282,7 +299,11 @@ for pid in $PIDS; do for arm in "0 0" "0 1"; do set -- $arm; want_p=$1; want_e=$2 tlog="$OUT/track-$pid-$want_p$want_e.log" - start_hold "$pid" "$tlog" + # --phydm-watchdog because Jaguar1's EDCCA tracker is only built when + # tuning.phydm_watchdog is set; without it this cell measures a + # backend whose tracking path was never instantiated and calls that + # "no tracker". Jaguar3 ignores the flag. + start_hold "$pid" "$tlog" --phydm-watchdog if ! wait_marker "$tlog" "^GATES set-primary$want_p-edcca$want_e "; then fail "$pid track: probe never reported primary=$want_p edcca=$want_e" stop_hold; continue