Accept native SD cards for internal settings alongside legacy NVMe, reject USB ancestry and ambiguous disks, and preserve read-only boot loading with explicit recovery writes. Require built-in MMC drivers, validate cached kernel configuration, and provide SD-only, SD-first and USB-first EEPROM profiles. Expose typed create and inspect commands through fds-flash, reuse the existing cartridge creation code, and document the optional e2fsprogs package dependency. Extend storage, EEPROM, flashing and hardware-test coverage and advance the wiki reference to the published SD guide. Validation: rootfs checks for CLI/development, boot matrix, internal storage, EEPROM, flash, workstation, emulator, make check and wiki checks passed. The full internal suite passed on an unchanged rerun after one unexplained VM shutdown stall. Standalone init-test was blocked by its unavailable pinned upstream kernel. Physical Pi checks remain pending.
65 lines
3.9 KiB
Python
65 lines
3.9 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, boot_order in [('production','0xf1'),('development','0xf41'),('maintenance','0xf14')]:
|
|
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
|
|
assert manifest['settings']['BOOT_ORDER'] == boot_order
|
|
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=0xf1' 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')
|