99 lines
4.1 KiB
Markdown
99 lines
4.1 KiB
Markdown
|
|
# 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`.
|