mtk-wifi-fw/docs/findings.md

212 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Findings log
One entry per claim. Every claim carries evidence (kernel source reference or
blob offset + observed bytes). Claims that cannot be evidenced are registered
in the Unknowns section instead. Blobs referenced by filename are the
linux-firmware copies listed in `manifest.json` (SHA-256 TBD in fixtures).
Blobs observed: mt7981/mt7915/mt7916/mt7986 × {wm, wa, rom_patch}
(linux-firmware snapshot 2026-04-20, Debian).
## F1 — Connac2 RAM container layout
`[region data, packed from offset 0 in table order][optional hidden trailer
(see F2)][n_region × 40-byte region table][36-byte trailer at EOF]`
Evidence: `mt76_connac_mcu.c` `mt76_connac2_load_ram()` +
`mt76_connac2_send_ram()` (region data streamed sequentially from offset 0;
table and trailer located from EOF); `mt76_connac_mcu.h`
`struct mt76_connac2_fw_region` / `_fw_trailer`. Confirmed by exact layout
match on 8 RAM blobs (sum(region.len) + table + trailer (+ hidden trailer) ==
file size).
Region fields: `decomp_crc/decomp_len/decomp_blk_sz` (MCU-side decompression,
unused when decomp_len==0 — all observed blobs), `addr`, `len`,
`feature_set`, `type`.
## F2 — Hidden second trailer carries the full build string
Between region data and the kernel-parsed table sits a trailer the upstream
loader never reads. WM blobs: 100 bytes; WA blobs: 36 bytes (no string).
Observed family-wide (all 4 chips).
WM hidden trailer = 16×'#' + `40 00 00 00` + 1-byte string length + flags +
full build string + '#'-padding. Recovered strings:
- mt7981_wm: `t-neptune-main-mt7915-1953-MT7981_MP2111_IMP-20240823161204`
- mt7916_wm: `t-neptune-main-mt7915-1953-MT7916_MP2111_IMP-20240823170147`
- mt7986_wm: `t-neptune-main-mt7915-1953-MT7986_MP2111_IMP-20240823160608`
- mt7915_wm: `t-neptune-mp-mt7915-2045-MT7915_MP_7_4_2045-20220929103802`
Significance: the kernel-visible trailer's `fw_ver[10]` is stripped
(`____000000` WM / `DEV_000000` WA); the hidden trailer restores branch
("main"/"mp"), internal build number (1953/2045), MP tag, and build
timestamp. This is the version metadata the changelog tooling keys on.
Evidence: bytes at offset `sum(region.len)` in each blob (see
`manifest.json` → `hidden_trailer.raw_head`).
## F3 — region `type` is a content tag used by some loaders
All parsed mt79-family blobs: `type == 0` (`FW_TYPE_DEFAULT`). It is not
ignored upstream: mt7921's loader selects
`feature_set & FW_FEATURE_NON_DL && type == FW_TYPE_CLC` regions as
country/location-calibration data instead of downloading them to the MCU.
Evidence: `mt7921/mcu.c` CLC handling; `FW_TYPE_CLC` definition in
`mt76_connac_mcu.h`.
## F4 — Patch container format
92-byte big-endian header (16-byte build date, 4-byte platform tag —
"ALPS" on all four — hw_sw_ver, patch_ver, checksum, descriptor), 64-byte BE
section table, plaintext section data at explicit offsets. All observed
patch sections: `enc_type == 0` (plaintext).
Evidence: `mt76_connac2_load_patch()` + `struct mt76_connac2_patch_hdr/_sec`
in `mt76_connac_mcu.h`; exact parse of 4 patch blobs.
Section counts: mt7915 patch = 2 sections, all others = 1; section `type`
constant `0x30002` on every observed section (loader downloads all sections
regardless; field semantics otherwise unobserved).
## F5 — mt7981_rom_patch internal structure (first disassembly, 2026-08-20)
The 9.7KB patch section (downloaded to 0x00900000) is a boot-ROM function
replacement table + code + strings, little-endian NDS32:
| offset | content |
|---|---|
| 0x000 | `0x000003ff`, 0 |
| 0x008 | 10 LE pointers into boot ROM (`0x00801xxx–0x0082bxxx`) |
| 0x108 | 10 LE pointers into patch RAM (`0x009002xx–0x009012xx`) — replacements |
| ~0x1e0 | NDS32 code (Ghidra: 49 functions + entry marker) |
| 0x901000+ | string table |
Decompilation is viable end-to-end (Ghidra 12 `NDS32:LE:32`, official
module; our ELF imports directly — deferred acceptance check now PASSED).
Evidence: `ghidra-proj/patch_decomp.txt` (unpublished, blob-derived).
Verified observations:
- Assert anchors: `patch/wf/wm/sys_patch_mcu.c` (lines 0x426, 0x5de...),
`common/sys_patch_common_mcu.c` (0x20f, 0x248...) via ROM `func_0x008004b8`.
- 20+ distinct direct calls into boot ROM (`func_0x0080xxxx`) + GP-relative
indirect calls (`unaff_gp - 0x116xx`) — ROM provides the runtime library.
- Named-entry strings: `MCU_Patch_init`, `ENTRY_wsysMboxSendMsg`,
`ENTRY_wsysMboxRcvAllMsg` (mailbox IPC), `From_CCIF__host_cpu_sw_interrupt`
(CCIF = host↔MCU channel), `WDT_to_Host` / `WDT_to_N9` (confirms the MCU
core is called N9), `Patch_dic_handler_extend`, `WF_Lt_Sec_handler`.
- Diagnostics suite strings: `AXI_Bus_monitor_detect`, `APB_AHB_bus_timeout`,
`IDLM_monitor`, `CPU_UTLZ_CNT_*` (utilization counters),
`cache_miss_ratio`, register-dump prints (`0x8800_0430` etc.).
- `FUN_009002a6`: bus-register writer — LE dword writes to offsets
0x110/0x114/0x118/0x11c on bus 5, RMW `| 0x400000c0`, busy-poll bit
`0x40000000`. `FUN_0090044e`: bounded delay loop via ROM timer reads
(`func_0x00801e20`/`func_0x00801e16`).
Interpretation (labelled): the patch extends boot ROM with host-comm hooks
(CCIF interrupt, mailboxes, watchdog-to-host) and bus diagnostics, plus
download-plumbing register programming — the glue the ROM needs before
WM/WA firmware arrives.
## F6 — mt7981_wa survey (2026-08-20)
Ghidra project `wa` imported + analyzed: 120 functions, 450 strings.
Strings self-identify WA's role — host command handling and TX bookkeeping
(`MCU_EXT_CMD` protocol), matching the driver's separate WA MCU queue:
- `cmdEventParserCmd` with `ucCID` printing; `EXT_CMD_ID_STAREC_UPDATE`,
`DevInfo Update Command`, `BssInfo Update Command` (own-MAC/BSS record
management), `staRec with invalid wandidx` (station records)
- Per-STA loss accounting: `PKTLOSS[%d]times/cnt[tot_tx,drop_tx,seq]`,
`lost seq`, `dup seq`, `dlycnt/maxdly`, plus `[proto,port,src_ip,dest_ip]`
flow dump formatting
Next: match `MCU_EXT_CMD_*` enum ids (mt76 headers) to `cmdEventParserCmd`
dispatch — the ABI anchor map (P1-C/D).
## F7 — WA EXT_CMD dispatch table located (2026-08-20)
Method: string xrefs fail on this code (GP-relative data addressing;
decompiler shows `unaff_gp + imm`) — dispatch found structurally instead:
`tools/scan_tables.py` scans for consecutive LE pointers into ILM
(0x10200000–0x10233370).
- **65-entry table @ ILM+0x1304 (vaddr 0x10201304)** = `MCU_EXT_CMD`
dispatch, indexed by command id. Two default stubs: 0x10200278
(unhandled) / 0x10200270 (reserved). Verified alignments with mt76 enum:
[0x07] PM_STATE_CTRL→0x10201934, [0x08] CHANNEL_SWITCH→0x10201910,
[0x11] SET_TX_POWER_CTRL→0x1020164c, [0x25] STA_REC_UPDATE→0x10201530,
[0x26] BSS_INFO_UPDATE→0x1020155c, [0x2a] DEV_INFO_UPDATE→0x102015c4,
[0x32] WTBL_UPDATE→0x10201688, [0x49] BCN_OFFLOAD→0x10201674.
- ~30 non-default handlers → immediately nameable; ~15 non-default entries
at ids beyond mt76's public enum (MTK-internal commands — discovery list).
- Additional tables: 36-entry @ILM+0x330 (sparse; likely basic
MCU_CMD/mailbox dispatch), 16-entry @ILM+0x8248.
- Coverage prerequisite: forced linear disassembly (`ForceDisasmPost.py`)
28,540 → 125,621 instructions — default analysis leaves most firmware
code undisassembled (no entry graph reaches it).
Next: same scan on WM; handler-by-handler decompile + naming pass.
## F8 — mt7981_wm survey (2026-08-20)
Ghidra project `wm`: forced disassembly to **555,385 instructions / 4,422
functions** (import-time analysis alone: 226K/4,355).
- ~~Code model claim: "only 27 GP-relative refs"~~ CORRECTED in F9: WM is
GP-relative; the operand-object scan undercounted (missed memory-operand
forms). GP value still unsolved, but the dispatcher's GP-relative
constants identified the registration array structurally.
- Structural scan found no *absolute-pointer* dispatch table — resolved in
F9: dispatch is a runtime registration list, and the static registration
arrays use GP-reachable data in region r5.
- Regions: code 0xe003b000 (398KB) + 0xe009c400 (473KB mixed);
data 0x0231dc00 (205KB), 0x0041xxxx pair; 0xf0xxxxxx block (feat 0x80)
still uncharacterized (U1).
## F9 — WM dispatch solved: runtime registration list (2026-08-20, U4 resolved)
WM's command parser found via string xref (`EXT_CMD_ID:0x%02x, SEQ:%u...`
@0xe00a64b0 → **FUN_0xe00412e6**): walks a handler registration array
`{u8 cmd_id; code* handler}` (stride 8) at `gp+0x1b6a0`, count at
`gp+0x227bc`; match → `handler(msg)`; miss → `Not_handled_ucExtenCID`.
A top-level class array (r5 @0x02238f74) maps class 0xed → this parser.
Region r5 (0x02229800) holds the *static* registration arrays:
**15 arrays / 407 `{id,fn}` entries** (`tools/scan_registrations.py` →
`dataset/wm_registrations.txt`). Enum join names **130 handlers** from
mt76's ABI (`dataset/wm_handlers_named.txt`) — e.g. 0x08
CHANNEL_SWITCH→0xe003ff5c, 0x25 STA_REC_UPDATE, 0x32 WTBL→0xe00402fa,
0x81 SER_TRIGGER→0xe005d480, 0x94 TWT→0xe0040de4, 0x9f
MURU_CTRL→0xe0045f3c; 277 entries carry MTK-internal ids beyond the public
enum (discovery list).
Labels applied (`LabelHandlers.py`): WM **371 functions** (115 created +
256 renamed), WA **29** (from F7's table). r5 additionally carries 192
function-name strings (`wsysWfdmaCmdHandle`, `muruExtCmdSetMuruStatistic`,
...) — `__FUNCTION__`-style assert anchors for the next naming pass.
## Unknowns registry
- **U1 — `feature_set` bit 7 (0x80):** observed only on WM regions at
`0xf0xxxxxx` addresses (mt7981 r7–r10, mt7986 equivalents). Upstream
defines bits 0–6 only; vendor-SDK search found no definition (2026-08-20).
Correlation with address family noted; semantics undetermined. May relate
to a second download destination or content class.
- **U2 — hidden-trailer non-string fields:** the `40 00 00 00` and flag
bytes before/around the string (F2) are uninterpreted.
- **U3 — WO container:** `mt7981_wo.bin` uses the mtk_wed loader
(`mtk_wed_mcu.c`), a different format; not yet parsed.
- **U4 — WM command dispatch mechanism: RESOLVED (F9)** — runtime
registration list at gp+0x1b6a0; static arrays in region r5; parser
0xe00412e6.
## Related verified facts
- NDS32LE target: `GCC: (2018-10-23_nds32le-elf-mculib-v3) 4.9.4` string in
mt7981_wa region data; FreeRTOS v10.0.0 + `portable/GCC/NDS32/port.c`
strings in mt7981_wo.
- No per-region encryption in any observed blob (`FW_FEATURE_SET_ENCRYPT`
clear; patch `enc_type == 0`). Optional protocol support exists in the
loader (key from EFUSE path) but is not exercised by shipped images.