diff --git a/docs/scheduled-mac.md b/docs/scheduled-mac.md index 22eb345..d3ec34a 100644 --- a/docs/scheduled-mac.md +++ b/docs/scheduled-mac.md @@ -186,6 +186,11 @@ witness capture of a 9-copy truth. Kestrel has no CCX `tx.report` path, so the witness copy-count is the retry ground truth there — the fw-level delivery outcome stays invisible until a receipts-tier consumer counts it. +The ACK window itself is a knob — `DEVOURER_ACK_TIMEOUT_US`, the +hardware-ARQ *range* lever (round-trip propagation eats ~6.7 µs/km; the +per-chip defaults and the bench proof live at the field doc in +`src/DeviceConfig.h`). + Choosing the limit (`tests/arq_retry_sweep.sh`, collision regime: a ~1 k fps retrying unicast flood into an 8812EU duplex ground station airing PixelPilot-shaped feedback bursts, near-field): retries are backoff-spaced, diff --git a/examples/common/env_config.cpp b/examples/common/env_config.cpp index 7d98775..606d266 100644 --- a/examples/common/env_config.cpp +++ b/examples/common/env_config.cpp @@ -121,6 +121,10 @@ devourer::DeviceConfig devourer_config_from_env() { std::fflush(stderr); } } + if (env_long("DEVOURER_ACK_TIMEOUT_US", &v) && v >= 1) + /* 1..255; out-of-range low keeps the library default (a 0 collapsing + * to 1 us would write off every frame). */ + cfg.tx.ack_timeout_us = static_cast(v > 255 ? 255 : v); if (env_long("DEVOURER_TX_RETRY_LIMIT", &v)) cfg.tx.retry_limit = static_cast(v < 0 ? 0 : (v > 63 ? 63 : v)); if (const char *e = env_str("DEVOURER_TX_RETRY_FALLBACK")) { diff --git a/src/DeviceConfig.h b/src/DeviceConfig.h index d90b4f0..de8d8e3 100644 --- a/src/DeviceConfig.h +++ b/src/DeviceConfig.h @@ -213,15 +213,38 @@ struct DeviceConfig { * Runtime equivalent: StartCwTone/StopCwTone on the concrete device. */ bool cw_tone = false; uint8_t cw_tone_gain = 0; - /* env: DEVOURER_TX_RETRY_LIMIT — per-frame hardware retry limit (0..63). - * Maps to the TX descriptor DATA_RETRY_LIMIT / RTS_DATA_RTY_LMT field - * (Dword4 bits 18-23). 0 = no retries (WFB default: FEC provides - * reliability, not MAC retries). On a busy half-duplex link retries flood - * the air and blind the receiver. Hardware-ARQ (SetAckResponder + unicast - * TA, docs/scheduled-mac.md) needs a nonzero value. Inert on Kestrel - * (firmware-level retry) and on the 8814A die (vendor DATA_RETRY_LIMIT=0 - * carve-out kept). */ + /* env: DEVOURER_TX_RETRY_LIMIT — per-frame hardware retry limit (0..63; + * Kestrel ceiling 62 — its attempts-counting WD field folds +1). Maps to + * the TX descriptor DATA_RETRY_LIMIT / RTS_DATA_RTY_LMT field on the + * 11ac generations and wd_info DATA_TXCNT_LMT on Kestrel. 0 = no retries + * (WFB default: FEC provides reliability, not MAC retries). On a busy + * half-duplex link retries flood the air and blind the receiver. + * Hardware-ARQ (SetAckResponder + unicast TA, docs/scheduled-mac.md) + * needs a nonzero value. Inert on the 8814A die only (vendor + * DATA_RETRY_LIMIT=0 carve-out kept pending its bench). */ int retry_limit = 0; + /* env: DEVOURER_ACK_TIMEOUT_US — hardware ACK response window in µs + * (1..255, clamped), the hardware-ARQ RANGE lever: the MAC writes a + * frame off (and retries) when no ACK is counted within this window, + * and round-trip propagation eats ~6.7 µs per km. ONE default, 128 µs, + * programmed identically on every generation at bring-up — the same + * knob value means the same range budget (~15 km round trip) no matter + * which die is plugged. 128 is the vendor's interop-blessed J1/J2 + * value and covers the slowest narrowband ACK in the tree (the 5 MHz + * per-bandwidth vendor value is 117 µs), so it also replaces the + * per-chip / per-bandwidth vendor defaults (which ranged 33..128 µs + * and made hardware-ARQ range silently die-dependent). The register: + * REG_ACKTO 0x640 on the 11ac generations, R_AX_RSP_CHK_SIG 0xCC00 + * byte0 on Kestrel; the CTS window (REG_CTS2TO 0x641) is separate and + * untouched. Sizing: ~6.7 µs x round-trip km + ~50 µs ACK flight and + * detection margin; a longer window is NOT free — every retry of a + * LOST frame waits the full window, measured (dead RA, retry 8, max + * duty): 2719 write-offs/8 s at 33 µs vs 2015 at 128 vs 1507 at 255. + * Bench proof the register gates the ARQ verdict: at 8 µs (below the + * ACK's flight time) retries pin at the limit with 0% ok against a + * live responder; at 128/255 the responder cell runs 100% ok, + * retries ~0. */ + int ack_timeout_us = 128; /* 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 / diff --git a/src/jaguar1/RtlJaguarDevice.cpp b/src/jaguar1/RtlJaguarDevice.cpp index 27013bf..70829fa 100644 --- a/src/jaguar1/RtlJaguarDevice.cpp +++ b/src/jaguar1/RtlJaguarDevice.cpp @@ -80,6 +80,12 @@ void RtlJaguarDevice::InitWrite(SelectedChannel channel) { * DEVOURER_DIS_CCA. Always applied — the enable path is what programs * the BB EDCCA thresholds off their parked never-trigger table value. */ SetCcaMode(_cfg.tuning.disable_cca); + /* ACK window (DEVOURER_ACK_TIMEOUT_US): one library default on every + * generation — see the DeviceConfig field doc. */ + _device.rtw_write8(0x0640, static_cast( + _cfg.tx.ack_timeout_us > 255 ? 255 + : _cfg.tx.ack_timeout_us < 1 ? 1 + : _cfg.tx.ack_timeout_us)); /* DEVOURER_XTAL_CAP — crystal-cap trim (issue #217, narrowband CFO lever). */ if (_cfg.tuning.xtal_cap) @@ -1333,6 +1339,12 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor, * DEVOURER_DIS_CCA. Always applied — the enable path is what programs * the BB EDCCA thresholds off their parked never-trigger table value. */ SetCcaMode(_cfg.tuning.disable_cca); + /* ACK window (DEVOURER_ACK_TIMEOUT_US): one library default on every + * generation — see the DeviceConfig field doc. */ + _device.rtw_write8(0x0640, static_cast( + _cfg.tx.ack_timeout_us > 255 ? 255 + : _cfg.tx.ack_timeout_us < 1 ? 1 + : _cfg.tx.ack_timeout_us)); /* DEVOURER_XTAL_CAP — crystal-cap trim (issue #217, narrowband CFO lever). */ if (_cfg.tuning.xtal_cap) diff --git a/src/jaguar2/RtlJaguar2Device.cpp b/src/jaguar2/RtlJaguar2Device.cpp index 5eb26f6..25706be 100644 --- a/src/jaguar2/RtlJaguar2Device.cpp +++ b/src/jaguar2/RtlJaguar2Device.cpp @@ -249,6 +249,13 @@ void RtlJaguar2Device::bring_up(SelectedChannel channel) { if (_cfg.tuning.disable_cca) SetCcaMode(true); + /* ACK window (DEVOURER_ACK_TIMEOUT_US): one library default on every + * generation — see the DeviceConfig field doc. */ + _device.rtw_write8(0x0640, static_cast( + _cfg.tx.ack_timeout_us > 255 ? 255 + : _cfg.tx.ack_timeout_us < 1 ? 1 + : _cfg.tx.ack_timeout_us)); + /* DEVOURER_XTAL_CAP — apply the crystal-cap trim once the AFE is up * (issue #217, the narrowband CFO lever). */ if (_cfg.tuning.xtal_cap) diff --git a/src/jaguar3/RtlJaguar3Device.cpp b/src/jaguar3/RtlJaguar3Device.cpp index 89b1f9b..e443ff5 100644 --- a/src/jaguar3/RtlJaguar3Device.cpp +++ b/src/jaguar3/RtlJaguar3Device.cpp @@ -878,6 +878,14 @@ void RtlJaguar3Device::InitWrite(SelectedChannel channel) { else _txpkt_img.store(0, std::memory_order_relaxed); apply_dpdt_route_8822e(); /* 8822E DPDT/eFEM pin-mux (post-coex) */ + /* ACK window (DEVOURER_ACK_TIMEOUT_US): one library default on every + * generation, replacing the halmac per-bandwidth REG_ACKTO defaults + * init_wmac_cfg just wrote (the 128 default covers the slowest + * narrowband ACK) — see the DeviceConfig field doc. */ + _device.rtw_write8(0x0640, static_cast( + _cfg.tx.ack_timeout_us > 255 ? 255 + : _cfg.tx.ack_timeout_us < 1 ? 1 + : _cfg.tx.ack_timeout_us)); apply_replay_wseq(); /* DEVOURER_REPLAY_WSEQ golden-init replay (debug) */ if (_cfg.debug.bb_dump) { /* Full MAC+BB dump (0x000..0x4ffc — MAC plane, then BB incl. the RF diff --git a/src/kestrel/RtlKestrelDevice.cpp b/src/kestrel/RtlKestrelDevice.cpp index a153416..a4983e8 100644 --- a/src/kestrel/RtlKestrelDevice.cpp +++ b/src/kestrel/RtlKestrelDevice.cpp @@ -254,6 +254,13 @@ void RtlKestrelDevice::InitWrite(SelectedChannel channel) { * re-asserts explicitly (idempotent) when the knob is set. */ if (_cfg.tuning.disable_cca) SetCcaMode(true); + /* ACK window (DEVOURER_ACK_TIMEOUT_US): one library default on every + * generation — byte0 of R_AX_RSP_CHK_SIG (the field the vendor's + * narrowband path scales); see the DeviceConfig field doc. */ + _device.rtw_write8(0xCC00, static_cast( + _cfg.tx.ack_timeout_us > 255 ? 255 + : _cfg.tx.ack_timeout_us < 1 ? 1 + : _cfg.tx.ack_timeout_us)); _tx_mgmt_ep = _device.nth_bulk_out_ep(0); /* B0MG -> BULKOUTID0 */ _tx_data_ep = _device.nth_bulk_out_ep(3); /* ACH0 -> BULKOUTID3 */ if (_tx_mgmt_ep == 0) {