summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/gsp_boot.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/gsp_boot.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/sim/gsp_boot.py548
1 files changed, 548 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/gsp_boot.py b/debug/risc-v-gsp-rm-vm/sim/gsp_boot.py
new file mode 100644
index 0000000..c586ab3
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/sim/gsp_boot.py
@@ -0,0 +1,548 @@
+"""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.
+"""
+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 sim.devices import load_profile, registry_from_profile
+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'
+
+
+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_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)
+ milestones: list = field(default_factory=list)
+ 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,
+ "milestones": list(self.milestones),
+ }
+
+
+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,
+) -> GspBootResult:
+ """Run one bounded boot attempt and record a session."""
+ 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)
+
+ 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)
+
+ 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] = []
+ 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)
+ shim_meta["rung"] = rung
+ 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,
+ }
+ 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
+ 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,
+ milestones=list(milestones_hit),
+ )
+
+
+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) -> str:
+ """Observed rung from milestones 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.
+ """
+ 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"
+ 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)",
+ }.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")
+ 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) so the app's own "
+ "self-relocation copy is self-consistent; real value is "
+ "booter-DMA-provided" % addr)
+ shim_meta["image_at"] = addr
+ shim_meta["image_bytes"] = len(blob)
+
+
+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)
+ 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("--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,
+ )
+ 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())