From c295a1b785efd01547f7d86dc30270360edf817a Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 1 Oct 2026 10:07:14 -0400 Subject: gsp-phys: real GSP boot stages 3d/3e and the GSP-RM run-sequencer event Stage 3d stages the GSP-RM boot from host sysmem (FMC, radix3 over the GSP-RM ELF, signature, message queues, libos LOGINIT/LOGRM/RMARGS and GSP arguments) and runs Booter Load on SEC2: GSP RISC-V reset, libos mailbox, IMEM/DMEM direct load, BOOTVEC=0, M0==0, FALCON_OS, RISC-V active. Stage 3e links the status queue and polls for GSP-RM events; it now also executes the inline run-sequencer command buffer (event 0x1002) against BAR0 (REG_WRITE/MODIFY/POLL, DELAY, STORE, CORE_* including CORE_RESUME) and then waits for GSP_INIT_DONE 0x1001. Gated by gsp_phys_boot / gsp_phys_boot_dryrun (default off; the normal load path writes nothing). gsp_phys_fwsec=1 without gsp_phys_boot=1 is refused: a FWSEC-only carve cannot pass the booter-unload handoff gate. Release quiesces the GSP, runs the existing FWSEC-SB/booter-unload teardown and frees all staging. tools/gen_gsp_bindata.py decodes the public generated archives into the Booter Load image (prod signature patched at 0x8700) and the GSP-RM boot (FMC) image; both are self-checked at build time. --- tools/gen_gsp_bindata.py | 273 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 tools/gen_gsp_bindata.py (limited to 'tools/gen_gsp_bindata.py') diff --git a/tools/gen_gsp_bindata.py b/tools/gen_gsp_bindata.py new file mode 100644 index 0000000..c59b9d1 --- /dev/null +++ b/tools/gen_gsp_bindata.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) 2026 Open-IOV contributors +# +# Generate the kernel bindata headers for the real-GSP boot stages from the +# public open kernel-modules generated bindata archives. +# +# The archives are C files holding raw-deflate compressed byte arrays. This +# tool extracts the production booter-load image (runs on SEC2 to load, +# verify and start the GSP-RM RISC-V image) and the GSP-RM boot image (the +# "FMC" 4 KB bootloader), applies the production signature patch the driver +# applies at runtime, verifies sizes/headers, and cross-checks the booter +# image against the distribution firmware blob when present. +# +# Usage: python3 tools/gen_gsp_bindata.py [--check] + +import argparse +import hashlib +import re +import struct +import sys +import zlib +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +GENERATED = ROOT / "nvidia-open-gpu-kernel-modules" / "src" / "nvidia" / "generated" +KERNEL = ROOT / "kernel" + +BOOTER_ARCHIVE = GENERATED / "g_bindata_kgspGetBinArchiveBooterLoadUcode_TU102.c" +FMC_ARCHIVE = GENERATED / "g_bindata_kgspGetBinArchiveGspRmBoot_TU102.c" +DEFAULT_FW_BOOTER = Path("/lib/firmware/nvidia/tu102/gsp/booter_load-570.144.bin") + +# Booter-load production image expectations (reference ABI, Turing). +BOOTER_IMAGE_SIZE = 59136 +BOOTER_SIG_OFFSET = 0x8700 +BOOTER_SIG = bytes.fromhex("3260436798897b06e85f1aa871435dcb") +BOOTER_HEADER = (0x0, 0x100, 0x8500, 0x6200, 0x1, 0x100, 0x8400, 0x100, 0x0) +BOOTER_PATCH_META = (0x0, 0x1, 0xd) + +# GSP-RM boot image expectations (FMC). +FMC_IMAGE_SIZE = 4096 +FMC_DESC_SIZE = 76 +FMC_DESC_VERSION = 4 +FMC_DESC_WORDS = [FMC_DESC_VERSION, 0, 0x488, 0x488, 0x10] + [0] * 14 + +EXPECTED_BOOTER_SHA256 = "1de31591beae01d3a59778d9066532c6e808029258927d270c31193265c3305f" +EXPECTED_FMC_SHA256 = "8765694cd38beaa4e2b8b1b83a31fb7aee6cb9d8b7c6bc7a86e6d931a3a056c2" + +FIELD_RE = re.compile(r'// FUNCTION: .*\("([A-Z0-9_]+)"\)') +COMPRESSION_RE = re.compile(r"// COMPRESSION: (YES|NO)") + + +def parse_archive(path): + """Return {label: (compressed, bytes)} from a generated bindata C file.""" + entries = {} + label = None + compressed = None + data = bytearray() + with open(path) as fh: + for line in fh: + stripped = line.strip() + m = FIELD_RE.match(stripped) + if m: + if label is not None: + entries[label] = (compressed, bytes(data)) + label, compressed, data = m.group(1), None, bytearray() + continue + if label is None: + continue + m = COMPRESSION_RE.match(stripped) + if m: + compressed = m.group(1) == "YES" + continue + if stripped.startswith("//") or stripped == "": + continue + if stripped == "};": + entries[label] = (compressed, bytes(data)) + label = None + continue + if stripped.startswith("{") or stripped.startswith("static") or \ + stripped.startswith("BINDATA") or stripped.startswith("#"): + continue + for token in re.findall(r"0x[0-9a-fA-F]{1,2}", line): + data.append(int(token, 16)) + if label is not None: + entries[label] = (compressed, bytes(data)) + return entries + + +def extract(entries, label, want_compressed=None): + if label not in entries: + raise SystemExit(f"missing bindata label {label}") + compressed, raw = entries[label] + if want_compressed is not None and compressed != want_compressed: + raise SystemExit( + f"{label}: compression flag {compressed} != expected {want_compressed}" + ) + if compressed: + return zlib.decompress(raw, -15) + return raw + + +def emit_header(path, guard, name, image, note_lines): + sha = hashlib.sha256(image).hexdigest() + out = [] + out.append("/* SPDX-License-Identifier: GPL-2.0 */") + out.append("/*") + for line in note_lines: + out.append(f" * {line}" if line else " *") + out.append(" *") + out.append(" * Generated from the open kernel-modules bindata archives; do") + out.append(" * not edit by hand.") + out.append(" *") + out.append(f" * image size: {len(image)} bytes") + out.append(f" * sha256: {sha}") + out.append(" */") + out.append("") + out.append(f"#ifndef {guard}") + out.append(f"#define {guard}") + out.append("") + out.append("#include ") + out.append("") + out.append(f"static const u8 {name}[{len(image)}] = {{") + for i in range(0, len(image), 12): + row = ", ".join(f"0x{b:02x}" for b in image[i : i + 12]) + out.append(f"\t{row},") + out.append("};") + out.append("") + out.append(f"#endif /* {guard} */") + path.write_text("\n".join(out) + "\n") + return sha + + +def cross_check_booter(fw_path, image): + if not fw_path.exists(): + raise SystemExit(f"cross-check firmware not found: {fw_path}") + fw = fw_path.read_bytes() + if len(fw) < 0x88 + len(image): + raise SystemExit(f"cross-check firmware too small: {len(fw)} bytes") + if len(fw) != 0x88 + len(image): + raise SystemExit( + f"cross-check firmware size {len(fw)} != 0x88 + {len(image)}" + ) + for i, byte in enumerate(image): + if BOOTER_SIG_OFFSET <= i < BOOTER_SIG_OFFSET + len(BOOTER_SIG): + continue + if fw[0x88 + i] != byte: + raise SystemExit(f"cross-check mismatch at image offset 0x{i:x}") + if fw[0x88 + BOOTER_SIG_OFFSET : 0x88 + BOOTER_SIG_OFFSET + 16] != bytes(16): + # The distribution blob carries a zeroed patch slot; anything else + # means this check is no longer comparing what we think it is. + raise SystemExit("cross-check firmware has a non-zero signature patch slot") + return True + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--check", + action="store_true", + help="verify everything but do not rewrite the headers", + ) + parser.add_argument( + "--firmware-booter", + type=Path, + default=DEFAULT_FW_BOOTER, + help=f"distribution booter blob for the cross-check (default {DEFAULT_FW_BOOTER})", + ) + args = parser.parse_args() + + if not BOOTER_ARCHIVE.exists() or not FMC_ARCHIVE.exists(): + raise SystemExit(f"generated bindata sources not found under {GENERATED}") + + booter_entries = parse_archive(BOOTER_ARCHIVE) + fmc_entries = parse_archive(FMC_ARCHIVE) + + image = extract(booter_entries, "BINDATA_LABEL_IMAGE_PROD", want_compressed=True) + header = extract(booter_entries, "BINDATA_LABEL_HEADER_PROD", want_compressed=True) + sig = extract(booter_entries, "BINDATA_LABEL_SIG_PROD", want_compressed=False) + patch_loc = extract(booter_entries, "BINDATA_LABEL_PATCH_LOC", want_compressed=False) + patch_sig = extract(booter_entries, "BINDATA_LABEL_PATCH_SIG", want_compressed=False) + patch_meta = extract(booter_entries, "BINDATA_LABEL_PATCH_META", want_compressed=False) + num_sigs = extract(booter_entries, "BINDATA_LABEL_NUM_SIGS", want_compressed=False) + + if len(image) != BOOTER_IMAGE_SIZE: + raise SystemExit(f"booter image size {len(image)} != {BOOTER_IMAGE_SIZE}") + if len(header) != 36: + raise SystemExit(f"booter header size {len(header)} != 36") + fields = struct.unpack("<9I", header) + if fields != BOOTER_HEADER: + raise SystemExit(f"booter header {fields} != {BOOTER_HEADER}") + if len(sig) != 16: + raise SystemExit(f"booter signature size {len(sig)} != 16") + if patch_loc != struct.pack("