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
18 changes: 14 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ construction from the `SYS_CFG2` chip-id (Kestrel: PID-first — see
RX bulk-IN delivery requires the USB RXAGG engine enabled
(`B_AX_RXAGG_EN`). TX power is a fixed BB dBm (`halbb_set_txpwr_dbm`,
default 20 dBm, `DEVOURER_TX_PWR` override) with a runtime
`SetTxPowerOffsetQdb` lever; `ReadTsf` reads the per-port MAC TSF;
`SetTxPowerOffsetQdb` lever and per-packet radiotap `DBM_TX_POWER`
(fixed-dBm rewrite between frames — see **Per-packet TX power** under
Configuration); `ReadTsf` reads the per-port MAC TSF;
`StartBeacon` drives the AX HW beacon engine. **HE ER SU + DCM extended
range** (both dies): per-packet via radiotap-HE FORMAT=EXT_SU or
`DEVOURER_TX_RATE=.../ER[/DCM]`; RX classifies the format in
Expand Down Expand Up @@ -399,13 +401,21 @@ the 0 dB baseline). On-air-validated on 8822CU + 8822EU, sticky across
`SetMonitorChannel`/`FastRetune`; the E compresses deep cuts (≈−6 dB floor,
same TSSI reshape as its offset slope). **8814A** — the same 3-bit LUT field
at the 8822B position (dword5 [30:28]), `SetTxPacketPowerStep` on
`RtlJaguarDevice`. **8812AU/8821AU** — no descriptor field exists (per-rate
`RtlJaguarDevice`. **Kestrel** — no WD-descriptor field; `send_packet`
honours the radiotap delta by rewriting the fixed-dBm BB target between
frames (2 BB RMWs on value change, free while constant; 0.25 dB steps,
clamped to the 0..23 dBm PA window around the `DEVOURER_TX_PWR` base —
whole dBm on this family; global, so a HW beacon airing between frames
follows the last-written level; a field-less frame restores the
`SetTxPowerOffsetQdb` session offset). **8812AU/8821AU** — no descriptor
field exists (per-rate
selection via radiotap is their only per-packet lever); the compensating
fast lever is `FastSetTxPowerOffsetQdb` (BB-swing TxScale `0xc1c/0xe1c`:
global per-burst, 1–4 writes, 0.5 dB steps, −12..+2 dB, folded through the
8812A thermal tracker; on-air-validated on the 8812AU). Sweep harnesses:
`tests/txpkt_pwr_ofset_onair.sh` (TX_PID/TX_VID select the DUT),
`tests/txpkt_fastswing_onair.sh`, `tests/txpkt_hop_persist.sh`.
`tests/txpkt_pwr_ofset_onair.sh` (TX_PID/TX_VID select the DUT; Kestrel
DUTs need `TX_PWR=14`-style dBm bases), `tests/txpkt_fastswing_onair.sh`,
`tests/txpkt_hop_persist.sh`.

Per-packet unequal error protection: `svctx` classifies stdin HEVC NALs by
temporal layer and injects each at its ladder's rate
Expand Down
14 changes: 10 additions & 4 deletions examples/tx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,11 +563,13 @@ int main(int argc, char **argv) {

/* DEVOURER_TX_PKT_OFSET=N — default per-packet TX-power LUT step written
* into every TX descriptor: 0=none, 1=-3dB, 2=-7dB, 3=-11dB, 4=+3dB,
* 5=+6dB. One env var, three families: Jaguar2 8822B/8821C
* 5=+6dB. One env var, four families: Jaguar2 8822B/8821C
* (descriptor TXPWR_OFSET, on-air-confirmed), Jaguar1 8814A (dword5
* [30:28], same LUT), and Jaguar3 8822C/8822E (the step's nominal dB is
* mapped onto a programmable 0x1e70 offset bank). The measurement driver
* for tests/txpkt_pwr_ofset_onair.sh. */
* [30:28], same LUT), Jaguar3 8822C/8822E (the step's nominal dB is
* mapped onto a programmable 0x1e70 offset bank), and Kestrel 8852B/8852C
* (no descriptor field — the nominal dB rides the session fixed-dBm
* offset, which a radiotap DBM_TX_POWER still overrides per frame). The
* measurement driver for tests/txpkt_pwr_ofset_onair.sh. */
if (const char *p = std::getenv("DEVOURER_TX_PKT_OFSET")) {
const uint8_t step = static_cast<uint8_t>(std::strtol(p, nullptr, 0));
#if defined(DEVOURER_HAVE_JAGUAR2)
Expand All @@ -583,6 +585,10 @@ int main(int argc, char **argv) {
jag3->SetTxPacketPowerOffsetQdb(4 *
devourer::txpkt_pwr_db_for_step(step));
#endif
if (rtlDevice->GetAdapterCaps().generation ==
devourer::ChipGeneration::Kestrel)
rtlDevice->SetTxPowerOffsetQdb(4 *
devourer::txpkt_pwr_db_for_step(step));
(void)step;
}

Expand Down
20 changes: 12 additions & 8 deletions src/AdapterCaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,25 +139,29 @@ struct AdapterCaps {
bool ldpc_rx_flag = false;

/* --- feature flags --- */
/* Per-packet TX power: the TX descriptor carries a per-frame
* power trim, driven by radiotap DBM_TX_POWER (dB delta vs the calibrated
* table) or a session default. Two hardware shapes:
/* Per-packet TX power: a per-frame power trim driven by radiotap
* DBM_TX_POWER (dB delta vs the calibrated table / session base) or a
* session default. Three hardware shapes:
* - Jaguar2 (8822B/8821C) + 8814A: a fixed 6-rung LUT in the descriptor
* ({0,-3,-7,-11,+3,+6} dB) — per_pkt_txpwr_steps = 6, step_qdb = 0.
* - Jaguar3 (8822C/8822E): a 2-bit bank selector; the banks are
* programmable 7-bit signed offsets (0x1e70) in per_pkt_txpwr_step_qdb
* units (nominally 4 = 1 dB), 2 concurrent non-zero levels —
* per_pkt_txpwr_steps = 0 (continuous), min/max give the travel.
* per_pkt_txpwr_measured stays false until the family's descriptor path
* has been proven to move on-air power (tests/txpkt_pwr_ofset_onair.sh) —
* the honest flag for the vendor-defined-but-unvalidated 8814A/Jaguar3
* fields. */
* - Kestrel (8852B/8852C): no descriptor field; the fixed-dBm BB target
* is rewritten between frames on value change (2 RMWs, free while
* constant; global, so HW beacons follow) — per_pkt_txpwr_steps = 0,
* step_qdb = 1 (0.25 dB).
* per_pkt_txpwr_measured stays false until the family's path has been
* proven to move on-air power (tests/txpkt_pwr_ofset_onair.sh) — the
* honest flag for a vendor-defined-but-unvalidated field (the 8814A
* today). */
bool per_packet_txpower = false;
uint8_t per_pkt_txpwr_steps = 0; /* 6 = LUT rungs; 0 = continuous qdb */
uint8_t per_pkt_txpwr_step_qdb = 0; /* Jaguar3 bank step (qdB); 0 for LUT */
int16_t per_pkt_txpwr_min_qdb = 0; /* most negative per-packet trim */
int16_t per_pkt_txpwr_max_qdb = 0; /* most positive per-packet trim */
bool per_pkt_txpwr_measured = false; /* on-air-confirmed (J2 today) */
bool per_pkt_txpwr_measured = false; /* on-air-confirmed for this family */
bool narrowband_ok = false; /* 5/10 MHz re-clock (Jaguar2/Jaguar3) */
uint8_t xtal_cap_max = 0; /* crystal-cap trim range top (0 = no trim;
* 0x3f on Jaguar1/2, 0x7f on Jaguar3) */
Expand Down
47 changes: 46 additions & 1 deletion src/kestrel/RtlKestrelDevice.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "RtlKestrelDevice.h"

#include <climits> /* INT_MIN = "no radiotap DBM_TX_POWER" sentinel */
#include <cstdint>
#include <span>
#include <stdexcept>
Expand Down Expand Up @@ -458,6 +459,8 @@ int RtlKestrelDevice::SetTxPowerOffsetQdb(int qdb) {
if (eff < kKestrelTxMinQdb) eff = kKestrelTxMinQdb;
if (eff > kKestrelTxMaxQdb) eff = kKestrelTxMaxQdb;
const int16_t applied = static_cast<int16_t>(eff - base);
_sess_pwr_qdb = applied; /* restore target for frames without a radiotap
* DBM_TX_POWER field (see send_packet) */
_hal.set_txpwr_offset_qdb(applied);
_logger->info("Kestrel: SetTxPowerOffsetQdb({}) -> applied {} qdB "
"(effective {} dBm)",
Expand Down Expand Up @@ -630,7 +633,24 @@ devourer::AdapterCaps RtlKestrelDevice::GetAdapterCaps() {
c.bw_mask = devourer::bw_mask_for_generation(c.generation);
if (_variant == kestrel::ChipVariant::C8852C)
c.bw_mask |= devourer::kBw160; /* 8852C-only (vendor bw_sup BW_CAP_160M) */
c.per_packet_txpower = false; /* AX power is TSSI, not the J2 descriptor LUT */
/* Per-packet TX power: no WD-descriptor field exists on AX — a radiotap
* DBM_TX_POWER dB-delta is honoured per frame by rewriting the fixed-dBm BB
* target between frames (2 RMWs on value change, free while constant;
* global, so a HW beacon airing between frames follows). Continuous
* quarter-dB, clamped to the [0, 23] dBm PA window around the session
* base. */
c.per_packet_txpower = true;
c.per_pkt_txpwr_steps = 0; /* continuous qdB, not a LUT */
c.per_pkt_txpwr_step_qdb = 1;
{
const int16_t base = _hal.txpwr_base_qdb();
c.per_pkt_txpwr_min_qdb = static_cast<int16_t>(kKestrelTxMinQdb - base);
c.per_pkt_txpwr_max_qdb = static_cast<int16_t>(kKestrelTxMaxQdb - base);
}
c.per_pkt_txpwr_measured = true; /* on-air: both dies, LUT-mapped session
* sweep 5/5 + radiotap cells 2/2
* (tests/txpkt_pwr_ofset_onair.sh, ch36,
* 14 dBm base, chip-RSSI ground) */
c.narrowband_ok = true; /* 5/10 MHz BB small-BW (SDR-validated); FastSetBandwidth toggle */
c.fastretune_ok = true; /* lean intra-band 20 MHz retune (FastRetune) */
/* HE ER SU + DCM: both dies (AX_TXD_DATA_ER/_BW_ER/_DCM in the TX WD; RX
Expand Down Expand Up @@ -687,6 +707,8 @@ bool RtlKestrelDevice::send_packet(const uint8_t *packet, size_t length) {
bool he = false; /* HE rate set directly on tr.rate (no MGN_HE code) */
bool rate_from_radiotap = false; /* a per-packet radiotap rate overrides the
* SetTxMode default */
int pkt_pwr_db = INT_MIN; /* radiotap DBM_TX_POWER dB-delta vs the session
* base; INT_MIN = field absent (session offset) */
kestrel::TxRate tr{}; /* defaults: 6M, 20MHz, LGI */
auto *rth = reinterpret_cast<struct ieee80211_radiotap_header *>(
const_cast<uint8_t *>(packet));
Expand All @@ -698,6 +720,11 @@ bool RtlKestrelDevice::send_packet(const uint8_t *packet, size_t length) {
mgn = *it.this_arg;
rate_from_radiotap = true;
break;
case IEEE80211_RADIOTAP_DBM_TX_POWER:
/* Per-packet TX power: a signed dB delta vs the session base, applied
* as a fixed-dBm BB rewrite before this frame airs (see below). */
pkt_pwr_db = *reinterpret_cast<const int8_t *>(it.this_arg);
break;
case IEEE80211_RADIOTAP_MCS: {
rate_from_radiotap = true;
uint8_t flags = it.this_arg[1];
Expand Down Expand Up @@ -847,6 +874,24 @@ bool RtlKestrelDevice::send_packet(const uint8_t *packet, size_t length) {
}
if (!he)
tr.rate = kestrel::mgn_to_ax_rate(mgn);
/* Per-packet TX power: the AX WD descriptor has no power field, but the
* fixed-dBm BB target is a 2-RMW rewrite — reprogram it between frames when
* the requested level changes (free while it stays constant), and restore
* the session offset for a frame without the field. Global, like the J1
* BB-swing lever: a HW beacon airing between frames follows the last-written
* level. Clamped to the same PA-valid window as SetTxPowerOffsetQdb. */
{
int16_t target = _sess_pwr_qdb;
if (pkt_pwr_db != INT_MIN) {
const int16_t base = _hal.txpwr_base_qdb();
int eff = base + pkt_pwr_db * 4;
if (eff < kKestrelTxMinQdb) eff = kKestrelTxMinQdb;
if (eff > kKestrelTxMaxQdb) eff = kKestrelTxMaxQdb;
target = static_cast<int16_t>(eff - base);
}
if (target != _hal.txpwr_offset_qdb())
_hal.set_txpwr_offset_qdb(target);
}
/* Route by 802.11 frame type: data frames ride the AC0 queue (BULKOUTID3) so
* they exercise the data power-by-rate path; mgmt/beacon uses the MG0 queue
* (BULKOUTID0). Falls back to the mgmt ep if the data ep was not resolved. */
Expand Down
7 changes: 6 additions & 1 deletion src/kestrel/RtlKestrelDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ class RtlKestrelDevice : public IRtlDevice {

/* Runtime TX-power lever: fixed-dBm BB power (halbb_set_txpwr_dbm) with a
* quarter-dB offset relative to the DEVOURER_TX_PWR base. Sticky across
* SetMonitorChannel. */
* SetMonitorChannel. Per-packet: a radiotap DBM_TX_POWER dB-delta on a
* frame overrides this for that frame via a fixed-dBm rewrite in
* send_packet (2 BB RMWs on value change, free while constant). */
devourer::TxPowerCaps GetTxPowerCaps() override;
int SetTxPowerOffsetQdb(int qdb) override;
devourer::TxPowerState GetTxPowerState() override;
Expand Down Expand Up @@ -199,6 +201,9 @@ class RtlKestrelDevice : public IRtlDevice {
uint8_t _tx_data_ep = 0; /* AC0 data bulk-OUT ep (BULKOUTID3) */
uint16_t _tx_seq = 0; /* rolling 12-bit wifi sequence for injected frames */
std::optional<devourer::TxMode> _tx_mode_default; /* SetTxMode default */
int16_t _sess_pwr_qdb = 0; /* offset applied by SetTxPowerOffsetQdb — the
* restore target for frames without a radiotap
* DBM_TX_POWER field */
/* Per-chain RSSI (RSSI% = dBm+110) cached from the last PPDU-status physts
* header, attached to the following WIFI frame(s) in the aggregate. */
uint8_t _last_rssi[2] = {0, 0};
Expand Down
8 changes: 6 additions & 2 deletions tests/txpkt_pwr_ofset_onair.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,14 @@
# TX_BUS/TX_PORT USB topology pin when VID:PID is ambiguous (8814A)
# GROUND_PID/GROUND_VID RSSI sensor identity (default 8812CU 0bda:c812)
# CH channel (default 36; 8822E DUTs are 5 GHz-only TX)
# TX_PWR DUT base power (default 40 = TXAGC index on the
# Jaguar families; Kestrel reads it as whole dBm,
# clamped 0..23 — use TX_PWR=14 for offset headroom)
set -u
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT="${TXPKT_OUT:-/tmp/devourer-txpkt-ofset}"
CH="${CH:-36}"
TX_PWR="${TX_PWR:-40}"
TX_PID="${TX_PID:-0x012d}" TX_VID="${TX_VID:-0x2357}"
TX_BUS="${TX_BUS:-}" TX_PORT="${TX_PORT:-}"
GROUND_PID="${1:-0xc812}" GROUND_VID="${GROUND_VID:-0x0bda}"
Expand Down Expand Up @@ -61,7 +65,7 @@ for step in $STEPS; do
t0="$(date +%s.%N)"
# Fixed rate MCS3, fixed base power, only the per-packet offset varies.
sudo -n env DEVOURER_PID="$TX_PID" DEVOURER_VID="$TX_VID" DEVOURER_CHANNEL="$CH" \
DEVOURER_TX_RATE=MCS3 DEVOURER_TX_PWR=40 DEVOURER_TX_PKT_OFSET="$step" \
DEVOURER_TX_RATE=MCS3 DEVOURER_TX_PWR="$TX_PWR" DEVOURER_TX_PKT_OFSET="$step" \
DEVOURER_TX_GAP_US=1500 "${TX_EXTRA[@]}" \
timeout 12 "$ROOT/build/txdemo" >/dev/null 2>&1 || true
t1="$(date +%s.%N)"
Expand All @@ -85,7 +89,7 @@ for pair in "0:0" "6:6" "-11:-11"; do
db="${pair%%:*}"
t0="$(date +%s.%N)"
sudo -n env DEVOURER_PID="$TX_PID" DEVOURER_VID="$TX_VID" DEVOURER_CHANNEL="$CH" \
DEVOURER_TX_RATE=MCS3 DEVOURER_TX_PWR=40 DEVOURER_TX_PKT_PWR_DB="$db" \
DEVOURER_TX_RATE=MCS3 DEVOURER_TX_PWR="$TX_PWR" DEVOURER_TX_PKT_PWR_DB="$db" \
DEVOURER_TX_GAP_US=1500 "${TX_EXTRA[@]}" \
timeout 12 "$ROOT/build/txdemo" >/dev/null 2>&1 || true
t1="$(date +%s.%N)"
Expand Down
Loading