Files
fds-os/tests/integration/m12-eeprom.py
T
2026-09-21 22:29:23 +08:00

64 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""Real Pi 5 firmware configuration roundtrips without any hardware access."""
import importlib.machinery
import importlib.util
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
project=Path(__file__).resolve().parents[2]
sys.path.insert(0,str(project/'tools'))
from eeprom_inputs import prepare,sha256
cache,pin=prepare()
work=Path(tempfile.mkdtemp(prefix='m12-eeprom.',dir=project/'out'))
base=cache/'pieeprom.bin';before=sha256(base)
# Use the pinned upstream parser to compare immutable firmware payloads as well
# as configuration text, including both AB slots where present.
loader=importlib.machinery.SourceFileLoader('upstream_eeprom',str(cache/'rpi-eeprom-config'))
spec=importlib.util.spec_from_loader(loader.name,loader);upstream=importlib.util.module_from_spec(spec);loader.exec_module(upstream)
original=upstream.BootloaderImage(str(base))
protected={s.filename:original.get_file(s.filename) for s in original._sections if s.filename and s.filename not in ['bootconf.txt','bootconf.sig']}
def run(arguments,ok=True):
result=subprocess.run([str(project/'tools/configure-pi-eeprom'),*map(str,arguments)],capture_output=True,text=True,env={**os.environ,'FDS_OFFLINE':'1'})
assert (result.returncode==0)==ok,(arguments,result.stdout,result.stderr)
return result
for profile in ['production','development']:
output=work/profile
run(['--profile',profile,'--output-directory',output])
manifest=json.loads((output/'manifest.json').read_text())
assert manifest['hardware_modified'] is False and manifest['custom_inputs_provided'] is False
for name,digest in manifest['files'].items():assert sha256(output/name)==digest
image=upstream.BootloaderImage(str(output/'configured.bin'))
for name,data in protected.items():assert image.get_file(name)==data,(profile,name)
assert 'NET_INSTALL_ENABLED=0' in image.get_file('bootconf.txt').decode()
second=work/(profile+'-repeat');run(['--profile',profile,'--output-directory',second])
assert sha256(output/'configured.bin')==sha256(second/'configured.bin')
assert sha256(output/'rollback.bin')==sha256(second/'rollback.bin')
run(['--profile',profile,'--output-directory',output],ok=False)
print(f'PASS: {profile}: actual Pi 5 firmware roundtrip, immutable payloads, deterministic files and overwrite refusal',flush=True)
saved=work/'board.conf'
saved.write_text('[all]\nBOOT_ORDER=0xf461\nCUSTOM_BOARD_SETTING=retained\n[gpio8=0]\nBOOT_ORDER=0xf7\nNET_INSTALL_ENABLED=1\nOTHER_SETTING=unchanged\n')
output=work/'custom'
run(['--current-config',saved,'--output-directory',output])
changed=(output/'configured.conf').read_text()
assert changed.count('BOOT_ORDER=')==1 and 'BOOT_ORDER=0xf6' in changed
assert 'CUSTOM_BOARD_SETTING=retained' in changed and '[gpio8=0]\nOTHER_SETTING=unchanged' in changed
rollback=upstream.BootloaderImage(str(output/'rollback.bin')).get_file('bootconf.txt').decode()
assert rollback==saved.read_text(),'Rollback lost the original conditional settings'
bad=work/'bad.bin';bad.write_bytes(b'not firmware')
run(['--base-image',bad,'--output-directory',work/'bad'],ok=False)
run(['--base-image','/dev/null','--output-directory',work/'device'],ok=False)
large=work/'large.conf';large.write_bytes(b'x'*5000)
run(['--current-config',large,'--output-directory',work/'large'],ok=False)
assert sha256(base)==before
(work/'evidence.json').write_text(json.dumps({'format':1,'upstream_commit':pin['commit'],'base_sha256':before,'hardware_modified':False},indent=2)+'\n')
link=project/'out/m12-eeprom-latest';link.unlink(missing_ok=True);link.symlink_to(work.name)
print(f'PASS: preserved custom settings, exact rollback, invalid-input rejection and unchanged firmware input: {work}')
print('SKIP: EEPROM application, physical boot order, PMIC and Pi timing')