diff --git a/src/DeviceConfig.h b/src/DeviceConfig.h index d3c10de9..83549860 100644 --- a/src/DeviceConfig.h +++ b/src/DeviceConfig.h @@ -117,7 +117,9 @@ struct DeviceConfig { struct Rx { /* env: DEVOURER_RX_KEEP_CORRUPTED — pass frames that fail the 802.11 FCS * (CRC32) or decryption-ICV check up to the host instead of dropping them - * at the WMAC filter (sets RCR ACRC32|AICV). Jaguar1 + Jaguar2. */ + * at the MAC RX filter (Realtek: RCR ACRC32|AICV; MT7612U: its monitor + * RX filter). Jaguar1, Jaguar2, Jaguar3, the RTL8733B and the MT7612U; + * not ported on Kestrel, where it is silently inert. */ bool keep_corrupted = false; /* env: DEVOURER_TX_WITH_RX — Jaguar3 only: keep the RX filters open and * enable the RX path during a TX (InitWrite) bring-up so StartRxLoop can diff --git a/src/jaguar3/HalJaguar3.cpp b/src/jaguar3/HalJaguar3.cpp index 66abdca6..ba938e18 100644 --- a/src/jaguar3/HalJaguar3.cpp +++ b/src/jaguar3/HalJaguar3.cpp @@ -461,9 +461,13 @@ void HalJaguar3::rtw_hal_deinit() { } /* Monitor-mode RX configuration (devourer-specific; the vendor driver has no - * pure-monitor path). Accept all frames incl. CRC/ICV errors, append PHY status - * drvinfo (so parse_rx_8822c's drvinfo_size is consistent), all RX filter maps - * open. RCR bits: AAP/APM/AM/AB/ACF/AICV/ACRC32 + APP_PHYSTS. */ + * pure-monitor path). Accept all frames, append PHY status drvinfo (so + * parse_rx_8822c's drvinfo_size is consistent), all RX filter maps open. + * RCR base 0xF410400F = AAP/APM/AM/AB + HTC_LOC_CTRL + PKTCTL_DLEN + VHT_DACK + * + APP_FCS/APP_MIC/APP_ICV/APP_PHYSTS (halmac_bit_8822c.h). ACRC32 (BIT8) + * and AICV (BIT9) are NOT in the base — the WMAC drops FCS/ICV-failed frames + * — and are added only under rx.keep_corrupted, same as Jaguar1/2 and the + * RTL8733B. */ void HalJaguar3::monitor_rx_cfg() { constexpr uint16_t REG_RCR_8822C = 0x0608; constexpr uint16_t REG_RXFLTMAP0_8822C = 0x06A0; @@ -474,14 +478,24 @@ void HalJaguar3::monitor_rx_cfg() { * MACRXEN(+ENSWBCN). init_mac_cfg only set CR=0x0F (DMA enable); without * MACRXEN (BIT7) the MAC RX engine never runs — the structured-path RX gap. */ _device.rtw_write16(0x0100, 0x06FF); - /* accept-all + keep FCS/ICV-error frames + append phy-status (BIT28). + /* accept-all + append phy-status (BIT28 is inside the leading 0xF nibble). * BIT0 (AAP) is what makes monitor mode promiscuous for unicast: without it * the WMAC passes only broadcast/multicast/physical-match (APM|AM|AB) up, * silently dropping unicast frames addressed to third parties — e.g. NDPA * control frames and VHT beamforming reports, which is why the beamformee * (whose arm programs the self-MAC to the NDPA RA) saw sounding frames while * a plain monitor did not. */ - _device.rtw_write32(REG_RCR_8822C, 0xF410400F | (1u << 28)); + uint32_t rcr = 0xF410400F; + /* DEVOURER_RX_KEEP_CORRUPTED: also pass FCS/ICV-failed frames (ACRC32 BIT8, + * AICV BIT9). The vendor 8822E driver clears both in init_misc and on every + * opmode change except monitor; the RX descriptor's crc_err/icv_err bits + * (parse_rx_8822c) mark the frames so a FEC consumer can salvage them. + * Verified on one 8812EU unit: with the bits set, FCS-failed frames reach + * the host; without them the count is zero regardless of channel + * conditions. */ + if (_cfg.rx.keep_corrupted) + rcr |= (1u << 8) | (1u << 9); + _device.rtw_write32(REG_RCR_8822C, rcr); _device.rtw_write8(REG_RX_DRVINFO_SZ_8822C, 0x04); _device.rtw_write16(REG_RXFLTMAP0_8822C, 0xFFFF); _device.rtw_write16(REG_RXFLTMAP1_8822C, 0xFFFF); diff --git a/tests/keep_corrupted_j3_ab.sh b/tests/keep_corrupted_j3_ab.sh new file mode 100755 index 00000000..394fb9ff --- /dev/null +++ b/tests/keep_corrupted_j3_ab.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# A/B for DEVOURER_RX_KEEP_CORRUPTED on a Jaguar3 DUT: run rxdemo twice on the +# same channel (knob off, then on), count frames the host saw with the RX +# descriptor's crc_err bit set. Expected: 0 with the knob off (WMAC drops them), +# non-zero with it on — given any ambient traffic weak enough to fail FCS. +# +# sudo tests/keep_corrupted_j3_ab.sh [channel] [seconds] +set -euo pipefail +PID=${1:?usage: $0 [channel] [seconds]} +CH=${2:-6} +SECS=${3:-20} +ROOT=$(cd "$(dirname "$0")/.." && pwd) +RX=$ROOT/build/rxdemo +OUT=${OUT:-/tmp/keep_corrupted_j3_ab} +mkdir -p "$OUT" +pids=() +cleanup() { for p in "${pids[@]:-}"; do [ -n "$p" ] && kill -INT "$p" 2>/dev/null || true; done; } +trap cleanup EXIT + +run_cell() { + local knob=$1 + local log=$OUT/pid${PID}_keep${knob}.jsonl + # The off arm scrubs the knob from the environment (env -u) rather than + # relying on the caller not having exported it; the on arm sets it. Events + # are forced to stdout because that is the stream the counts come from. + local env=(-u DEVOURER_RX_KEEP_CORRUPTED + DEVOURER_PID="$PID" DEVOURER_CHANNEL="$CH" DEVOURER_RX_DUMP_ALL=1 + DEVOURER_EVENTS=stdout DEVOURER_LOG_LEVEL=warn) + [ "$knob" = 1 ] && env+=(DEVOURER_RX_KEEP_CORRUPTED=1) + env "${env[@]}" "$RX" >"$log" 2>"$log.err" & + local p=$!; pids+=("$p") + sleep "$SECS" + # A cell whose rxdemo is no longer running did not measure anything (device + # open / claim / bring-up failed) — refuse to print a zero as a result. + if ! kill -0 "$p" 2>/dev/null; then + local rc=0; wait "$p" 2>/dev/null || rc=$? + echo "FAIL: pid=$PID keep_corrupted=$knob rxdemo exited early (rc=$rc); stderr tail:" >&2 + tail -n 5 "$log.err" >&2 + exit 1 + fi + kill -INT "$p" 2>/dev/null || true; wait "$p" 2>/dev/null || true + local total crc + total=$(grep -c -F '"ev":"rx.corrupt"' "$log" || true) + crc=$(grep -F '"ev":"rx.corrupt"' "$log" | grep -c -F '"crc":1' || true) + echo "pid=$PID keep_corrupted=$knob ch=$CH secs=$SECS frames=$total crc_err=$crc" +} +run_cell 0 +run_cell 1