From 0ad818eefb48e5aa54dacb3d0fc57ae3e4e92b4e Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:21:47 +0300 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20MCC/FCS=20investigation=20=E2=80=94?= =?UTF-8?q?=20reject=20as=20an=20FHSS=20primitive=20(exp=203/5)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source-maps the vendor MCC state machine, H2C 0x10/0x16/0x17/0x18/0x19 schedule-upload sequence, C2H MCC_RPT_* status codes, and the live policy table (fixed 100 TU period, 20-80 TU slot splits). Hardware finding: the capability gate engages (en_mcc=1 with two non-groupable linked STAs) but the firmware schedule (mcc_status=DOING_MCC) does not start from the cfg80211 association path — it needs the vendor's internal P2P-anchored join flow, unreachable in the constrained measurement VM; no on-air dwell is claimed from a schedule not observed running. The chbw_grouped gate also means adjacent pairs like 36+40 take the single-radio union path and never engage MCC. Go/no-go: reject. MCC is a two-context time-share (TU-quantized slots, fixed ~102 ms period, two channels, non-groupable pairs, P2P-gated) — not a hopping engine. Its slot-boundary switch is the same H2C 0x1D already shipped as DEVOURER_FASTRETUNE_FW; exp 4-5 should build on that direct switch, not the MCC scheduler. README index + offload-doc forward reference updated. Co-Authored-By: Claude Opus 4.8 --- README.md | 8 +- docs/kernel-channel-switch-offload.md | 7 +- docs/mcc-fcs-investigation.md | 136 ++++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 docs/mcc-fcs-investigation.md diff --git a/README.md b/README.md index 521ac145..d686e4f3 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/kernel-channel-switch-offload.md b/docs/kernel-channel-switch-offload.md index 93adc2c7..9a6796d7 100644 --- a/docs/kernel-channel-switch-offload.md +++ b/docs/kernel-channel-switch-offload.md @@ -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). diff --git a/docs/mcc-fcs-investigation.md b/docs/mcc-fcs-investigation.md new file mode 100644 index 00000000..1699aa01 --- /dev/null +++ b/docs/mcc-fcs-investigation.md @@ -0,0 +1,136 @@ +# 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). + +## 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). + +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. From 1f45f5716bf3f1bc44dd850339b49c710372f05d Mon Sep 17 00:00:00 2001 From: Joseph <162703152+josephnef@users.noreply.github.com> Date: Mon, 20 Jul 2026 14:25:30 +0300 Subject: [PATCH 2/2] docs: MCC verdict holds across all 11ac chips by shared hal_mcc.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The policy table (fixed 100 TU period, 20/80/36/30 TU splits) is identical in the Jaguar1/2/3 vendor trees — 8812/8814/8821A, 8822B, 8821C, 8822C, 8822E (8822E differs only by 3 TU of guard). Kestrel has no hal_mcc.c; its multi-channel support is rtw89 chanctx, separately P2P-driven. So the reject-as-FHSS conclusion generalises by shared source, not just the measured 8822B. Co-Authored-By: Claude Opus 4.8 --- docs/mcc-fcs-investigation.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/mcc-fcs-investigation.md b/docs/mcc-fcs-investigation.md index 1699aa01..83ef538e 100644 --- a/docs/mcc-fcs-investigation.md +++ b/docs/mcc-fcs-investigation.md @@ -86,6 +86,18 @@ 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 @@ -124,7 +136,10 @@ running, it is structurally a two-channel time-share: 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). +- 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