diff --git a/Makefile b/Makefile index f4fd96e..d9c24c1 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ BLOBS ?= $(wildcard /lib/firmware/mediatek/mt79*_wm.bin) \ $(wildcard /lib/firmware/mediatek/mt79*_wa.bin) \ $(wildcard /lib/firmware/mediatek/mt79*_rom_patch.bin) -.PHONY: help extract +.PHONY: help extract test help: @echo "make extract - carve all MediaTek Connac2 blobs found on this host" @@ -10,3 +10,6 @@ help: extract: python3 tools/mtk_fw_extract.py $(BLOBS) + +test: + python3 -m unittest discover -s tests -v diff --git a/docs/findings.md b/docs/findings.md index 6a0b268..80e129c 100644 --- a/docs/findings.md +++ b/docs/findings.md @@ -66,6 +66,10 @@ 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). + ## Unknowns registry - **U1 — `feature_set` bit 7 (0x80):** observed only on WM regions at diff --git a/tests/test_extract.py b/tests/test_extract.py new file mode 100644 index 0000000..a04e03a --- /dev/null +++ b/tests/test_extract.py @@ -0,0 +1,105 @@ +"""Golden-manifest tests for the Connac2 container extractor. + +Runs against whatever MediaTek blobs exist on this host (skip otherwise). +Golden values observed from linux-firmware snapshot 2026-04-20; when a value +here fails against a newer snapshot, the blob changed — update the golden +value *and* note it in the changelog, don't silently absorb it. +""" +import subprocess +import sys +import unittest +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +FW = Path('/lib/firmware/mediatek') + +# filename -> (chip_id, eco, n_region, hidden_trailer_string_or_None) +GOLDEN_WM = { + 'mt7981_wm.bin': (0x14, 0, 11, + 't-neptune-main-mt7915-1953-MT7981_MP2111_IMP-20240823161204'), + 'mt7986_wm.bin': (0x0f, 0, 11, + 't-neptune-main-mt7915-1953-MT7986_MP2111_IMP-20240823160608'), + 'mt7916_wm.bin': (0x13, 0, 9, + 't-neptune-main-mt7915-1953-MT7916_MP2111_IMP-20240823170147'), + 'mt7915_wm.bin': (0x0b, 1, 7, + 't-neptune-mp-mt7915-2045-MT7915_MP_7_4_2045-20220929103802'), +} +GOLDEN_WA = { + 'mt7981_wa.bin': (0x00, 0, 3, None), + 'mt7986_wa.bin': (0x00, 0, 3, None), + 'mt7916_wa.bin': (0x00, 0, 3, None), + 'mt7915_wa.bin': (0x00, 1, 3, None), +} + + +def parse(path: Path, out: Path) -> dict: + out.mkdir(parents=True, exist_ok=True) + sys.path.insert(0, str(REPO / 'tools')) + import mtk_fw_extract as x + return (x.carve_patch if 'patch' in path.name else x.carve_ram)(path, out) + + +class LayoutClosure(unittest.TestCase): + """sum(region.len) + hidden trailer + table + trailer == file size.""" + + def test_closure(self): + blobs = sorted(FW.glob('mt79*_wm.bin')) + sorted(FW.glob('mt79*_wa.bin')) + if not blobs: + self.skipTest('no mediatek blobs on this host') + import struct + T = struct.Struct('<5B2s10s15sI') + R = struct.Struct('