"""Real-firmware GSP boot runner (device model + versioned profile + shim). Loads ``gsp-570.144.fwimage.bin`` at its **link addresses** (RAM base 0), builds the :mod:`sim.devices` registry from a ``greeneye-sim-profile/1`` document, runs bounded (<= 2M instructions per attempt), and records a normal ``greeneye-session/1`` directory carrying the device state, unknown-MMIO summary and the boot-shim assumptions. Boot ladder rungs (docs/35-GREENEYE-SIMULATOR-DESIGN.md §3.3) reported by :class:`GspBootResult`: * ``B0`` -- flat image loads, first MMIO observed at ``0x2000_0000_0000_0A00``. * ``B1`` -- first fault past the QEMU point, explained by the device model (the QEMU fault is the PMC ``BOOT_42`` chip-ID read; modelled by :class:`sim.devices.pmc.PmcBlock`). * ``B2`` -- boot-stage progress: vector/MPU/TCM setup and bootargs consumption exercised with the profile. * ``B3`` -- first CSB/TRANSCOM message-window poll served with read-back. * ``B4`` -- message loop reached (waits on a host message). The boot shim supplies reset-time state that the real SEC2 booter/host would provide (bootargs and the register arguments); every shim value is an explicit, documented assumption in the profile and the session metadata. The device model itself never fakes a register read. GMMU/FBIF observability (``--trace-out``, default off): the profile's ``gmmu``/``fbif`` sections declare observe-only register windows (:mod:`sim.devices.observe`); every access in them is recorded (never served, never faked) and, when requested, exported as a ``greeneye-gmmu-fbif-trace/1`` document. ``--symbols`` optionally resolves recorded PCs through a ``greeneye-symbols/1`` JSON database, and ``--observe-window BASE:SIZE[:LABEL[:MODE]]`` adds a runtime window list for surfaces whose base is not established yet. The count summary is always part of the boot report; see ``docs/42-GMMU-FBIF-OBSERVABILITY.md``. """ from __future__ import annotations import argparse import hashlib import json import os import sys from dataclasses import dataclass, field from greeneye.device_map import firmware_sha256 from greeneye.elf import load_elf from greeneye.errors import GreenEyeError from greeneye.symbols import SymbolDB from sim.devices import load_profile, registry_from_profile from sim.libos import LibosSyscalls, PROCESSOR_SUSPEND, TASK_PORT_SEND_RECV_WAIT from sim.devices.observe import ( ObservedWindow, build_trace, parse_window_spec, ) from sim.machine import ( R_BP, R_BUDGET, R_EBREAK, R_ECALL, R_FAULT, R_STEP, R_WATCH, ) TOOL_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DEFAULT_IMAGE = os.path.normpath( os.path.join(TOOL_ROOT, "..", "..", "firmware", "57X-GSP", "570.144", "gsp-570.144.fwimage.bin") ) DEFAULT_PROFILE = os.path.join(TOOL_ROOT, "sim", "profiles", "tu104-570.144.json") #: Hard cap for one attempt (task rule: <= 2M instructions per attempt). MAX_ATTEMPT_INSTRUCTIONS = 2_000_000 #: Default bounded budget for a boot attempt. DEFAULT_INSTRUCTIONS = 2_000_000 STOP_REASONS = { R_BUDGET: "budget", R_BP: "breakpoint", R_WATCH: "watch", R_EBREAK: "ebreak", R_FAULT: "fault", R_STEP: "step", R_ECALL: "ecall", } # GSP RISC-V address-map aperture starts (header constants). SYSGPA_START = 0x8180000000000000 FBGPA_START = 0x6180000000000000 PRIV_START = 0x2000000000000000 # GspFwWprMeta field constants (header). GSP_FW_WPR_META_MAGIC = 0xdc3aae21371a60b3 GSP_FW_WPR_META_REVISION = 1 GSP_FW_WPR_META_VERIFIED = 0xa0a0a0a0a0a0a0a0 WPR_META_SIZE = 256 # NV_RISCV_BOOTLDR_PARAMS / NV_RISCV_BOOT_PARAMS layout (header). BOOTLDR_BOOT_TYPE_RM = 0x4E56524D # 'NVRM' BOOTLDR_VERSION = 1 BOOTARGS_DEFAULT_SIZE = 0x78 # NV_RISCV_BOOT_PARAMS (120 bytes) LIBOS_MEMORY_REGION_CONTIGUOUS = 1 LIBOS_MEMORY_REGION_LOC_FB = 2 LIBOS_MEMORY_REGION_LOC_SYSMEM = 1 WPRM_ID8 = 0x5750524D # 'WPRM' #: ``libos_bootstrap_args_t``: IMEM then DMEM ``base/size/bs`` u64 fields. BOOTSTRAP_ARGS_SIZE = 6 * 8 def _int(value, default=None): if value is None: return default if isinstance(value, str): return int(value, 0) return int(value) def _write_u32(machine, addr: int, value: int) -> None: machine.write_mem(addr, (value & 0xFFFFFFFF).to_bytes(4, "little")) def _write_u64(machine, addr: int, value: int) -> None: machine.write_mem(addr, (value & 0xFFFFFFFFFFFFFFFF).to_bytes(8, "little")) def build_bootargs(machine, *, bootargs_pa: int, core_dump_pa: int, core_dump_size: int, wpr_meta_pa: int, wpr_meta_size: int = WPR_META_SIZE, bootargs_size: int = BOOTARGS_DEFAULT_SIZE) -> None: """Write ``NV_RISCV_BOOT_PARAMS`` and its ``WPRM`` init arg. Assumption (profile-recorded): the real SEC2 booter/host fills these; we reconstruct the documented structure and one ``WPRM`` libos init argument pointing at the ``GspFwWprMeta`` written by :func:`build_wpr_meta`. """ _write_u32(machine, bootargs_pa + 0x00, BOOTLDR_BOOT_TYPE_RM) machine.write_mem(bootargs_pa + 0x04, (bootargs_size & 0xFFFF).to_bytes(2, "little")) machine.write_mem(bootargs_pa + 0x06, bytes([BOOTLDR_VERSION])) machine.write_mem(bootargs_pa + 0x07, b"\x00") _write_u64(machine, bootargs_pa + 0x08, core_dump_pa) _write_u32(machine, bootargs_pa + 0x10, core_dump_size) # libos.initArgs[0]: WPRM in FB, contiguous. base = bootargs_pa + 0x18 _write_u64(machine, base + 0x00, WPRM_ID8) _write_u64(machine, base + 0x08, wpr_meta_pa) _write_u64(machine, base + 0x10, wpr_meta_size) machine.write_mem(base + 0x18, bytes([LIBOS_MEMORY_REGION_CONTIGUOUS, LIBOS_MEMORY_REGION_LOC_FB])) # initArgs[1..2] stay zero (id8 == 0 terminates the array). def build_bootstrap_args(machine, *, pa: int, imem_base_pa: int, imem_size: int, imem_bs_size: int, dmem_base_pa: int, dmem_size: int, dmem_bs_size: int) -> None: """Write the ``libos_bootstrap_args_t`` the bootloader passes in ``a0``. Assumption (profile-recorded): the real libos bootloader builds this structure (IMEM/DMEM base+size+bs) from its TCM configuration and calls the firmware entry with a pointer to it; we reconstruct the documented layout. Values are declared in the profile, never derived from silicon. """ machine.write_mem(pa, bytes(BOOTSTRAP_ARGS_SIZE)) _write_u64(machine, pa + 0x00, imem_base_pa) _write_u64(machine, pa + 0x08, imem_size) _write_u64(machine, pa + 0x10, imem_bs_size) _write_u64(machine, pa + 0x18, dmem_base_pa) _write_u64(machine, pa + 0x20, dmem_size) _write_u64(machine, pa + 0x28, dmem_bs_size) def build_wpr_meta(machine, *, wpr_meta_pa: int, elf_pa: int, elf_size: int, wpr_start: int, wpr_end: int, boot_bin_offset: int, gsp_fw_offset: int, fb_size: int) -> None: """Write a ``GspFwWprMeta`` (header layout) into RAM. Assumption (profile-recorded): the booter/CPU-RM produces this structure; we place it with a self-consistent layout that satisfies the documented validation (magic/revision/verified, monotonic FB ranges). Values are declared in the profile, never derived from silicon. """ machine.write_mem(wpr_meta_pa, bytes(WPR_META_SIZE)) _write_u64(machine, wpr_meta_pa + 0x00, GSP_FW_WPR_META_MAGIC) _write_u64(machine, wpr_meta_pa + 0x08, GSP_FW_WPR_META_REVISION) _write_u64(machine, wpr_meta_pa + 0x10, elf_pa) _write_u64(machine, wpr_meta_pa + 0x18, elf_size) _write_u64(machine, wpr_meta_pa + 0x20, 0) # sysmemAddrOfBootloader _write_u64(machine, wpr_meta_pa + 0x28, 0) # sizeOfBootloader _write_u64(machine, wpr_meta_pa + 0x30, 0) # bootloaderCodeOffset _write_u64(machine, wpr_meta_pa + 0x38, 0) # bootloaderDataOffset _write_u64(machine, wpr_meta_pa + 0x40, 0) # bootloaderManifestOffset _write_u64(machine, wpr_meta_pa + 0x48, 0) # sysmemAddrOfSignature _write_u64(machine, wpr_meta_pa + 0x50, 0) # sizeOfSignature _write_u64(machine, wpr_meta_pa + 0x58, wpr_start) # gspFwRsvdStart _write_u64(machine, wpr_meta_pa + 0x60, wpr_start) # nonWprHeapOffset _write_u64(machine, wpr_meta_pa + 0x68, 0x10000) # nonWprHeapSize _write_u64(machine, wpr_meta_pa + 0x70, wpr_start) # gspFwWprStart _write_u64(machine, wpr_meta_pa + 0x78, wpr_start) # gspFwHeapOffset _write_u64(machine, wpr_meta_pa + 0x80, wpr_end - wpr_start) # heap size _write_u64(machine, wpr_meta_pa + 0x88, gsp_fw_offset) # gspFwOffset _write_u64(machine, wpr_meta_pa + 0x90, boot_bin_offset) # bootBinOffset _write_u64(machine, wpr_meta_pa + 0x98, 0) # frtsOffset _write_u64(machine, wpr_meta_pa + 0xA0, 0) # frtsSize _write_u64(machine, wpr_meta_pa + 0xA8, wpr_end) # gspFwWprEnd _write_u64(machine, wpr_meta_pa + 0xB0, fb_size) # fbSize _write_u64(machine, wpr_meta_pa + 0xB8, wpr_end) # vgaWorkspaceOffset _write_u64(machine, wpr_meta_pa + 0xC0, 0x100000) # vgaWorkspaceSize _write_u64(machine, wpr_meta_pa + 0xC8, 0) # bootCount _write_u64(machine, wpr_meta_pa + 0xD0, 0) # partitionRpcAddr _write_u64(machine, wpr_meta_pa + 0xF8, GSP_FW_WPR_META_VERIFIED) #: Observed boot milestones (firmware vaddrs) used to classify the rung. #: Evidence: disassembly of the 570.144 image (bootargs consumer reads #: NV_RISCV_BOOT_PARAMS; the relocation loop copies from the DMEM aperture #: into the 0x400E000 runtime region). MILESTONES = ( ("bootargs_consumer", 0x4007A30), ("relocation_copy", 0x4007B7C), ) @dataclass class GspBootResult: ran: bool reason: str image: str profile: str | None = None profile_name: str | None = None image_sha256: str | None = None session_dir: str | None = None instructions: int = 0 cycles: int = 0 cycle_model: str | None = None stop_reason: str | None = None stop_pc: int | None = None stop_pc_fw: int | None = None rung: str = "B0" first_unknown_mmio: dict | None = None mmio_unknown: int = 0 device_unhandled: int = 0 device_accesses: int = 0 device_state_sha256: str | None = None registers: dict = field(default_factory=dict) shim: dict = field(default_factory=dict) libos: dict = field(default_factory=dict) milestones: list = field(default_factory=list) trace_summary: dict | None = None trace_path: str | None = None device_map: object | None = field(default=None, repr=False) def to_dict(self) -> dict: return { "schema": "greeneye-gsp-boot/1", "ran": self.ran, "reason": self.reason, "image": self.image, "image_sha256": self.image_sha256, "profile": self.profile, "profile_name": self.profile_name, "session": self.session_dir, "rung": self.rung, "instructions_executed": self.instructions, "cycles": self.cycles, "cycle_model": self.cycle_model, "stop": {"reason": self.stop_reason, "pc": self.stop_pc, "pc_fw": self.stop_pc_fw}, "first_unknown_mmio": self.first_unknown_mmio, "mmio_unknown": self.mmio_unknown, "device_unhandled": self.device_unhandled, "device_accesses": self.device_accesses, "device_state_sha256": self.device_state_sha256, "registers": self.registers, "boot_shim": self.shim, "libos": self.libos, "milestones": list(self.milestones), "gmmu_fbif_trace": { "path": self.trace_path, "summary": self.trace_summary, }, } def _fw_pc(pc: int) -> int: return pc def classify_rung(machine, *, shim: dict) -> str: """Best rung supported by the observed run (never inferred, only observed).""" if machine.mmio_unknown and machine.mmio_log: first = machine.mmio_log[0] if first["addr"] == 0x2000000000000A00: return "B0" if shim.get("applied"): # The firmware consumed the boot shim and advanced past the first # unknown; B2 is claimed only when the run went past the chip gate and # exercised the MPU/bootargs path (the runner checks the PC). return shim.get("rung", "B1") return "B1" def run_boot( *, image: str = DEFAULT_IMAGE, profile_path: str = DEFAULT_PROFILE, instructions: int = DEFAULT_INSTRUCTIONS, record=None, name: str = "gsp-boot", use_shim: bool = True, shim_overrides: dict | None = None, trace_out: str | None = None, symbols_path: str | None = None, observe_windows: list[str] | None = None, ) -> GspBootResult: """Run one bounded boot attempt and record a session. ``trace_out`` (default off) writes the ``greeneye-gmmu-fbif-trace/1`` observer document to that path (a directory gets ``gmmu-fbif-trace.json`` inside); ``symbols_path`` resolves recorded PCs through a ``greeneye-symbols/1`` JSON database; ``observe_windows`` adds runtime observer windows (``BASE:SIZE[:LABEL[:MODE]]``, repeatable). """ if instructions <= 0 or instructions > MAX_ATTEMPT_INSTRUCTIONS: raise GreenEyeError( f"instructions must be in 1..{MAX_ATTEMPT_INSTRUCTIONS}, got {instructions}") if not os.path.isfile(image): return GspBootResult(ran=False, reason=f"image absent: {image}", image=image) if not os.path.isfile(profile_path): return GspBootResult(ran=False, reason=f"profile absent: {profile_path}", image=image) resolve_pc = None if symbols_path: db = SymbolDB() db.load_json(symbols_path) def resolve_pc(pc, _db=db): sym = _db.resolve(pc) return sym.label(pc) if sym is not None else None profile = load_profile(profile_path) image_obj = load_elf(image) if not image_obj.is_riscv: raise GreenEyeError(f"{image}: not a RISC-V ELF image") sha = firmware_sha256(image) load = profile.get("load", {}) ram_base = _int(load.get("ram_base", 0)) memory_mb = _int(load.get("memory_mb", 12288)) from greeneye.sim_backend import launch_native # deferred session = launch_native( kernel=image, memory_mb=memory_mb, ram_base=ram_base, record=True if record is None else record, name=name, device_map=None, unknown_mmio="lenient", bias=0, ) machine = session.target.machine machine.devices = registry_from_profile(profile, machine=machine) machine.translator = getattr(machine.devices, "dma", None) libos = LibosSyscalls() machine.syscall_handler = libos.handle for spec in observe_windows or (): win = parse_window_spec(spec) machine.devices.add(ObservedWindow( base=win["base"], size=win["size"], label=win["label"], mode=win["mode"], registers=win["registers"], evidence="runtime --observe-window", clock=machine.insn_count, )) shim_meta = {"enabled": bool(use_shim), "assumptions": []} if use_shim: shim = dict(profile.get("boot_shim", {})) if shim_overrides: shim.update(shim_overrides) shim["_image_path"] = image _apply_boot_shim(machine, shim, image_obj, shim_meta) milestones_hit: list[str] = [] trace_path: str | None = None try: reason, pc, a, b = _run_with_milestones(machine, instructions, milestones_hit) model = machine.cycle_model stop_reason = STOP_REASONS.get(reason, str(reason)) first = machine.mmio_log[0] if machine.mmio_log else None reg = machine.devices state = reg.state() if reg is not None else None state_json = json.dumps(state, sort_keys=True, default=str) if state else "" state_sha = hashlib.sha256(state_json.encode()).hexdigest() if state else None registers = {f"x{i}": machine.x[i] for i in range(32) if i and machine.x[i]} registers["pc"] = pc rung = _rung_from_run(machine, shim_meta, milestones_hit, libos) shim_meta["rung"] = rung trace_doc = build_trace( reg, image_sha256=sha, profile=os.path.basename(profile_path), profile_name=profile.get("name"), symbols_path=symbols_path, resolve=resolve_pc, ) if reg is not None else None trace_summary = trace_doc["summary"] if trace_doc is not None else None if trace_out and trace_doc is not None: trace_path = _write_trace_file(trace_out, trace_doc) meta = { "kind": "gsp-boot", "profile": os.path.basename(profile_path), "profile_name": profile.get("name"), "firmware_sha256": sha, "ram_base": ram_base, "instructions_executed": machine.insns, "cycles": machine.cycles, "cycle_model": model.name if model is not None else None, "stop_reason": stop_reason, "stop_pc": pc, "rung": rung, "milestones": list(milestones_hit), "mmio_unknown": machine.mmio_unknown, "first_unknown_mmio": first, "device_unhandled": getattr(reg, "unhandled", 0), "device_accesses": len(getattr(reg, "accesses", ())), "device_state_sha256": state_sha, "boot_shim": shim_meta, "mpu": machine.mpu.state(), "libos": libos.state(), } if trace_summary is not None: meta["gmmu_fbif"] = trace_summary if trace_path is not None: meta["gmmu_fbif_trace"] = trace_path if getattr(session.recorder, "dir", None) is not None: if state is not None: rel = session.recorder.blob( "device-state.json", json.dumps(state, indent=1, sort_keys=True, default=str).encode(), ) meta["device_state_blob"] = rel if trace_doc is not None and trace_out: rel = session.recorder.blob( "gmmu-fbif-trace.json", json.dumps(trace_doc, indent=1, sort_keys=True).encode(), ) meta["gmmu_fbif_trace_blob"] = rel session.recorder.meta.update(meta) session.note( "gsp_boot", rung=rung, instructions=machine.insns, stop_reason=stop_reason, stop_pc=pc, ) session_dir = getattr(session.recorder, "dir", None) finally: session.close() return GspBootResult( ran=True, reason="ok", image=image, profile=profile_path, profile_name=profile.get("name"), image_sha256=sha, session_dir=session_dir, instructions=machine.insns, cycles=machine.cycles, cycle_model=model.name if model is not None else None, stop_reason=stop_reason, stop_pc=pc, stop_pc_fw=_fw_pc(pc), rung=rung, first_unknown_mmio=first, mmio_unknown=machine.mmio_unknown, device_unhandled=getattr(reg, "unhandled", 0), device_accesses=len(getattr(reg, "accesses", ())), device_state_sha256=state_sha, registers=registers, shim=shim_meta, libos=libos.state(), milestones=list(milestones_hit), trace_summary=trace_summary, trace_path=trace_path, ) def _run_with_milestones(machine, budget: int, hit: list[str]): """Run the budget, stopping at each milestone once to record it. Milestone hits are evidence of boot-stage progress (they are firmware vaddrs reached), not inferred from timing or heuristics. """ remaining = budget while remaining > 0: pending = {addr for name, addr in MILESTONES if name not in hit} reason, pc, a, b = machine.run(remaining, breakpoints=pending) remaining = budget - machine.insns if reason == R_BP: for name, addr in MILESTONES: if addr == pc and name not in hit: hit.append(name) break continue return reason, pc, a, b return R_BUDGET, machine.pc, 0, 0 def _rung_from_run(machine, shim_meta: dict, milestones_hit, libos=None) -> str: """Observed rung from milestones, libos services and the unknown-MMIO surface. B0: the first unmodelled access is the PMC ``BOOT_42`` chip-ID read. B1: the device model served the whole run (no unknown MMIO) and the boot chip gate was passed; the run may stop at the bootargs panic/halt. B2: the boot shim was consumed and the firmware reached its relocation stage (milestone ``relocation_copy``) -- boot-stage progress beyond the bootargs consumer. B3: the libos init path completed its services (TCM/pagetable registration, DMA flush) and the message-loop task blocks in the port wait -- the CSB/TRANSCOM poll itself is host-traffic-triggered and remains owed to the message-injection rung (docs/35 §3.3 rev). """ first_unknown = machine.mmio_log[0] if machine.mmio_log else None if first_unknown is not None and first_unknown["addr"] == 0x2000000000000A00: return "B0" stopped = getattr(libos, "stopped", None) if libos is not None else None if stopped and stopped.get("num") in (TASK_PORT_SEND_RECV_WAIT, PROCESSOR_SUSPEND): return "B3" if "relocation_copy" in milestones_hit: return "B2" return "B1" def _rung_note(rung: str) -> str: return { "B0": "first MMIO is the unmodelled PMC BOOT_42 chip-ID read", "B1": "device model serves the boot path; bootargs consumer reached", "B2": "boot-stage progress past the bootargs consumer (relocation)", "B3": "init complete; message-loop task blocks in the port wait " "(CSB poll awaits host traffic)", }.get(rung, rung) def _apply_boot_shim(machine, shim: dict, image_obj, shim_meta: dict) -> None: """Write profile-declared reset state (documented assumption).""" registers = {k: (int(v, 0) if isinstance(v, str) else int(v)) for k, v in shim.get("registers", {}).items()} machine.apply_reset_regs(registers) bootargs_pa = shim.get("bootargs_pa") if bootargs_pa is not None: bootargs_pa = int(bootargs_pa, 0) if isinstance(bootargs_pa, str) else int(bootargs_pa) core_dump_pa = _int(shim.get("core_dump_pa", 0x40000)) core_dump_size = _int(shim.get("core_dump_size", 0x10000)) build_bootargs( machine, bootargs_pa=bootargs_pa, core_dump_pa=core_dump_pa, core_dump_size=core_dump_size, wpr_meta_pa=_int(shim.get("wpr_meta_pa", 0x90000)), wpr_meta_size=_int(shim.get("wpr_meta_size", WPR_META_SIZE)), bootargs_size=_int(shim.get("bootargs_size", BOOTARGS_DEFAULT_SIZE)), ) shim_meta["assumptions"].append( "NV_RISCV_BOOT_PARAMS reconstructed at 0x%x from the documented " "layout (header); real value is host/booter-provided" % bootargs_pa) if shim.get("wpr_meta_pa") is not None and shim.get("wpr"): wpr = shim["wpr"] build_wpr_meta( machine, wpr_meta_pa=_int(shim["wpr_meta_pa"]), elf_pa=_int(wpr.get("elf_pa", image_obj.entry)), elf_size=_int(wpr.get("elf_size", image_obj.max_vaddr - 0x4000000)), wpr_start=_int(wpr.get("wpr_start", 0x2000000)), wpr_end=_int(wpr.get("wpr_end", 0x6000000)), boot_bin_offset=_int(wpr.get("boot_bin_offset", 0x2000000)), gsp_fw_offset=_int(wpr.get("gsp_fw_offset", 0x4000000)), fb_size=_int(wpr.get("fb_size", 0x200000000)), ) shim_meta["assumptions"].append( "GspFwWprMeta reconstructed from the documented layout (header); " "real value is booter-provided") bootstrap = shim.get("bootstrap_args") if bootstrap: args_pa = _int(bootstrap.get("pa", 0x81000)) build_bootstrap_args( machine, pa=args_pa, imem_base_pa=_int(bootstrap.get("imem_base_pa", 0x0)), imem_size=_int(bootstrap.get("imem_size", 0x10000)), imem_bs_size=_int(bootstrap.get("imem_bs_size", 0x0)), dmem_base_pa=_int(bootstrap.get("dmem_base_pa", 0x100000000)), dmem_size=_int(bootstrap.get("dmem_size", 0x10000)), dmem_bs_size=_int(bootstrap.get("dmem_bs_size", 0x0)), ) shim_meta["assumptions"].append( "libos_bootstrap_args_t (IMEM/DMEM base+size+bs) reconstructed at " "0x%x from the documented layout; real value is bootloader-built " "and passed in a0" % args_pa) shim_meta["bootstrap_args_pa"] = args_pa shim_meta["applied"] = True shim_meta["registers"] = {k: registers[k] for k in sorted(registers)} image_at = shim.get("image_at") if image_at is not None: addr = _int(image_at) with open(shim["_image_path"], "rb") as fh: blob = fh.read() machine.write_mem(addr, blob) shim_meta["assumptions"].append( "raw firmware image mirrored at 0x%x (DMEM); legacy fallback for " "profiles without a bootstrap-args record (no longer used by the " "tu104-570.144 profile: the MMU model resolves the copy source)" % addr) shim_meta["image_at"] = addr shim_meta["image_bytes"] = len(blob) def _write_trace_file(path: str, doc: dict) -> str: """Write the observer trace document; a directory gets the canonical name.""" target = path if os.path.isdir(path) or path.endswith(os.sep): target = os.path.join(path, "gmmu-fbif-trace.json") parent = os.path.dirname(os.path.abspath(target)) os.makedirs(parent, exist_ok=True) with open(target, "w", encoding="utf-8") as fh: json.dump(doc, fh, indent=1, sort_keys=True) fh.write("\n") return target def _print_result(result: GspBootResult, out=sys.stdout) -> None: print(f"image: {result.image}", file=out) print(f"profile: {result.profile_name} ({result.profile})", file=out) print(f"session: {result.session_dir}", file=out) print(f"rung: {result.rung}", file=out) print(f"instructions: {result.instructions}", file=out) print(f"cycles (modeled): {result.cycles}", file=out) print(f"stop: {result.stop_reason} pc={result.stop_pc:#x}" if result.stop_pc is not None else f"stop: {result.stop_reason}", file=out) print(f"unknown MMIO accesses: {result.mmio_unknown}", file=out) print(f"device accesses: {result.device_accesses} " f"(unhandled in windows: {result.device_unhandled})", file=out) if result.first_unknown_mmio: first = result.first_unknown_mmio print(f"first unknown MMIO: pc={first['pc']:#x} addr={first['addr']:#x} " f"size={first['size']} rw={first['rw']}", file=out) if result.trace_summary is not None: summary = result.trace_summary print(f"GMMU/FBIF observers: {summary['windows']} windows, " f"{summary['accesses']} accesses ({summary['reads']} r / " f"{summary['writes']} w, {summary['served']} served, " f"{summary['windows_touched']} windows touched)", file=out) if result.trace_path is not None: print(f"GMMU/FBIF trace: {result.trace_path}", file=out) print("note: device semantics carry their evidence in docs/36; " "boot-shim values are declared assumptions", file=out) def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser( prog="python3 -m sim.gsp_boot", description=("Bounded real-firmware GSP boot attempt with the device model " "and versioned profile (read-only image; no hardware).")) parser.add_argument("--image", default=DEFAULT_IMAGE) parser.add_argument("--profile", default=DEFAULT_PROFILE) parser.add_argument("--instructions", type=int, default=DEFAULT_INSTRUCTIONS) parser.add_argument("--record", help="session directory (default: auto)") parser.add_argument("--name", default="gsp-boot") parser.add_argument("--no-shim", action="store_true", help="run without the boot shim (pure device model)") parser.add_argument("--trace-out", default=None, help="write the greeneye-gmmu-fbif-trace/1 observer " "document to this file (or directory); default off") parser.add_argument("--symbols", default=None, help="greeneye-symbols/1 JSON database for PC resolution") parser.add_argument("--observe-window", action="append", default=None, metavar="BASE:SIZE[:LABEL[:MODE]]", help="extra runtime observer window (repeatable); " "observe-only unless MODE=storage") parser.add_argument("--json", action="store_true") args = parser.parse_args(argv) try: result = run_boot( image=args.image, profile_path=args.profile, instructions=args.instructions, record=args.record, name=args.name, use_shim=not args.no_shim, trace_out=args.trace_out, symbols_path=args.symbols, observe_windows=args.observe_window, ) except GreenEyeError as exc: print(f"[gsp-boot] error: {exc}", file=sys.stderr) return 1 if not result.ran: print(f"[gsp-boot] skipped: {result.reason}", file=sys.stderr) return 2 if args.json: print(json.dumps(result.to_dict(), indent=1, sort_keys=True)) else: _print_result(result) return 0 if __name__ == "__main__": sys.exit(main())