4.1 KiB
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.