mtk-wifi-fw/docs/findings.md

187 lines
9.1 KiB
Markdown
Raw 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 differs from patch/WA: only **27 GP-relative references** in
the whole image → WM uses (near-)absolute global addressing, so GP-base
inference is not the unlock it would otherwise be.
- Structural dispatch scan (scan_tables.py) across every region pairing
found **no absolute function-pointer table** (unlike WA's 65-entry
table, F7). WM command dispatch must use another mechanism —
FP/register-relative tables, switch jump tables, or runtime handler
registration (unknown U4).
- Regions: code 0xe003b000 (398KB) + 0xe009c400 (473KB mixed);
data 0x0231dc00 (205KB), 0x0041xxxx pair; 0xf0xxxxxx block (feat 0x80)
still uncharacterized (U1).
## 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.
## 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.