From 58b4e634ee5e7728987341c56bcc7e529cf08b84 Mon Sep 17 00:00:00 2001 From: Xiang Ji Date: Wed, 21 Oct 2026 10:33:25 -0500 Subject: [PATCH] NV: lock GA10x's PCIe link at Gen1 (RMPcieLinkSpeed=0x800002AA) On an RTX A6000 (GA102, 10de:2230) in an Aoostar AG03 (Intel JHL9480, Thunderbolt 5), GSP-RM 570.144 leaves the link at Gen1 x4 by GSP_INIT_DONE (target still Gen4; the card trains at Gen4 x4 cold), and the link then dies under BAR1 traffic within seconds: the Mac's root port logs AER and a link timeout and disables the port, every read returns all ones, and the crash guard holds. Locking the link at load with RMPcieLinkSpeed=0x800002AA (gens 2-6 disabled; tinygrad PR #18215's value for Ada and Blackwell behind TB5 bridges) fixes it: the whole hardware matrix (16.4-16.11), the soaks, 20 warm boots, D1 SP and DP, kill-at-idle and the P4 recovery all passed on the A6000 with it. A Gen4 lock (0x80000295) broke the boot before INIT_DONE. - TinyGPUNVBoot.h: nv_ga10x_link_lock(); on architecture 0x17 the registry table gets the entry after tinygrad's two (the count no longer hard-coded at 2). BEAGLE_NV_GA10X_LINK_LOCK=0 sends tinygrad's table. - oracle/nv_init_helper.py: the same table for the goldens' tinygrad side. - golden_boot.py: GA102 cases from the A6000's own replies (early, NV_FLCN.init_sw on its VBIOS, the whole init_sw with and without the lock), each compared with the card's L0 recording when this Mac has it (as a prefix when the recording has no init_sw marker); a check that SET_REGISTRY carries the lock exactly on GA10x; perturbations of the value, the gate and the opt-out. Cases whose captured VBIOS is not on this Mac (the AD107 dumps) are reported SKIPPED instead of crashing the test. - TinyGPU.md: the Ampere row, the lock and its stickiness, the opt-out, and that TinyGPU.app c0d024f9 serves only display-class (0x0300) cards. Co-Authored-By: Claude Opus 5.5 --- libhmsbeagle/GPU/TinyGPU.md | 26 +++++- libhmsbeagle/GPU/TinyGPUNVBoot.h | 26 ++++-- libhmsbeagle/GPU/tinygpu_tests/golden_boot.py | 84 +++++++++++++++---- .../tinygpu_tests/oracle/nv_init_helper.py | 18 ++++ 4 files changed, 131 insertions(+), 23 deletions(-) diff --git a/libhmsbeagle/GPU/TinyGPU.md b/libhmsbeagle/GPU/TinyGPU.md index 393d6b16..67d17926 100644 --- a/libhmsbeagle/GPU/TinyGPU.md +++ b/libhmsbeagle/GPU/TinyGPU.md @@ -24,11 +24,13 @@ oracle. |---|---|---| | Ada (AD10x) | `0x26xx`-`0x28xx` | tested on an RTX 4060 (AD107); the other AD10x boot the same way, untested | | Blackwell (GB20x) | `0x2bxx`-`0x2dxx`, `0x2fxx` | tested on an RTX 5070 (GB205); the other GB20x boot the same way, untested | -| Ampere (GA10x) | `0x22xx`, `0x24xx`, `0x25xx` | boots as Ada does (TODO.md plan step G1), untested: no Ampere card has run it | +| Ampere (GA10x) | `0x22xx`, `0x24xx`, `0x25xx` | tested on an RTX A6000 (GA102, `10de:2230`, 48 GiB) in an Aoostar AG03 (Intel JHL9480, Thunderbolt 5; the link trains at Gen4 x4), with the Gen1 link lock below; the other GA10x boot the same way, untested | | AMD Navi 31 (gfx1100) | `1002:744c` | tested on an RX 7900 XT | | AMD Navi 48 (gfx1201) | `1002:7550` | tested on an RX 9070 XT (XFX); any other AMD device ID is refused before anything is sent to the card, and IP versions other than these two cards' at its boot | -These are tinygrad's NVIDIA families. Any other NVIDIA GPU (Turing, GA100, Hopper, GB100) is refused at +These are tinygrad's NVIDIA families. TinyGPU.app `c0d024f9` serves only display-class cards (its driver matches PCI +class `0x0300`), so an NVIDIA card that enumerates as a 3D controller (`0x0302`: the A2, A16, A30, A40 and A100, say) is +not served at all. Any other NVIDIA GPU (Turing, GA100, Hopper, GB100) is refused at `beagleCreateInstance`, with nothing written to it. The NVIDIA boot also needs a 256 MiB BAR1, as GeForce cards come: a card with another size (data-center and some professional cards have a larger one) is refused at the start of its boot (`BarLayoutError`), before any firmware is set up. @@ -38,6 +40,23 @@ architecture (sm_86, sm_89, sm_120), on AMD 18 HSACOs per architecture (gfx1100, well as single (TODO.md plan steps C16, A7); consumer GPUs run double precision natively, at a fraction of the single rate. A GPU whose architecture has no cubin is refused at `beagleCreateInstance`. +### Ampere: the Gen1 link lock + +On GA10x the plugin adds `RMPcieLinkSpeed=0x800002AA` to the registry table it hands GSP-RM (gens 2 to 6 disabled, locked +at load: the value tinygrad PR #18215 found for Ada and Blackwell behind Thunderbolt 5 bridges). Without it, the RTX A6000 +behind the AG03's JHL9480 came out of GSP-RM's boot at Gen1 x4 with its target still Gen4, and the link then died under the +first seconds of BAR1 traffic: the Mac's root port logged AER and a link timeout and disabled the port, every read returned +all ones, and the crash guard held. With the lock every boot passed (October 2026): the hardware matrix, the soaks and 20 +warm boots, D1 in both precisions, the crash guard at idle and the P4 recovery. The link then runs at Gen1 (2.5 GT/s x4). +What that costs is not measured, since no unlocked boot of this card survives long enough to compare; the A6000 ran 0.165 ms +per evaluation at 4 states, single precision. A Gen4 lock (`0x80000295`) broke the boot before GSP-RM was up. + +- The lock lasts until the enclosure is power-cycled, so a boot of another configuration after it is not a clean + comparison until then. +- `BEAGLE_NV_GA10X_LINK_LOCK=0` sends tinygrad's table instead (for diagnosis). +- The enclosure and bridge matter: an RTX 3080 in a Thunderbolt 3 enclosure (Titan Ridge) was reported to boot stock + tinygrad (tinygrad PR #16262), so the lock may be unnecessary there. It is on for every GA10x all the same. + ## Requirements - macOS on Apple silicon, with the eGPU attached. @@ -120,7 +139,7 @@ BEAGLE itself, as at exit, so it needs no power cycle. Power-cycle the eGPU (unplug it, then plug it in again) only then, and always before killing a holding guard. A GPU left warm by an earlier process that was not torn down (WPR2 still up) is refused with nothing written: `WARM GPU: WPR2 is up ... Power-cycle the eGPU`. The exception (plan step P4, on Ada and Ampere GPUs; the default since two -clean recoveries on the RTX 4060, untested on Ampere): a warm GPU whose GSP-RM was unloaded, so that only the teardown is +clean recoveries on the RTX 4060, and one on the RTX A6000): a warm GPU whose GSP-RM was unloaded, so that only the teardown is missing (after `BEAGLE_NV_TEARDOWN=0`, or a teardown that failed after the unload), is torn down at boot and then boots. Only reads come before that decision. NVIDIA's driver refuses such a GPU instead; `BEAGLE_NV_RECOVER=0` does too. @@ -140,6 +159,7 @@ For users: | `BEAGLE_NV_TEARDOWN=0` | the GSP unload only, no teardown: the next boot then needs a power cycle (for diagnosis) | | `BEAGLE_NV_UNLOAD_LEVEL=0` | the LEVEL_0 unload instead of FAST_UNLOAD (a fallback) | | `BEAGLE_NV_RECOVER=0` | a warm Ada or Ampere GPU whose GSP is suspended or halted is refused, as NVIDIA's driver does, instead of torn down at boot | +| `BEAGLE_NV_GA10X_LINK_LOCK=0` | Ampere: no Gen1 link lock in the registry table (tinygrad's table; see Supported GPUs) | | `BEAGLE_NV_GUARD` | the crash guard's path (default: next to the plugin); `BEAGLE_AMD_GUARD` for the AMD C++ boot | | `APL_REMOTE_SOCK` | TinyGPU.app's socket (default `$TMPDIR/tinygpu.sock`, as tinygrad) | | `BEAGLE_AMD_DATA_MB` | AMD: the VRAM pool, in MiB (default: half the VRAM) | diff --git a/libhmsbeagle/GPU/TinyGPUNVBoot.h b/libhmsbeagle/GPU/TinyGPUNVBoot.h index 85cf1cfe..4c21392f 100644 --- a/libhmsbeagle/GPU/TinyGPUNVBoot.h +++ b/libhmsbeagle/GPU/TinyGPUNVBoot.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -655,10 +656,23 @@ inline void nv_boot_rpc_set_gsp_system_info(NVBootDev& d, NVGspBoot& g) { g.cmd_q->send_rpc(nv::NV_VGPU_MSG_FUNCTION_GSP_SET_SYSTEM_INFO, nv_bytes(data)); } -// rpc_set_registry_table (ip.py:615-627): RMForcePcieConfigSave and RMSecBusResetEnable, 1 each -inline void nv_boot_rpc_set_registry_table(NVGspBoot& g) { - const std::pair table[] = {{"RMForcePcieConfigSave", 0x1}, {"RMSecBusResetEnable", 0x1}}; - const size_t hdr_size = sizeof(nv::struct_PACKED_REGISTRY_TABLE), entries_size = sizeof(nv::struct_PACKED_REGISTRY_ENTRY) * 2; +// BEAGLE's Ampere fix (reports/A6000_fix_plan_2026-10-19.md, Phase C): on GA10x (architecture 0x17) GSP-RM 570.144 leaves the +// link at Gen1 x4 by GSP_INIT_DONE through a TB5 bridge (the RTX A6000 in an AG03, JHL9480, which trains at Gen4 x4 cold), and +// the link then dies under BAR1 traffic: the root port logs AER and a link timeout and disables the port. RMPcieLinkSpeed +// 0x800002AA (gens 2-6 disabled, locked at load; tinygrad PR #18215's value for Ada and Blackwell behind TB5) holds it at Gen1, +// and every boot then passed (Phase B3). The lock lasts until the enclosure is power-cycled. BEAGLE_NV_GA10X_LINK_LOCK=0 sends +// tinygrad's table instead. +inline bool nv_ga10x_link_lock(uint32_t architecture) { + const char* e = getenv("BEAGLE_NV_GA10X_LINK_LOCK"); + return architecture == 0x17 && !(e && strcmp(e, "0") == 0); +} + +// rpc_set_registry_table (ip.py:615-627): RMForcePcieConfigSave and RMSecBusResetEnable, 1 each, then on GA10x the link lock +// (nv_init_helper's _rpc_set_registry_table_ga10x) +inline void nv_boot_rpc_set_registry_table(const NVBootDev& d, NVGspBoot& g) { + std::vector> table = {{"RMForcePcieConfigSave", 0x1}, {"RMSecBusResetEnable", 0x1}}; + if (nv_ga10x_link_lock(d.architecture)) table.push_back({"RMPcieLinkSpeed", 0x800002AA}); + const size_t hdr_size = sizeof(nv::struct_PACKED_REGISTRY_TABLE), entries_size = sizeof(nv::struct_PACKED_REGISTRY_ENTRY) * table.size(); std::vector entries, data; for (const auto& kv : table) { nv::struct_PACKED_REGISTRY_ENTRY e{}; @@ -670,7 +684,7 @@ inline void nv_boot_rpc_set_registry_table(NVGspBoot& g) { } nv::struct_PACKED_REGISTRY_TABLE header{}; header.size = (uint32_t)(hdr_size + entries.size() + data.size()); - header.numEntries = 2; + header.numEntries = (uint32_t)table.size(); std::vector msg = nv_bytes(header); msg.insert(msg.end(), entries.begin(), entries.end()); msg.insert(msg.end(), data.begin(), data.end()); @@ -683,7 +697,7 @@ inline void nv_boot_gsp_init_sw(NVBootDev& d, NVGspBoot& g, const NVFlcnImages* nv_boot_init_libos_args(d, g); nv_boot_init_wpr_meta(d, g, flcn); nv_boot_rpc_set_gsp_system_info(d, g); - nv_boot_rpc_set_registry_table(g); + nv_boot_rpc_set_registry_table(d, g); g.gpfifo_class = nv_gpu::AMPERE_CHANNEL_GPFIFO_A; g.compute_class = nv_gpu::AMPERE_COMPUTE_B; g.dma_class = nv_gpu::AMPERE_DMA_COPY_B; const std::string fam = d.chip_name.substr(0, 2); g.viddec_class = fam == "AD" ? nv_gpu::NVC9B0_VIDEO_DECODER : fam == "GB" ? nv_gpu::NVCFB0_VIDEO_DECODER : 0; // .get(): None diff --git a/libhmsbeagle/GPU/tinygpu_tests/golden_boot.py b/libhmsbeagle/GPU/tinygpu_tests/golden_boot.py index fb173748..5d71a95d 100644 --- a/libhmsbeagle/GPU/tinygpu_tests/golden_boot.py +++ b/libhmsbeagle/GPU/tinygpu_tests/golden_boot.py @@ -44,10 +44,15 @@ regs={BOOT_0: 0x1b5000a1, BOOT_42: 0x1b5a1000, SCRATCH_42: 0x2fc3, I2CS: 0xff}, l0="20260927-093033_Marcs-Mac-Studio-490_gb205_l0") # plan step G1: an Ampere (an RTX 3070's ids, architecture 0x17, implementation 4) on the AD107's BARs and VRAM; no card, no recording GA104 = dict(cfg={**AD107["cfg"], 0x0: 0x248410de}, bars=AD107["bars"], regs={BOOT_0: 0x174000a1, BOOT_42: 0x174a1000, SCRATCH_42: 0x1ffc}, l0=None) +# the RTX A6000 (GA102, 10de:2230 rev a1, Dell 1028:1459, 49140 MiB) in an Aoostar AG03 (JHL9480): its own replies, from the +# A6000 fix plan's B2 recording (reports/A6000_fix_plan_2026-10-19.md; the first boot that passed, with the Gen1 link lock) +GA102 = dict(cfg={0x0: 0x223010de, 0x4: 0x00100007, 0x8: 0x030000a1, 0x2c: 0x14591028}, + bars={0: (0x111000000, 16 * MB), 1: (0x112000000, 256 * MB), 3: (0x125cbc000, 32 * MB)}, + regs={BOOT_0: 0xb72000a1, BOOT_42: 0x172a1000, SCRATCH_42: 0xbff4}, l0="20261020-150047_xiang-mbp14-2025_b2_gen1lock") # ── the VBIOS: the captured dumps, the L0 recording's own, and malformed or ambiguous copies ─────────────────────────────── -def captured_vbios(): - return [pathlib.Path(p).read_bytes() for p in sorted(glob.glob(str(tgpaths.DATA / "vbios" / "AD107_*.rom")))] +def captured_vbios(chip="AD107"): + return [pathlib.Path(p).read_bytes() for p in sorted(glob.glob(str(tgpaths.DATA / "vbios" / f"{chip}_*.rom")))] def l0_vbios(name): """The VBIOS the recorded boot read: the reply to its prep_ucode read.""" @@ -89,8 +94,11 @@ def patched(vbios, **edits): return bytes(v) class Case: - def __init__(self, name, chip, mode="early", cfg=None, bars=None, regs=None, l0=False, expect=None, stream=None, vbios=None, teardown=True): + def __init__(self, name, chip, mode="early", cfg=None, bars=None, regs=None, l0=False, expect=None, stream=None, vbios=None, teardown=True, + env=None, lock=None): self.name, self.mode, self.l0, self.stream, self.teardown = name, mode, chip["l0"] if l0 else None, stream, teardown + self.env = env or {} # set for both clients + self.lock = lock # sw: whether SET_REGISTRY carries the GA10x link lock (RMPcieLinkSpeed), checked in sysmem self.cfg = {**chip["cfg"], **(cfg or {})} self.bars = {**chip["bars"], **(bars or {})} self.regs = {**chip["regs"], **(regs or {})} @@ -111,8 +119,28 @@ def cases(): Case("an unknown architecture", AD107, regs={BOOT_42: 0x187a1000}, expect=r"^error KeyError: 24$"), Case("GB205, the FSP never ready", GB205, regs={I2CS: 0x7}, expect=r"^error TimeoutError: FSP not ready: NV_THERM_I2CS_SCRATCH=0x00000007 after N ms"), ] + cs.append( # needs no captured VBIOS: the COT boot reads none + Case("GB205 cold, the whole init_sw", GB205, mode="sw", l0=True, + expect=r"^chip 0x1b5000a1 .*\ncot boot_args 0x[0-9a-f]+ fmc 0x[0-9a-f]+ hash 12 .*\ngsp rm_args .* seq 2 classes 0xc96f 0xcec0 0xcab5 0xcfb0$", lock=False)) + # the A6000 fix (Phase C): GA10x boots send RMPcieLinkSpeed=0x800002AA in SET_REGISTRY, other families tinygrad's table + ga102_rom = l0_vbios(GA102["l0"]) or next(iter(captured_vbios("GA102")), None) + if ga102_rom is None: SKIPPED.append("the GA102 cases (no A6000 recording or GA102 VBIOS on this Mac)") + else: + ga_sw = r"\ngsp rm_args 0x[0-9a-f]+ libos 0x[0-9a-f]+ .* seq 2 classes 0xc56f 0xc7c0 0xc7b5 0x0$" + cs += [ + Case("GA102 cold", GA102, l0=True, expect=r"^chip 0xb72000a1 GA102 ga102 mmu 2 fmc 0 vram 51527024640 bar1 268435456 large 0 root "), + Case("GA102 cold, NV_FLCN.init_sw (the A6000's VBIOS)", GA102, mode="flcn", l0=True, vbios=ga102_rom, + expect=r"\nflcn frts 0xbff200000 at 0x[0-9a-f]+ desc .* booter at 0x[0-9a-f]+ data .*\nteardown sb at 0x[0-9a-f]+ unload at 0x[0-9a-f]+ data "), + Case("GA102 cold, the whole init_sw: SET_REGISTRY carries the Gen1 link lock", GA102, mode="sw", l0=True, vbios=ga102_rom, + expect=ga_sw, lock=True), + Case("GA102, BEAGLE_NV_GA10X_LINK_LOCK=0: tinygrad's registry table", GA102, mode="sw", vbios=ga102_rom, + env={"BEAGLE_NV_GA10X_LINK_LOCK": "0"}, expect=ga_sw, lock=False), + ] roms = captured_vbios() rec_rom = l0_vbios(AD107["l0"]) + if not roms: # the author's AD107 dumps: without them the VBIOS cases below cannot run here (said, not passed) + SKIPPED.append("the AD107 VBIOS cases (no AD107_*.rom in the data directory)") + return cs lay = vbios_layout(roms[0]) ok_images = (r"\nflcn frts 0x1ffa00000 at 0x1200000 desc .* booter at 0x1211000 data 0x[0-9a-f]+\+0x[0-9a-f]+ code 0x[0-9a-f]+\+0x[0-9a-f]+\n" r"teardown sb at 0x121f000 unload at 0x1230000 data 0x5000\+0x4e00 code 0x100\+0x4f00$") @@ -122,13 +150,11 @@ def cases(): *[Case(f"NV_FLCN.init_sw on the captured VBIOS {i + 1} of {len(roms)}", AD107, mode="flcn", vbios=r, expect=ok_images) for i, r in enumerate(roms)], Case("NV_FLCN.init_sw, the teardown off", AD107, mode="flcn", vbios=roms[0], teardown=False, expect=r"booter at 0x1211000 .*\nteardown off$"), Case("AD107 cold, the whole init_sw" + (" (the recording's VBIOS)" if rec_rom else ""), AD107, mode="sw", l0=rec_rom is not None, - vbios=rec_rom or roms[0], expect=r"\ngsp rm_args 0x[0-9a-f]+ libos 0x[0-9a-f]+ .* seq 2 classes 0xc56f 0xc9c0 0xc7b5 0xc9b0$"), + vbios=rec_rom or roms[0], expect=r"\ngsp rm_args 0x[0-9a-f]+ libos 0x[0-9a-f]+ .* seq 2 classes 0xc56f 0xc9c0 0xc7b5 0xc9b0$", lock=False), Case("GA104 cold, NV_FLCN.init_sw (ga102's booters)", GA104, mode="flcn", vbios=roms[0], expect=r"\nflcn frts 0x1ffa00000 at 0x1200000 desc .* booter at 0x[0-9a-f]+ data .*\nteardown sb at 0x[0-9a-f]+ unload at 0x[0-9a-f]+ data "), Case("GA104 cold, the whole init_sw", GA104, mode="sw", vbios=roms[0], - expect=r"\ngsp rm_args 0x[0-9a-f]+ libos 0x[0-9a-f]+ .* seq 2 classes 0xc56f 0xc7c0 0xc7b5 0x0$"), - Case("GB205 cold, the whole init_sw", GB205, mode="sw", l0=True, - expect=r"^chip 0x1b5000a1 .*\ncot boot_args 0x[0-9a-f]+ fmc 0x[0-9a-f]+ hash 12 .*\ngsp rm_args .* seq 2 classes 0xc96f 0xcec0 0xcab5 0xcfb0$"), + expect=r"\ngsp rm_args 0x[0-9a-f]+ libos 0x[0-9a-f]+ .* seq 2 classes 0xc56f 0xc7c0 0xc7b5 0x0$", lock=True), Case("a later ucode entry FWSEC_PROD too: the last wins", AD107, mode="flcn", expect=r"\nflcn frts 0x1ffa00000 at 0x1200000 desc ", vbios=patched(roms[0], e=(lay["later_entry"], bytes([nv.FALCON_UCODE_ENTRY_APPID_FWSEC_PROD])))), Case("a bad BIT signature", AD107, mode="flcn", vbios=patched(roms[0], s=(0x1b2, struct.pack("= d.vram_size;", "d.large_bar = d.bar1_size > d.vram_size;", "AD107, a BAR1 as large as VRAM"), ("TinyGPUNVBoot.h", '? "ad102" : "ga102";', '? "ad102" : "ad102";', "GA104 cold"), # plan step G1 + # the A6000 fix: the lock's value, its gate (GA10x only), and its opt-out + ("TinyGPUNVBoot.h", 'table.push_back({"RMPcieLinkSpeed", 0x800002AA});', 'table.push_back({"RMPcieLinkSpeed", 0x80000295});', "GA102 cold, the whole init_sw"), + ("TinyGPUNVBoot.h", "return architecture == 0x17 && !(e && strcmp(e, \"0\") == 0);", "return architecture == 0x19 && !(e && strcmp(e, \"0\") == 0);", "GA102 cold, the whole init_sw"), + ("TinyGPUNVBoot.h", "return architecture == 0x17 && !(e && strcmp(e, \"0\") == 0);", "return architecture == 0x17;", "GA102, BEAGLE_NV_GA10X_LINK_LOCK=0"), # C11b ("TinyGPUNVBoot.h", "if (code_type == nv::NV_BCRT_HASH_INFO_BASE_CODE_TYPE_VBIOS_BASE) block_size = imglen;", "if (code_type == nv::NV_BCRT_HASH_INFO_BASE_CODE_TYPE_VBIOS_BASE && block_size < 0) block_size = imglen;", "two base images before the expansion ROM"), @@ -354,7 +405,7 @@ def main(): priv = tempfile.mkdtemp(dir="/tmp", prefix="tgboot.") tempfile.tempdir = priv all_cases, fails = cases(), 0 - targets = [next(c for c in all_cases if c.name.startswith(case)) for *_, case in PERTURBED] + targets = [next((c for c in all_cases if c.name.startswith(case)), None) for *_, case in PERTURBED] hits = [None] * len(PERTURBED) for c in all_cases: ok, summary, py = run_case(c, exe, priv) @@ -362,12 +413,17 @@ def main(): print(f"{'IDENTICAL' if ok else 'MISMATCH '} {c.name}: {summary}") # the perturbed copies this case must catch, run now: they reuse its tinygrad side (VRAM is 256 MiB: one case's at a time) for i in (i for i, t in enumerate(targets) if t is c): hits[i] = not run_case(c, builds[i].result(), priv, quiet=True, py=py)[0] - caught = 0 + caught, skipped = 0, 0 for (hdr, old, new, case), hit in zip(PERTURBED, hits): + if hit is None: # its case cannot run on this Mac (a captured VBIOS it lacks) + print(f"perturbed {hdr} ({old.strip()[:60]} -> {new.strip()[:40]}): SKIPPED ('{case}' cannot run on this Mac)") + skipped += 1; continue caught += hit print(f"perturbed {hdr} ({old.strip()[:60]} -> {new.strip()[:40]}): {'REJECTED' if hit else 'NOT CAUGHT'} by '{case}'") - fails += len(PERTURBED) - caught - print(f"C11 boot vs tinygrad: {'all identical' if not fails else f'{fails} FAILED'}") + fails += len(PERTURBED) - skipped - caught + for s in SKIPPED: print(f"SKIPPED: {s}") + print(f"C11 boot vs tinygrad: {'all identical' if not fails else f'{fails} FAILED'}" + + (f" ({len(SKIPPED)} case group(s) and {skipped} perturbation(s) skipped on this Mac)" if SKIPPED or skipped else "")) sys.exit(1 if fails else 0) if __name__ == "__main__": diff --git a/libhmsbeagle/GPU/tinygpu_tests/oracle/nv_init_helper.py b/libhmsbeagle/GPU/tinygpu_tests/oracle/nv_init_helper.py index 029c70ab..98a9ead6 100644 --- a/libhmsbeagle/GPU/tinygpu_tests/oracle/nv_init_helper.py +++ b/libhmsbeagle/GPU/tinygpu_tests/oracle/nv_init_helper.py @@ -94,6 +94,24 @@ def _patched_gsp_init_hw(self): _in_gsp_init[0] = False NV_GSP.init_hw = _patched_gsp_init_hw +# BEAGLE's Ampere fix (reports/A6000_fix_plan_2026-10-19.md, Phase C; TinyGPUNVBoot.h's nv_ga10x_link_lock): on GA10x, GSP-RM +# leaves a TB5-bridged link at Gen1 by GSP_INIT_DONE and the link then dies under BAR1 traffic; RMPcieLinkSpeed=0x800002AA +# (gens 2-6 disabled, locked at load) holds it at Gen1. NV_GSP.rpc_set_registry_table (ip.py:615-627) with that entry +# appended; BEAGLE_NV_GA10X_LINK_LOCK=0 sends tinygrad's table. +def _rpc_set_registry_table_ga10x(self): + from tinygrad.runtime.autogen import nv + table = {'RMForcePcieConfigSave': 0x1, 'RMSecBusResetEnable': 0x1} + if self.nvdev.chip_name[:2] == "GA" and os.environ.get("BEAGLE_NV_GA10X_LINK_LOCK") != "0": table['RMPcieLinkSpeed'] = 0x800002AA + entries_bytes, data_bytes = bytes(), bytes() + hdr_size, entries_size = ctypes.sizeof(nv.PACKED_REGISTRY_TABLE), ctypes.sizeof(nv.PACKED_REGISTRY_ENTRY) * len(table) + for k, v in table.items(): + entries_bytes += bytes(nv.PACKED_REGISTRY_ENTRY(nameOffset=hdr_size + entries_size + len(data_bytes), + type=nv.REGISTRY_TABLE_ENTRY_TYPE_DWORD, data=v, length=4)) + data_bytes += k.encode('utf-8') + b'\x00' + header = nv.PACKED_REGISTRY_TABLE(size=hdr_size + len(entries_bytes) + len(data_bytes), numEntries=len(table)) + self.cmd_q.send_rpc(nv.NV_VGPU_MSG_FUNCTION_SET_REGISTRY, bytes(header) + entries_bytes + data_bytes) +NV_GSP.rpc_set_registry_table = _rpc_set_registry_table_ga10x + _orig_start_cpu = NV_FLCN.start_cpu def _patched_start_cpu(self, base): _orig_start_cpu(self, base)