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
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,13 @@ one `IRtlDevice` interface covers all four generations.
**Spectrum agility:**

- [Frequency hopping](docs/frequency-hopping.md) — how per-packet hopping
works and what it costs on each chip.
works and what it costs on each chip, including the 8822B/C/E firmware
channel-switch fast path (`DEVOURER_FASTRETUNE_FW`).
- [Kernel channel-switch baseline](docs/kernel-channel-switch-baseline.md) +
[firmware offload](docs/kernel-channel-switch-offload.md) +
[MCC/FCS](docs/mcc-fcs-investigation.md) — how the standard Linux/Realtek
drivers retune measured against devourer, and where the chip firmware's own
H2C 0x1D switch beats them.
- [FHSS](docs/fhss.md) — the anti-jam design article: keyed SipHash hop
schedules, slot-locked lockstep RX, and
[jammer resilience](docs/jammer-resilience.md) — measured delivery against
Expand Down
7 changes: 5 additions & 2 deletions docs/kernel-channel-switch-offload.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,8 @@ already has (H2C submit, C2H over the RX path). Adopting it as a
Caveats to carry into the port: verify per-rate TX power after fw
switches (`PWR_IDX_UPDATE_EN` semantics), and the C2H completion must be
drained — TX-only sessions need `DEVOURER_TX_WITH_RX=thread` or a poll.
For experiment 3, MCC/FCS remains the scheduler-grade candidate (dwell
schedules, two contexts); 0x1D is the retune primitive underneath it.
MCC/FCS — the two-context firmware time-share — was examined next and
rejected as a hopping primitive: its schedule is coarse (TU-quantized
slots, fixed ~102 ms period, two contexts, non-groupable pairs only) and
the fast switch underneath it is this same H2C 0x1D. See
[mcc-fcs-investigation.md](mcc-fcs-investigation.md).
151 changes: 151 additions & 0 deletions docs/mcc-fcs-investigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Realtek MCC / FCS as an FHSS primitive — investigation

Multi-Channel Concurrency (MCC) and its firmware Fast Channel Switch (FCS —
Realtek's "fast channel switch", not the 802.11 frame check sequence) let one
Realtek radio hold two channel contexts and time-share between them under a
firmware schedule. This note determines whether that schedule can be
repurposed as a frequency-hopping primitive, the third in the standard-driver
FHSS series after the
[channel-switch baseline](kernel-channel-switch-baseline.md) and the
[firmware channel-switch offload](kernel-channel-switch-offload.md).

The verdict is up front: **MCC is a two-context time-share for concurrent
connections, not a hopping engine.** Its schedule is coarse (TU-quantized
slots, a fixed ~102 ms period, two contexts, non-groupable channel pairs
only), and the one part of it worth having — the fast firmware RF switch at a
slot boundary — is the same H2C 0x1D switch already isolated in experiment 2
and shipped directly as `DEVOURER_FASTRETUNE_FW`, without MCC's association
and scheduler machinery.

## State machine (vendor `rtl88x2bu`, `hal/hal_mcc.c` + `core/rtw_mlme_ext.c`)

```
two vifs associate ──► mlme_join_done (2nd vif)
MCC_EN(adapter)? ── no ──► single-radio union channel
│ yes
rtw_hal_set_mcc_setting_join_done_chk_ch
rtw_is_chbw_grouped(chA, chB)?
│yes │no (chbw_allow == FALSE)
one radio covers both rtw_hal_set_mcc_setting(START_CONNECT)
(NO MCC — union path) │
upload schedule + contexts (H2C, below)
C2H MCC_RPT_READY ──► mcc_status = DOING_MCC
firmware autonomously alternates context 0/1
per the policy table; C2H SWITCH_CHANNEL_NOTIFY
on each slot boundary
disconnect / stop ──► H2C MCC_CTRL(totalnum=0xff)
C2H MCC_RPT_STOPMCC ──► teardown
```

The gate that matters for hopping is `rtw_is_chbw_grouped`: MCC starts **only
when the two channels cannot be grouped into one radio channel**. Adjacent
20 MHz channels that fit inside one 40/80 MHz channel (36+40, 36+44, …) take
the single-radio union path and never engage MCC — so the series' nominal
"36↔40" candidate is precisely a pair MCC declines. A non-groupable pair
(different sub-bands, e.g. 36+149, or 2.4 GHz 1+11) is required to reach the
firmware schedule.

## Schedule protocol

Host→firmware, in start order (`rtw_hal_set_mcc_start_setting`):

| step | H2C | id | len | carries |
|---|---|---|---|---|
| contexts | `MCC_LOCATION` | 0x10 | 7 (V2) | reserved-page indices per context (null-data/CTS frame + PHY/IQK snapshot) |
| MAC ids | `MCC_MACID_BITMAP` | 0x16 | 6 | per-context MAC-ID bitmap |
| control | `MCC_CTRL_V2` / `MCC_CTRL` | 0x17 / 0x18 | 7 | order, total contexts, centre ch, primary-ch idx, bw, role, report flags |
| timing | `MCC_TIME_SETTING` | 0x19 | 6 | TSF-sync offset, start-time offset, interval, order-0 duration |
| (V1 IQK) | `MCC_IQK_PARAM` | 0x1A | 7 | per-path IQK cal (V2 offloads to firmware) |

Firmware→host: `C2H_MCC` (0x17). Status codes (`MCC_RPT_*`):
`READY=3` (schedule armed → `DOING_MCC`), `SWICH_CHANNEL_NOTIFY=7` (per
slot-boundary switch — the natural per-transition instrument), `TSF=9`,
`STOPMCC=2` (stop complete), `SUCCESS=0`, `TXNULL_FAIL=1`.

**Policy table** (`mcc_switch_channel_policy_table`, live-read from the DUT,
all values in TU = 1024 µs):

| policy | order-0 duration | TSF-sync | start-offset | interval | guards |
|---|---|---|---|---|---|
| 0 | 20 | 50 | 40 | **100** | 0/0 |
| 1 | 80 | 50 | 10 | **100** | 0/0 |
| 2 | 36 | 50 | 32 | **100** | 0/0 |
| 3 | 30 | 50 | 35 | **100** | 0/0 |

The period is a fixed **100 TU ≈ 102.4 ms** in every policy; the split is
order-0 duration / (interval − duration). The smallest context slot the
firmware exposes is 20 TU ≈ 20.5 ms. There is no dwell below TU granularity
and no path to more than two contexts. Transitions are firmware-internal: the
two contexts are pre-loaded into reserved pages once, and the firmware flips
between them on its own timer — a slot boundary is not a per-slot host H2C, so
the switch itself is at least as fast as the ~1 ms on-chip RF retune measured
in experiment 2 (which uses the same switch engine).

**This is shared code, not one chip's quirk.** The same `hal_mcc.c` engine
and this exact policy table (fixed 100 TU period, 20/80/36/30 TU splits) ship
across every 11ac generation — Jaguar1 (8812/8814/8821A), Jaguar2 (8822B,
8821C) and Jaguar3 (8822C, 8822E); the 8822E differs only by 3 TU of guard
time, which shortens nothing meaningful. So the structural verdict below
holds for the whole 11ac family by shared source, not just the measured
8822B. The runtime `mcc_duration` knob moves the split *within* the 100 TU
period; no chip and no runtime path reaches a sub-TU dwell or a shorter
period. Kestrel (8852B/C) has no `hal_mcc.c` at all — its multi-channel
support rides rtw89's `chanctx`, a separate, P2P-driven path that is equally
off the hopping track.

## What was reached on hardware

Measured on the RTL8822BU (T3U) with the MCC-variant vendor build
(`CONFIG_MCC_MODE=y CONFIG_CONCURRENT_MODE`, two auto-created vifs), in the
pinned-kernel VM (the vendor module deadlocks the host — see the offload
note), two host helper APs, and the DUT's two STA vifs associating to them:

- **The MCC capability gate engages.** With two vifs linked on a
non-groupable pair (2.4 GHz ch1 + ch11), `en_mcc:1` and both vifs hold
`STA ASOC` stably. On a groupable pair (36+40) the driver takes the union
path and one vif starves — direct confirmation of the `chbw_grouped` gate.
- **The firmware schedule does not start from the cfg80211 association
path.** Across every pairing, `mcc_status` stays 0 (never `DOING_MCC`),
`mcc_rf_ch[0/1]` stay 0, the radio stays parked on one channel, and no
`MCC_RPT_READY` appears. The start hook lives in the vendor's internal
`mlme_join_done` flow and, for a plain dual-STA case driven by `iw
connect`, is never taken to the point of `rtw_hal_set_mcc_setting(START)`.
The reserved-page context builder emits NOA content for a P2P GO; this
matches the in-tree rtw89 note that MCC is P2P-driven — the schedule
appears to require a GO/GC (P2P) anchor, not two open-BSS STAs.

Bringing the schedule to a running state would need the vendor's private
association path (a P2P GO/GC bring-up, or `wpa_supplicant` driving the
vendor SME) — neither reachable in the constrained measurement VM
(no `wpa_supplicant`/`hostapd`, package egress firewalled). Per the
experiment's own rule, no on-air dwell is claimed from a schedule that was
not observed running; the schedule structure above is source-derived and
confirmed against the live policy table, not from an on-air A/B capture.

## Go / no-go

**Reject MCC/FCS as an FHSS hopping primitive.** Even with the schedule
running, it is structurally a two-channel time-share:

- slots are TU-quantized with a ~20 ms floor and a **fixed ~102 ms period** —
no dwell-1, no agile per-packet hopping (the decision gate's requirement);
- exactly two contexts — no arbitrary-N channel set;
- restricted to non-groupable channel pairs;
- gated behind a full dual-interface association (P2P GO/GC in practice);
- identical by shared `hal_mcc.c` across all four 11ac chips, and a
separate but equally P2P-driven `chanctx` path on Kestrel — no generation
offers a finer schedule.

The only element with hopping value is the slot-boundary RF switch, and that
is the **H2C 0x1D firmware channel switch** already isolated in experiment 2
and delivered as `DEVOURER_FASTRETUNE_FW` (1–2 ms, arbitrary channel,
including cross-band) — with none of MCC's association, reserved-page, or
two-context constraints. Any firmware-assisted hopping work (experiments
4–5) should build on that direct switch, not on the MCC scheduler. MCC
remains what it is designed to be: concurrent-connection time-sharing for
STA+GO / STA+AP, off the FHSS path.
Loading