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
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 15 additions & 4 deletions src/jaguar3/FrameParserJaguar3.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
};
Expand All @@ -239,6 +244,7 @@ inline bool parse_rx_8822c(const uint8_t *buf, size_t buflen,
out.rx_rate = static_cast<uint8_t>(GET_RX_DESC_RX_RATE_8822C(buf));
out.tsfl = static_cast<uint32_t>(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<uint8_t>(GET_RX_DESC_PPDU_CNT_8822C(buf));

uint32_t frame_off =
Expand Down Expand Up @@ -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. */
Expand All @@ -303,7 +312,9 @@ inline void parse_phy_sts_jgr3(const uint8_t *physts, uint16_t physts_len,
a.evm[i] = static_cast<int8_t>(physts[16 + i]);
a.snr[i] = static_cast<int8_t>(physts[24 + i]);
}
return true;
}
return false;
}

} /* namespace jaguar3 */
Expand Down
15 changes: 10 additions & 5 deletions src/jaguar3/RtlJaguar3Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment on lines +330 to +333

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Physt semantics diverge by chipset 🐞 Bug ≡ Correctness

Jaguar3 now reports RxAtrib.physt=false when the descriptor bit is set but the report is short or
uses an unsupported page, while Jaguar1 and RTL8733B expose the raw descriptor bit. Consumers of the
shared packet callback can therefore no longer use physt consistently to identify which A-MPDU
subframe carried PHY status.
Agent Prompt
## Issue description
Jaguar3 assigns PHY parse success to the shared `RxAtrib.physt` field, whereas other generations assign the raw RX-descriptor PHYST bit. Preserve the descriptor-bit meaning and use a separate local parse-success flag to gate RF EMAs.

## Issue Context
`RxAtrib.physt` is exposed through the generation-neutral `Packet` callback. `parse_phy_sts_jgr3` may return false even when the descriptor PHYST bit is set, including for unsupported pages, so these two facts must remain separate.

## Fix Focus Areas
- src/jaguar3/RtlJaguar3Device.cpp[297-340]
- src/jaguar3/FrameParserJaguar3.h[278-317]
- src/jaguar1/FrameParser.cpp[72-89]
- src/rtl8733b/Rtl8733bDevice.cpp[353-380]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

p.Data = std::span<uint8_t>(const_cast<uint8_t *>(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 */
Expand Down
58 changes: 58 additions & 0 deletions tests/rx_physt_selftest.cpp
Original file line number Diff line number Diff line change
@@ -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 <cstdio>
#include <cstring>

#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;
}