107 lines
4 KiB
Python
107 lines
4 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Walk linux-firmware git history for MediaTek Connac2 WiFi blobs.
|
||
|
|
|
||
|
|
For every revision of every mt79*_wm/wa/rom_patch/wo file, extract
|
||
|
|
version-recovering metadata (kernel trailer date, hidden build string,
|
||
|
|
region table) and emit JSONL. Metadata only — no blob bytes are stored.
|
||
|
|
|
||
|
|
Usage: python3 tools/fw_history.py [linux-firmware-repo] -o dataset/history.jsonl
|
||
|
|
"""
|
||
|
|
import argparse
|
||
|
|
import hashlib
|
||
|
|
import json
|
||
|
|
import re
|
||
|
|
import subprocess
|
||
|
|
import struct
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||
|
|
from mtk_fw_extract import PATCH_HDR, REGION, TRAILER # noqa: E402
|
||
|
|
|
||
|
|
TARGET_RE = re.compile(r'^mediatek/mt79(15|16|81|86)_(wm|wa|rom_patch|wo(_[01])?)\.bin$')
|
||
|
|
|
||
|
|
|
||
|
|
def git(repo, *args, binary=False):
|
||
|
|
r = subprocess.run(['git', '-C', str(repo), *args], capture_output=True)
|
||
|
|
if r.returncode:
|
||
|
|
raise RuntimeError(r.stderr.decode())
|
||
|
|
return r.stdout if binary else r.stdout.decode()
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
def peek_ram(d: bytes) -> dict:
|
||
|
|
t = TRAILER.unpack_from(d, len(d) - TRAILER.size)
|
||
|
|
chip, eco, n = t[0], t[1], t[2]
|
||
|
|
bdate = t[7].decode(errors='replace')
|
||
|
|
table = len(d) - TRAILER.size - n * REGION.size
|
||
|
|
off, addrs, feats = 0, [], []
|
||
|
|
for i in range(n):
|
||
|
|
r = REGION.unpack_from(d, table + i * REGION.size)
|
||
|
|
addrs.append(f'0x{r[4]:08x}')
|
||
|
|
feats.append(f'0x{r[6]:02x}')
|
||
|
|
off += r[5]
|
||
|
|
hidden = None
|
||
|
|
gap = table - off
|
||
|
|
if gap > 0:
|
||
|
|
runs = [x.strip(b'#') for x in re.findall(rb'[\x20-\x7e]{8,}', d[off:table])]
|
||
|
|
runs = [x for x in runs if len(x) >= 8]
|
||
|
|
hidden = runs[0].decode() if runs else None
|
||
|
|
return {'chip_id': chip, 'eco': eco, 'n_region': n,
|
||
|
|
'trailer_fw_ver': t[6].decode(errors='replace'),
|
||
|
|
'trailer_date': bdate, 'hidden': hidden,
|
||
|
|
'region_addrs': addrs, 'region_feats': feats, 'gap': gap}
|
||
|
|
|
||
|
|
|
||
|
|
def peek_patch(d: bytes) -> dict:
|
||
|
|
h = PATCH_HDR.unpack_from(d, 0)
|
||
|
|
n = h[9]
|
||
|
|
return {'platform': h[1].decode(errors='replace'),
|
||
|
|
'patch_date': h[0].decode(errors='replace').strip('\x00'),
|
||
|
|
'hw_sw_ver': f'0x{h[2]:08x}', 'n_section': n}
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
ap = argparse.ArgumentParser()
|
||
|
|
ap.add_argument('repo', nargs='?', default='firmware/linux-firmware')
|
||
|
|
ap.add_argument('-o', '--out', default='dataset/history.jsonl')
|
||
|
|
args = ap.parse_args()
|
||
|
|
repo = Path(args.repo)
|
||
|
|
head = git(repo, 'rev-parse', 'HEAD').strip()
|
||
|
|
files = [f for f in git(repo, 'ls-tree', '-r', '--name-only', 'HEAD',
|
||
|
|
'mediatek').splitlines()
|
||
|
|
if TARGET_RE.match(f)]
|
||
|
|
out = Path(args.out)
|
||
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||
|
|
n_rev = 0
|
||
|
|
with out.open('w') as fh:
|
||
|
|
for f in sorted(files):
|
||
|
|
log = git(repo, 'log', '--format=%H%x00%cs%x00%s', '--', f)
|
||
|
|
revs = [l.split('\x00') for l in log.strip().splitlines()]
|
||
|
|
for commit, date, subject in reversed(revs): # oldest first
|
||
|
|
d = git(repo, 'cat-file', 'blob', f'{commit}:{f}', binary=True)
|
||
|
|
rec = {'file': f, 'commit': commit[:12], 'date': date,
|
||
|
|
'subject': subject, 'repo_head': head[:12],
|
||
|
|
'sha256': hashlib.sha256(d).hexdigest()[:16],
|
||
|
|
'size': len(d)}
|
||
|
|
try:
|
||
|
|
if '_rom_patch' in f:
|
||
|
|
rec.update(peek_patch(d))
|
||
|
|
elif '_wo' in f:
|
||
|
|
rec['format'] = 'wed-wo (unsupported parser)'
|
||
|
|
else:
|
||
|
|
rec.update(peek_ram(d))
|
||
|
|
except Exception as e: # noqa: BLE001
|
||
|
|
rec['parse_error'] = str(e)
|
||
|
|
fh.write(json.dumps(rec) + '\n')
|
||
|
|
n_rev += 1
|
||
|
|
hv = rec.get('hidden') or rec.get('patch_date') or ''
|
||
|
|
print(f"{f.split('/')[-1]:26s} {date} {commit[:8]} "
|
||
|
|
f"{rec.get('trailer_date', rec.get('patch_date', '?'))} "
|
||
|
|
f"{hv[:60]}")
|
||
|
|
print(f'--- {n_rev} revisions -> {out}')
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
main()
|