mtk-wifi-fw/PLAN.md

150 lines
7.7 KiB
Markdown
Raw Permalink Normal View History

# mtk-wifi-fw (working name) — Project Plan
Goal: cut teeth on real firmware RE while producing community-useful tooling,
docs, and update tracking for MediaTek Connac2 WiFi firmware. The product is
the tooling/knowledge and the skills built making it — not a modified router.
## Target facts (verified, keep cited)
- Hardware: GL-MT3000 (Beryl AX), MT7981B (Filogic 820), OpenWrt 24.10.5.
Radios on-SoC at `platform/soc/18000000.wifi`, driver `mt7915e` (mt76).
- "The blob" = 4 images, 3 NDS32LE cores inside the SoC:
- `mt7981_wm.bin` 2.05MB — WM, main MAC/PHY MCU ("neptune", build 20240823)
- `mt7981_wa.bin` 494KB — WA, offload/stats MCU
- `mt7981_wo.bin` 2.45MB — WED packet offload core (FreeRTOS v10, loaded by
`mtk_wed`, not mt76 — separate container format, TODO)
- `mt7981_rom_patch.bin` 9.8KB — boot-ROM patch ("ALPS")
- NDS32LE evidence: `GCC: (2018-10-23_nds32le-elf-mculib-v3) 4.9.4` in wa;
`portable/GCC/NDS32/port.c` in wo.
- No signature/encryption wall: loader is the kernel driver
(`mt7915_load_firmware` → `mt76_connac2_load_patch` + `load_ram`); protocol
supports per-region encrypt (`FW_FEATURE_SET_ENCRYPT`) but blobs ship
plaintext. Source-verified; empirical proof = M3 milestone.
- Container formats (from `mt76_connac_mcu.h`):
- RAM images: region data from offset 0, n×40B region table, 36B trailer at
EOF. `fw_ver` stripped (`____000000`); build_date is the only versioning.
- Patch: 92B BE header + 64B BE section table at start.
- Family coverage: mt7915/7916/7981/7986 all parse. mt7915 blobs = 2022
build, others = 2024 — free pilot for version diffing.
- Tooling: Ghidra has an official NDS32 module (`NDS32:LE:32:default`, some
V3 instr gaps); Andes GNU toolchain public (exact 2018 GCC vintage exists).
- Prior art: cyrozap/mediatek-wifi-re (older chip gen; CC-BY-SA notes,
compatible). No public RE of Connac2-generation firmware exists.
## Strategy shape
RE first, fork later. Nobody can price replacement firmware before the boot
and core-boundary picture exists, so the plan front-loads cheap-certain RE and
gates expensive authoring behind a data-informed go/no-go.
- Phase 1 static RE: carve → ELF → Ghidra → boot/ABI map (no decision needed)
- Phase 2 dynamic RE: patch bytes, reload, observe (patching = verification
instrument, no decision needed)
- Fork point (after boot/handshake + WM↔WA boundary mapped): choose between
a) audit & document track, and/or
b) authoring ladder, easiest-first: custom WO (self-contained, no radio)
→ WM handshake-only → beacon TX → association → data path.
Each rung independently valuable/stoppable.
- Version diffing runs parallel throughout (pure static, high community value).
## MVP v0.1 — "toolkit + changelog" (no hardware required, ~8–12 evenings)
Scope: extract (family-wide, correct) + to-elf + diff report over
linux-firmware git history + format/boot docs + CI cron that tracks upstream
blob drops. The repo becomes the public changelog for silently-updated
firmware.
Worklist in order:
1. Repo scaffolding: git init, dirs, dual LICENSE (MIT code / CC-BY-SA
docs), Makefile; commit existing extractor.
2. Parser correctness: resolve or document-as-unknown the 100/36B gap,
region `type` semantics, feature bit 0x80.
3. Test fixtures: golden manifests per blob (metadata only, no blob bytes).
4. ELF emitter at true load addresses (Ghidra/objdump acceptance test).
5. Dataset: walk linux-firmware git history for mediatek WiFi files.
6. Diff engine + pilot report (mt7915 2022 vs 2024; string-table add/remove
is the human-readable signal).
7. Docs: format.md (every field cited), boot.md, README quickstart.
8. CI: fixtures on commit; weekly cron vs linux-firmware HEAD regenerating
reports.
9. Polish, tag v0.1.0, undersold announcement (OpenWrt forum + r/re).
Not in v0.1: Ghidra annotation projects, symbol DBs, patched blobs, anything
router-dependent.
## Quality gates ("not noise")
- Every claim cites evidence (kernel line, blob offset) or is marked unknown.
No invented semantics.
- Family coverage verified in CI, not asserted in README.
- Reproducible in ~3 commands, plain Linux, no blobs committed
(`make fetch-fw` pulls from kernel.org).
- Undersell announcements.
## Status log
- 2026-08-20: Extractor `tools/mtk_fw_extract.py` written and run on
mt7981/7915/7916/7986 wm+wa+patch (7 files, 4 chips) — all parse clean.
Output in `extracted*/`, `extracted/manifest.json`. Solved same day: the
"gap" is a second trailer family-wide (wm 100B, wa 36B) sitting between
region data and the kernel-parsed trailer; WM's carries the full build
string MTK strips from the kernel-visible trailer (branch, MP tag, build
timestamp) — recovered version metadata for the changelog. Region type:
all parsed blobs are FW_TYPE_DEFAULT(0); loader does use `type` elsewhere
(mt7921 FW_TYPE_CLC). Remaining unknowns: feature bit 0x80 (wm 0xf0xxxxxx
regions), hidden-trailer non-string fields. Verified claims + citations:
docs/findings.md.
- Router unreachable (thinkpad jump host down). Not blocking: v0.1 is pure
static. Hardware options when needed: fix thinkpad / wire devuan-hpz to
router LAN / join `zach` WiFi from devuan-hpz (key + likely MAC-whitelisted,
but changes this machine's network path mid-session).
2026-08-20 17:27:14 +00:00
- 2026-08-20 (later): MVP v0.1 complete. All 9 worklist steps: scaffold,
parser correctness (hidden trailer = recovered version metadata),
dataset-driven golden tests, ELF emitter (readelf-validated; Ghidra
open-check deferred to Ghidra bring-up — deviation noted), 54-revision
dataset, diff engine + 3 pilot reports (2026 build: 0xe003b000 region
~95% changed, new RA/DPD/thermal strings), docs (format/boot), CI
(push + weekly track), v0.1.0 tagged. Announcement drafted, not posted —
hosting decision still open.
## Open items
- Repo name + hosting (GitHub reach vs Codeberg/self-hosted Forgejo;
mirroring makes this non-blocking).
- Router access path (needed from M3/dynamic phase).
- Fork decision (audit vs authoring ladder mix) — after boot/boundary map.
- WO container format (from `mtk_wed_mcu.c`, different loader) — M2-ish.
## Decision function & finish criteria
Project has no natural completion point; finish = satisficing set + per-node
gates. All checks binary.
**Finish conditions (both required):**
- F1 public artifact: v0.1 shipped passing quality gates, then one of
community uptake / cron keeps it alive / documented sunset ("last verified
against linux-firmware X"). Silent rot is noise; sunset is finish.
- F2 teeth cut, each backed by an artifact: container parsed (done); one
*verified cited* claim from disassembly; changed firmware behavior and
observed it on hardware (P2); optionally self-written code on target.
**Universal gate (every checkpoint):** advance iff next gate is reachable
with known techniques AND has a stated cost ceiling AND its failure would not
invalidate shipped artifacts. Slipped >2x twice → stop-and-replan. Outputs
ship as verified or documented-unknown, never "probably"; unknowns are valid
P1 exits but never authoring foundations.
**Node exits:** MVP = worklist 1–9 + quality gates. P1 = boot chain writable
step-by-step with citations; ≥30 functions named via dispatch anchors, 3
traced end-to-end; memory map complete; WM↔WA characterized; fork memo.
Authoring rung N+1 opens iff rung N demonstrated on hardware AND N+1
interface mapped AND unbrick path proven.
**Kill criteria:** 3 consecutive flash cycles with no hypothesis advance →
rung closed, findings published. ROM-verified lockout discovered → authoring
dead, audit track unaffected. Router unreachable >4 weeks → P2 paused only.
**Zombie rule:** no commits and no CI activity for 2 quarters → archive with
last-verified statement.