diff --git a/CMakeLists.txt b/CMakeLists.txt index fbf4798..cd8a49b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1112,6 +1112,16 @@ target_link_libraries(TxPktBankSelftest PRIVATE devourer) target_include_directories(TxPktBankSelftest PRIVATE src) add_test(NAME txpkt_bank_policy COMMAND TxPktBankSelftest) +# Headless guard for the Jaguar3 RX-descriptor PHYST bit (DW0 bit 26): the +# "a PHY-status report was actually written for this frame" flag that keeps +# A-MPDU subframes' reserved-but-unwritten drvinfo bytes out of the RF EMAs. +if(DEVOURER_JAGUAR3) + add_executable(RxPhystSelftest tests/rx_physt_selftest.cpp) + target_link_libraries(RxPhystSelftest PRIVATE devourer) + target_include_directories(RxPhystSelftest PRIVATE src) + add_test(NAME rx_physt_bit COMMAND RxPhystSelftest) +endif() + # Headless guard for the USB TX-aggregation URB packing (src/TxAggPlan.h) — # block alignment, the never-a-bulk-multiple boundary shim, the OQT # descs-per-bulk guard, the frame/byte caps — plus the HalMAC descriptor agg diff --git a/src/jaguar3/FrameParserJaguar3.h b/src/jaguar3/FrameParserJaguar3.h index 9da2b48..2370227 100644 --- a/src/jaguar3/FrameParserJaguar3.h +++ b/src/jaguar3/FrameParserJaguar3.h @@ -217,6 +217,11 @@ struct Rx8822cFrame { uint8_t shift; /* SHIFT_SZ */ uint32_t tsfl; /* hardware TSF-low at receive */ bool paggr; /* MPDU arrived inside an A-MPDU */ + bool physt; /* a PHY-status report was written for THIS frame; + * drvinfo space is reserved on every frame + * (RX_DRVINFO_SZ is global), so without this bit the + * area holds stale bytes — notably on all-but-one + * subframe of an A-MPDU */ uint8_t ppdu_cnt; /* 2-bit received-PPDU counter */ uint32_t next_offset; /* 8-byte-aligned offset of the next frame in an agg */ }; @@ -239,6 +244,7 @@ inline bool parse_rx_8822c(const uint8_t *buf, size_t buflen, out.rx_rate = static_cast(GET_RX_DESC_RX_RATE_8822C(buf)); out.tsfl = static_cast(GET_RX_DESC_TSFL_8822C(buf)); out.paggr = GET_RX_DESC_PAGGR_8822C(buf) != 0; + out.physt = GET_RX_DESC_PHYST_8822C(buf) != 0; out.ppdu_cnt = static_cast(GET_RX_DESC_PPDU_CNT_8822C(buf)); uint32_t frame_off = @@ -269,16 +275,19 @@ inline bool parse_rx_8822c(const uint8_t *buf, size_t buflen, * vendor's s(8,1) fields). The page type is taken from byte0 low nibble * (page_num) rather than guessed from the rate: 0 = CCK type0, else an OFDM * page; per-stream EVM/SNR are only present on the type1 OFDM page. - * Requires physts_len >= 28. */ -inline void parse_phy_sts_jgr3(const uint8_t *physts, uint16_t physts_len, + * Requires physts_len >= 28. Returns true iff physts pointed at a page + * layout this function actually understands (page 0 CCK or page 1 OFDM + * type1) and `a` was filled from it; false on a null/short buffer or any + * other page number (caller should not trust `a`'s signal fields then). */ +inline bool parse_phy_sts_jgr3(const uint8_t *physts, uint16_t physts_len, rx_pkt_attrib &a) { if (physts == nullptr || physts_len < 28) - return; + return false; const uint8_t page_num = physts[0] & 0x0f; if (page_num == 0) { /* type0 (CCK): DW0 = page_num(0), pwdb_a(1). Single path-A power. */ a.rssi[0] = physts[1]; - return; + return true; } /* OFDM header (valid for every jgr3 OFDM page): per-path pwdb[4] at bytes * 1..4, DW1 byte5 l_rxsc[3:0]/ht_rxsc[7:4], DW1 byte7 flags. */ @@ -303,7 +312,9 @@ inline void parse_phy_sts_jgr3(const uint8_t *physts, uint16_t physts_len, a.evm[i] = static_cast(physts[16 + i]); a.snr[i] = static_cast(physts[24 + i]); } + return true; } + return false; } } /* namespace jaguar3 */ diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 77c70b5..cdbabad 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -322,12 +322,17 @@ void RtlJaguar3Device::StartRxLoop(Action_ParsedRadioPacket packetProcessor) { * present (monitor_rx_cfg enables APP_PHYSTS + RX_DRVINFO_SZ=4, so the * 32-byte report is counted in drvinfo). Skips C2H reports and any * frame whose drvinfo is too short (e.g. CCK, which carries no OFDM - * report). The report sits immediately after the 24-byte descriptor. */ - if (!is_c2h && f.drvinfo_size >= 28) - jaguar3::parse_phy_sts_jgr3(data + off + jaguar3::RXDESC_SIZE_8822C, - f.drvinfo_size, p.RxAtrib); + * report). The report sits immediately after the 24-byte descriptor. + * f.physt (RX desc DW0 bit 26) says the PHY actually WROTE a report + * for this frame — the drvinfo space itself is reserved on every + * frame, so on A-MPDU subframes without the bit it holds stale bytes + * whose page nibble can alias 0/1 (contaminated RSSI/SNR tails). */ + if (!is_c2h && f.physt && f.drvinfo_size >= 28) + p.RxAtrib.physt = jaguar3::parse_phy_sts_jgr3( + data + off + jaguar3::RXDESC_SIZE_8822C, f.drvinfo_size, + p.RxAtrib); p.Data = std::span(const_cast(f.frame), f.frame_len); - if (!p.RxAtrib.crc_err) { + if (!p.RxAtrib.crc_err && p.RxAtrib.physt) { _rxq.add(p.RxAtrib.rssi[0], p.RxAtrib.snr[0], p.RxAtrib.evm[0]); _rxpaths.add(p.RxAtrib.rssi, p.RxAtrib.snr, p.RxAtrib.evm, 2); /* 8822C/8822E are 2T2R */ diff --git a/tests/rx_physt_selftest.cpp b/tests/rx_physt_selftest.cpp new file mode 100644 index 0000000..14fe4ce --- /dev/null +++ b/tests/rx_physt_selftest.cpp @@ -0,0 +1,58 @@ +/* Headless guard for the Jaguar3 RX-descriptor PHYST bit (DW0 bit 26, + * "the drvinfo area of THIS frame holds a written PHY-status report"). + * Inside an A-MPDU the drvinfo space is reserved on every subframe + * (RX_DRVINFO_SZ is a global register) but the PHY writes a report only + * where this bit is set — parsing the reserved bytes anyway reads stale + * garbage that can alias a valid page number, which contaminates the + * RSSI/SNR tails. A bit-position or plumbing regression here fails ctest + * instead of poisoning the RF EMAs. */ +#include +#include + +#include "jaguar3/FrameParserJaguar3.h" + +static int g_fail = 0; +#define CHECK(cond, ...) \ + do { \ + if (!(cond)) { \ + ++g_fail; \ + std::printf("FAIL: " __VA_ARGS__); \ + std::printf("\n"); \ + } \ + } while (0) + +/* 24-byte descriptor + 32-byte drvinfo + a 60-byte PSDU. */ +static constexpr uint32_t kDrvInfo = 32; +static constexpr uint32_t kFrameLen = 60; +static constexpr size_t kBufLen = + jaguar3::RXDESC_SIZE_8822C + kDrvInfo + kFrameLen; + +static void make_desc(uint8_t *buf, bool physt) { + std::memset(buf, 0, kBufLen); + /* DW0: PKT_LEN[13:0] = 60, DRV_INFO_SIZE[19:16] = 4 (units of 8 bytes), + * SHIFT[25:24] = 0, PHYST = bit 26. */ + buf[0] = kFrameLen; + buf[2] = kDrvInfo / 8; + if (physt) + buf[3] |= 0x04; +} + +static void test_physt_bit_decoded() { + uint8_t buf[kBufLen]; + jaguar3::Rx8822cFrame f; + + make_desc(buf, true); + CHECK(jaguar3::parse_rx_8822c(buf, kBufLen, f), "physt=1 desc must parse"); + CHECK(f.physt, "PHYST set in DW0 bit 26 -> Rx8822cFrame.physt true"); + + make_desc(buf, false); + CHECK(jaguar3::parse_rx_8822c(buf, kBufLen, f), "physt=0 desc must parse"); + CHECK(!f.physt, "PHYST clear -> Rx8822cFrame.physt false"); +} + +int main() { + test_physt_bit_decoded(); + if (g_fail == 0) + std::printf("rx_physt_selftest: all checks passed\n"); + return g_fail == 0 ? 0 : 1; +}