Add opt-in cmpunlocker unlock step with post-unlock Gen2 x16 acceptance

Insert a "cmpunlocker unlock (install)" step after unlock verification:
y/N confirmation (default n) or --unlock runs install.sh from the
harness clone (main branch, ff-only pull first) with live terminal
output and full log to cards/<serial>/install_unlocker.log. On success
the Vulkan stress is skipped until after the cold reboot.

Score now enforces the post-unlock invariant: when verify reports
unlocked-ok, any final link other than Gen2 x16 fails with a red
[ERROR] and exit 1. README safety section and AGENTS.md harness
cloning invariant updated to match.
This commit is contained in:
2026-09-28 15:39:10 +02:00
parent a84aa494d0
commit 7f3cfcf058
4 changed files with 220 additions and 27 deletions
+64 -10
View File
@@ -3,11 +3,15 @@
Per run: wait for card -> auto serial -> VBIOS backup -> PCIe link/AER/config
checks -> retrain + rescan tests -> dmesg scrape -> cmpunlocker verify.sh
(read-only) -> optional 5-min Vulkan VRAM stress (last step, ask yes/no)
(read-only) -> opt-in cmpunlocker unlock (install.sh, y/N or --unlock; a
successful install skips VRAM stress and asks for a cold reboot, then you
re-run this script for the final Gen2 x16 PCIe check)
-> optional 5-min Vulkan VRAM stress (last step, ask yes/no)
-> cards/<serial>/report.json.
Scoring (lenient): FAIL only on no-device/link-down/lost-after-rescan;
WARN on correctable AER/Xid/width< x16/verify issues.
Scoring: FAIL on no-device/link-down/lost-after-rescan, and on a final
post-unlock link other than Gen2 x16; WARN on correctable AER/Xid/width< x16/
verify issues/failed unlock install.
Exit codes: 0 = PASS/WARN, 1 = FAIL, 2 = no card, 3 = preflight blocking.
"""
@@ -24,6 +28,8 @@ from cxlib import C, ask, detect_gpus, err, info, now_iso, ok, sanitize, step, w
def score(rep):
fails, warns = [], []
ver = rep.get("cmpunlocker_verify", {})
unlocked = ver.get("status") == "unlocked-ok"
if not rep.get("bdf"):
fails.append("no CMP 170HX device detected on the bus")
link = rep.get("link", {})
@@ -33,11 +39,20 @@ def score(rep):
if link.get("lnksta_width") in ("x0", "x1", None) or not link.get("lnksta_speed"):
fails.append(f"link down or untrained: {link.get('lnksta_raw') or 'no LnkSta'}")
width = link.get("lnksta_width")
if width and width != "x16":
warns.append(f"link width {width} (capacitor mod expects x16 - mod likely "
f"missing/incomplete on this card)")
if link.get("lnksta_gen") == 1:
warns.append("link negotiated Gen1 only (cmpunlocker Gen2 patch/service not active?)")
gen = link.get("lnksta_gen")
if unlocked:
# Post-unlock final acceptance: only Gen2 x16 passes. The pre-unlock
# width/Gen1 warns are suppressed here - the error replaces them.
if width != "x16" or gen is None or gen < 2:
fails.append(f"[ERROR] final PCIe link after unlock is "
f"{link.get('lnksta_speed') or '?'} {width or '?'} "
f"- expected Gen2 x16")
else:
if width and width != "x16":
warns.append(f"link width {width} (capacitor mod expects x16 - mod likely "
f"missing/incomplete on this card)")
if gen == 1:
warns.append("link negotiated Gen1 only (cmpunlocker Gen2 patch/service not active?)")
if rep.get("rescan", {}).get("ok") is False:
fails.append("device lost after bus rescan")
if rep.get("retrain", {}).get("ok") is False:
@@ -60,9 +75,11 @@ def score(rep):
warns.append("kernel errors around VRAM stress window")
if not rep.get("bios", {}).get("ok"):
warns.append("VBIOS backup failed/incomplete")
ver = rep.get("cmpunlocker_verify", {})
if ver.get("ran") and ver.get("status") not in ("unlocked-ok",):
warns.append(f"cmpunlocker verify status: {ver.get('status')}")
unlock = rep.get("unlock", {})
if unlock.get("attempted") and not unlock.get("ok"):
warns.append("cmpunlocker install failed (see install_unlocker.log)")
result = "FAIL" if fails else ("WARN" if warns else "PASS")
return result, fails, warns
@@ -156,8 +173,38 @@ def process_card(args):
info("verify: " + (f"rc={v.get('rc')} -> {v.get('status')}" if v.get("ran")
else f"skipped - {v.get('note')}"))
step("cmpunlocker unlock (install)")
unlock_installed = False
if rep["cmpunlocker_verify"].get("status") == "unlocked-ok":
info("memory already unlocked - skipping install")
rep["unlock"] = {"attempted": False, "ok": False, "note": "already unlocked"}
elif getattr(args, "demo", False):
# --demo never prompts: force the unlock install path off.
rep["unlock"] = {"attempted": False, "ok": False, "note": "demo"}
info("unlock install skipped (demo mode)")
elif not X.INSTALL_SH.exists():
rep["unlock"] = {"attempted": False, "ok": False,
"note": "harness/cmpunlocker not cloned - run --preflight"}
warn("harness/cmpunlocker not cloned - run --preflight (unlock install skipped)")
elif args.unlock or ask("Run cmpunlocker install.sh now? (y/N)", "n").lower().startswith("y"):
X.update_cmpunlocker()
rep["unlock"] = X.run_installer(folder / "install_unlocker.log")
unlock_installed = bool(rep["unlock"].get("attempted") and rep["unlock"].get("ok"))
if unlock_installed:
# Kernel modules are replaced but the card is not unlocked until the
# cold reboot - VRAM stress against the half-applied state is unsafe.
rep["vulkan_test"] = {"requested": False, "ran": False,
"note": "skipped - unlock installed, reboot first"}
else:
warn("cmpunlocker install failed - continuing with the Vulkan step")
else:
rep["unlock"] = {"attempted": False, "ok": False, "note": "operator declined"}
info("unlock install skipped")
step("optional Vulkan VRAM test (last step)")
if args.vulkan == "never":
if unlock_installed:
info("vulkan test skipped - unlock installed, cold reboot first")
elif args.vulkan == "never":
rep["vulkan_test"] = {"requested": False, "ran": False, "note": "disabled via --vulkan never"}
elif args.vulkan == "always":
rep["vulkan_test"] = X.run_vulkan_test()
@@ -201,6 +248,7 @@ def demo(args):
orig_aer, orig_dm, orig_cfg = X.aer_snapshot, X.dmesg_capture, X.dump_config
orig_retrain, orig_rescan = X.retrain_test, X.rescan_test
orig_smi, orig_verify = X.nvidia_smi_info, X.run_verify_unlocker
orig_update, orig_installer = X.update_cmpunlocker, X.run_installer
globals()["wait_for_gpu"] = lambda: "0000:0a:00.0"
X.detect_gpus = lambda: ["0000:0a:00.0"]
X.check_link = lambda b: ({"lnksta_speed": "5GT/s", "lnksta_width": "x4", "lnksta_gen": 2,
@@ -218,6 +266,9 @@ def demo(args):
X.rescan_test = lambda b: {"attempted": True, "ok": True, "note": "re-enumerated"}
X.nvidia_smi_info = lambda: {"available": False}
X.run_verify_unlocker = lambda: {"ran": False, "note": "demo"}
X.update_cmpunlocker = lambda: {"ok": True, "note": "demo"}
X.run_installer = lambda log_path: {"attempted": False, "ok": False, "rc": None,
"note": "demo", "log": str(log_path)}
try:
rep = process_card(args)
finally:
@@ -226,6 +277,7 @@ def demo(args):
X.aer_snapshot, X.dmesg_capture, X.dump_config = orig_aer, orig_dm, orig_cfg
X.retrain_test, X.rescan_test = orig_retrain, orig_rescan
X.nvidia_smi_info, X.run_verify_unlocker = orig_smi, orig_verify
X.update_cmpunlocker, X.run_installer = orig_update, orig_installer
return rep
@@ -241,6 +293,8 @@ def main():
ap.add_argument("--skip-bios", action="store_true", help="skip VBIOS backup")
ap.add_argument("--vulkan", choices=("ask", "always", "never"), default="ask",
help="5-min Vulkan VRAM test policy (default ask, runs as last step)")
ap.add_argument("--unlock", action="store_true",
help="run cmpunlocker install.sh without prompting (default: ask y/N)")
ap.add_argument("--no-install", action="store_true",
help="do not auto-install packages (skips the sudo precondition)")
ap.add_argument("--no-clone", action="store_true", help="do not clone harness repos")