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 docs/rx-spectrum-sensing.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ hardware it runs on:
|---|---|---|
| returns | `ChannelBusy` — busy airtime + energy-above-floor | `RxEnergy` — the phydm counter set |
| available on | any backend with a hardware busy-airtime counter | Realtek only |
| today | Jaguar1/2/3 (CCX CLM), MT7612U (MAC channel timers, **unvalidated**) | Jaguar1/2/3, Kestrel (floor only) |
| today | Jaguar1/2/3 (CCX CLM), MT7612U (MAC channel timers) | Jaguar1/2/3, Kestrel (floor only) |
| not available | Kestrel, RTL8733B — both report *no reading*, never zero | RTL8733B, MT7612U |

Advertised statically by `AdapterCaps::busy_airtime_ok` /
Expand Down
12 changes: 8 additions & 4 deletions src/AdapterCaps.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,14 @@ struct AdapterCaps {
* channel timers. FALSE on Kestrel (its NHM rides the halbb glue, not
* NhmReader, so it has no CLM) and on the RTL8733B.
*
* busy_airtime_measured: that reading has been separated arm-vs-quiet ON AIR
* for this family (tests/ccx_clm_probe.sh), not merely implemented. False on
* Jaguar1 (shares the validated Jaguar2 11AC map, unmeasured) and on the
* MT7612U (no adapter available). False-as-unmeasured, per the house rule.
* busy_airtime_measured: that reading has been separated from a quiet
* channel ON AIR for this family, not merely implemented. The harness is
* tests/busy_window_probe.sh, which pits an armed window against the quiet
* floor under a known load, and against each way a window can be spoiled.
* True today on all four backends that set busy_airtime_ok. The flag stays
* because the two facts are independent: a port can land the engine before
* anyone has run it on air, and false-as-unmeasured is the house rule for
* that state.
*
* rx_energy_ok: IRtlRadio::GetRxEnergy returns real phydm FA/CCA/IGI
* counters. Always false on a non-Realtek radio; false on the RTL8733B and
Expand Down
34 changes: 34 additions & 0 deletions tests/busy_window_selftest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,40 @@ int main() {
check("early: consumed by the completed read", w.armed() ? 1 : 0, 0);
}

/* --- the other half of that rule: a SPOILED window IS consumed.
*
* "Not elapsed" keeps the window because nothing disturbed it — the caller
* was merely early. Interrupted and Retuned are the opposite: the hardware
* window was re-armed underneath it, or it spans two channels, so there is
* nothing left to wait for.
*
* Consuming it is what hands the caller BACK to the sampled path.
* IRtlRadio::GetChannelBusy takes the armed branch on armed(), so a spoiled
* window left armed strands every later call there: `spoil_` is sticky
* (only arm() clears it), so each one re-enters the spoil branch and
* returns invalid-Retuned, and the sampled fallback is unreachable for
* every unrelated consumer until something re-arms or resets the window.
* Not a wrong number — a sensor that stops answering.
*
* Without this block the asymmetry is invisible to the suite: a build that
* keeps the window armed on EVERY verdict passes every other test in this
* file. It is exactly the difference a later editor removes while tidying,
* so it is asserted rather than only described in the header. --- */
{
MockBb bb;
ClmWindow w;
w.arm(regs, 240000, 0, bb.wr());
w.note_retune();
const ChannelBusy spoiled = w.read(regs, 0, bb.rd());
check("spoiled: refused", spoiled.valid, 0);
/* On the READING, not just in last_spoil(): the block above checks the
* accessor, so a branch that reported every spoil as Interrupted would
* pass it. What a consumer acts on is the field. */
check("spoiled: with its reason", static_cast<long>(spoiled.spoil),
static_cast<long>(BusySpoil::Retuned));
check("spoiled: window consumed", w.armed() ? 1 : 0, 0);
}

/* --- reading without arming is not a quiet channel --- */
{
MockBb bb;
Expand Down
15 changes: 15 additions & 0 deletions tests/channel_busy_selftest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,21 @@ int main() {
static_cast<long>(b.spoil),
static_cast<long>(BusySpoil::Interrupted));
}
{ /* The negative control for the block above. The SAME dead counters
* with no window armed are the sampled path finding a backend that is
* not counting — "no sensor here", which is what a bare invalid
* reading means, and it must stay bare. A build that reported a spoil
* reason here would tell every sampled caller that its window was
* lost, when it never armed one.
*
* This is what pins the `w.armed &&` half of the guard: without it the
* reason leaks onto the sampled path and no other test notices. */
ChTimeWindow w; /* unarmed */
const ChannelBusy b = busy_from_ch_time_window(w, 0, 0, 250000, false, 0);
check("mt unarmed over dead counters: no reading", b.valid, 0);
check("mt unarmed over dead counters: and no window to have lost",
static_cast<long>(b.spoil), static_cast<long>(BusySpoil::None));
}
}

if (g_fail) {
Expand Down
Loading