rushabhcodes/fidget-counter
A tscircuit package created by rushabhcodes.
- Version
- 1.0.31
- License
- unset
- Stars
- 0
firmware/tools/verify_build.py
"""Fail-closed checks on the actual linked RevC image, configuration and memory."""
import hashlib, json, re, struct, subprocess, sys
from pathlib import Path
APP = Path(__file__).resolve().parents[1]
def check_hex(path):
memory = {}
upper = 0
eof = False
for line in path.read_text().splitlines():
if not line.startswith(":"):
raise ValueError("Invalid Intel HEX line")
row = bytes.fromhex(line[1:])
if len(row) != row[0] + 5 or sum(row) & 255:
raise ValueError("Invalid Intel HEX length/checksum")
length, address, kind = row[0], int.from_bytes(row[1:3], "big"), row[3]
data = row[4:4 + length]
if eof:
raise ValueError("Data after EOF")
if kind == 0:
for offset, value in enumerate(data):
key = upper + address + offset
if key in memory and memory[key] != value:
raise ValueError("Conflicting HEX data")
memory[key] = value
elif kind == 1:
if length or address: raise ValueError("Malformed EOF")
eof = True
elif kind in (2, 4):
if length != 2 or address: raise ValueError("Malformed address")
upper = int.from_bytes(data, "big") << (4 if kind == 2 else 16)
elif kind in (3, 5):
# Start-segment/start-linear metadata do not write memory.
# Actual boot uses the independently checked Cortex-M vectors.
if length != 4 or address: raise ValueError("Malformed start address")
else:
raise ValueError("Unsupported HEX record type")
if not eof or not memory: raise ValueError("Incomplete HEX")
# Allow application flash only. UICR reset is configured by Zephyr startup code.
if any(address >= 0x2e000 for address in memory):
raise ValueError("HEX writes outside the184KiB application partition")
vector = bytes(memory[i] for i in range(8))
stack, reset = struct.unpack("<II", vector)
if not 0x20000000 < stack <= 0x20006000 or stack % 8:
raise ValueError("Initial stack is outside nRF52810 RAM")
if not reset & 1 or not (8 <= (reset & ~1) < 0x2e000):
raise ValueError("Invalid Cortex-M reset vector")
if (reset & ~1) not in memory or (reset & ~1) + 1 not in memory:
raise ValueError("HEX does not contain its reset handler")
return {"initial_stack":hex(stack), "reset_vector":hex(reset),
"flash_data_bytes":len(memory), "highest_flash_address":hex(max(memory))}
def main():
build = Path(sys.argv[1] if len(sys.argv) > 1 else APP / "build")
config = (build / "zephyr/.config").read_text()
required = ["SOC_NRF52810_QFAA", "BOARD_FIDGET_COUNTER", "BT", "BT_CTLR", "BT_PERIPHERAL",
"CLOCK_CONTROL_NRF_K32SRC_XTAL", "NVS_DATA_CRC"]
for symbol in required:
assert re.search(r"^CONFIG_" + symbol + r"=y$", config, re.M), symbol
for symbol in ["SOC_DCDC_NRF52X","SERIAL","CONSOLE","BT_SMP","NRF_APPROTECT_LOCK"]:
assert not re.search(r"^CONFIG_" + symbol + r"=y$", config, re.M), symbol
for symbol, value in [("FLASH_SIZE",192),("SRAM_SIZE",24)]:
assert re.search(r"^CONFIG_" + symbol + "=" + str(value) + "$", config, re.M), symbol
board = (APP / "boards/others/fidget_counter/fidget_counter.dts").read_text()
assert "NRF_PSEL(TWIM_SCL, 0, 26)" in board
assert "NRF_PSEL(TWIM_SDA, 0, 27)" in board
assert "gpio-as-nreset" in board and "0x0002e000 0x00002000" in board
circuit = (APP.parent / "index.circuit.tsx").read_text()
for label, port in [("SCL","P0_26"),("SDA","P0_27"),("HALL_INT","P0_28")]:
assert re.search(r'n: "' + label + '", p: "' + port + '"', circuit), label
assert '"SW1"' not in circuit and "GPIO_CAP_PRESS" not in circuit
dts = (build / "zephyr/zephyr.dts").read_text()
assert 'model = "Fidget Counter RevC CR2450"' in dts
assert '"gpio-leds"' not in dts and '"gpio-keys"' not in dts
elf = build / "zephyr/zephyr.elf"
raw = subprocess.check_output(["arm-none-eabi-readelf","-lW",str(elf)], text=True)
ram_end = 0x20000000
for line in raw.splitlines():
columns = line.split()
if not columns or columns[0] != "LOAD": continue
virtual, physical, filesz, memsz = [int(columns[i],16) for i in [2,3,4,5]]
if filesz and physical < 0x20000000: assert physical + filesz <= 0x2e000
if 0x20000000 <= virtual < 0x30000000:
assert virtual + memsz <= 0x20006000
ram_end = max(ram_end, virtual + memsz)
# Thread stacks are already in RAM usage; leave2KiB additional headroom.
assert ram_end <= 0x20005800, "Less than2KiB static RAM headroom"
hexpath = build / "zephyr/zephyr.hex"
result = check_hex(hexpath)
result.update({"compiled_for":"nRF52810 QFAA / RevC", "application_flash_limit":188416,
"ram_bytes":ram_end - 0x20000000, "ram_headroom_bytes":0x20006000 - ram_end,
"physical_tested":False, "zephyr_commit":"19aeae80fac00aa3310ef9a26d08fab4502e812f",
"compiler":subprocess.check_output(["arm-none-eabi-gcc","--version"],text=True).splitlines()[0],
"source_commit":subprocess.check_output(["git","rev-parse","HEAD"],cwd=APP,text=True).strip(),
"files":{name:hashlib.sha256(p.read_bytes()).hexdigest() for name,p in
[("fidget-counter-revc.hex",hexpath),("fidget-counter-revc.elf",elf),
("build.config",build/"zephyr/.config"),("zephyr.dts",build/"zephyr/zephyr.dts")]}})
(build / "firmware-build-manifest.json").write_text(json.dumps(result,indent=2)+"\n")
print(json.dumps(result,indent=2))
if __name__ == "__main__": main()