Docs: format spec (cited field tables), boot architecture (WM/WA/WO + load sequence), README quickstart
This commit is contained in:
parent
be11e41043
commit
384919f8fc
3 changed files with 196 additions and 11 deletions
50
README.md
50
README.md
|
|
@ -5,19 +5,47 @@ firmware blobs (`mt7915/7916/7981/7986_{wm,wa,rom_patch}.bin` as shipped in
|
|||
linux-firmware) — the WiFi firmware running on Filogic 820/830 routers
|
||||
(GL.iNet, Banana Pi, Xiaomi, Netgear, TP-Link, ...).
|
||||
|
||||
Status: pre-release. Working now: container extractor (`tools/mtk_fw_extract.py`).
|
||||
Planned: ELF export for Ghidra/objdump, cross-version diff reports, CI that
|
||||
tracks upstream blob updates, format + boot-architecture documentation.
|
||||
MTK strips version strings from these blobs (`fw_ver = "____000000"`) and
|
||||
ships silent updates; this project recovers the version metadata, documents
|
||||
the container formats, and diffs builds so changes are visible.
|
||||
|
||||
## What's here
|
||||
|
||||
- `tools/mtk_fw_extract.py` — carve containers into regions + manifest
|
||||
(plaintext NDS32 code/data at true load addresses)
|
||||
- `tools/mtk_fw_elf.py` — emit `EM_NDS32` ELF per blob (Ghidra-ready,
|
||||
PT_LOAD per region)
|
||||
- `tools/fw_history.py` — walk a linux-firmware git checkout and recover
|
||||
every revision's build metadata → `dataset/history.jsonl`
|
||||
- `tools/fw_diff.py` — version diff: region-aligned byte equality +
|
||||
plausible string-set changes (the human-readable changelog)
|
||||
- `docs/format.md` — container format spec, cited to kernel source
|
||||
- `docs/boot.md` — the four images / three cores, load sequence, cited
|
||||
- `docs/findings.md` — verified-claims log + unknowns registry
|
||||
- `docs/reports/` — pilot version-diff reports (mt7981 2022→2024→2026)
|
||||
- `tests/` — golden-manifest + emitter tests
|
||||
|
||||
## Quickstart
|
||||
|
||||
# any host with linux-firmware's mediatek blobs (or set MTK_FW_DIR):
|
||||
make extract # carve every mt79xx wm/wa/patch blob found
|
||||
make elf # emit ELFs into extracted/<blob>/<blob>.elf
|
||||
make test # golden tests (skips if no blobs present)
|
||||
|
||||
# full dataset from linux-firmware history (~5 min clone):
|
||||
git clone https://git.kernel.org/pub/scm/linux/kernel/git/firmware/linux-firmware.git firmware/linux-firmware
|
||||
python3 tools/fw_history.py firmware/linux-firmware -o dataset/history.jsonl
|
||||
python3 tools/fw_diff.py old.bin new.bin --md report.md
|
||||
|
||||
No proprietary firmware bytes are committed to this repository. Tools operate
|
||||
on blobs from your own linux-firmware copy (see `make extract`).
|
||||
on blobs from your own linux-firmware copy. Derived artifacts under
|
||||
`extracted*/` and `firmware/` are gitignored.
|
||||
|
||||
Quickstart:
|
||||
## Conventions
|
||||
|
||||
make extract # carve every MediaTek Connac2 blob on this host
|
||||
python3 tools/mtk_fw_extract.py /lib/firmware/mediatek/mt7981_wm.bin -o extracted
|
||||
Every documented claim cites evidence (kernel source line, blob offset) or is
|
||||
marked unknown — see `docs/findings.md`. When a golden test fails against a
|
||||
newer snapshot, the blob changed: update the golden *and* record the diff.
|
||||
|
||||
Project plan and working notes: [PLAN.md](PLAN.md) (development log, quality
|
||||
gates, decision function). Documentation as it lands: [docs/](docs/).
|
||||
|
||||
License: MIT (code), CC-BY-SA-4.0 (docs).
|
||||
License: MIT (code), CC-BY-SA-4.0 (docs). Project log and decision function:
|
||||
[PLAN.md](PLAN.md).
|
||||
|
|
|
|||
59
docs/boot.md
Normal file
59
docs/boot.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# How Filogic (MT7981/MT7986) WiFi boots — the four images and three cores
|
||||
|
||||
All claims cited to Linux source (mt76 driver unless noted) or to observed
|
||||
blob content (`docs/findings.md`, `dataset/history.jsonl`). Inference is
|
||||
marked INFERENCE.
|
||||
|
||||
## The cast
|
||||
|
||||
The WiFi subsystem of a Filogic SoC contains three processor cores plus a
|
||||
masked boot ROM, programmed by the kernel driver at probe time:
|
||||
|
||||
| Image | Runs on | Role |
|
||||
|---|---|---|
|
||||
| `mt79xx_rom_patch.bin` | patch RAM over boot ROM behavior | small fixup applied before main firmware (evidence: `mt76_connac2_load_patch()`; patch strings `sys_patch_common_mcu.c`) |
|
||||
| `mt79xx_wm.bin` | WM core ("neptune") | main MAC/PHY firmware: TX/RX control, rate adaptation, MU (MURU) scheduling, calibration, power/thermal (INFERENCE from strings: `RA_PARAM_*`, `MURU_*`, `DPD Pparam … Do Cal`, `LPTPO`/`EVMTPO`) |
|
||||
| `mt79xx_wa.bin` | WA core | offload/queue-management firmware (INFERENCE: separate load target in driver; distinct ILM/DLM addresses `0x10200000`/`0x10300000`) |
|
||||
| `mt79xx_wo*.bin` | WED offload core | packet DMA between WiFi and ethernet; FreeRTOS v10.0/NDS32 (string evidence). Loaded NOT by mt76 but by the ethernet WED driver: `drivers/net/ethernet/mediatek/mtk_wed_mcu.c` |
|
||||
|
||||
WM/WA/WO firmware is NDS32LE (`docs/format.md` §ISA).
|
||||
|
||||
## Load sequence (mt7915e driver, SoC/PCIe)
|
||||
|
||||
Cited: `mt7915/mcu.c: mt7915_mcu_init()` →
|
||||
`mt7915_load_firmware()`; helpers in `mt76_connac_mcu.c`.
|
||||
|
||||
1. Driver acquires the patch semaphore
|
||||
(`mt76_connac_mcu_get_patch_sem()`, MCU PATCH_SEM_CONTROL message).
|
||||
2. Patch download: `mt76_connac2_load_patch()` parses the BE patch
|
||||
container, busmaster-writes each section to its `addr` (observed
|
||||
`0x00900000` patch RAM), resets the MCU, waits for `FW_STATE_RDY`
|
||||
handshake.
|
||||
3. Patch semaphore released; WM container loaded
|
||||
(`mt76_connac2_load_ram` → `mt76_connac2_send_ram`): regions streamed in
|
||||
table order to their `addr` destinations.
|
||||
4. WA container loaded the same way, then the driver polls for
|
||||
firmware-ready (`mt7915_mcu_fw_log`, `fw_own` handshake in
|
||||
`mt76_connac_mcu.c`).
|
||||
5. On SoCs with WED, `mtk_wed` (ethernet driver) separately loads WO
|
||||
firmware for the offload core (cited: `mtk_wed_mcu.c`).
|
||||
|
||||
No signature verification exists anywhere on this path: the loader computes
|
||||
nothing cryptographic, and the optional per-region encryption
|
||||
(`FW_FEATURE_SET_ENCRYPT`) is unset in every observed revision — so blobs on
|
||||
this platform are freely replaceable at the OS level
|
||||
(INFERENCE from absence + plaintext; hardware-empirical confirmation is a
|
||||
planned P2 milestone).
|
||||
|
||||
## Download destinations (observed, mt7981 WM)
|
||||
|
||||
| Address family | feature_set | observed in |
|
||||
|---|---|---|
|
||||
| `0x0041xxxx`, `0x0023xxxx` | 0x00 | data-ish regions |
|
||||
| `0x0220xxxx–0x0233xxxx` | 0x00 / 0x20 | code+data (region 0 carries OVERRIDE_ADDR) |
|
||||
| `0xe003xxxx–0xe009xxxx` | 0x00 | large code regions |
|
||||
| `0xf000xxxx–0xf009xxxx` | **0x80 (bit7, undefined upstream — U1)** | consistently flagged |
|
||||
|
||||
Meaning of these bus ranges beyond "download destinations": not yet
|
||||
established (see PLAN open items; will be mapped in P1 RE against
|
||||
`mt7915/regs.h`).
|
||||
98
docs/format.md
Normal file
98
docs/format.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# MediaTek Connac2 WiFi firmware container format
|
||||
|
||||
Scope: `mt7915/7916/7981/7986_{wm,wa,rom_patch}.bin` as shipped in
|
||||
linux-firmware. All field layouts are transcribed from
|
||||
`drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h` (structs
|
||||
`mt76_connac2_fw_trailer`, `mt76_connac2_fw_region`,
|
||||
`mt76_connac2_patch_hdr`, `mt76_connac2_patch_sec`); loader behavior from
|
||||
`mt76_connac_mcu.c` (`mt76_connac2_load_patch`, `mt76_connac2_load_ram`,
|
||||
`mt76_connac2_send_ram`). Verified against 54 historical blob revisions
|
||||
(`dataset/history.jsonl`).
|
||||
|
||||
## RAM images (`*_wm.bin`, `*_wa.bin`) — little-endian
|
||||
|
||||
```
|
||||
offset 0: region data, packed back-to-back in region-table order
|
||||
(driver streams from offset 0, advancing by each region's len)
|
||||
+sum(len): [optional] hidden second trailer — 100B on WM, 36B on WA
|
||||
(present on every observed revision; loader never reads it)
|
||||
+gap: region table: n_region × 40-byte records
|
||||
end-36: 36-byte trailer
|
||||
```
|
||||
|
||||
### Region record (40 bytes)
|
||||
|
||||
| off | size | field | notes |
|
||||
|---|---|---|---|
|
||||
| 0 | 4 | decomp_crc | CRC of decompressed data (unused when decomp_len==0) |
|
||||
| 4 | 4 | decomp_len | 0 = stored uncompressed (all observed revisions) |
|
||||
| 8 | 4 | decomp_blk_sz | MCU-side decompression block size |
|
||||
| 12 | 4 | rsv | |
|
||||
| 16 | 4 | addr | download destination (bus address) |
|
||||
| 20 | 4 | len | stored length == download length |
|
||||
| 24 | 1 | feature_set | bit0 ENCRYPT, bits1-2 KEY_IDX, bit4 ENCRY_MODE, bit5 OVERRIDE_ADDR, bit6 NON_DL, **bit7: undefined upstream — see U1** |
|
||||
| 25 | 1 | type | content tag: 0 DEFAULT everywhere in this family; mt7921 uses 2 = CLC (country/location data, paired with NON_DL) |
|
||||
| 26 | 14 | rsv1 | |
|
||||
|
||||
Observed `feature_set` bits in the wild: `0x20` (OVERRIDE_ADDR, WM region 0),
|
||||
`0x80` (unknown, all `0xf0xxxxxx` regions), `0xc0` (NON_DL|0x80, zero-length
|
||||
entries). No observed blob sets ENCRYPT.
|
||||
|
||||
### Trailer (36 bytes, at EOF)
|
||||
|
||||
| off | size | field | notes |
|
||||
|---|---|---|---|
|
||||
| 0 | 1 | chip_id | 0x0b mt7915, 0x13 mt7916, 0x14 mt7981, 0x0f mt7986 (WM); 0x00 on WA |
|
||||
| 1 | 1 | eco_code | silicon eco |
|
||||
| 2 | 1 | n_region | |
|
||||
| 3 | 1 | format_ver | |
|
||||
| 4 | 1 | format_flag | |
|
||||
| 5 | 2 | rsv | |
|
||||
| 6 | 10 | fw_ver[10] | **stripped by MTK**: `____000000` (WM) / `DEV_000000` (WA) |
|
||||
| 16 | 15 | build_date[15] | `%Y%m%d%H%M%S` ASCII |
|
||||
| 31 | 4 | crc | LE32 |
|
||||
|
||||
### Hidden second trailer (not read by the upstream loader)
|
||||
|
||||
WM (100B): `16×'#'`, `40 00 00 00`, 1-byte string length, flags, then the
|
||||
full build string `t-neptune-<branch>-mt7915-<n>-<CHIP>_<MPTAG>_<MPVER>-<build-ts>`,
|
||||
`'#'`-padded. WA (36B): no string. This restores the version metadata MTK
|
||||
strips from fw_ver — the key input to version diffing (`tools/fw_history.py`).
|
||||
|
||||
Observed build strings per revision: `dataset/history.jsonl`.
|
||||
|
||||
## Patch images (`*_rom_patch.bin`) — big-endian
|
||||
|
||||
```
|
||||
offset 0: 92-byte header
|
||||
offset 92: n_section × 64-byte section records
|
||||
explicit: section data at each section's `offs`
|
||||
```
|
||||
|
||||
Header: `build_date[16]`, `platform[4]` ("ALPS" on all observed), `hw_sw_ver`,
|
||||
`patch_ver`, `checksum(u16)`, `rsv(u16)`, descriptor (`patch_ver`, `subsys`,
|
||||
`feature`, `n_section`, `crc`, `rsv[11]`).
|
||||
|
||||
Section record: `type`, `offs`, `size`, info union (`addr`, `len`,
|
||||
`key_idx`/enc-mode, `align_len`). Observed: `type == 0x30002` on every
|
||||
section; `enc_type == 0` (plaintext); download addresses `0x00900000`-range;
|
||||
section counts 1 (7916/7981/7986) and 2 (7915, one small + one data section).
|
||||
|
||||
## WO images (`mt7981_wo.bin`, `mt7986_wo_{0,1}.bin`)
|
||||
|
||||
Different container, loaded by the ethernet-side WED driver
|
||||
(`drivers/net/ethernet/mediatek/mtk_wed_mcu.c`), not mt76. Not yet parsed
|
||||
(unknown U3). Firmware content: FreeRTOS v10.0 + NDS32 port, "coe/" source
|
||||
paths (string evidence).
|
||||
|
||||
## Firmware ISA
|
||||
|
||||
NDS32 (Andes) 32-bit little-endian. Evidence: `GCC: (2018-10-23_nds32le-elf-mculib-v3) 4.9.4` in mt7981_wa data;
|
||||
`portable/GCC/NDS32/port.c` in mt7981_wo. `tools/mtk_fw_elf.py` emits
|
||||
`EM_NDS32`(167) ET_EXEC ELF for Ghidra import.
|
||||
|
||||
## Unknowns
|
||||
|
||||
U1 `feature_set` bit7 (0x80): correlated with `0xf0xxxxxx` destinations;
|
||||
undefined upstream. U2 hidden-trailer non-string fields. U3 WO container
|
||||
format. See `docs/findings.md`.
|
||||
Loading…
Reference in a new issue