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
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -610,7 +610,7 @@ add_test(NAME tone_mask_math COMMAND ToneMaskSelftest)

# Headless guard for the active idle-noise-floor math (src/NoiseFloorMath.h): the
# phydm sign/pwdb helpers the Jaguar1 0x0FA0 debug-port measurement uses. Pure
# math, header-only (issue #202).
# math, header-only.
add_executable(NoiseFloorMathSelftest tests/noise_floor_math_selftest.cpp)
target_include_directories(NoiseFloorMathSelftest PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src)
Expand Down
9 changes: 9 additions & 0 deletions docs/rx-spectrum-sensing.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ Two noise floors are exposed on `GetRxQuality()`:
- **Jaguar3 (8822C/8822E)** — no vendor idle-noise path (the report dispatch
excludes the 8822C), so `abs_noise_floor_dbm` is always null; the passive
floor is J3's only floor.
- **Kestrel (8852B/8852C, Wi-Fi 6)** — the cleanest source: Realtek's halbb
**NHM env-monitor** (`halbb_env_mntr_trigger`/`result` → `nhm_pwr − 110`), a
proper frame-free BB measurement with no clock-stop → no wedge. On-air the
**8852B reads a correct idle floor (−93 dBm, cross-matches the passive floor
within ~1 dB)**; the 8852C triggers but its `nhm_pwr` reads ~25 dB high (an
8852C scaling difference, unresolved) so it stays null there. Kestrel also
gets the passive floor + LinkHealth (per-frame RSSI from the physts header +
SNR from IE_01) on the 8852B; the 8852C physts layout differs (SNR unparsed →
passive floor null there). Both 8852C gaps are follow-ups.

Validation: `tests/rx_noise_floor_active_onair.sh` (anti-wedge + sanity /
cross-chip agreement — a B210 injected-noise sweep isn't used because the bench
Expand Down
4 changes: 2 additions & 2 deletions examples/rx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1208,7 +1208,7 @@ int main() {
ev.f("igi", e.igi);
else
ev.f("igi", nullptr);
if (e.valid_noise_floor) /* active absolute floor, #202 (opt-in) */
if (e.valid_noise_floor) /* active absolute floor (opt-in) */
ev.f("abs_noise_floor_dbm", e.abs_noise_floor_dbm);
else
ev.f("abs_noise_floor_dbm", nullptr);
Expand Down Expand Up @@ -1294,7 +1294,7 @@ int main() {
ev.f("noise_floor_dbm", nullptr);
/* Active/frame-free absolute floor (DEVOURER_RX_NOISE_FLOOR): the
* companion to the passive floor above. Null unless opted-in AND the
* generation supports it (Jaguar2 live / Jaguar1 8812A CAL). #202. */
* generation supports it (Jaguar2 live / Jaguar1 8812A CAL). */
if (q.abs_nf_valid)
ev.f("abs_noise_floor_dbm", q.abs_noise_floor_dbm);
else
Expand Down
2 changes: 1 addition & 1 deletion examples/streamtx/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ int main(int argc, char **argv) {
auto stream_cfg = devourer_config_from_env();
/* FPV downlink default: disable the MAC carrier-sense gate so the video TX
* punches through co-channel traffic instead of deferring — on-air ~1.5-2.2x
* inject-rate recovery under a co-channel transmitter (issue #199, SetCcaMode /
* inject-rate recovery under a co-channel transmitter (SetCcaMode /
* DEVOURER_DIS_CCA). The link owns the channel, so CSMA back-off only stutters
* it. Explicit DEVOURER_DIS_CCA=0 still forces standard carrier-sense back on. */
if (std::getenv("DEVOURER_DIS_CCA") == nullptr)
Expand Down
57 changes: 57 additions & 0 deletions hal/halbb/g6/kestrel_halbb_glue.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ struct kestrel_halbb_ctx {
struct rtw_hal_com_t hal;
struct rtw_phl_com_t phl;
struct bb_cmn_info cmn;
int env_mntr_ready; /* lazy halbb_env_mntr init done */
};

struct kestrel_halbb_ctx *kestrel_halbb_create(struct kestrel_halbb_bridge *br,
Expand Down Expand Up @@ -39,6 +40,9 @@ struct kestrel_halbb_ctx *kestrel_halbb_create(struct kestrel_halbb_bridge *br,
c->hal.chip_id = CHIP_WIFI6_8852C;
}
c->bb.rx_path = RF_PATH_AB;
c->bb.cr_type = BB_CLIENT; /* USB client CR bank; without it the env-monitor
* CR-init switch falls through and the NHM
* ready-bit address stays 0 (report never rdy) */
c->bb.bb0_cr_offset = 0; /* bridge adds the BB window (wIndex=1) */
/* halbb_init_cr_default guards on this flag; it asserts exactly the wiring
* done above (hal_com/phl_com/bb_cmn_hooker present). */
Expand Down Expand Up @@ -118,6 +122,59 @@ void kestrel_halbb_ctrl_tx_path(struct kestrel_halbb_ctx *ctx) {
#endif
}

/* One-shot active/frame-free NHM absolute noise floor. See the header. */
int kestrel_halbb_env_mntr_nhm(struct kestrel_halbb_ctx *ctx,
unsigned short mntr_time_ms,
signed char *nhm_pwr_dbm) {
struct bb_info *bb;
struct ccx_para_info para;
struct env_trig_rpt trig;
struct env_mntr_rpt rpt;
void *drv;
u16 mtime;
int i;

if (!ctx || !nhm_pwr_dbm)
return 0;
bb = &ctx->bb;
drv = bb->hal_com->drv_priv;
mtime = mntr_time_ms ? mntr_time_ms : 100;

/* Lazy env-monitor init: the RX bring-up flow (kestrel_halbb_init_reg) only
* runs halbb_init_reg, so the NHM CR addresses (incl. nhm_rdy) and the ccx/nhm
* state are still zero. Run the two vendor inits once before the first
* trigger. */
if (!ctx->env_mntr_ready) {
halbb_cr_cfg_env_mntr_init(bb);
halbb_env_mntr_init(bb);
ctx->env_mntr_ready = 1;
}

/* One-shot NHM in the vendor's IEEE-11k idle-noise mode. A zeroed para gives
* nhm_incld_cca=NHM_EXCLUDE_CCA + nav/rssi gating DISABLED (all enum 0). */
_os_mem_set(0, &para, 0, sizeof(para));
para.rac_lv = RAC_LV_4; /* debug priority: always wins the racing arbiter */
para.mntr_time = mtime; /* ms; drives ccx_period (must be nonzero) */
para.nhm_app = NHM_DBG_11K; /* 802.11k noise-floor thresholds */

if (!(halbb_env_mntr_trigger(bb, &para, &trig, HW_PHY_0) & NHM_SUCCESS))
return 0;

/* The measurement runs mntr_time in HW; wait the window, then poll the ready
* bit (halbb_env_mntr_result returns NHM_SUCCESS once nhm_rdy is set). */
_os_delay_ms(drv, mtime);
for (i = 0; i < 8; ++i) {
_os_mem_set(0, &rpt, 0, sizeof(rpt));
if ((halbb_env_mntr_result(bb, &rpt, HW_PHY_0) & NHM_SUCCESS) &&
rpt.nhm_pwr != ENV_MNTR_FAIL_BYTE) {
*nhm_pwr_dbm = (signed char)((int)rpt.nhm_pwr - 110); /* 110+x dBm */
return 1;
}
_os_delay_ms(drv, 3);
}
return 0;
}

void kestrel_halbb_destroy(struct kestrel_halbb_ctx *ctx) {
if (ctx)
free(ctx);
Expand Down
10 changes: 10 additions & 0 deletions hal/halbb/g6/kestrel_halbb_glue.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ void kestrel_halbb_ctrl_bw_ch(struct kestrel_halbb_ctx *ctx, unsigned char pri_c
* model, so this is a no-op there. Single-PHY, RF_PATH_A. */
void kestrel_halbb_ctrl_tx_path(struct kestrel_halbb_ctx *ctx);

/* One-shot active/frame-free NHM absolute noise floor. Runs a manual IEEE-11k
* NHM measurement over `mntr_time_ms` (0 -> 100) and returns the
* idle in-band power as signed dBm (nhm_pwr - 110). Frame-free, BB-driven, no
* clock-stop -> no RX wedge. Lazily runs halbb_cr_cfg_env_mntr_init +
* halbb_env_mntr_init on first call (the RX bring-up path does not). Returns 1
* on a valid reading (fills *nhm_pwr_dbm), 0 on timeout/failure. */
int kestrel_halbb_env_mntr_nhm(struct kestrel_halbb_ctx *ctx,
unsigned short mntr_time_ms,
signed char *nhm_pwr_dbm);

#ifdef __cplusplus
}
#endif
Expand Down
9 changes: 9 additions & 0 deletions hal/halbb/g6/kestrel_halbb_helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,12 @@ void halbb_tssi_ctrl_set_bandedge_cfg(struct bb_info *bb,
enum tssi_bandedge_cfg bandedge_cfg) {
(void)bb; (void)phy_idx; (void)bandedge_cfg;
}

/* BE (11BE / WiFi7) primary-sub-band helper referenced by halbb_env_mntr.c's
* EDCCA-opt path only in its 11BE branch - dead code on the AX Kestrel ICs
* (halbb_api_be.c is not vendored). A link-only stub, never called at runtime. */
u8 halbb_get_prim_sb(struct bb_info *bb, u8 central_ch, u8 pri_ch,
enum channel_width bw) {
(void)bb; (void)central_ch; (void)pri_ch; (void)bw;
return 0;
}
4 changes: 2 additions & 2 deletions src/DeviceConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ struct DeviceConfig {
* OFF by default: the vendor idle-noise measurement adds ~10 ms of USB
* round-trips. Jaguar2 measures it live (wedge-free HW idle-noise report);
* Jaguar1 8812A/8821A measure it RX-idle (a CAL bracket); Jaguar3 and others
* leave it invalid (no vendor path). Issue #202. */
* leave it invalid (no vendor path). */
bool abs_noise_floor = false;
/* env: DEVOURER_IGI — Jaguar2 fixed initial-gain index override, 7 bits
* (unset = 0x40, the FA-rate-validated default). */
Expand Down Expand Up @@ -209,7 +209,7 @@ struct DeviceConfig {
/* env: DEVOURER_DIS_CCA — Jaguar2/3 MAC carrier-sense disable at bring-up
* (primary CCA 0x520[14] + EDCCA [15]): injected/beacon TX stops deferring to
* a busy channel and punches through co-channel traffic. Runtime equivalent:
* SetCcaMode. Default-on on the streamtx FPV downlink (issue #199). */
* SetCcaMode. Default-on on the streamtx FPV downlink. */
bool disable_cca = false;
/* env: DEVOURER_TXPKT_STEP_QDB — Jaguar3 per-packet power-bank step size
* in quarter-dB: the dB weight of one 0x1e70 offset-index step
Expand Down
12 changes: 7 additions & 5 deletions src/IRtlDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -389,14 +389,16 @@ class IRtlDevice {
* co-channel transmitter. Measured on-air (Jaguar3, 8822EU/8812CU): monitor
* injection otherwise defers ~40-60% to a co-channel 802.11 flooder; clearing
* the gate recovers ~1.5-2.2x, back to ~90% of the unimpeded rate
* (tests/dis_cca_tx_onair.sh, issue #199). The energy bit [15] alone is null
* (tests/dis_cca_tx_onair.sh). The energy bit [15] alone is null
* against a decodable preamble — the primary-CCA bit [14] is what recovers the
* inject path. This is the MAC-gate only; the vendor's BB CCA-off writes are
* NOT applied (they deafen the RX). Also DEVOURER_DIS_CCA at construction.
* Implemented on Jaguar2/3; a DELIBERATE no-op on Jaguar1, whose baseband EDCCA
* is already disabled by its init table (0x8A4 = 0x7F7F7F7F) and whose
* hardware-beacon downlink measures ~0.34 µs RMS on a crowded channel with no
* MAC-side gate. */
* Implemented on Jaguar2/3 and Kestrel (the AX R_AX_CCA_CFG_0 all-CCA-EN field —
* on Kestrel injection is already CCA-off by default via sch_tx_en, so this is a
* runtime toggle rather than the deferral fix it is on Jaguar); a DELIBERATE
* no-op on Jaguar1, whose baseband EDCCA is already disabled by its init table
* (0x8A4 = 0x7F7F7F7F) and whose hardware-beacon downlink measures ~0.34 µs RMS
* on a crowded channel with no MAC-side gate. */
virtual void SetCcaMode(bool disabled) { (void)disabled; }

/* Shift the next hardware beacon TBTT by `microseconds` (>0 = later/retard,
Expand Down
2 changes: 1 addition & 1 deletion src/NoiseFloorMath.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* Verbatim ports of the two phydm_math_lib helpers the active idle-noise-floor
* measurement needs (reference/rtl8812au/hal/phydm/phydm_math_lib.c). Kept in a
* standalone header so the per-generation noise-floor code and a selftest share
* one copy. See docs/rx-spectrum-sensing.md and issue #202.
* one copy. See docs/rx-spectrum-sensing.md.
*
* These drive the Jaguar1 (8812A/8821A) active-sampling path: the 0x0FA0 debug
* port packs rx I/Q as 10-bit signed (sign_conversion), and the per-sample power
Expand Down
2 changes: 1 addition & 1 deletion src/RxQuality.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ struct RxQuality {
* floor above: the vendor idle-noise monitor, measured with no wanted signal
* (site survey / channel selection). Present only when the caller opted in
* (DEVOURER_RX_NOISE_FLOOR) and the generation supports it (Jaguar2 live,
* Jaguar1 8812A/8821A RX-idle CAL; false elsewhere). Issue #202. */
* Jaguar1 8812A/8821A RX-idle CAL; false elsewhere). */
int8_t abs_noise_floor_dbm = 0;
bool abs_nf_valid = false;

Expand Down
2 changes: 1 addition & 1 deletion src/RxSense.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ struct RxEnergy {
* (~10 ms of USB round-trips), so it is only filled when the caller opted in
* (DEVOURER_RX_NOISE_FLOOR). Jaguar2 fills it live (HW idle-noise report,
* wedge-free); Jaguar1 8812A/8821A fill it from an RX-idle CAL measurement;
* Jaguar3 and others leave it invalid (no vendor path). Issue #202. */
* Jaguar3 and others leave it invalid (no vendor path). */
bool valid_noise_floor = false;
int8_t abs_noise_floor_dbm = 0;
};
Expand Down
8 changes: 4 additions & 4 deletions src/jaguar1/RtlJaguarDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "EepromManager.h"
#include "Hal8812PhyReg.h"
#include "NhmReader.h"
#include "NoiseFloorMath.h" /* active idle-noise-floor sign/pwdb helpers (#202) */
#include "NoiseFloorMath.h" /* active idle-noise-floor sign/pwdb helpers */
#include "RadioManagementModule.h"
#include "AckResponder.h" /* hardware ACK responder recipe */
#include "RadiotapPeek.h" /* send_packets batch pre-parse */
Expand Down Expand Up @@ -301,7 +301,7 @@ RxEnergy RtlJaguarDevice::GetRxEnergy() {
},
e);

/* Active absolute floor (#202): the debug-port measurement wedges live RX, so
/* Active absolute floor: the debug-port measurement wedges live RX, so
* it is NOT re-run here — GetRxEnergy just surfaces the RX-idle CAL taken at
* bring-up (measure_idle_noise_floor). Left invalid on the 8814A / when off. */
if (_cfg.rx.abs_noise_floor) {
Expand All @@ -313,7 +313,7 @@ RxEnergy RtlJaguarDevice::GetRxEnergy() {

/* Vendor odm_inband_noise_monitor_ac (8812/8821 active-sampling path). MUST run
* RX-idle: the clock-stop (0x8B4[6]) + BB/PMAC/CCK resets collide with a live
* bulk-IN RX DMA (#202). Reads the path-A RX I/Q off debug port 0x0FA0, forms
* bulk-IN RX DMA. Reads the path-A RX I/Q off debug port 0x0FA0, forms
* pwdb = 10*log10(I^2+Q^2), averages VALID_CNT idle samples in [-27,0) dB, and
* offsets to dBm: noise = avg + 12(ADC backoff) + 0x1C(IGI ref) - 110 - 3. */
void RtlJaguarDevice::measure_idle_noise_floor() {
Expand Down Expand Up @@ -1280,7 +1280,7 @@ void RtlJaguarDevice::Init(Action_ParsedRadioPacket packetProcessor,

/* DEVOURER_RX_NOISE_FLOOR — measure the absolute idle floor RX-idle here,
* before StartRxLoop starts the bulk-IN DMA (the active-sampling path wedges a
* live RX). 8812A/8821A only; the 8814A uses a different vendor path (#202). */
* live RX). 8812A/8821A only; the 8814A uses a different vendor path. */
if (_cfg.rx.abs_noise_floor &&
_eepromManager->version_id.ICType != CHIP_8814A)
measure_idle_noise_floor();
Expand Down
2 changes: 1 addition & 1 deletion src/jaguar1/RtlJaguarDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ class RtlJaguarDevice : public IRtlDevice {
void SetRxPathMask(uint8_t mask);
int GetRxPathMask();

/* Active idle-noise-floor CAL (#202, 8812A/8821A only). The vendor
/* Active idle-noise-floor CAL (8812A/8821A only). The vendor
* odm_inband_noise_monitor_ac debug-port path stops CK320/CK88 and resets
* BB/PMAC/CCK, which wedges a live bulk-IN RX DMA — so it MUST run RX-idle.
* Called at bring-up (before StartRxLoop); result cached and surfaced via
Expand Down
6 changes: 3 additions & 3 deletions src/jaguar2/RtlJaguar2Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ RxEnergy RtlJaguar2Device::GetRxEnergy() {
},
e);

/* DEVOURER_RX_NOISE_FLOOR — active/frame-free absolute floor (#202). The
/* DEVOURER_RX_NOISE_FLOOR — active/frame-free absolute floor. The
* vendor phydm_idle_noise_measure_ac: the BB maintains an idle-time power
* report at 0x0FF0 (path-A [7:0], path-B [15:8], sval = byte>>1); freeze it
* (0x9E4[30]=1) for a clean read, then noise = -110 + IGI(0xC50/0xE50) + mean.
Expand All @@ -879,7 +879,7 @@ RxEnergy RtlJaguar2Device::GetRxEnergy() {
*
* BEST-EFFORT on the tested 8812BU (1x1 8822B cut): the report is only rarely
* populated in devourer's monitor bring-up — most reads return the 0x80/0x00
* "no idle sample" sentinels. Investigated (issue #202): there is no vendor
* "no idle sample" sentinels. Investigated: there is no vendor
* enable step for it (the report is HW-automatic and the vendor runs the
* measurement from a full associated init, not monitor mode); the sentinel
* rate is channel-independent, oversampling barely helps, and pausing DIG
Expand Down Expand Up @@ -1709,7 +1709,7 @@ void RtlJaguar2Device::SetCcaMode(bool disabled) {
std::lock_guard<std::mutex> lk(_reg_mu);
/* Both MAC carrier-sense bits in REG_TX_PTCL_CTRL: primary CCA 0x520[14] +
* EDCCA [15], plus EDCCA_MSK_COUNTDOWN 0x524[11]. The primary-CCA bit is the
* one that stops TX deferring to a co-channel transmitter (issue #199); 0x520
* one that stops TX deferring to a co-channel transmitter; 0x520
* is the same HalMAC layout as the on-air-validated Jaguar3. */
uint32_t v520 = _device.rtw_read<uint32_t>(0x0520);
uint32_t v524 = _device.rtw_read<uint32_t>(0x0524);
Expand Down
6 changes: 3 additions & 3 deletions src/jaguar3/RtlJaguar3Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,7 @@ RxEnergy RtlJaguar3Device::GetRxEnergy() {
_device.phy_set_bb_reg(0x1eb4, 1u << 25, 0x0);
_device.phy_set_bb_reg(0x1d2c, 1u << 31, 0x1);

/* No active absolute noise floor on Jaguar3 (#202): the vendor 8822C driver
/* No active absolute noise floor on Jaguar3: the vendor 8822C driver
* has no idle-noise path (phydm_noisemonitor.c dispatches the report only to
* 8822B/8821C and returns 0 for the 8822C), so e.valid_noise_floor stays
* false. The passive rssi-snr floor (RxQuality.noise_floor_dbm) is J3's only
Expand All @@ -1116,8 +1116,8 @@ RxEnergy RtlJaguar3Device::GetRxEnergy() {
* flooder vs a far-channel USB-contention control; tests/dis_cca_tx_onair.sh).
* Clearing [14] removes that deferral (~1.5x recovery, back to ~90% of the
* unimpeded rate) so injected/beacon TX punches through a busy channel — the
* "keep transmitting through interference" lever the OpenIPC-FPV community wants
* (issue #199). The energy bit [15] alone is null against a decodable preamble;
* "keep transmitting through interference" lever the OpenIPC-FPV community wants.
* The energy bit [15] alone is null against a decodable preamble;
* it is kept because it also relaxes deferral to non-802.11 in-band energy.
*
* The vendor dis_cca recipe ALSO writes three BB registers (0x1a9c[20],
Expand Down
10 changes: 10 additions & 0 deletions src/kestrel/HalKestrel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1691,6 +1691,16 @@ void HalKestrel::vnd_bb_ctrl_bw_ch(uint8_t pri_ch, uint8_t center,
kestrel_halbb_ctrl_bw_ch(_halbb_ctx, pri_ch, center, hbw, band_type);
}

bool HalKestrel::nhm_noise_floor(int8_t &dbm, uint16_t mntr_time_ms) {
if (!_halbb_ctx)
return false;
signed char v = 0;
if (!kestrel_halbb_env_mntr_nhm(_halbb_ctx, mntr_time_ms, &v))
return false;
dbm = static_cast<int8_t>(v);
return true;
}

HalKestrel::~HalKestrel() {
if (_halrf_ctx)
kestrel_halrf_destroy(_halrf_ctx);
Expand Down
4 changes: 4 additions & 0 deletions src/kestrel/HalKestrel.h
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,10 @@ class HalKestrel {
* connects the BB IFFT output to the TX chain on the 8852C (glue no-op on
* the 8852B, which selects the TX antenna per-STA via the CMAC model). */
void vnd_bb_ctrl_tx_path();
/* Active/frame-free NHM absolute noise floor: one-shot halbb env-monitor.
* Returns true + fills `dbm` on a valid reading, false otherwise (report not
* ready / no halbb ctx). */
bool nhm_noise_floor(int8_t &dbm, uint16_t mntr_time_ms = 100);
::kestrel_halbb_ctx *_halbb_ctx = nullptr;
void *_halbb_bridge = nullptr; /* heap kestrel_halbb_bridge, outlives ctx */
static unsigned int halbb_r32(void *dev, unsigned int addr);
Expand Down
Loading
Loading