Skip to content
Merged
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
11 changes: 11 additions & 0 deletions docs/scheduled-mac.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ worst case doubles, then stops growing). Prefer **16** on an ARQ link, or
**8 + a light FEC floor** where airtime is precious; the per-run residual
gap analysis is `tests/arq_fec_dimension.py`.

Retries also change RATE by default: every generation's inject path leaves
the firmware fallback ladder enabled, and retried frames step down (measured
on the 8812CU: MCS3 → 54M → 24M → 18M → 9M → 6M, ~10% of retried frames
finishing below the original rate). `DEVOURER_TX_RETRY_FALLBACK=off` pins
every retry at the descriptor rate (measured: 1,200/1,200) — for
constant-rate links where a 6M re-air of an MCS3 frame costs ~4× the
airtime. There is deliberately no floor form: bounding the ladder via
DATA_RTY_LOWEST_RATE was measured anomalous (the fw reinterprets the bound
inside the RA-group rate space — retries wandered into VHT rates with a 20×
retry inflation), see the `RetryFallback` note in `src/DeviceConfig.h`.

Responder-side capability (same setup, J3 TX as the reference soliciting
station): **8814AU** closes the loop at retries ~0.1 (the bench responder of
choice); **8812AU** works but degraded (97% delivery at ~7 mean retries —
Expand Down
13 changes: 13 additions & 0 deletions examples/common/env_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ devourer::DeviceConfig devourer_config_from_env() {
}
if (env_long("DEVOURER_TX_RETRY_LIMIT", &v))
cfg.tx.retry_limit = static_cast<int>(v < 0 ? 0 : (v > 63 ? 63 : v));
if (const char *e = env_str("DEVOURER_TX_RETRY_FALLBACK")) {
if (str_ieq(e, "off")) {
cfg.tx.retry_fallback = devourer::RetryFallback::Off;
} else {
/* A floor form was measured and rejected — the fw reinterprets
* DATA_RTY_LOWEST_RATE inside the RA-group rate space (see the
* RetryFallback enum note in DeviceConfig.h). */
std::fprintf(stderr,
"devourer [W] DEVOURER_TX_RETRY_FALLBACK='%s' unsupported "
"(only \"off\") — keeping the firmware ladder\n", e);
Comment thread
josephnef marked this conversation as resolved.
std::fflush(stderr); /* the diagnostic plane is per-line flushed */
}
}

/* ---- bf ---- */
if (const char *snd = env_str("DEVOURER_BF_ARM_SOUNDER")) {
Expand Down
19 changes: 19 additions & 0 deletions src/DeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ enum class RxMode : uint8_t {
Decoupled, /* naive worker-thread hand-off (known-bad A/B control) */
};

/* Hardware retry rate-fallback control for injected frames. Every generation
* defaults to the firmware's fallback ladder (measured on the 8812CU:
* retried frames step MCS3 → 54M → 24M → 18M → 9M → 6M, the fw's own 5 GHz
* floor). Off pins every retry at the descriptor DATARATE (DISDATAFB /
* DISABLE_FB = 1; measured: 1,200/1,200 retried frames re-aired at the
* original rate) — for constant-rate links where a low-rate retry costs
* multiples of the frame's airtime. A DATA_RTY_LOWEST_RATE floor form was
* measured and REJECTED: the fw reinterprets the bound inside the RA-group
* rate space (inject runs RA-group 9), wandering retries into VHT rates
* (final_rate 45 = VHT1SS_MCS1 on an HT frame) with a 20x retry inflation —
* the field is not a plain DESC_RATE bound on this fw. */
enum class RetryFallback : uint8_t { Default, Off };

/* What the spsc-fat ring does when its buffer pool runs dry (consumer behind
* under sustained overload). The choice decides which side of the hardware-ARQ
* contract survives overload — the chip ACKs on FIFO admission, so anything
Expand Down Expand Up @@ -205,6 +218,12 @@ struct DeviceConfig {
* (firmware-level retry) and on the 8814A die (vendor DATA_RETRY_LIMIT=0
* carve-out kept). */
int retry_limit = 0;
/* env: DEVOURER_TX_RETRY_FALLBACK — "off" | unset. Unset = the firmware
* fallback ladder with its own floor (the current behaviour, descriptors
* byte-identical). "off" disables per-retry rate fallback (DISDATAFB /
* DISABLE_FB = 1): retries re-air at the descriptor rate. See the
* RetryFallback enum for why there is no floor form. */
RetryFallback retry_fallback = RetryFallback::Default;
/* env: DEVOURER_TX_USB_AGG — USB TX aggregation: max frames packed into
* one bulk-OUT URB by send_packets (0 = off, the default: send_packets
* degrades to a per-frame loop and every TX path is byte-identical to
Expand Down
7 changes: 7 additions & 0 deletions src/jaguar1/RtlJaguarDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,13 @@ size_t RtlJaguarDevice::build_tx_block(const uint8_t *packet, size_t length,
? 1
: 0);
SET_TX_DESC_RETRY_LIMIT_ENABLE_8812(usb_frame, 1);
/* Retry rate-fallback control (DEVOURER_TX_RETRY_FALLBACK): the data path
* leaves DISABLE_FB at 0 (the fw ladder) by default — only the NDPA branch
* disables it. Off pins retries at the descriptor rate; there is no floor
* form (the RetryFallback note in DeviceConfig.h has the measurement).
* Dword3, inside the checksummed 32 bytes. */
if (_cfg.tx.retry_fallback == devourer::RetryFallback::Off)
SET_TX_DESC_DISABLE_FB_8812(usb_frame, 1);
if (!is_8814a) {
/* 88XXau leaves DATA_RETRY_LIMIT=0 for monitor injection on 8814A
* (RETRY_LIMIT_ENABLE stays set to 1 in both).
Expand Down
5 changes: 5 additions & 0 deletions src/jaguar2/FrameParserJaguar2.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ constexpr size_t RXDESC_SIZE_8822B = 24; /* RX_DESC_SIZE_88XX */
#define SET_TX_DESC_CHK_EN_8822B(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x0C, 14, 1, v)
#define SET_TX_DESC_DISDATAFB_8822B(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x0C, 10, 1, v)
#define SET_TX_DESC_DATARATE_8822B(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x10, 0, 7, v)
/* DATA_RTY_LOWEST_RATE (0x10 [12:8]): measured NOT a plain DESC_RATE bound on
* the sibling 8822C fw (RA-group reinterpretation — RetryFallback note in
* DeviceConfig.h). Field documented, deliberately unwritten. */
#define SET_TX_DESC_DATA_RTY_LOWEST_RATE_8822B(d, v) \
SET_BITS_TO_LE_4BYTE((d) + 0x10, 8, 5, v)
#define SET_TX_DESC_DATA_SC_8822B(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x14, 0, 4, v)
#define SET_TX_DESC_DATA_SHORT_8822B(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x14, 4, 1, v)
#define SET_TX_DESC_DATA_BW_8822B(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x14, 5, 2, v)
Expand Down
6 changes: 6 additions & 0 deletions src/jaguar2/RtlJaguar2Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1539,6 +1539,12 @@ size_t RtlJaguar2Device::build_tx_block(const uint8_t *packet, size_t length,
* half-duplex link. Both fields sit inside the checksummed span. */
SET_TX_DESC_RTY_LMT_EN_8822B(out, 1);
SET_TX_DESC_RTS_DATA_RTY_LMT_8822B(out, _cfg.tx.retry_limit);
/* Retry rate-fallback control (DEVOURER_TX_RETRY_FALLBACK): default leaves
* the builder's DISDATAFB=0 (the fw ladder); Off pins retries at the
* descriptor rate. No floor form (the RetryFallback note in DeviceConfig.h
* has the measurement). Same checksummed span as the retry limit. */
if (_cfg.tx.retry_fallback == devourer::RetryFallback::Off)
SET_TX_DESC_DISDATAFB_8822B(out, 1);
jaguar2::cal_txdesc_chksum_8822b(out);
const devourer::AmpduMode am = _ampdu; /* one lock-free load */
if (am.enabled || _cfg.debug.tx_qsel || _cfg.debug.tx_ampdu_max) {
Expand Down
6 changes: 6 additions & 0 deletions src/jaguar3/FrameParserJaguar3.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ constexpr size_t RXDESC_SIZE_8822C = 24; /* RX_DESC_SIZE_88XX */
#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)
#define SET_TX_DESC_DATARATE_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x10, 0, 7, v)
/* DATA_RTY_LOWEST_RATE (0x10 [12:8]): measured NOT a plain DESC_RATE bound —
* the fw reinterprets it inside the RA-group rate space (see the
* RetryFallback note in DeviceConfig.h). Field documented, deliberately
* unwritten. */
#define SET_TX_DESC_DATA_RTY_LOWEST_RATE_8822C(d, v) \
SET_BITS_TO_LE_4BYTE((d) + 0x10, 8, 5, v)
#define SET_TX_DESC_DATA_SC_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x14, 0, 4, v)
#define SET_TX_DESC_DATA_SHORT_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x14, 4, 1, v)
#define SET_TX_DESC_DATA_BW_8822C(d, v) SET_BITS_TO_LE_4BYTE((d) + 0x14, 5, 2, v)
Expand Down
7 changes: 7 additions & 0 deletions src/jaguar3/RtlJaguar3Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,13 @@ size_t RtlJaguar3Device::build_tx_block(const uint8_t *packet, size_t length,
* half-duplex link. Both fields sit inside the checksummed span. */
SET_TX_DESC_RTY_LMT_EN_8822C(out, 1);
SET_TX_DESC_RTS_DATA_RTY_LMT_8822C(out, _cfg.tx.retry_limit);
/* Retry rate-fallback control (DEVOURER_TX_RETRY_FALLBACK): default leaves
* the builder's DISDATAFB=0 (the fw ladder — measured stepping toward 6M
* on deep retries); Off pins retries at the descriptor rate. No floor form
* (the RetryFallback note in DeviceConfig.h has the measurement). Same
* checksummed span as the retry limit. */
if (_cfg.tx.retry_fallback == devourer::RetryFallback::Off)
SET_TX_DESC_DISDATAFB_8822C(out, 1);
jaguar3::cal_txdesc_chksum_8822c(out);
const devourer::AmpduMode am = _ampdu; /* one lock-free load */
if (am.enabled || _cfg.debug.tx_qsel || _cfg.debug.tx_ampdu_max) {
Expand Down
2 changes: 2 additions & 0 deletions tests/arq_e2e_delivery.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ MAC1=${MAC1:-02:12:34:56:78:9a} # DUT responder identity = drone RA
TX_SA=${TX_SA:-02:aa:bb:cc:dd:01} # drone TA (unicast — the I/G footgun)
RETRY_LIMIT=${RETRY_LIMIT:-3} # field report used 3
DRONE_REPORT_N=${DRONE_REPORT_N:-1} # CCX sampling divisor (1 = every frame)
DRONE_FALLBACK=${DRONE_FALLBACK:-} # retry rate fallback: off | "" (=fw ladder)
RECEIPT_MS=${RECEIPT_MS:-} # windowed RX receipts cadence (off="")
DRONE_RATE=${DRONE_RATE:-MCS3}
DRONE_PAYLOAD=${DRONE_PAYLOAD:-512} # >= 30 so the pctr stamp fits
Expand Down Expand Up @@ -167,6 +168,7 @@ env DEVOURER_VID="$DRONE_VID" DEVOURER_PID="$DRONE_PID" DEVOURER_CHANNEL="$CH" \
DEVOURER_TX_RATE="$DRONE_RATE" DEVOURER_TX_PAYLOAD_BYTES="$DRONE_PAYLOAD" \
DEVOURER_TX_GAP_US="$DRONE_GAP_US" \
DEVOURER_TX_REPORT="$DRONE_REPORT_N" DEVOURER_TX_RETRY_LIMIT="$RETRY_LIMIT" \
${DRONE_FALLBACK:+DEVOURER_TX_RETRY_FALLBACK="$DRONE_FALLBACK"} \
${RECEIPT_MS:+DEVOURER_TX_RECEIPTS=1 DEVOURER_TX_WITH_RX=thread} \
DEVOURER_DIS_CCA="$DRONE_DIS_CCA" \
DEVOURER_TX_PWR_OFFSET_QDB="$PWR_QDB" \
Expand Down
Loading