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
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@ Behavioural traps the per-field docs can't carry:
been located and measured there, so `SetCcaMode(true)` throws (loudly, but
without tearing the session down) while `SetCcaMode(false)`, the state its
MAC bring-up already leaves programmed, succeeds as a no-op; setting the
config knob warns at `InitWrite` and airs with carrier-sense. Does NOT apply
config knob warns once at bring-up (RX-only sessions included) and airs
with carrier-sense. Does NOT apply
the vendor BB CCA-off writes (they deafen the RX). RX-decode side is a
separate null (`tests/dis_cca_onair.sh`).

Expand Down
121 changes: 91 additions & 30 deletions docs/rtl8733b.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,50 @@ factory path:
adapter-doctor checks.

Per-rate delivery, witnessed by an RTL8812AU (`0bda:8812`) in monitor mode on
channel 6, 300 submitted frames per rate. The witness's hit counter reports the
first ten hits then every hundredth, so these are floors at 100-frame
granularity:

| requested | captured | FCS errors |
channel 6, 300 submitted frames per rate. Counted per frame from the witness's
own RX events, so these are exact rather than sampled:

| requested | delivered | decoded as | FCS errors |
| --- | --- | --- | --- |
| 1 Mbps CCK | 298 / 300 | 1 Mbps | 0 |
| 2 Mbps CCK | 300 / 300 | 2 Mbps | 0 |
| 5.5 Mbps CCK | 300 / 300 | 5.5 Mbps | 0 |
| 11 Mbps CCK | 294 / 300 | 11 Mbps | 0 |
| 6 Mbps OFDM | 300 / 300 | 6 Mbps | 0 |
| HT MCS0 | 270 / 300 | MCS0 | 0 |
| HT MCS7 | 277 / 300 | MCS7 | 0 |

Every frame decoded at the rate it was requested at, and no captured frame
carried an FCS error at any rate. These are single probes, so per this repo's
own guidance they are worth about +/-3 points each.

The two HT readings sitting below the legacy ones are **not** a transmitter
effect, and the witness was qualified rather than assumed
(`tests/ground_station_qualify.sh` exists for exactly this question). Sweeping
the RTL8812AU witness across the HT ladder, 300 frames each:

| rate | delivered | witness RSSI |
| --- | --- | --- |
| 1 Mbps CCK | 300 / 300 | 0 |
| 6 Mbps OFDM | 300 / 300 | 0 |
| HT MCS7 | 300 / 300 | 0 |
| 11 Mbps CCK | >= 900 / 1000 | 0 |

11 Mbps was re-run at 1,000 frames because its first result sat on a counter
boundary; both repetitions landed in the same bucket, so the small deficit
relative to the other three rates is repeatable rather than noise — expected,
since 11 Mbps is the least robust CCK rate.
| MCS0 | 270 / 300 | 57.8 |
| MCS1 | 300 / 300 | 65.0 |
| MCS3 | 300 / 300 | 65.3 |
| MCS5 | 300 / 300 | 65.2 |
| MCS6 | 296 / 300 | 65.1 |
| MCS7 | 277 / 300 | 60.3 |

The ladder is flat at 100% through MCS5, so the witness is nowhere near its
modulation cliff and is fit to measure this rate range — a receiver measuring
itself would fall off progressively at the top, not dip at both ends. MCS0 is
the *most* robust rate in the set, so its being the lowest reading rules out a
cliff at either end. Both low readings also came with 5-7 units less RSSI than
the flat middle, which places the variation in the per-run link conditions
rather than in modulation. Treat the whole HT column as one flat band around
90-100% until something moves it by more than the probe noise.

A mixed run alternating all eight rates (1/2/5.5/11 Mbps CCK, 6 Mbps OFDM,
MCS0/4/7) submitted 250 frames through a CCK/OFDM TSSI table switch on every
frame. The witness decoded all eight `rate_hw` values on air with valid FCS,
every frame at 20 MHz with STBC and LDPC clear, matching the forced-BCC
MCS0/4/7) submitted 250 frames, crossing the CCK/OFDM rate-class boundary on
every frame. The witness decoded all eight `rate_hw` values on air with valid
FCS, every frame at 20 MHz with STBC and LDPC clear, matching the forced-BCC
contract.

RX aggregation is capped at 12 KiB (`rtl8733b::kRxAggregateBytes8733b`) because
Expand All @@ -96,19 +120,49 @@ the meter is a PA-bias tracking index rather than a calibrated junction
temperature, so it is not sound input for a TX gate
(`docs/warm-tx-degradation.md`). During the mixed CCK/OFDM witness run the raw
code stayed at 32–33 against baseline 32 across 650 submitted frames; 636 were
captured with valid FCS across all five requested rates. A separate
transition-churn run submitted 1,000/1,000 frames through 118 CCK/OFDM TSSI
table changes; the witness captured 660 canonical frames across both rates,
all with valid FCS, while the same 32–33 thermal range held.

Switching the TSSI thermal table between its CCK and OFDM/HT variants costs
84 ms — 136 USB register round trips, no sleeps — because the backend tears
down and rebuilds the whole TSSI front-end to change 16 BB dwords.
`select_tssi_rate_table` early-returns when the rate class is unchanged, so a
single-rate stream pays nothing; only a stream that alternates CCK and OFDM
rates is affected, and it is capped at roughly 11 fps.
[OpenIPC/devourer#389](https://github.com/OpenIPC/devourer/issues/389) tracks
the measurement and a validated in-place alternative.
captured with valid FCS across all five requested rates. A separate 1,000-frame
run alternating CCK and OFDM submitted 1,000/1,000; the witness captured 660
canonical frames across both rates, all with valid FCS, while the same 32–33
thermal range held. Both runs are rate-coverage and thermal evidence only —
neither says anything about TX-power tracking, and at these deltas (+0 to +1)
the two thermal-compensation curves are the same bits anyway.

## TX power control

On a unit whose EFUSE is in TSSI-offset PG mode — the tested RTL8731BU is —
closed-loop TSSI *is* the TX-power control, so it runs by default. It is not
optional there: the flat fallback index (`kSafeTxAgcIndex8733b`) is a
conservative bring-up value, and on air it runs cold enough that HT rates do
not survive the link. Measured with the witness, 300 frames submitted at MCS7
with the flat index: zero captured, twice. A unit whose EFUSE carries no TSSI
calibration has nothing to drive the loop and takes the flat path.

The chip keeps two thermal-compensation curves, one for CCK and one for
OFDM/HT. The table is chosen once per channel set from the configured TX mode
and then left alone, which is what the vendor driver does —
`_halrf_tssi_set_tmeter_tbl_8733b` is only ever called from full TSSI setup,
keyed on `phydm_get_tx_rate` at that instant, and never re-selected at runtime.

There is deliberately no runtime switch, and the reason is worth recording so
nobody adds one back.

The two curves are **bit-identical for thermal deltas 0..+17** and first differ
at **+18** — the swing ramp starts there, and the words either side of the
baseline are zero in both tables. A five-minute max-duty MCS7 soak on the
validation unit plateaued at **+8** after two minutes and stopped climbing,
less than half the delta needed for the tables to differ by a single entry. So
on this silicon a runtime switch would spend 84 ms — 136 USB register round
trips, *inside `send_packet`*, capping a mixed-rate stream near 11 fps — to
install a table bit-identical to the one already loaded.

An opt-in knob for it was written, measured on air and then deleted on that
evidence. Room temperature agreed too: 300 frames of 1 Mbps CCK delivered
300/300 with the OFDM table loaded and 299/300 with the CCK table, zero FCS
errors either way.
[OpenIPC/devourer#389](https://github.com/OpenIPC/devourer/issues/389) has the
cost breakdown and a validated in-place alternative (13.8 ms, tracking left
enabled) — if a future board does reach +18, that is the implementation to
revisit, not the teardown/rebuild.

A related property of the closed loop, worth knowing before reading power
numbers from a fast rate-switching run: TSSI needs settling time. Alternating
Expand All @@ -123,6 +177,13 @@ state this backend's MAC bring-up leaves programmed — succeeds as a no-op.
gate has not been located and measured here, and the backend refuses rather
than silently pretending. The refusal leaves the session running.

The construction-time form of the same knob — `DeviceConfig::tuning::disable_cca`,
which every other generation applies during bring-up — cannot be honoured
either, and bring-up emits one warning saying so rather than dropping it
silently or failing to initialise. Silence is the only outcome that would let a
refused knob look like a granted one; a hard failure would be a harsh answer to
a request a caller may be making only through an inherited environment.

## Known gaps and deferred validation

These results have **not** been claimed:
Expand Down
44 changes: 30 additions & 14 deletions src/rtl8733b/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,32 @@ unrelated register map.
either alone reintroduces the straddle, from opposite sides.
- **TSSI closed loop.** Power runs from a fixed safe target
(`kSafeTssiTargetQdbm8733b`); none of the runtime TX-power levers are ported.
The CCK and OFDM/HT variants of the thermal-compensation table are different
tables, and the table cannot be changed while tracking is enabled — so
`select_tssi_rate_table` disables tracking, rewrites, and re-enables, with
each stage verified by register readback. Note what the failure path is and
is not: `Phy8733b::enable_tssi_tracking` restores its own analog/BB snapshot
when its verdict fails, but the *transition* has no rollback to the previous
table — `select_tssi_rate_table` returns false with tracking left off, and
the caller responds by tearing the session down rather than transmitting at
an unverified power setting. That transition costs **84 ms / 136 USB
register round trips** (bench, one unit), so a stream alternating CCK and
OFDM rates is capped around 11 fps; a single-rate stream early-returns and
pays nothing.
On a TSSI-offset PG unit the loop **is** the TX-power control, so it is not
optional there — an attempt to make it opt-in with a fall back to the flat
`kSafeTxAgcIndex8733b` could not carry HT at all (witnessed: MCS7, 300/300
submitted, 0 captured, twice). A unit whose EFUSE carries no TSSI calibration
has nothing to drive the loop and takes the flat path.
- **The thermal table is chosen once per channel set, not per frame.** The CCK
and OFDM/HT variants of the thermal-compensation table are different tables.
`configure_tx_power` picks one from the configured TX mode and leaves it,
which is what the vendor does: `_halrf_tssi_set_tmeter_tbl_8733b` is
reachable only from full TSSI setup, keyed on `phydm_get_tx_rate` at that
instant, and never re-selected at runtime. So nothing reads or writes a
register per frame on the send path, matching the other four HALs.
- **The curve choice is inert at any temperature this part reaches, so do not
reintroduce runtime switching.** The CCK and OFDM/HT tables are
bit-identical for thermal deltas 0..+17 and first differ at **+18** (the
swing ramp starts there; the words either side of the baseline are zero in
both). A five-minute max-duty MCS7 soak on the validation unit plateaued at
**+8** after two minutes and stopped climbing — less than half the delta
needed for the tables to differ by a single entry. A runtime switch
therefore costs **84 ms / 136 USB register round trips** per crossing
(OpenIPC/devourer#389) to install a table that is bit-identical to the one
already loaded. An opt-in knob for it was written, measured and deleted on
that evidence. If a future board reaches +18 — a sealed module at high
ambient might — the switch is worth revisiting, but implement it as the
in-place rewrite validated in #389 (13.8 ms, tracking left enabled), not the
teardown/rebuild.
- **The loop needs settling time, so a fast rate-switching run misreports
power.** Alternating CCK and OFDM at a few ms per frame leaves CCK
transmitting above its settled level until the loop converges; pacing the
Expand Down Expand Up @@ -124,9 +138,11 @@ loudly — without tearing the session down, since an unported optional knob is
not a hardware-safety event — while `false` succeeds as a no-op because that is
the state MAC bring-up already leaves programmed.

`DeviceConfig::tuning::disable_cca` cannot be honoured either, and `InitWrite`
`DeviceConfig::tuning::disable_cca` cannot be honoured either, and bring-up
warns rather than dropping it — a config knob must not be the one door where a
request the setter refuses loudly instead vanishes without a word.
request the setter refuses loudly instead vanishes without a word. The warning
sits in `bring_up_to_phy`, not `InitWrite`, so an RX-only session that set the
knob is told too, and so it fires exactly once per bring-up.

## Validation status

Expand Down
Loading
Loading