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
32 changes: 16 additions & 16 deletions src/jaguar3/FrameParserJaguar3.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ constexpr size_t RXDESC_SIZE_8822C = 24; /* RX_DESC_SIZE_88XX */
#define SET_TX_DESC_RATE_ID_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x04, 16, 5, v)
#define SET_TX_DESC_PKT_OFFSET_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x04, 24, 5, v)
#define SET_TX_DESC_USE_RATE_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x0C, 8, 1, v)
#define SET_TX_DESC_DISRTSFB_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x0C, 9, 1, v)
#define SET_TX_DESC_DISDATAFB_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x0C, 10, 1, v)
#define SET_TX_DESC_NAVUSEHDR_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x0C, 15, 1, v)
#define SET_TX_DESC_NDPA_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x0C, 22, 2, v)
Expand Down Expand Up @@ -82,6 +83,7 @@ constexpr size_t RXDESC_SIZE_8822C = 24; /* RX_DESC_SIZE_88XX */
* cal_txdesc_chksum_8822c. */
#define SET_TX_DESC_DMA_TXAGG_NUM_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x1C, 24, 8, v)
#define SET_TX_DESC_EN_HWSEQ_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x20, 15, 1, v)
#define SET_TX_DESC_SW_SEQ_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x24, 12, 12, v)
#define GET_TX_DESC_PKT_OFFSET_8822C(d) LE_BITS_TO_4BYTE((d) + 0x04, 24, 5)

/* --- RX descriptor fields --- */
Expand Down Expand Up @@ -125,38 +127,35 @@ inline void cal_txdesc_chksum_8822c(uint8_t *txdesc) {
/* Fill an 8822C data/monitor-inject TX descriptor (48 bytes, zeroed by caller)
* and finalise its checksum. `bw` is the descriptor BW code (0=20,1=40,2=80),
* `rate_hw` the DESC_RATE* index (MRateToHwRate output), `rate_id` 8(HT)/9(VHT).
* Field choices mirror the Jaguar1 monitor-inject path (MACID 1, USE_RATE,
* DISDATAFB, HW sequence). */
* Field choices mirror SVPcom rtl8812eu's RTL8822E monitor-inject path
* (MACID 1, USE_RATE, DISRTSFB, DISDATAFB, supplied 802.11 sequence). */
inline void fill_data_tx_desc_8822c(uint8_t *d, uint16_t pkt_size,
uint8_t rate_hw, uint8_t rate_id, uint8_t bw,
bool short_gi, bool ldpc, uint8_t stbc,
bool bmc = false, bool ndpa = false,
uint8_t data_sc = 0,
uint8_t pwr_ofset_type = 0,
uint8_t pkt_offset = 0) {
uint8_t pkt_offset = 0,
bool retry = false,
uint16_t seqnum = 0) {
SET_TX_DESC_TXPKTSIZE_8822C(d, pkt_size);
SET_TX_DESC_OFFSET_8822C(d, static_cast<uint32_t>(TXDESC_SIZE_8822C));
SET_TX_DESC_LS_8822C(d, 1);
/* Broadcast/multicast marker: the kernel sets this whenever addr1 is a group
* address, so mark group-addressed frames the same way for a faithful port. */
SET_TX_DESC_BMC_8822C(d, bmc ? 1 : 0);
/* Remaining fields mirror the kernel's 8822e data/inject descriptor so the
* on-wire TXDESC is byte-identical (verified against a usbmon capture of the
* kernel's MCS7 inject): hw-managed sequence (DISQSELSEQ), SU/broadcast group
* (G_ID=63), a bounded per-frame retry limit (RTY_LMT_EN + RTS_DATA_RTY_LMT),
* RA-group 9, and a non-zero SW_DEFINE. NB: these do not change on-air
* throughput (the chip already airs a saturating MCS7 flood at ~77% duty with
* or without them) — they are here for a faithful port, not a fix. */
SET_TX_DESC_DISQSELSEQ_8822C(d, 1);
SET_TX_DESC_G_ID_8822C(d, 0x3f);
/* SVPcom monitor injection keeps the frame's 802.11 sequence number:
* EN_HWSEQ is clear and SW_SEQ is copied from the header. */
SET_TX_DESC_RTY_LMT_EN_8822C(d, 1);
SET_TX_DESC_RTS_DATA_RTY_LMT_8822C(d, 12);
/* RADIOTAP_F_TX_NOACK means zero MAC retries; other injected frames use
* SVPcom's six-attempt limit. */
SET_TX_DESC_RTS_DATA_RTY_LMT_8822C(d, retry ? 6 : 0);

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.

Blocking: this silently disables the jaguar3 hardware-ARQ loop for every devourer consumer.

All in-tree radiotap producers stamp TX_FLAGS NOACK unconditionally (RadiotapBuilder's kTxFlagsNoAck, and examples/tx's hand-rolled headers with 0x08) — including the QoS-Data soliciting path that SetAckResponder + tests/ack_txreport_matrix.sh build on, where the MAC is supposed to retransmit until the hardware ACK arrives. Measured on an 8812CU: master pins retries_max=12 on unanswered unicast, this branch reads retries_max=0, and the matrix's off-phase check (retries_max >= 8) now fails.

The NOACK semantics are right for wfb-ng (it sets the flag deliberately per link). The problem is only that devourer's demos treat NOACK as boilerplate. Suggested resolution — either:

  1. Update the in-tree producers to stop stamping NOACK on frames that solicit ACKs, and re-baseline ack_txreport_matrix.sh in this PR, or
  2. Only honor NOACK→0 when the frame actually carried a radiotap TX_FLAGS field (keep the current 12, or 6, when the field is absent), so existing consumers keep their contract and wfb-ng gets the new one.

Either is fine — it just has to be a deliberate change, not a side effect.

SET_TX_DESC_MACID_8822C(d, 0x01);
SET_TX_DESC_QSEL_8822C(d, 0x12); /* MGMT queue (mirrors Jaguar1 inject) */
SET_TX_DESC_RATE_ID_8822C(d, 9); /* kernel uses RA-group 9 for the inject path */
SET_TX_DESC_USE_RATE_8822C(d, 1);
SET_TX_DESC_DISDATAFB_8822C(d, 0);
SET_TX_DESC_SW_DEFINE_8822C(d, 1);
SET_TX_DESC_DISRTSFB_8822C(d, 1);
SET_TX_DESC_DISDATAFB_8822C(d, 1);
SET_TX_DESC_DATARATE_8822C(d, rate_hw);
SET_TX_DESC_DATA_BW_8822C(d, bw);
/* Data sub-channel: which 20/40 MHz slice of a wider configured channel the
Expand All @@ -170,7 +169,8 @@ inline void fill_data_tx_desc_8822c(uint8_t *d, uint16_t pkt_size,
* descriptor). Inside the checksummed span (0x14) — before the checksum. */
if (pwr_ofset_type)
SET_TX_DESC_TXPWR_OFSET_TYPE_8822C(d, pwr_ofset_type & 0x3);
SET_TX_DESC_EN_HWSEQ_8822C(d, 1);
SET_TX_DESC_EN_HWSEQ_8822C(d, 0);
SET_TX_DESC_SW_SEQ_8822C(d, seqnum & 0x0fff);

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.

Blocking: EN_HWSEQ=0 here applies to all injected frames, not just wfb-ng's.

wfb-ng manages its own 802.11 sequence numbers, so copying the header seq is correct there. But devourer's demos hardcode seq 0x0000 in their headers — examples/tx's QoS builder even comments it /* seq (EN_HWSEQ overwrites) */ — so after this change every demo frame airs seq 0 on jaguar3. That breaks anything ordering on SSN: the A-MPDU/BlockAck path (SetAmpduMode — the measured +30% goodput in docs/aggregation.md was taken under hwseq, and a BA reorder window needs ascending SSNs) and receiver-side dedup heuristics.

Suggestion: fall back to the old hwseq behavior when the caller's header seq is 0 (seqnum == 0EN_HWSEQ=1, else SW_SEQ) — wfb-ng's incrementing counter passes through untouched, callers that never managed seq keep hardware sequencing, and no new knob is needed. Gating on the same TX_FLAGS-present signal as the retry change would also work.

/* USB-agg boundary shim: pkt_offset × 8 bytes of pad between this descriptor
* and its frame (halmac PKT_OFFSET, unit 8 B). 0 = none (byte-identical).
* MUST precede the checksum: on the 8822C the checksum span itself extends
Expand Down
13 changes: 12 additions & 1 deletion src/jaguar3/RtlJaguar3Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1813,6 +1813,7 @@ size_t RtlJaguar3Device::build_tx_block(const uint8_t *packet, size_t length,

uint8_t fixed_rate = MGN_1M;
uint8_t sgi = 0, ldpc = 0, stbc = 0;
uint16_t tx_flags = 0;
ChannelWidth_t bwidth = CHANNEL_WIDTH_20;
bool vht = (radiotap_length != 0x0d);
bool rate_from_radiotap = false; /* did the frame's radiotap carry a rate? */
Expand All @@ -1837,6 +1838,9 @@ size_t RtlJaguar3Device::build_tx_block(const uint8_t *packet, size_t length,
fixed_rate = *it.this_arg;
rate_from_radiotap = true;
break;
case IEEE80211_RADIOTAP_TX_FLAGS:
tx_flags = get_unaligned_le16(it.this_arg);
break;
case IEEE80211_RADIOTAP_CHANNEL:
/* 2 x __le16: frequency (MHz), then flags. Frequency is authoritative
* for the per-packet hop target; flags are ignored (rate/BW come from
Expand Down Expand Up @@ -1964,6 +1968,9 @@ size_t RtlJaguar3Device::build_tx_block(const uint8_t *packet, size_t length,
* the kernel's descriptor for group-addressed frames. */
const uint8_t *dot11 = packet + radiotap_length;
bool bmc = frame_len >= 6 && (dot11[4] & 0x01);
uint16_t seqnum = 0;
if (frame_len >= 24)
seqnum = (get_unaligned_le16(dot11 + 22) >> 4) & 0x0fff;
/* STBC guard (IRtlDevice contract) — 8822C/8822E are 2T2R so this never
* fires today, but keeps the invariant uniform across families: never air an
* STBC frame the chip can't do. */
Expand All @@ -1975,10 +1982,14 @@ size_t RtlJaguar3Device::build_tx_block(const uint8_t *packet, size_t length,
uint8_t pwr_type = _txpkt_dflt_type.load(std::memory_order_relaxed);
if (radiotap_pkt_pwr_db != INT_MIN)
pwr_type = txpkt_type_for_idx(txpkt_idx_for_qdb(radiotap_pkt_pwr_db * 4));
/* SVPcom monitor injection maps RADIOTAP_F_TX_NOACK to no MAC retry.
* wfb-ng sets this for raw-link packets, avoiding a repeated TX burst. */
constexpr uint16_t kRadiotapTxNoAck = 0x0008;

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.

Nit: IEEE80211_RADIOTAP_F_TX_NOACK already exists in src/ieee80211_radiotap.h (0x0008) — use it instead of a local constant.

Related observation (fine as a follow-up, not this PR): jaguar1 parses TX_FLAGS into a variable it never uses, and jaguar2/kestrel ignore the field entirely — after this PR, NOACK means something on jaguar3 only. Worth either a docs note or a TxCaps flag so callers can tell.

const bool retry = (tx_flags & kRadiotapTxNoAck) == 0;
jaguar3::fill_data_tx_desc_8822c(
out, static_cast<uint16_t>(frame_len), MRateToHwRate(fixed_rate), rate_id,
bw_desc, sgi != 0, ldpc != 0, stbc, bmc, ndpa, data_sc, pwr_type,
pkt_offset);
pkt_offset, retry, seqnum);
if (_cfg.tx.report) {
/* DEVOURER_TX_REPORT: SPE_RPT asks the fw for a per-frame CCX TX report;
* the report echoes SW_DEFINE's low byte, so stamp a rotating tag for
Expand Down
16 changes: 15 additions & 1 deletion tests/txagg_selftest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,21 @@ static void test_desc_8822c() {
jaguar3::fill_data_tx_desc_8822c(blk, 1500, 7, 9, 0, false, false, 0,
/*bmc=*/false, /*ndpa=*/false,
/*data_sc=*/0, /*pwr_ofset_type=*/0,
/*pkt_offset=*/1);
/*pkt_offset=*/1, /*retry=*/false,
/*seqnum=*/0xabc);
CHECK((blk[0x0d] & 0x06) == 0x06,
"8822e: forced-rate fallback bits=0x%02x want DISRTSFB|DISDATAFB",
blk[0x0d] & 0x06);
CHECK((blk[0x12] & 0x02) != 0 && (blk[0x12] & 0xfc) == 0,
"8822e: TX_NOACK retry descriptor byte=0x%02x want enable/limit=0",

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.

Good coverage of the retry=false leg (and the bit positions all check out against the descriptor defines). Please add the retry=true leg too — one more fill with /*retry=*/true asserting the limit field reads 6 — so both halves of the new policy are pinned.

blk[0x12]);
CHECK((blk[0x21] & 0x80) == 0 &&
LE_BITS_TO_4BYTE(blk + 0x24, 12, 12) == 0xabc,
"8822e: SW sequence mismatch (hwseq=%u swseq=%u)",
(blk[0x21] >> 7) & 1, LE_BITS_TO_4BYTE(blk + 0x24, 12, 12));
CHECK(LE_BITS_TO_4BYTE(blk + 0x08, 24, 6) == 0 &&
LE_BITS_TO_4BYTE(blk + 0x18, 0, 12) == 0,
"8822e: injected frame must leave G_ID/SW_DEFINE at the SVP defaults");
CHECK((blk[0x07] & 0x1f) == 1, "8822c: pkt_offset field=%u want 1",
blk[0x07] & 0x1f);
const uint16_t ck_pad0 = static_cast<uint16_t>(blk[0x1C] | (blk[0x1D] << 8));
Expand Down
Loading