diff --git a/README.md b/README.md index 2406509..e27d7bb 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,16 @@ sudo ./install.sh --profile=8gb # 8GB card → 64GB unlock sudo ./install.sh --profile=10gb # 10GB card → 40GB unlock ``` +### IOMMU + +The installer also enables the IOMMU in passthrough mode, appending `intel_iommu=on iommu=pt` (Intel) or `amd_iommu=on iommu=pt` (AMD) to the kernel command line via `/etc/default/grub` or `/etc/kernel/cmdline`, then regenerating the boot config. Conflicting `iommu=` / `*_iommu=` entries are replaced, the original file is backed up to `*.cmpunlocker.bak`, and `remove.sh` restores it. + +This takes effect on the next reboot, and still requires VT-d / AMD-Vi to be enabled in BIOS/UEFI. To leave the kernel command line untouched: + +```bash +sudo ./install.sh --no-iommu +``` + Then perform a **cold reboot** (full power off, then boot) if modules did not hot-reload cleanly, or if memory still shows the stock size. --- @@ -74,25 +84,25 @@ nvidia-smi # 8GB card: expect ~65536 MiB # 10GB card: expect ~40960 MiB -nvidia-smi --query-gpu=memory.total,clocks.max.sm --format=csv +nvidia-smi --query-gpu=memory.total,pcie.link.gen.current,pcie.link.gen.max,clocks.max.sm --format=csv +# Expect pcie.link.gen.current=2 and pcie.link.gen.max=2 after reboot + +sudo lspci -d 10de:20c2 -vv | grep -E 'LnkCap:|LnkSta:' +# Expect LnkSta: Speed 5GT/s (not 2.5GT/s) sudo dmesg | grep SEC2_DEBUG # Expected: PLMs opening to 0xffffffff, CFG1/LMR/SS0/SS1 writes, late PMA - cat /lib/modules/$(uname -r)/updates/cmpunlocker/card_profile # 8gb or 10gb ``` -Booter status codes such as `0x31` / `0xffff` during the early PLM Booter passes can appear and are often harmless if the final boot succeeds. - ---- - ## What Gets Unlocked | Feature | Status | |---|---| | Full SM compute throughput (SS0/SS1) | Working ✓ | | Memory geometry (64GB on 8GB cards, 40GB on 10GB cards) | Working ✓ | +| PCIe Gen2 link (`5GT/s`, Device Max ≥ 2) | Working ✓ | | Persistence across reboot (patched modules) | Working ✓ | --- diff --git a/common/constants.yaml b/common/constants.yaml index 5e6d989..d444458 100644 --- a/common/constants.yaml +++ b/common/constants.yaml @@ -12,6 +12,20 @@ compute: ss0: "0x88888888" ss1: "0x00000008" +pcie: + target_gen: 2 + link_speed_gen2: "0x2" + xve_link_control_status: "0x00088088" + xve_link_control_2: "0x000880a8" + pl_link_rate_addr: "0x0008c1c0" + pl_link_rate_value: "0x00240036" + vsec_hierarchy_addr: "0x00088610" + vsec_device_addr: "0x0008860c" + xp_fuse_override_base: "0x0008e110" + xp_fuse_override_val_base: "0x0008e120" + opt_gen23_addr: "0x0082057c" + opt_magic_a100: "0x00200000" + profiles: "8gb": stock_mib: 8192 @@ -19,11 +33,9 @@ profiles: cfg1: "0x02779000" lmr: "0x0000020B" fb_bytes: "0x0000001000000000" - comment: "8GB physical card → 64GB geometry" "10gb": stock_mib: 10240 unlocked_mib: 40960 cfg1: "0x02669000" lmr: "0x0000028A" fb_bytes: "0x0000000A00000000" - comment: "10GB physical card → 40GB geometry" diff --git a/driver/build.sh b/driver/build.sh index 5aaa7f5..4227878 100755 --- a/driver/build.sh +++ b/driver/build.sh @@ -54,7 +54,7 @@ else ok "Using cached tarball ${TARBALL}" fi -info "Extracting clean stock sources..." +info "Extracting sources..." rm -rf "${SRC_DIR}" tar -xzf "${TARBALL}" -C "${BUILD_ROOT}" if [[ ! -d "${SRC_DIR}" ]]; then @@ -79,6 +79,7 @@ PROFILE="${CMPUNLOCKER_CARD_PROFILE:-8gb}" GSP_C="${SRC_DIR}/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c" [[ -f "${GSP_C}" ]] || die "Missing ${GSP_C} after patching" +SKIP_GEOMETRY_REWRITE=0 case "${PROFILE}" in 8gb|8GB) PROFILE="8gb" @@ -94,19 +95,28 @@ case "${PROFILE}" in FB_BYTES="0x0000000A00000000" UNLOCK_LABEL="40GB" ;; + mixed|MIXED) + PROFILE="mixed" + CFG1="0x02779000" + LMR="0x0000020B" + FB_BYTES="0x0000001000000000" + UNLOCK_LABEL="mixed" + SKIP_GEOMETRY_REWRITE=1 + ;; *) - die "Unknown CMPUNLOCKER_CARD_PROFILE='${PROFILE}' (use 8gb or 10gb)" + die "Unknown CMPUNLOCKER_CARD_PROFILE='${PROFILE}' (use 8gb, 10gb, or mixed)" ;; esac info "Applying memory profile ${PROFILE} (${UNLOCK_LABEL} geometry)..." -python3 - "${GSP_C}" "${CFG1}" "${LMR}" "${FB_BYTES}" "${UNLOCK_LABEL}" <<'PY' +if [[ "${SKIP_GEOMETRY_REWRITE}" -eq 1 ]]; then + info "mixed profile: runtime device-id geometry (no build-time CFG1/LMR rewrite)" +else + python3 - "${GSP_C}" "${CFG1}" "${LMR}" "${FB_BYTES}" "${UNLOCK_LABEL}" <<'PY' import pathlib, re, sys path, cfg1, lmr, fb, label = sys.argv[1:6] text = pathlib.Path(path).read_text() - -# Dual-device path: both geometries are already baked into the patch. if ( "SEC2_POSTBL_TIMING_CMP_170HX_8GB_PCI_DEVICE_ID" in text and "SEC2_POSTBL_TIMING_CMP_170HX_10GB_PCI_DEVICE_ID" in text @@ -143,11 +153,18 @@ if n1 != 1 or n2 != 1 or n3 != 1: pathlib.Path(path).write_text(text2) print(f"cfg1={cfg1} lmr={lmr} fb={fb} ({label})") PY -ok "Memory profile ${PROFILE}: CFG1=${CFG1} LMR=${LMR} fb=${FB_BYTES} (${UNLOCK_LABEL})" +fi +ok "Memory profile ${PROFILE}: unlock_geometry=${UNLOCK_LABEL}" mkdir -p "${INSTALL_MOD_DIR}" printf '%s\n' "${VERSION}" > "${INSTALL_MOD_DIR}/driver_version" printf '%s\n' "${PROFILE}" > "${INSTALL_MOD_DIR}/card_profile" printf '%s\n' "${UNLOCK_LABEL}" > "${INSTALL_MOD_DIR}/unlock_geometry" +if [[ -n "${CMPUNLOCKER_GPU_INVENTORY:-}" ]]; then + printf '%s\n' "${CMPUNLOCKER_GPU_INVENTORY}" > "${INSTALL_MOD_DIR}/gpu_inventory" + ok "Wrote gpu_inventory ($(echo "${CMPUNLOCKER_GPU_INVENTORY}" | grep -c . || true) GPU(s))" +else + : > "${INSTALL_MOD_DIR}/gpu_inventory" +fi info "Building modules for kernel ${KVER}..." cd "${SRC_DIR}" @@ -157,7 +174,6 @@ make clean 2>/dev/null || true JOBS="$(nproc)" make -j"${JOBS}" modules SYSSRC="${KSRC}" ok "Modules built" - info "Installing modules to ${INSTALL_MOD_DIR}..." mkdir -p "${INSTALL_MOD_DIR}" @@ -175,22 +191,21 @@ done depmod -a "${KVER}" ok "depmod complete" - rebuild_initramfs() { if command -v update-initramfs &>/dev/null; then - info "Rebuilding initramfs (update-initramfs) so patched modules load at boot..." + info "Rebuilding initramfs (update-initramfs)..." update-initramfs -u -k "${KVER}" ok "initramfs rebuilt" return 0 fi if command -v dracut &>/dev/null; then - info "Rebuilding initramfs (dracut) so patched modules load at boot..." + info "Rebuilding initramfs (dracut)..." dracut --force --kver "${KVER}" ok "initramfs rebuilt" return 0 fi if command -v mkinitcpio &>/dev/null; then - info "Rebuilding initramfs (mkinitcpio) so patched modules load at boot..." + info "Rebuilding initramfs (mkinitcpio)..." mkinitcpio -P ok "initramfs rebuilt" return 0 @@ -200,19 +215,16 @@ rebuild_initramfs() { } rebuild_initramfs || true - resolved="$(modprobe -n -v nvidia 2>/dev/null | awk '/insmod/ {print $2; exit}' || true)" if [[ -n "${resolved}" ]]; then info "modprobe will load: ${resolved}" if [[ "${resolved}" != *"/updates/cmpunlocker/"* ]]; then - warn "Resolved nvidia.ko is not under updates/cmpunlocker/ — stock may still win" + warn "Resolved nvidia.ko is not under updates/cmpunlocker/" fi fi - -info "Attempting to unload existing NVIDIA modules..." +info "Attempting to unload NVIDIA modules..." systemctl stop nvidia-persistenced 2>/dev/null || true systemctl stop nvidia-fabricmanager 2>/dev/null || true - reload_ok=0 if lsmod | grep -q '^nvidia'; then for mod in nvidia_drm nvidia_uvm nvidia_modeset nvidia; do @@ -234,20 +246,17 @@ if ! lsmod | grep -q '^nvidia '; then reload_ok=0 fi else - warn "modprobe failed after install" + warn "modprobe failed" fi else - warn "Could not unload nvidia modules (in use) — cold reboot required" + warn "Could not unload nvidia modules" fi - echo "" if [[ "${reload_ok}" -eq 1 ]]; then ok "Build and install finished. Verify with: nvidia-smi" - info "If memory still shows stock size, do a cold shutdown (power off), then boot." + info "If memory shows stock size, do cold reboot." else - warn "Modules installed but the running driver is still stock (or unload failed)." - info "Perform a cold reboot: shutdown -h now (then power on)" - info "After boot, confirm: cat /proc/driver/nvidia/version (should NOT say dvs-builder)" - info "And: sudo dmesg | grep SEC2_DEBUG" + warn "Modules installed but running driver is still stock." + info "Perform cold reboot: shutdown -h now" fi echo "" diff --git a/driver/patches/0001-sec2-postbl-plm-ss-cfg.patch b/driver/patches/0001-sec2-postbl-plm-ss-cfg.patch index 05d578a..90fed08 100644 --- a/driver/patches/0001-sec2-postbl-plm-ss-cfg.patch +++ b/driver/patches/0001-sec2-postbl-plm-ss-cfg.patch @@ -37,7 +37,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ struct MIG_CI_UPDATE_CALLBACK_PARAMS { -@@ -4805,14 +4820,22 @@ +@@ -4805,14 +4824,23 @@ if (kgspIsWpr2Up_HAL(pGpu, pKernelGsp) && (!pGpu->getProperty(pGpu, PDB_PROP_GPU_PREINITIALIZED_WPR_REGION))) { @@ -48,7 +48,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ + "WPR2 already up before GSP boot; continuing for recovery\n"); } -- // Populate WPR meta structure (requires knowing FB size on dGPU, which depends on GFW_BOOT) + // Populate WPR meta structure (requires knowing FB size on dGPU, which depends on GFW_BOOT) NV_CHECK_OK_OR_RETURN(LEVEL_ERROR, kgspPopulateWprMeta_HAL(pGpu, pKernelGsp, pGspFw)); + if (_kgspSec2PostblTimingEnabled(pGpu)) @@ -64,7 +64,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ { // If the new FB layout requires a scrubber ucode to scrub additional space, prepare it now NV_CHECK_OK_OR_RETURN(LEVEL_ERROR, _kgspPrepareScrubberImageIfNeeded(pGpu, pKernelGsp)); -@@ -4821,6 +4844,117 @@ +@@ -4821,6 +4849,122 @@ // Setup arguments for bootstrapping GSP NV_CHECK_OK_OR_RETURN(LEVEL_ERROR, kgspPrepareForBootstrap_HAL(pGpu, pKernelGsp, KGSP_BOOT_MODE_NORMAL)); @@ -79,6 +79,11 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ + { 0x009a0148U, 0xffffffffU, "FBPA" }, + { 0x001fa7c4U, 0xffffffffU, "WPR" }, + { 0x00823804U, 0xffffffffU, "FEAT" }, ++ { 0x00088ff4U, 0xffffffffU, "XVE" }, ++ { 0x00088ab4U, 0xffffffffU, "XVE_B" }, ++ { 0x00088ff8U, 0xffffffffU, "XVE_C" }, ++ { 0x00823b00U, 0xffffffffU, "FEAT2" }, ++ { 0x008200fcU, 0xffffffffU, "OPT_PLM" }, + }; + + NvU32 wpr2Lo = GPU_REG_RD32(pGpu, 0x001fa824U); @@ -87,7 +92,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ + "SEC2_DEBUG: saved WPR2 lo=0x%08x hi=0x%08x\n", + wpr2Lo, wpr2Hi); + -+ for (plmIdx = 0; plmIdx < 4; plmIdx++) ++ for (plmIdx = 0; plmIdx < 9; plmIdx++) + { + NvBool opened = NV_FALSE; + for (attempt = 0; attempt < 2 && !opened; attempt++) @@ -135,12 +140,12 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ + + if (devId == SEC2_POSTBL_TIMING_CMP_170HX_8GB_PCI_DEVICE_ID) + { -+ cfg1Value = 0x02779000U; // 8GB card: 64GB unlock ++ cfg1Value = 0x02779000U; + lmrValue = 0x0000020BU; + } + else + { -+ cfg1Value = 0x02669000U; // 10GB card: 40GB unlock ++ cfg1Value = 0x02669000U; + lmrValue = 0x0000028AU; + } + @@ -182,7 +187,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ // Release the API lock if relaxed locking for parallel init is enabled NvBool bRelaxedLocking = _kgspShouldRelaxGspInitLocking(pGpu); if (bRelaxedLocking) -@@ -5164,6 +5285,53 @@ +@@ -5164,6 +5289,53 @@ goto done; } @@ -195,9 +200,9 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ + NV2080_CTRL_CMD_FB_GET_FB_REGION_INFO_PARAMS *pFbRegionInfoParams = + &pGSCI->fbRegionInfoParams; + NvU64 targetFbBytes = (devId == SEC2_POSTBL_TIMING_CMP_170HX_8GB_PCI_DEVICE_ID) -+ ? 0x0000001000000000ULL /* 64GB for 8GB card */ -+ : 0x0000000A00000000ULL; /* 40GB for 10GB card */ -+ NvU64 stockFbBytes = 0x0000000200000000ULL; /* 8GB */ ++ ? 0x0000001000000000ULL ++ : 0x0000000A00000000ULL; ++ NvU64 stockFbBytes = 0x0000000200000000ULL; + NvU32 numRegions = pFbRegionInfoParams->numFBRegions; + + NV_PRINTF(LEVEL_ERROR, @@ -236,7 +241,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ NV_ASSERT_OK_OR_GOTO(status, kgspInitGspTraceCrashBuffer(pGpu, pKernelGsp), done); // Set PDB properties as per data from GSP. -@@ -5662,6 +5827,84 @@ +@@ -5662,6 +5831,84 @@ pKernelGsp->gspRmBootUcodeSize = 0; } @@ -321,7 +326,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ static NV_STATUS _kgspCreateSignatureMemdesc ( -@@ -5672,6 +5915,9 @@ +@@ -5672,6 +5919,9 @@ { NV_STATUS status = NV_OK; NvU8 *pSignatureVa = NULL; @@ -331,7 +336,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ NvU64 flags = MEMDESC_FLAGS_NONE; if (confComputeForceUnprotAlloc(pGpu)) -@@ -5682,7 +5928,7 @@ +@@ -5682,7 +5932,7 @@ // NOTE: align to 256 because that's the alignment needed for Booter DMA NV_CHECK_OK_OR_RETURN(LEVEL_ERROR, memdescCreate(&pKernelGsp->pSignatureMemdesc, pGpu, @@ -340,7 +345,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ NV_TRUE, ADDR_SYSMEM, NV_MEMORY_CACHED, flags)); memdescTagAlloc(status, -@@ -5694,8 +5940,48 @@ +@@ -5694,8 +5944,48 @@ (pSignatureVa != NULL) ? NV_OK : NV_ERR_INSUFFICIENT_RESOURCES, fail_alloc); @@ -391,7 +396,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c b/src/nvidia/src/kernel/ memdescUnmapInternal(pGpu, pKernelGsp->pSignatureMemdesc, 0); pSignatureVa = NULL; -@@ -5709,6 +5995,69 @@ +@@ -5709,6 +5999,69 @@ memdescDestroy(pKernelGsp->pSignatureMemdesc); pKernelGsp->pSignatureMemdesc = NULL; diff --git a/driver/patches/0003-late-pma.patch b/driver/patches/0003-late-pma.patch index 9118fee..218302b 100644 --- a/driver/patches/0003-late-pma.patch +++ b/driver/patches/0003-late-pma.patch @@ -101,7 +101,7 @@ diff -Naur a/src/nvidia/src/kernel/gpu/mem_mgr/mem_mgr.c b/src/nvidia/src/kernel + if (devId != 0x20C2 && devId != 0x2082) + return NV_OK; + -+ stockFbBytes = 0x200000000ULL; /* 8GB */ ++ stockFbBytes = 0x200000000ULL; + + pHeap = pMemoryManager->pHeap; + if (pHeap == NULL || pHeap->pPmaObject == NULL || !memmgrIsPmaInitialized(pMemoryManager)) diff --git a/driver/patches/0007-pcie-gen2.patch b/driver/patches/0007-pcie-gen2.patch new file mode 100644 index 0000000..db73e34 --- /dev/null +++ b/driver/patches/0007-pcie-gen2.patch @@ -0,0 +1,310 @@ +--- a/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c ++++ b/src/nvidia/src/kernel/gpu/gsp/kernel_gsp.c +@@ -4942,6 +4942,260 @@ + devId); + } + ++ ++ { ++ #define PCIE_GEN2_LINK_CAP_ADDR 0x00088084U ++ #define PCIE_GEN2_LINK_CAP2_ADDR 0x000880a4U ++ #define PCIE_GEN2_LINK_CTRL_2_ADDR 0x000880a8U ++ #define PCIE_GEN2_LINK_CTRL_STATUS_ADDR 0x00088088U ++ #define PCIE_GEN2_PL_LINK_RATE_ADDR 0x0008c1c0U ++ #define PCIE_GEN2_LTSSM_ADDR 0x0008872cU ++ #define PCIE_GEN2_VSEC_DEVICE_ADDR 0x0008860cU ++ #define PCIE_GEN2_VSEC_HIERARCHY_ADDR 0x00088610U ++ #define PCIE_GEN2_XP3G_OVR_BASE 0x0008e110U ++ #define PCIE_GEN2_XP3G_VAL_BASE 0x0008e120U ++ #define PCIE_GEN2_XP3G_STATUS_BASE 0x0008e100U ++ #define PCIE_GEN2_OPT_GEN23_ADDR 0x0082057cU ++ #define PCIE_GEN2_OPT_GEN3_ADDR 0x00820580U ++ #define PCIE_GEN2_OPT_MAGIC_ADDR 0x00820520U ++ #define PCIE_LINK_SPEED_OF(stat) (((stat) >> 16) & 0xFU) ++ const NvU32 PCIE_GEN2_LINK_SPEED = 0x00000002U; ++ const NvU32 PCIE_GEN2_PL_LINK_RATE_VALUE = 0x00240036U; ++ ++ NvU32 capBefore = GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CAP_ADDR); ++ NvU32 statBefore = GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CTRL_STATUS_ADDR); ++ NvU32 vsecDev = GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_DEVICE_ADDR); ++ NvU32 vsecHier = GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_HIERARCHY_ADDR); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe pre CAP=0x%08x CAP2=0x%08x LC2=0x%08x PL=0x%08x " ++ "STAT=0x%08x VSEC dev=0x%08x hier=0x%08x OPT=%08x/%08x/%08x " ++ "XP3G st0=0x%08x ovr0=0x%08x val0=0x%08x " ++ "st3=0x%08x ovr3=0x%08x val3=0x%08x speed=%u\n", ++ capBefore, ++ GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CAP2_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CTRL_2_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_PL_LINK_RATE_ADDR), ++ statBefore, ++ vsecDev, ++ vsecHier, ++ GPU_REG_RD32(pGpu, PCIE_GEN2_OPT_GEN23_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_OPT_GEN3_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_OPT_MAGIC_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_STATUS_BASE), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_OVR_BASE), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_VAL_BASE), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_STATUS_BASE + 0xCU), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_OVR_BASE + 0xCU), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_VAL_BASE + 0xCU), ++ PCIE_LINK_SPEED_OF(statBefore)); ++ ++ { ++ static const struct { NvU32 addr; NvU32 value; const char *name; } xp3gTable[] = { ++ { 0x0008e1b0U, 0xffffffffU, "XP3G_PLM" }, ++ { 0x0008e1b4U, 0xffffffffU, "XP3G_PLM4" }, ++ { 0x0008e1b8U, 0xffffffffU, "XP3G_PLM8" }, ++ { 0x0008e1bcU, 0xffffffffU, "XP3G_PLMC" }, ++ { 0x00088fe8U, 0xffffffffU, "XVE_D0" }, ++ { 0x00088fecU, 0xffffffffU, "XVE_D4" }, ++ { 0x00088ff0U, 0xffffffffU, "XVE_D8" }, ++ { 0x008200d0U, 0xffffffffU, "OPTB_D0" }, ++ { 0x008200d4U, 0xffffffffU, "OPTB_D4" }, ++ { 0x008200d8U, 0xffffffffU, "OPTB_D8" }, ++ { 0x008200dcU, 0xffffffffU, "OPTB_DC" }, ++ { 0x008200e0U, 0xffffffffU, "OPTB_E0" }, ++ { 0x008200e4U, 0xffffffffU, "OPTB_E4" }, ++ { 0x008200e8U, 0xffffffffU, "OPTB_E8" }, ++ { 0x008200ecU, 0xffffffffU, "OPTB_EC" }, ++ { 0x008200f0U, 0xffffffffU, "OPTB_F0" }, ++ { 0x008200f4U, 0xffffffffU, "OPTB_F4" }, ++ { 0x00823800U, 0xffffffffU, "FEAT_OVR_ECC_PLM" }, ++ { 0x0082057cU, 0x00000000U, "OPT_GEN23" }, ++ { 0x0008e120U, 0x00000000U, "XP3G_VAL0" }, ++ { 0x0008e110U, 0x00000001U, "XP3G_OVR0" }, ++ { 0x0008e12cU, 0x00200000U, "XP3G_VAL3" }, ++ { 0x0008e11cU, 0x00000004U, "XP3G_OVR3" }, ++ }; ++ NvU32 xi, xattempt; ++ NV_STATUS xpStatus; ++ const NvU32 xp3gCount = sizeof(xp3gTable) / sizeof(xp3gTable[0]); ++ #define PCIE_GEN2_PRIV_MISC_1_ADDR 0x0008841cU ++ #define PCIE_GEN2_PRIV_MISC_1_GEN2_EN ((1U << 11) | (1U << 13)) ++ #define PCIE_GEN2_PRIV_MISC_1_GEN2_VAL ((1U << 12) | (1U << 14)) ++ ++ for (xi = 0; xi < xp3gCount; xi++) ++ { ++ NvBool wrote = NV_FALSE; ++ for (xattempt = 0; xattempt < 2 && !wrote; xattempt++) ++ { ++ GPU_REG_WR32(pGpu, 0x001fa824U, wpr2Lo); ++ GPU_REG_WR32(pGpu, 0x001fa828U, wpr2Hi); ++ ++ xpStatus = kgspSec2PostblTimingRefillPayload(pGpu, pKernelGsp, ++ xp3gTable[xi].addr, xp3gTable[xi].value); ++ if (xpStatus != NV_OK) ++ continue; ++ ++ xpStatus = kgspExecuteBooterLoad_HAL(pGpu, pKernelGsp, ++ memdescGetPhysAddr(pKernelGsp->pWprMetaDescriptor, AT_GPU, 0)); ++ ++ NvU32 tgtRd = GPU_REG_RD32(pGpu, xp3gTable[xi].addr); ++ NvU32 ovr0Rd = GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_OVR_BASE); ++ NvU32 val0Rd = GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_VAL_BASE); ++ NvU32 ovr3Rd = GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_OVR_BASE + 0xCU); ++ NvU32 val3Rd = GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_VAL_BASE + 0xCU); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe xp3g booter %s(0x%x)=0x%08x attempt=%u " ++ "status=0x%x rd=0x%08x OVR0=0x%08x VAL0=0x%08x " ++ "OVR3=0x%08x VAL3=0x%08x\n", ++ xp3gTable[xi].name, xp3gTable[xi].addr, ++ xp3gTable[xi].value, xattempt, xpStatus, ++ tgtRd, ovr0Rd, val0Rd, ovr3Rd, val3Rd); ++ if (tgtRd == xp3gTable[xi].value) ++ wrote = NV_TRUE; ++ } ++ if (!wrote) ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe xp3g booter FAILED to set %s\n", ++ xp3gTable[xi].name); ++ } ++ ++ { ++ NvU32 capNow = GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CAP_ADDR); ++ NvU32 cap2Now = GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CAP2_ADDR); ++ NvU32 opt23Now = GPU_REG_RD32(pGpu, 0x0082057cU); ++ NvU32 plmXp = GPU_REG_RD32(pGpu, 0x0008e1b0U); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe Cap oracle CAP=0x%08x CAP2=0x%08x " ++ "OPT_GEN23=0x%08x XP3G_PLM=0x%08x\n", ++ capNow, cap2Now, opt23Now, plmXp); ++ } ++ ++ { ++ NvU32 vsecDevNow = GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_DEVICE_ADDR); ++ NvU32 vsecDevWant = vsecDevNow | (1U << 0); ++ NvBool wrote = NV_FALSE; ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe VSEC_DEVICE pre=0x%08x want=0x%08x\n", ++ vsecDevNow, vsecDevWant); ++ for (xattempt = 0; xattempt < 2 && !wrote; xattempt++) ++ { ++ GPU_REG_WR32(pGpu, 0x001fa824U, wpr2Lo); ++ GPU_REG_WR32(pGpu, 0x001fa828U, wpr2Hi); ++ xpStatus = kgspSec2PostblTimingRefillPayload(pGpu, pKernelGsp, ++ PCIE_GEN2_VSEC_DEVICE_ADDR, vsecDevWant); ++ if (xpStatus != NV_OK) ++ continue; ++ xpStatus = kgspExecuteBooterLoad_HAL(pGpu, pKernelGsp, ++ memdescGetPhysAddr(pKernelGsp->pWprMetaDescriptor, AT_GPU, 0)); ++ NvU32 rd = GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_DEVICE_ADDR); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe VSEC_DEVICE booter attempt=%u " ++ "status=0x%x rd=0x%08x\n", ++ xattempt, xpStatus, rd); ++ if (rd == vsecDevWant) ++ wrote = NV_TRUE; ++ } ++ if (!wrote) ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe VSEC_DEVICE booter FAILED\n"); ++ } ++ ++ { ++ NvU32 misc1 = GPU_REG_RD32(pGpu, PCIE_GEN2_PRIV_MISC_1_ADDR); ++ NvU32 misc1Want = (misc1 | PCIE_GEN2_PRIV_MISC_1_GEN2_EN) ++ & ~PCIE_GEN2_PRIV_MISC_1_GEN2_VAL; ++ NvBool wrote = NV_FALSE; ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe PRIV_MISC_1 pre=0x%08x want=0x%08x\n", ++ misc1, misc1Want); ++ for (xattempt = 0; xattempt < 2 && !wrote; xattempt++) ++ { ++ GPU_REG_WR32(pGpu, 0x001fa824U, wpr2Lo); ++ GPU_REG_WR32(pGpu, 0x001fa828U, wpr2Hi); ++ xpStatus = kgspSec2PostblTimingRefillPayload(pGpu, pKernelGsp, ++ PCIE_GEN2_PRIV_MISC_1_ADDR, misc1Want); ++ if (xpStatus != NV_OK) ++ continue; ++ xpStatus = kgspExecuteBooterLoad_HAL(pGpu, pKernelGsp, ++ memdescGetPhysAddr(pKernelGsp->pWprMetaDescriptor, AT_GPU, 0)); ++ NvU32 rd = GPU_REG_RD32(pGpu, PCIE_GEN2_PRIV_MISC_1_ADDR); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe PRIV_MISC_1 booter attempt=%u " ++ "status=0x%x rd=0x%08x\n", ++ xattempt, xpStatus, rd); ++ if (rd == misc1Want) ++ wrote = NV_TRUE; ++ } ++ if (!wrote) ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe PRIV_MISC_1 booter FAILED\n"); ++ } ++ ++ GPU_REG_WR32(pGpu, 0x001fa824U, wpr2Lo); ++ GPU_REG_WR32(pGpu, 0x001fa828U, wpr2Hi); ++ } ++ ++ { ++ NvU32 hier = GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_HIERARCHY_ADDR); ++ hier = (hier & ~(1U << 12)) | (1U << 0); ++ GPU_REG_WR32(pGpu, PCIE_GEN2_VSEC_HIERARCHY_ADDR, hier); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe vsec hierarchy=0x%08x device=0x%08x\n", ++ GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_HIERARCHY_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_DEVICE_ADDR)); ++ } ++ ++ { ++ NvU32 lc2 = GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CTRL_2_ADDR); ++ NvU32 linkCfg; ++ NvU32 cya0; ++ lc2 = (lc2 & ~0x0000000FU) | PCIE_GEN2_LINK_SPEED; ++ lc2 = (lc2 & ~0x000F0000U) | 0x000F0000U; ++ GPU_REG_WR32(pGpu, PCIE_GEN2_LINK_CTRL_2_ADDR, lc2); ++ cya0 = GPU_REG_RD32(pGpu, 0x0008c2c0U); ++ cya0 = cya0 & ~(1U << 2); ++ GPU_REG_WR32(pGpu, 0x0008c2c0U, cya0); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe CYA_0 after clear DIS_G2: 0x%08x (bit2=%u)\n", ++ GPU_REG_RD32(pGpu, 0x0008c2c0U), ++ (GPU_REG_RD32(pGpu, 0x0008c2c0U) >> 2) & 1U); ++ linkCfg = GPU_REG_RD32(pGpu, 0x0008c040U); ++ linkCfg = (linkCfg & ~0x000C0000U) | (0x2U << 18); ++ GPU_REG_WR32(pGpu, 0x0008c040U, linkCfg); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe LINK_CONFIG_0 after MAX_RATE=2: 0x%08x\n", ++ GPU_REG_RD32(pGpu, 0x0008c040U)); ++ GPU_REG_WR32(pGpu, PCIE_GEN2_PL_LINK_RATE_ADDR, PCIE_GEN2_PL_LINK_RATE_VALUE); ++ GPU_REG_WR32(pGpu, 0x0008872cU, 0x00000006U); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe XVE_OVR@8872c=0x%08x; skip mid-boot retrain\n", ++ GPU_REG_RD32(pGpu, 0x0008872cU)); ++ } ++ ++ NvU32 statAfter = GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CTRL_STATUS_ADDR); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe retrain done CAP=0x%08x CAP2=0x%08x LC2=0x%08x " ++ "PL=0x%08x STAT=0x%08x hier=0x%08x dev=0x%08x OPT=%08x/%08x/%08x " ++ "XP3G ovr0=0x%08x val0=0x%08x ovr3=0x%08x val3=0x%08x " ++ "MISC1=0x%08x speed=%u\n", ++ GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CAP_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CAP2_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_LINK_CTRL_2_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_PL_LINK_RATE_ADDR), ++ statAfter, ++ GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_HIERARCHY_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_VSEC_DEVICE_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_OPT_GEN23_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_OPT_GEN3_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_OPT_MAGIC_ADDR), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_OVR_BASE), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_VAL_BASE), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_OVR_BASE + 0xCU), ++ GPU_REG_RD32(pGpu, PCIE_GEN2_XP3G_VAL_BASE + 0xCU), ++ GPU_REG_RD32(pGpu, 0x0008841cU), ++ PCIE_LINK_SPEED_OF(statAfter)); ++ } ++ + plmStatus = kgspSec2PostblTimingRebuildStockSignature(pGpu, pKernelGsp); + if (plmStatus != NV_OK) + { +--- a/src/nvidia/src/kernel/gpu/gsp/arch/turing/kernel_gsp_tu102.c ++++ b/src/nvidia/src/kernel/gpu/gsp/arch/turing/kernel_gsp_tu102.c +@@ -611,6 +611,44 @@ + } + } + ++ ++ { ++ NvU32 lateDevId = pGpu->idInfo.PCIDeviceID >> 16; ++ if ((lateDevId == 0x20C2 || lateDevId == 0x2082) && status == NV_OK) ++ { ++ #define PCIE_GEN2_PRIV_MISC_1_ADDR_LATE 0x0008841cU ++ #define PCIE_GEN2_PRIV_MISC_1_GEN2_EN_LATE ((1U << 11) | (1U << 13)) ++ #define PCIE_GEN2_PRIV_MISC_1_GEN2_VAL_LATE ((1U << 12) | (1U << 14)) ++ NvU32 misc1Late = GPU_REG_RD32(pGpu, PCIE_GEN2_PRIV_MISC_1_ADDR_LATE); ++ NvU32 misc1WantLate = (misc1Late | PCIE_GEN2_PRIV_MISC_1_GEN2_EN_LATE) ++ & ~PCIE_GEN2_PRIV_MISC_1_GEN2_VAL_LATE; ++ GPU_REG_WR32(pGpu, PCIE_GEN2_PRIV_MISC_1_ADDR_LATE, misc1WantLate); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe PRIV_MISC_1 late pre=0x%08x post=0x%08x\n", ++ misc1Late, ++ GPU_REG_RD32(pGpu, PCIE_GEN2_PRIV_MISC_1_ADDR_LATE)); ++ { ++ NvU32 cyaLate = GPU_REG_RD32(pGpu, 0x0008c2c0U); ++ cyaLate = cyaLate & ~(1U << 2); ++ GPU_REG_WR32(pGpu, 0x0008c2c0U, cyaLate); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe CYA_0 late clear DIS_G2: 0x%08x (bit2=%u)\n", ++ GPU_REG_RD32(pGpu, 0x0008c2c0U), ++ (GPU_REG_RD32(pGpu, 0x0008c2c0U) >> 2) & 1U); ++ NvU32 linkCfgLate = GPU_REG_RD32(pGpu, 0x0008c040U); ++ linkCfgLate = (linkCfgLate & ~0x000C0000U) | (0x2U << 18); ++ GPU_REG_WR32(pGpu, 0x0008c040U, linkCfgLate); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe LINK_CONFIG_0 late MAX_RATE=2: 0x%08x\n", ++ GPU_REG_RD32(pGpu, 0x0008c040U)); ++ GPU_REG_WR32(pGpu, 0x0008872cU, 0x00000006U); ++ NV_PRINTF(LEVEL_ERROR, ++ "SEC2_DEBUG: PCIe XVE_OVR late=0x%08x\n", ++ GPU_REG_RD32(pGpu, 0x0008872cU)); ++ } ++ } ++ } ++ + if (status != NV_OK) + { + NV_PRINTF(LEVEL_ERROR, "failed to execute Booter Load (ucode for initial boot): 0x%x\n", status); diff --git a/driver/patches/0008-pcie-gen2-probe-retrain.patch b/driver/patches/0008-pcie-gen2-probe-retrain.patch new file mode 100644 index 0000000..4f2e90b --- /dev/null +++ b/driver/patches/0008-pcie-gen2-probe-retrain.patch @@ -0,0 +1,110 @@ +--- a/kernel-open/nvidia/nv.c ++++ b/kernel-open/nvidia/nv.c +@@ -65,6 +65,10 @@ + + #include + #include ++#include ++#include ++#include ++#include + + #include /* HDA struct snd_card */ + +@@ -1360,6 +1364,87 @@ + #if defined(NV_UVM_ENABLE) + nv_uvm_resume_P2P(pUuid); + #endif ++} ++ ++static void ++nv_cmp170hx_retrain_gen2(nv_state_t *nv) ++{ ++ nv_linux_state_t *nvl = NV_GET_NVL_FROM_NV_STATE(nv); ++ struct pci_dev *gpu = nvl->pci_dev; ++ struct pci_dev *upstream; ++ void __iomem *bar0; ++ u16 gpu_ctl2, upstream_ctl2, upstream_ctl, link_status = 0; ++ u32 cya0, link_config; ++ int ret, attempt; ++ ++ if ((gpu == NULL) || ++ ((gpu->device != 0x20c2) && (gpu->device != 0x2082))) ++ return; ++ ++ upstream = pci_upstream_bridge(gpu); ++ if (upstream == NULL) ++ { ++ NV_DEV_PRINTF(NV_DBG_ERRORS, nv, ++ "CMP Gen2: no upstream PCIe bridge; skipping link retrain\n"); ++ return; ++ } ++ bar0 = ioremap(pci_resource_start(gpu, 0), 0x90000); ++ if (bar0 == NULL) ++ { ++ NV_DEV_PRINTF(NV_DBG_ERRORS, nv, ++ "CMP Gen2: cannot map BAR0; skipping link retrain\n"); ++ return; ++ } ++ cya0 = readl(bar0 + 0x8c2c0); ++ writel(cya0 & ~(1U << 2), bar0 + 0x8c2c0); ++ link_config = readl(bar0 + 0x8c040); ++ writel((link_config & ~0x000c0000U) | (2U << 18), bar0 + 0x8c040); ++ writel(0x00000006U, bar0 + 0x8872c); ++ (void)readl(bar0 + 0x8872c); ++ iounmap(bar0); ++ msleep(50); ++ ret = pcie_capability_read_word(gpu, PCI_EXP_LNKCTL2, &gpu_ctl2); ++ if (ret) ++ goto capability_error; ++ ret = pcie_capability_read_word(upstream, PCI_EXP_LNKCTL2, &upstream_ctl2); ++ if (ret) ++ goto capability_error; ++ gpu_ctl2 = (gpu_ctl2 & ~PCI_EXP_LNKCTL2_TLS) | PCI_EXP_LNKCTL2_TLS_5_0GT; ++ upstream_ctl2 = (upstream_ctl2 & ~PCI_EXP_LNKCTL2_TLS) | PCI_EXP_LNKCTL2_TLS_5_0GT; ++ ret = pcie_capability_write_word(gpu, PCI_EXP_LNKCTL2, gpu_ctl2); ++ if (ret) ++ goto capability_error; ++ ret = pcie_capability_write_word(upstream, PCI_EXP_LNKCTL2, upstream_ctl2); ++ if (ret) ++ goto capability_error; ++ ret = pcie_capability_read_word(upstream, PCI_EXP_LNKCTL, &upstream_ctl); ++ if (ret) ++ goto capability_error; ++ ret = pcie_capability_write_word(upstream, PCI_EXP_LNKCTL, ++ upstream_ctl | PCI_EXP_LNKCTL_RL); ++ if (ret) ++ goto capability_error; ++ ++ for (attempt = 0; attempt < 20; attempt++) ++ { ++ msleep(100); ++ ret = pcie_capability_read_word(gpu, PCI_EXP_LNKSTA, &link_status); ++ if (!ret && ++ ((link_status & PCI_EXP_LNKSTA_CLS) >= PCI_EXP_LNKSTA_CLS_5_0GB)) ++ { ++ NV_DEV_PRINTF(NV_DBG_INFO, nv, "CMP Gen2: PCIe link trained to Gen%u\n", ++ link_status & PCI_EXP_LNKSTA_CLS); ++ return; ++ } ++ } ++ ++ NV_DEV_PRINTF(NV_DBG_ERRORS, nv, ++ "CMP Gen2: PCIe retrain completed without Gen2 link (status=0x%04x, ret=%d)\n", ++ link_status, ret); ++ return; ++capability_error: ++ NV_DEV_PRINTF(NV_DBG_ERRORS, nv, ++ "CMP Gen2: PCIe capability access failed (%d); skipping link retrain\n", ret); + } + + /* +@@ -1530,6 +1624,8 @@ + nv->flags |= NV_FLAG_PERSISTENT_SW_STATE; + } + ++ nv_cmp170hx_retrain_gen2(nv); ++ + /* Generate and cache the UUID for future callers */ + (void)rm_get_gpu_uuid_raw(sp, nv); + diff --git a/install.sh b/install.sh index 4a86fb7..6f833ce 100755 --- a/install.sh +++ b/install.sh @@ -9,20 +9,33 @@ mkdir -p "${LOG_DIR}" LOG_FILE="${LOG_DIR}/install_$(date +%Y%m%d_%H%M%S).log" PROFILE_OVERRIDE="" +CONFIGURE_IOMMU=1 +CONFIGURE_GEN2_SERVICE=1 for arg in "$@"; do case "${arg}" in --profile=8gb|--profile=8GB) PROFILE_OVERRIDE="8gb" ;; --profile=10gb|--profile=10GB) PROFILE_OVERRIDE="10gb" ;; + --no-iommu) CONFIGURE_IOMMU=0 ;; + --no-gen2-service) CONFIGURE_GEN2_SERVICE=0 ;; -h|--help) cat <<'EOF' -Usage: sudo ./install.sh [--profile=8gb|10gb] +Usage: sudo ./install.sh [--profile=8gb|10gb] [--no-iommu] [--no-gen2-service] - --profile=8gb Force 8GB physical card → 64GB unlock geometry - --profile=10gb Force 10GB physical card → 40GB unlock geometry + --profile=8gb Force 8GB metadata label (geometry is still chosen per PCI ID) + --profile=10gb Force 10GB metadata label (geometry is still chosen per PCI ID) + --no-iommu Do not touch the kernel command line (leave IOMMU settings alone) + --no-gen2-service + Do not install the early-boot PCIe Gen2 retrain service -Without --profile, stock nvidia-smi memory.total selects the profile: - ~8192 MiB → 8gb / 64GB unlock - ~10240 MiB → 10gb / 40GB unlock +By default the installer appends intel_iommu=on / amd_iommu=on plus iommu=pt to +the kernel command line so the IOMMU runs in passthrough mode. This takes effect +on the next reboot. + +Without --profile, each unlockable GPU is classified by PCI device ID: + 10de:20c2 → 8gb / 64GB unlock + 10de:2082 → 10gb / 40GB unlock + +Multi-GPU and mixed 8GB+10GB systems are supported in one install. EOF exit 0 ;; @@ -55,33 +68,47 @@ step() { echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } -detect_card_profile() { - local mem_mib="" - if command -v nvidia-smi &>/dev/null; then - mem_mib="$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -d '[:space:]' || true)" - fi - if [[ -z "${mem_mib}" || ! "${mem_mib}" =~ ^[0-9]+$ ]]; then - return 1 +normalize_bus_id() { + local raw="$1" + raw="$(echo "${raw}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" + if [[ "${raw}" =~ ^([0-9a-f]+):([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f])$ ]]; then + printf '%04x:%s:%s.%s\n' "$((16#${BASH_REMATCH[1]}))" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" + elif [[ "${raw}" =~ ^[0-9a-f]{2}:[0-9a-f]{2}\.[0-9a-f]$ ]]; then + echo "0000:${raw}" + else + echo "${raw}" fi +} - if (( mem_mib >= 60000 )); then - echo "8gb" - return 0 - fi - if (( mem_mib >= 35000 && mem_mib < 60000 )); then - echo "10gb" - return 0 - fi - if (( mem_mib >= 7680 && mem_mib <= 8704 )); then - echo "8gb" - return 0 - fi - if (( mem_mib >= 9728 && mem_mib <= 10752 )); then - echo "10gb" - return 0 - fi - echo "unknown:${mem_mib}" >&2 - return 1 +profile_from_devid() { + case "$1" in + 20c2) echo "8gb" ;; + 2082) echo "10gb" ;; + *) echo "unsupported" ;; + esac +} + +expected_mib_for_profile() { + case "$1" in + 8gb) echo "65536" ;; + 10gb) echo "40960" ;; + *) echo "" ;; + esac +} + +smi_memory_for_bus() { + local want="$1" + local line bus mem + [[ -n "${SMI_MEM_CACHE:-}" ]] || return 0 + while IFS= read -r line; do + [[ -n "${line}" ]] || continue + bus="$(normalize_bus_id "$(echo "${line}" | cut -d, -f1)")" + mem="$(echo "${line}" | cut -d, -f2 | tr -d '[:space:]')" + if [[ "${bus}" == "${want}" ]]; then + echo "${mem}" + return 0 + fi + done <<< "${SMI_MEM_CACHE}" } echo "" @@ -94,48 +121,111 @@ step "Step 1/6: Verifying root privileges" [[ "${EUID}" -eq 0 ]] || die "Run as root: sudo ./install.sh" ok "Running as root" -step "Step 2/6: Detecting CMP 170HX GPU" -PCI_LINE="$(lspci -nn 2>/dev/null | grep -iE '10de:20b0|10de:20c2|10de:2082' | head -1 || true)" -[[ -n "${PCI_LINE}" ]] || die "No CMP 170HX GPU found (10de:20b0 / 10de:20c2 / 10de:2082)" -PCI="$(echo "${PCI_LINE}" | awk '{print $1}')" -PCI_FULL="0000:${PCI}" -DEVID="$(echo "${PCI_LINE}" | grep -oE '10de:[0-9a-fA-F]{4}' | head -1 | cut -d: -f2 | tr '[:upper:]' '[:lower:]')" -ok "GPU detected: ${PCI_FULL} (10de:${DEVID})" -if [[ "${DEVID}" != "20c2" && "${DEVID}" != "2082" ]]; then - warn "In-driver unlock path is gated on PCI ID 0x20C2 / 0x2082." - warn "This card reports 0x${DEVID}; install will continue, but unlock may not activate." +step "Step 2/6: Detecting CMP 170HX GPU(s)" +mapfile -t PCI_LINES < <(lspci -nn 2>/dev/null | grep -iE '10de:20b0|10de:20c2|10de:2082' || true) +[[ ${#PCI_LINES[@]} -gt 0 ]] || die "No CMP 170HX GPU found (10de:20b0 / 10de:20c2 / 10de:2082)" + +SMI_MEM_CACHE="" +if command -v nvidia-smi &>/dev/null; then + SMI_MEM_CACHE="$(nvidia-smi --query-gpu=pci.bus_id,memory.total --format=csv,noheader,nounits 2>/dev/null || true)" +fi + +GPU_BDFS=() +GPU_DEVIDS=() +GPU_PROFILES=() +GPU_EXPECTED=() +GPU_CURRENT=() +COUNT_8GB=0 +COUNT_10GB=0 +COUNT_UNSUPPORTED=0 + +for PCI_LINE in "${PCI_LINES[@]}"; do + PCI="$(echo "${PCI_LINE}" | awk '{print $1}')" + PCI_FULL="$(normalize_bus_id "${PCI}")" + DEVID="$(echo "${PCI_LINE}" | grep -oE '10de:[0-9a-fA-F]{4}' | head -1 | cut -d: -f2 | tr '[:upper:]' '[:lower:]')" + PROF="$(profile_from_devid "${DEVID}")" + CUR_MEM="$(smi_memory_for_bus "${PCI_FULL}" || true)" + [[ -n "${CUR_MEM}" ]] || CUR_MEM="?" + + if [[ "${PROF}" == "unsupported" ]]; then + COUNT_UNSUPPORTED=$((COUNT_UNSUPPORTED + 1)) + warn "GPU ${PCI_FULL} (10de:${DEVID}) — unlock path not gated for this ID; skipping" + continue + fi + + EXP="$(expected_mib_for_profile "${PROF}")" + GPU_BDFS+=("${PCI_FULL}") + GPU_DEVIDS+=("${DEVID}") + GPU_PROFILES+=("${PROF}") + GPU_EXPECTED+=("${EXP}") + GPU_CURRENT+=("${CUR_MEM}") + + if [[ "${PROF}" == "8gb" ]]; then + COUNT_8GB=$((COUNT_8GB + 1)) + else + COUNT_10GB=$((COUNT_10GB + 1)) + fi + + if [[ "${CUR_MEM}" != "?" ]]; then + ok "GPU ${PCI_FULL} (10de:${DEVID}) → ${PROF} (current ${CUR_MEM} MiB, expect ~${EXP} MiB unlocked)" + else + ok "GPU ${PCI_FULL} (10de:${DEVID}) → ${PROF} (expect ~${EXP} MiB unlocked)" + fi +done + +[[ ${#GPU_BDFS[@]} -gt 0 ]] || die "No unlockable CMP 170HX GPUs found (need 10de:20c2 and/or 10de:2082)" +if (( COUNT_UNSUPPORTED > 0 )); then + info "Inventory: ${#GPU_BDFS[@]} unlockable (${COUNT_8GB}× 8gb, ${COUNT_10GB}× 10gb), ${COUNT_UNSUPPORTED} unsupported" +else + info "Inventory: ${#GPU_BDFS[@]} unlockable (${COUNT_8GB}× 8gb, ${COUNT_10GB}× 10gb)" fi step "Step 3/6: Selecting card memory profile" CARD_PROFILE="" -EXPECTED_MIB="" -if [[ -n "${PROFILE_OVERRIDE}" ]]; then - CARD_PROFILE="${PROFILE_OVERRIDE}" - ok "Profile forced via --profile=${CARD_PROFILE}" +if (( COUNT_8GB > 0 && COUNT_10GB > 0 )); then + CARD_PROFILE="mixed" + ok "Mixed variants detected → profile mixed (runtime geometry by PCI ID)" + if [[ -n "${PROFILE_OVERRIDE}" ]]; then + warn "--profile=${PROFILE_OVERRIDE} ignored for mixed inventory; card_profile stays mixed (each card unlocks by PCI ID)" + fi +elif (( COUNT_8GB > 0 )); then + CARD_PROFILE="8gb" +elif (( COUNT_10GB > 0 )); then + CARD_PROFILE="10gb" else - if detected_profile="$(detect_card_profile)"; then - CARD_PROFILE="${detected_profile}" - mem_now="$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -d '[:space:]' || echo '?')" - ok "Detected stock/reported memory ${mem_now} MiB → profile ${CARD_PROFILE}" + die "Internal error: no unlockable profiles counted" +fi + +if [[ -n "${PROFILE_OVERRIDE}" && "${CARD_PROFILE}" != "mixed" ]]; then + if [[ "${PROFILE_OVERRIDE}" != "${CARD_PROFILE}" ]]; then + warn "Inventory is ${CARD_PROFILE} but --profile=${PROFILE_OVERRIDE} was forced (metadata only; geometry follows PCI ID)" else - die "Could not detect 8GB vs 10GB card. Re-run with --profile=8gb or --profile=10gb" + ok "Profile forced via --profile=${CARD_PROFILE}" fi + CARD_PROFILE="${PROFILE_OVERRIDE}" fi case "${CARD_PROFILE}" in 8gb) - EXPECTED_MIB=65536 - info "Unlock geometry: 64GB (CFG1=0x02779000 LMR=0x0000020B)" + info "Unlock geometry: 64GB per card (CFG1=0x02779000 LMR=0x0000020B)" ;; 10gb) - EXPECTED_MIB=40960 - info "Unlock geometry: 40GB (CFG1=0x02669000 LMR=0x0000028A)" + info "Unlock geometry: 40GB per card (CFG1=0x02669000 LMR=0x0000028A)" + ;; + mixed) + info "Unlock geometry: 64GB for 20c2 / 40GB for 2082" ;; *) die "Internal error: bad profile ${CARD_PROFILE}" ;; esac + +GPU_INVENTORY_LINES=() +for i in "${!GPU_BDFS[@]}"; do + GPU_INVENTORY_LINES+=("${GPU_BDFS[$i]} ${GPU_DEVIDS[$i]} ${GPU_PROFILES[$i]} ${GPU_EXPECTED[$i]}") +done export CMPUNLOCKER_CARD_PROFILE="${CARD_PROFILE}" +export CMPUNLOCKER_GPU_INVENTORY="$(printf '%s\n' "${GPU_INVENTORY_LINES[@]}")" step "Step 4/6: Verifying nvidia-open (${SUPPORTED_VERSIONS_CSV})" [[ ${#SUPPORTED_VERSIONS[@]} -gt 0 ]] || die "No supported versions listed in driver/VERSION" @@ -183,21 +273,201 @@ ok "Kernel headers present for $(uname -r)" step "Step 5/6: Building and installing patched modules" chmod +x "${SCRIPT_DIR}/driver/build.sh" -CMPUNLOCKER_DRIVER_VERSION="${detected}" CMPUNLOCKER_CARD_PROFILE="${CARD_PROFILE}" "${SCRIPT_DIR}/driver/build.sh" +CMPUNLOCKER_DRIVER_VERSION="${detected}" \ +CMPUNLOCKER_CARD_PROFILE="${CARD_PROFILE}" \ +CMPUNLOCKER_GPU_INVENTORY="${CMPUNLOCKER_GPU_INVENTORY}" \ + "${SCRIPT_DIR}/driver/build.sh" ok "Patched modules installed (profile ${CARD_PROFILE})" +step "Step 5b/6: Configuring PCIe Gen2" +cat > /etc/modprobe.d/cmp-pcie-gen2.conf <<'EOF' +options nvidia NVreg_RegistryDwords="RmForceEnableGen2=1;RMPcieLinkSpeed=0x1" +EOF +ok "Wrote /etc/modprobe.d/cmp-pcie-gen2.conf" + +for legacy_unit in cmpretrain.service cmp-gen2-retrain.service; do + systemctl disable --now "${legacy_unit}" 2>/dev/null || true + systemctl reset-failed "${legacy_unit}" 2>/dev/null || true +done +rm -f /etc/systemd/system/cmpretrain.service \ + /etc/systemd/system/cmp-gen2-retrain.service \ + /usr/local/sbin/retrain.sh \ + /usr/local/sbin/cmp-gen2-retrain.sh +systemctl daemon-reload +ok "Removed legacy PCIe retrain helpers" + +if (( CONFIGURE_GEN2_SERVICE == 1 )); then + chmod +x "${SCRIPT_DIR}/tools/hammer.sh" \ + "${SCRIPT_DIR}/tools/service.sh" + "${SCRIPT_DIR}/tools/service.sh" install + ok "Early-boot Gen2 retrain service armed (not started in this session)" +else + warn "--no-gen2-service given; early-boot PCIe retraining is not installed" +fi + +step "Step 5c/6: Configuring IOMMU (passthrough)" +IOMMU_STATUS="skipped" +IOMMU_PARAMS="" + +iommu_params_for_cpu() { + local vendor="" + vendor="$(awk -F': ' '/^vendor_id/{print $2; exit}' /proc/cpuinfo 2>/dev/null || true)" + case "${vendor}" in + GenuineIntel) echo "intel_iommu=on iommu=pt" ;; + AuthenticAMD) echo "amd_iommu=on iommu=pt" ;; + *) echo "" ;; + esac +} + +cmdline_merge() { + local current="$1" + local token out=() + for token in ${current}; do + case "${token}" in + intel_iommu=*|amd_iommu=*|iommu=*) continue ;; + *) out+=("${token}") ;; + esac + done + for token in ${IOMMU_PARAMS}; do + out+=("${token}") + done + echo "${out[*]}" +} + +configure_iommu_grub() { + local grub_file="/etc/default/grub" + local key="GRUB_CMDLINE_LINUX_DEFAULT" + local current merged + + grep -q "^${key}=" "${grub_file}" || key="GRUB_CMDLINE_LINUX" + if grep -q "^${key}=" "${grub_file}"; then + current="$(sed -n "s/^${key}=\"\(.*\)\"$/\1/p" "${grub_file}" | head -1)" + else + current="" + fi + merged="$(cmdline_merge "${current}")" + + if [[ "${current}" == "${merged}" ]]; then + ok "GRUB already has ${IOMMU_PARAMS} (${key})" + IOMMU_STATUS="already-set" + return 0 + fi + + cp -a "${grub_file}" "${grub_file}.cmpunlocker.bak" + if grep -q "^${key}=" "${grub_file}"; then + local escaped="${merged//\//\\/}" + sed -i "s/^${key}=.*/${key}=\"${escaped}\"/" "${grub_file}" + else + printf '%s="%s"\n' "${key}" "${merged}" >> "${grub_file}" + fi + ok "Set ${key}=\"${merged}\" (backup: ${grub_file}.cmpunlocker.bak)" + + if command -v update-grub &>/dev/null; then + update-grub + elif command -v grub2-mkconfig &>/dev/null; then + local cfg="/boot/grub2/grub.cfg" + local efi_cfg + efi_cfg="$(ls /boot/efi/EFI/*/grub.cfg 2>/dev/null | head -1 || true)" + [[ -n "${efi_cfg}" ]] && cfg="${efi_cfg}" + grub2-mkconfig -o "${cfg}" + elif command -v grub-mkconfig &>/dev/null; then + grub-mkconfig -o /boot/grub/grub.cfg + else + warn "No grub config generator found — regenerate grub.cfg manually" + IOMMU_STATUS="needs-grub-regen" + return 0 + fi + ok "Regenerated GRUB config" + IOMMU_STATUS="configured" +} + +configure_iommu_kernel_cmdline() { + local file="/etc/kernel/cmdline" + local current merged + current="$(tr -d '\n' < "${file}")" + merged="$(cmdline_merge "${current}")" + + if [[ "${current}" == "${merged}" ]]; then + ok "${file} already has ${IOMMU_PARAMS}" + IOMMU_STATUS="already-set" + return 0 + fi + + cp -a "${file}" "${file}.cmpunlocker.bak" + printf '%s\n' "${merged}" > "${file}" + ok "Set ${file} to \"${merged}\" (backup: ${file}.cmpunlocker.bak)" + + if command -v kernel-install &>/dev/null && [[ -d /boot/loader/entries ]]; then + for kdir in /lib/modules/*/; do + kver="$(basename "${kdir}")" + [[ -f "${kdir}/vmlinuz" ]] || continue + kernel-install add "${kver}" "${kdir}/vmlinuz" 2>/dev/null || true + done + ok "Refreshed systemd-boot entries" + IOMMU_STATUS="configured" + else + warn "Update your boot entries so ${file} takes effect" + IOMMU_STATUS="needs-boot-refresh" + fi +} + +if (( CONFIGURE_IOMMU == 0 )); then + warn "--no-iommu given; leaving kernel command line untouched" +else + IOMMU_PARAMS="$(iommu_params_for_cpu)" + if [[ -z "${IOMMU_PARAMS}" ]]; then + warn "Unrecognized CPU vendor — cannot pick IOMMU kernel parameters; skipping" + elif [[ -f /etc/default/grub ]]; then + info "Target: ${IOMMU_PARAMS} (GRUB)" + configure_iommu_grub + elif [[ -f /etc/kernel/cmdline ]]; then + info "Target: ${IOMMU_PARAMS} (systemd-boot)" + configure_iommu_kernel_cmdline + else + warn "No /etc/default/grub or /etc/kernel/cmdline found" + warn "Add these to your kernel command line manually: ${IOMMU_PARAMS}" + IOMMU_STATUS="manual" + fi + + if grep -qw iommu=pt /proc/cmdline 2>/dev/null && [[ -d /sys/class/iommu ]] && [[ -n "$(ls -A /sys/class/iommu 2>/dev/null)" ]]; then + ok "IOMMU is already active in passthrough mode on the running kernel" + elif [[ "${IOMMU_STATUS}" != "skipped" ]]; then + info "IOMMU passthrough takes effect after the next reboot" + warn "IOMMU must also be enabled in BIOS/UEFI (VT-d / AMD-Vi / SVM)" + fi +fi + step "Step 6/6: Done" echo "" echo -e "${CYAN}╔════════════════════════════════════════╗${NC}" -echo -e "${CYAN}║${NC} ${GREEN}✓ cmpunlocker install finished${CYAN} ║${NC}" +echo -e "${CYAN}║ cmpunlocker ║${NC}" echo -e "${CYAN}╚════════════════════════════════════════╝${NC}" echo "" -echo "Profile: ${CARD_PROFILE} → expect ~${EXPECTED_MIB} MiB after unlock" +echo "cmpunlocker install finished!" +echo "Profile: ${CARD_PROFILE} | ${#GPU_BDFS[@]} GPU(s): ${COUNT_8GB}× 8gb, ${COUNT_10GB}× 10gb" +if [[ -n "${IOMMU_PARAMS}" && "${IOMMU_STATUS}" != "skipped" ]]; then + echo "IOMMU: ${IOMMU_PARAMS} (${IOMMU_STATUS})" +else + echo "IOMMU: not configured" +fi +echo "" +echo "Per-GPU expectations after unlock:" +printf " %-16s %-8s %-8s %s\n" "BDF" "PCI ID" "Variant" "Expect MiB" +for i in "${!GPU_BDFS[@]}"; do + printf " %-16s %-8s %-8s ~%s\n" "${GPU_BDFS[$i]}" "${GPU_DEVIDS[$i]}" "${GPU_PROFILES[$i]}" "${GPU_EXPECTED[$i]}" +done +echo "" echo "Next:" echo -e " 1. Cold reboot recommended: ${CYAN}sudo shutdown -h now${NC} (then power on)" -echo -e " 2. Verify memory: ${CYAN}nvidia-smi${NC} (expect ~${EXPECTED_MIB} MiB)" -echo -e " 3. Verify unlock logs: ${CYAN}sudo dmesg | grep SEC2_DEBUG${NC}" -echo -e " 4. Verify SM clocks: ${CYAN}nvidia-smi --query-gpu=clocks.max.sm --format=csv,noheader${NC}" +echo -e " 2. Verify all GPUs: ${CYAN}sudo ./verify.sh${NC}" +echo -e " 3. Verify PCIe Gen2: ${CYAN}nvidia-smi --query-gpu=pcie.link.gen.current,pcie.link.gen.max --format=csv${NC} (expect 2,2)" +echo -e " 4. Or check manually: ${CYAN}nvidia-smi${NC}" +echo -e " 5. Unlock logs: ${CYAN}sudo dmesg | grep SEC2_DEBUG${NC}" +echo -e " 6. Verify IOMMU after reboot: ${CYAN}cat /proc/cmdline${NC} and ${CYAN}ls /sys/class/iommu${NC}" +if (( CONFIGURE_GEN2_SERVICE == 1 )); then + echo -e " 7. Verify negotiated Gen2: ${CYAN}sudo ./tools/service.sh verify${NC}" + echo -e " Recovery boot option: ${CYAN}systemd.mask=gen2.service${NC}" +fi echo "" echo "Log saved to: ${LOG_FILE}" echo "" diff --git a/remove.sh b/remove.sh index 09e15b7..d16eac7 100755 --- a/remove.sh +++ b/remove.sh @@ -27,16 +27,18 @@ step() { echo "" echo -e "${CYAN}╔════════════════════════════════════════╗${NC}" -echo -e "${CYAN}║ cmpunlocker — System Removal ║${NC}" +echo -e "${CYAN}║ cmpunlocker ║${NC}" echo -e "${CYAN}╚════════════════════════════════════════╝${NC}" echo "" if [[ "${1:-}" != "--yes" && "${1:-}" != "-y" ]]; then warn "This removes cmpunlocker patched kernel modules:" - echo " - Stops leftover cmpunlocker systemd service (if present)" + echo " - Stops cmpunlocker systemd service" echo " - Removes /lib/modules/*/updates/cmpunlocker/" echo " - Removes ${INSTALL_DIR} (legacy install dir, if present)" echo " - Reloads stock NVIDIA modules (brief display interruption)" + echo " - Removes cmpretrain service / modprobe Gen2 helpers" + echo " - Restores the pre-install kernel command line (reverts IOMMU changes)" echo "" echo "Run: sudo ./remove.sh --yes" exit 1 @@ -72,6 +74,40 @@ if [[ -f "${SERVICE_FILE}" ]]; then fi pkill -f "${INSTALL_DIR}/daemon/watchdog.py" 2>/dev/null || true +for legacy_unit in cmpretrain.service cmp-gen2-retrain.service; do + systemctl disable --now "${legacy_unit}" 2>/dev/null || true + systemctl reset-failed "${legacy_unit}" 2>/dev/null || true +done +rm -f /etc/systemd/system/cmpretrain.service /usr/local/sbin/retrain.sh +rm -f /etc/systemd/system/cmp-gen2-retrain.service /usr/local/sbin/cmp-gen2-retrain.sh +rm -f /etc/modprobe.d/cmp-pcie-gen2.conf +systemctl disable --now gen2.service 2>/dev/null || true +systemctl reset-failed gen2.service 2>/dev/null || true +rm -f /etc/systemd/system/gen2.service /usr/local/sbin/gen2-hammer +systemctl daemon-reload 2>/dev/null || true +ok "Removed PCIe Gen2 helpers" + +iommu_restored=0 +for cfg in /etc/default/grub /etc/kernel/cmdline; do + if [[ -f "${cfg}.cmpunlocker.bak" ]]; then + mv -f "${cfg}.cmpunlocker.bak" "${cfg}" + ok "Restored ${cfg} from pre-install backup" + iommu_restored=1 + fi +done +if (( iommu_restored )); then + if command -v update-grub &>/dev/null; then + update-grub 2>/dev/null || true + elif command -v grub2-mkconfig &>/dev/null; then + grub2-mkconfig -o /boot/grub2/grub.cfg 2>/dev/null || true + elif command -v grub-mkconfig &>/dev/null; then + grub-mkconfig -o /boot/grub/grub.cfg 2>/dev/null || true + fi + ok "Reverted IOMMU kernel parameters (effective after reboot)" +else + warn "No IOMMU config backup found — kernel command line left as-is" +fi + step "Step 3/5: Removing patched modules and legacy files" mod_removed=0 kernels_touched=() @@ -161,9 +197,11 @@ fi echo "" echo -e "${CYAN}╔════════════════════════════════════════╗${NC}" -echo -e "${CYAN}║${NC} ${GREEN}✓ cmpunlocker removed from system${CYAN} ║${NC}" +echo -e "${CYAN}║ cmpunlocker ║${NC}" echo -e "${CYAN}╚════════════════════════════════════════╝${NC}" echo "" + +echo "cmpunlocker has been removed from system." echo "Log saved to: ${LOG_FILE}" echo "" echo "If the GPU or display is not working, reboot once:" diff --git a/systemd/gen2.service b/systemd/gen2.service new file mode 100644 index 0000000..fae8005 --- /dev/null +++ b/systemd/gen2.service @@ -0,0 +1,15 @@ +[Unit] +Description=CMP 170HX early-boot PCIe Gen2 retrain +DefaultDependencies=no +After=sysinit.target +Before=basic.target multi-user.target graphical.target +ConditionPathExists=/usr/local/sbin/gen2-hammer + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/gen2-hammer +TimeoutStartSec=45 +RemainAfterExit=no + +[Install] +WantedBy=sysinit.target diff --git a/tools/hammer.sh b/tools/hammer.sh new file mode 100755 index 0000000..2b914a1 --- /dev/null +++ b/tools/hammer.sh @@ -0,0 +1,161 @@ +#!/bin/bash +# Early-boot PCIe Gen2 retrain loop for CMP 170HX (10de:20c2 / 10de:2082). +# +# The Gen2 capability the driver's probe-time patch exposes +# (driver/patches/0008-pcie-gen2-probe-retrain.patch) is only open for a +# short window during GSP bootstrap. If the driver's own one-shot retrain +# misses that window, this script keeps requesting Gen2 from the GPU's +# upstream bridge every ~50ms until it lands or the attempt budget runs out. +# Installed/removed via tools/service.sh; runs as +# systemd/gen2.service before basic.target. +set -uo pipefail + +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + +readonly VENDOR_ID="10de" +readonly LOG_FILE="/var/log/gen2.log" +readonly TARGET_GEN="${CMP170HX_GEN2_TARGET:-2}" +readonly MAX_ITERATIONS="${CMP170HX_GEN2_MAX_ITERATIONS:-600}" +readonly RETRAIN_INTERVAL="${CMP170HX_GEN2_RETRAIN_INTERVAL:-0.05}" + +log() { + local line + line="[$(date -Is)] $*" + echo "${line}" + echo "${line}" >> "${LOG_FILE}" 2>/dev/null || true +} + +read_link_status() { + setpci -s "$1" CAP_EXP+12.w 2>/dev/null || true +} + +link_generation() { + local status + status="$(read_link_status "$1")" + if [[ "${status}" =~ ^[[:xdigit:]]{4}$ ]]; then + echo $((0x${status} & 0x0f)) + else + echo "?" + fi +} + +is_supported_gpu() { + local bdf="$1" + local vendor device + [[ -r "/sys/bus/pci/devices/${bdf}/vendor" ]] || return 1 + [[ -r "/sys/bus/pci/devices/${bdf}/device" ]] || return 1 + vendor="$(<"/sys/bus/pci/devices/${bdf}/vendor")" + device="$(<"/sys/bus/pci/devices/${bdf}/device")" + [[ "${vendor,,}" == "0x${VENDOR_ID}" ]] || return 1 + [[ "${device,,}" == "0x20c2" || "${device,,}" == "0x2082" ]] +} + +upstream_bridge() { + local bdf="$1" + local parent + parent="$(basename "$(dirname "$(readlink -f "/sys/bus/pci/devices/${bdf}")")")" + [[ "${parent}" != "${bdf}" ]] || return 1 + [[ -e "/sys/bus/pci/devices/${parent}" ]] || return 1 + echo "${parent}" +} + +is_pci_bridge() { + local bdf="$1" + local class + [[ -r "/sys/bus/pci/devices/${bdf}/class" ]] || return 1 + class="$(<"/sys/bus/pci/devices/${bdf}/class")" + [[ "${class,,}" == 0x0604* ]] +} + +retrain_one() { + local gpu="$1" + local bridge status generation cap2 iteration initial + + if ! is_supported_gpu "${gpu}"; then + log "${gpu}: vendor/device guard failed; refusing to touch the device" + return 1 + fi + bridge="$(upstream_bridge "${gpu}" || true)" + if [[ -z "${bridge}" ]] || ! is_pci_bridge "${bridge}"; then + log "${gpu}: no valid upstream PCI bridge found; skipping" + return 1 + fi + + initial="$(link_generation "${gpu}")" + if [[ "${initial}" =~ ^[0-9]+$ ]] && (( initial >= TARGET_GEN )); then + log "${gpu}: already Gen${initial}; no retrain needed" + return 0 + fi + + log "${gpu}: start via upstream ${bridge}; initial Gen${initial}; target Gen${TARGET_GEN}" + for ((iteration = 1; iteration <= MAX_ITERATIONS; iteration++)); do + if ! is_supported_gpu "${gpu}"; then + log "${gpu}: disappeared during retrain; stopping immediately" + return 1 + fi + + # Preserve all unrelated bits; the endpoint may clamp this to Gen1 + # until the driver's probe-time patch briefly exposes Gen2. + setpci -s "${bridge}" CAP_EXP+30.w=0002:000f 2>/dev/null || true + setpci -s "${gpu}" CAP_EXP+30.w=0002:000f 2>/dev/null || true + + # Retraining must be initiated by the upstream bridge. + setpci -s "${bridge}" CAP_EXP+10.w=0020:0020 2>/dev/null || true + + status="$(read_link_status "${gpu}")" + if [[ "${status}" =~ ^[[:xdigit:]]{4}$ ]]; then + generation=$((0x${status} & 0x0f)) + if (( generation >= TARGET_GEN )); then + cap2="$(setpci -s "${gpu}" CAP_EXP+2c.l 2>/dev/null || echo unreadable)" + log "${gpu}: SUCCESS Gen${generation} at iteration ${iteration}; LnkSta=0x${status}; LnkCap2=0x${cap2}" + return 0 + fi + fi + sleep "${RETRAIN_INTERVAL}" + done + + generation="$(link_generation "${gpu}")" + log "${gpu}: no Gen2 window caught after ${MAX_ITERATIONS} attempts; final Gen${generation}" + return 1 +} + +main() { + local rc=0 + local -a gpus=() + + if [[ "${EUID}" -ne 0 ]]; then + echo "gen2-hammer must run as root" >&2 + exit 1 + fi + if [[ "${TARGET_GEN}" != "2" ]]; then + echo "Only Gen2 is supported by this safety-constrained helper" >&2 + exit 1 + fi + if ! [[ "${MAX_ITERATIONS}" =~ ^[1-9][0-9]*$ ]]; then + echo "CMP170HX_GEN2_MAX_ITERATIONS must be a positive integer" >&2 + exit 1 + fi + command -v lspci >/dev/null || { echo "lspci is required" >&2; exit 1; } + command -v setpci >/dev/null || { echo "setpci is required" >&2; exit 1; } + + : > "${LOG_FILE}" + log "early retrain started (attempts=${MAX_ITERATIONS}, interval=${RETRAIN_INTERVAL}s)" + + mapfile -t gpus < <( + for id in 20c2 2082; do + lspci -D -d "${VENDOR_ID}:${id}" 2>/dev/null | awk '{print $1}' + done + ) + if [[ ${#gpus[@]} -eq 0 ]]; then + log "no supported CMP 170HX found; nothing touched" + return 0 + fi + + for gpu in "${gpus[@]}"; do + retrain_one "${gpu}" || rc=1 + done + log "early retrain finished (rc=${rc})" + return "${rc}" +} + +main "$@" diff --git a/tools/retrain.sh b/tools/retrain.sh new file mode 100755 index 0000000..020566b --- /dev/null +++ b/tools/retrain.sh @@ -0,0 +1,106 @@ +#!/bin/bash +set -euo pipefail + +SYS=/sys/bus/pci/devices/0000:0a:00.0 +for i in $(seq 1 120); do + if [[ -e $SYS/resource0 ]] && nvidia-smi -L &>/dev/null; then + break + fi + sleep 1 +done +if ! nvidia-smi -L &>/dev/null; then + exit 0 +fi + +mem="$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -1 | tr -d ' ' || true)" +if [[ -z "${mem}" || "${mem}" == "[N/A]" ]]; then + exit 0 +fi + +cur="$(nvidia-smi --query-gpu=pcie.link.gen.current --format=csv,noheader 2>/dev/null | head -1 | tr -d ' ' || true)" +if [[ "${cur}" == "2" ]]; then + exit 0 +fi + +max="$(nvidia-smi --query-gpu=pcie.link.gen.max --format=csv,noheader 2>/dev/null | head -1 | tr -d ' ' || true)" +if [[ "${max}" != "2" && "${max}" != "3" && "${max}" != "4" ]]; then + exit 0 +fi + +python3 - <<'PY' +import os, mmap, struct, time, subprocess + +GPU, UP = "0a:00.0", "09:01.0" +PATH = "/sys/bus/pci/devices/0000:0a:00.0/resource0" + +def run(cmd): + return subprocess.check_output(cmd, text=True).strip() + +def bar0_open(): + fd = os.open(PATH, os.O_RDWR | os.O_SYNC) + m = mmap.mmap(fd, os.path.getsize(PATH), access=mmap.ACCESS_WRITE) + return fd, m + +def r(m, off): + return struct.unpack_from(">2)&1} " + f"MAX={(link>>18)&3} CAP={cap:08x} XVE={xve:08x} MISC1={misc1:08x}" +) +if bar0 == 0xFFFFFFFF or cya == 0xFFFFFFFF: + print("retrain: BAR0 dead; skip") + m.close(); os.close(fd) + raise SystemExit(0) +if ((cya >> 2) & 1) != 0: + print("retrain: DIS_G2 still set; skip") + m.close(); os.close(fd) + raise SystemExit(0) +if (cap & 0xF) < 2: + print(f"retrain: Cap Gen{cap & 0xF}; skip") + m.close(); os.close(fd) + raise SystemExit(0) +m.close(); os.close(fd) + +for bdf in (UP, GPU): + cur = int(run(["setpci", "-s", bdf, "CAP_EXP+30.w"]), 16) + subprocess.check_call(["setpci", "-s", bdf, f"CAP_EXP+30.w={(cur & ~0xF) | 0x2:04x}"]) + print(f"retrain: TLS {bdf} {cur:04x} -> {run(['setpci','-s',bdf,'CAP_EXP+30.w'])}") + +time.sleep(0.2) +fd, m = bar0_open() +if r(m, 0) == 0xFFFFFFFF: + print("retrain: BAR0 dead after TLS; skip") + m.close(); os.close(fd) + raise SystemExit(0) + +w(m, 0x8C2C0, r(m, 0x8C2C0) & ~(1 << 2)) +w(m, 0x8C040, (r(m, 0x8C040) & ~0xC0000) | (2 << 18)) +time.sleep(0.05) +cya = r(m, 0x8C2C0) +mx = (r(m, 0x8C040) >> 18) & 3 +alive = r(m, 0) != 0xFFFFFFFF +print(f"retrain: post CYA={cya:08x} DIS_G2={(cya>>2)&1} MAX={mx} bar0={r(m,0):08x}") +m.close(); os.close(fd) + +if not alive or ((cya >> 2) & 1) != 0 or mx != 2: + print("retrain: preconditions failed; skip") + raise SystemExit(0) + +cur = int(run(["setpci", "-s", UP, "CAP_EXP+10.w"]), 16) +subprocess.check_call(["setpci", "-s", UP, f"CAP_EXP+10.w={(cur | 0x20):04x}"]) +time.sleep(2.0) + +sta = int(run(["setpci", "-s", GPU, "CAP_EXP+12.w"]), 16) +print(f"retrain: speed_after={sta & 0xF}") +PY diff --git a/tools/service.sh b/tools/service.sh new file mode 100755 index 0000000..c775ca1 --- /dev/null +++ b/tools/service.sh @@ -0,0 +1,161 @@ +#!/bin/bash +# Install/remove/verify the early-boot PCIe Gen2 retrain service +# (systemd/gen2.service + tools/hammer.sh). +# Wired into install.sh (Step 5b/6), remove.sh, and verify.sh. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +SERVICE_NAME="gen2.service" +SERVICE_SOURCE="${PROJECT_DIR}/systemd/${SERVICE_NAME}" +SERVICE_TARGET="/etc/systemd/system/${SERVICE_NAME}" +HAMMER_SOURCE="${SCRIPT_DIR}/hammer.sh" +HAMMER_TARGET="/usr/local/sbin/gen2-hammer" +LOG_FILE="/var/log/gen2.log" + +info() { echo "==> $*"; } +ok() { echo "✓ $*"; } +warn() { echo "! $*" >&2; } +die() { echo "✗ $*" >&2; exit 1; } + +usage() { + cat </dev/null | awk '{print $1}' + done +} + +link_generation() { + local status + status="$(setpci -s "$1" CAP_EXP+12.w 2>/dev/null || true)" + if [[ "${status}" =~ ^[[:xdigit:]]{4}$ ]]; then + echo $((0x${status} & 0x0f)) + else + echo "?" + fi +} + +install_service() { + local module="/lib/modules/$(uname -r)/updates/cmpunlocker/nvidia.ko" + local -a gpus=() + + require_root + command -v install >/dev/null || die "install command not found" + command -v lspci >/dev/null || die "lspci not found (install pciutils)" + command -v setpci >/dev/null || die "setpci not found (install pciutils)" + [[ -f "${HAMMER_SOURCE}" ]] || die "Missing ${HAMMER_SOURCE}" + [[ -f "${SERVICE_SOURCE}" ]] || die "Missing ${SERVICE_SOURCE}" + + mapfile -t gpus < <(supported_gpus) + [[ ${#gpus[@]} -gt 0 ]] || die "No supported CMP 170HX found (10de:20c2 / 10de:2082)" + info "Detected: ${gpus[*]}" + + if [[ ! -f "${module}" ]]; then + die "Patched cmpunlocker module not found: ${module}" + fi + if ! grep -aFq 'CMP Gen2:' "${module}" 2>/dev/null; then + die "Installed module does not contain the Gen2 probe-retrain patch; refusing to arm a useless retrain service" + fi + ok "Installed NVIDIA module contains the Gen2 probe-retrain patch" + + install -m 0755 "${HAMMER_SOURCE}" "${HAMMER_TARGET}" + install -m 0644 "${SERVICE_SOURCE}" "${SERVICE_TARGET}" + systemctl daemon-reload + systemctl enable "${SERVICE_NAME}" >/dev/null + ok "Enabled ${SERVICE_NAME} for the next boot" + info "The service was not started now; the active desktop PCIe link was not touched." + info "Recovery boot option: systemd.mask=${SERVICE_NAME}" +} + +remove_service() { + require_root + systemctl disable --now "${SERVICE_NAME}" 2>/dev/null || true + rm -f "${SERVICE_TARGET}" "${HAMMER_TARGET}" + systemctl daemon-reload + systemctl reset-failed "${SERVICE_NAME}" 2>/dev/null || true + ok "Removed ${SERVICE_NAME}; cmpunlocker driver and memory unlock were left intact" + if [[ -f "${LOG_FILE}" ]]; then + info "Preserved diagnostic log: ${LOG_FILE}" + fi +} + +show_status() { + if [[ -f "${SERVICE_TARGET}" ]]; then + ok "Service file installed: ${SERVICE_TARGET}" + else + warn "Service file is not installed" + fi + systemctl is-enabled "${SERVICE_NAME}" 2>/dev/null || true + systemctl status "${SERVICE_NAME}" --no-pager 2>/dev/null || true +} + +verify_links() { + local gpu bridge status generation speed width max_speed result + local failures=0 + local -a gpus=() + + require_root + mapfile -t gpus < <(supported_gpus) + [[ ${#gpus[@]} -gt 0 ]] || die "No supported CMP 170HX found" + + printf '%-16s %-16s %-8s %-14s %-8s %s\n' "GPU" "Upstream" "LnkSta" "Current speed" "Width" "Result" + for gpu in "${gpus[@]}"; do + bridge="$(basename "$(dirname "$(readlink -f "/sys/bus/pci/devices/${gpu}")")")" + status="$(setpci -s "${gpu}" CAP_EXP+12.w 2>/dev/null || echo unreadable)" + generation="$(link_generation "${gpu}")" + speed="$(cat "/sys/bus/pci/devices/${gpu}/current_link_speed" 2>/dev/null || echo unknown)" + width="$(cat "/sys/bus/pci/devices/${gpu}/current_link_width" 2>/dev/null || echo unknown)" + max_speed="$(cat "/sys/bus/pci/devices/${gpu}/max_link_speed" 2>/dev/null || echo unknown)" + if [[ "${generation}" =~ ^[0-9]+$ ]] && (( generation >= 2 )); then + result="GEN2 OK" + else + result="GEN1" + failures=$((failures + 1)) + fi + printf '%-16s %-16s %-8s %-14s x%-7s %s\n' "${gpu}" "${bridge}" "0x${status}" "${speed}" "${width}" "${result}" + info "${gpu}: sysfs max=${max_speed}; negotiated generation is authoritative" + done + + if command -v nvidia-smi >/dev/null; then + echo + nvidia-smi --query-gpu=pci.bus_id,memory.total,pcie.link.gen.current,pcie.link.gen.max --format=csv 2>/dev/null || true + fi + + echo + if [[ -f "${LOG_FILE}" ]]; then + info "Last early-retrain log:" + tail -n 30 "${LOG_FILE}" + else + warn "No ${LOG_FILE}; the early service has not run yet" + fi + + if (( failures > 0 )); then + die "${failures} GPU(s) are still negotiated at Gen1" + fi + ok "All supported GPUs are negotiated at Gen2 or better" +} + +case "${1:-}" in + install) install_service ;; + remove) remove_service ;; + verify) verify_links ;; + status) show_status ;; + -h|--help|"") usage ;; + *) usage >&2; exit 1 ;; +esac diff --git a/verify.sh b/verify.sh new file mode 100755 index 0000000..9770dcc --- /dev/null +++ b/verify.sh @@ -0,0 +1,200 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +KVER="$(uname -r)" +INSTALL_MOD_DIR="/lib/modules/${KVER}/updates/cmpunlocker" +INVENTORY_FILE="${INSTALL_MOD_DIR}/gpu_inventory" + +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; CYAN='\033[0;36m'; NC='\033[0m' +else + RED=""; GREEN=""; YELLOW=""; CYAN=""; NC="" +fi + +info() { echo -e "${CYAN}==>${NC} $*"; } +ok() { echo -e "${GREEN}✓${NC} $*"; } +warn() { echo -e "${YELLOW}!${NC} $*"; } +err() { echo -e "${RED}✗${NC} $*" >&2; } +die() { err "$*"; exit 1; } + +normalize_bus_id() { + local raw="$1" + raw="$(echo "${raw}" | tr '[:upper:]' '[:lower:]' | tr -d '[:space:]')" + if [[ "${raw}" =~ ^([0-9a-f]+):([0-9a-f]{2}):([0-9a-f]{2})\.([0-9a-f])$ ]]; then + printf '%04x:%s:%s.%s\n' "$((16#${BASH_REMATCH[1]}))" "${BASH_REMATCH[2]}" "${BASH_REMATCH[3]}" "${BASH_REMATCH[4]}" + elif [[ "${raw}" =~ ^[0-9a-f]{2}:[0-9a-f]{2}\.[0-9a-f]$ ]]; then + echo "0000:${raw}" + else + echo "${raw}" + fi +} + +profile_from_devid() { + case "$1" in + 20c2) echo "8gb" ;; + 2082) echo "10gb" ;; + *) echo "unsupported" ;; + esac +} + +expected_mib_for_profile() { + case "$1" in + 8gb) echo "65536" ;; + 10gb) echo "40960" ;; + *) echo "" ;; + esac +} + +is_unlocked_memory() { + local profile="$1" + local mem_mib="$2" + [[ "${mem_mib}" =~ ^[0-9]+$ ]] || return 1 + case "${profile}" in + 8gb) + (( mem_mib >= 60000 )) && return 0 + ;; + 10gb) + (( mem_mib >= 35000 && mem_mib < 60000 )) && return 0 + ;; + esac + return 1 +} + +is_stock_memory() { + local profile="$1" + local mem_mib="$2" + [[ "${mem_mib}" =~ ^[0-9]+$ ]] || return 1 + case "${profile}" in + 8gb) + (( mem_mib >= 7680 && mem_mib <= 8704 )) && return 0 + ;; + 10gb) + (( mem_mib >= 9728 && mem_mib <= 10752 )) && return 0 + ;; + esac + return 1 +} + +smi_memory_for_bus() { + local want="$1" + local line bus mem + while IFS= read -r line; do + [[ -n "${line}" ]] || continue + bus="$(normalize_bus_id "$(echo "${line}" | cut -d, -f1)")" + mem="$(echo "${line}" | cut -d, -f2 | tr -d '[:space:]')" + if [[ "${bus}" == "${want}" ]]; then + echo "${mem}" + return 0 + fi + done <<< "${SMI_MEM_CACHE}" +} + +echo "" +echo -e "${CYAN}╔════════════════════════════════════════╗${NC}" +echo -e "${CYAN}║ cmpunlocker ║${NC}" +echo -e "${CYAN}╚════════════════════════════════════════╝${NC}" +echo "" + +command -v nvidia-smi &>/dev/null || die "nvidia-smi not found" +SMI_MEM_CACHE="$(nvidia-smi --query-gpu=pci.bus_id,memory.total --format=csv,noheader,nounits 2>/dev/null || true)" +[[ -n "${SMI_MEM_CACHE}" ]] || die "nvidia-smi returned no GPU memory data" + +GPU_BDFS=() +GPU_DEVIDS=() +GPU_PROFILES=() +GPU_EXPECTED=() + +if [[ -r "${INVENTORY_FILE}" ]] && [[ -s "${INVENTORY_FILE}" ]]; then + info "Using inventory: ${INVENTORY_FILE}" + while read -r bdf devid profile expected || [[ -n "${bdf:-}" ]]; do + [[ -n "${bdf:-}" ]] || continue + [[ "${bdf}" =~ ^# ]] && continue + GPU_BDFS+=("$(normalize_bus_id "${bdf}")") + GPU_DEVIDS+=("${devid}") + GPU_PROFILES+=("${profile}") + GPU_EXPECTED+=("${expected}") + done < "${INVENTORY_FILE}" +else + info "No installed gpu_inventory; enumerating via lspci" + mapfile -t PCI_LINES < <(lspci -nn 2>/dev/null | grep -iE '10de:20c2|10de:2082' || true) + [[ ${#PCI_LINES[@]} -gt 0 ]] || die "No unlockable CMP 170HX GPU found (10de:20c2 / 10de:2082)" + for PCI_LINE in "${PCI_LINES[@]}"; do + PCI="$(echo "${PCI_LINE}" | awk '{print $1}')" + PCI_FULL="$(normalize_bus_id "${PCI}")" + DEVID="$(echo "${PCI_LINE}" | grep -oE '10de:[0-9a-fA-F]{4}' | head -1 | cut -d: -f2 | tr '[:upper:]' '[:lower:]')" + PROF="$(profile_from_devid "${DEVID}")" + [[ "${PROF}" != "unsupported" ]] || continue + EXP="$(expected_mib_for_profile "${PROF}")" + GPU_BDFS+=("${PCI_FULL}") + GPU_DEVIDS+=("${DEVID}") + GPU_PROFILES+=("${PROF}") + GPU_EXPECTED+=("${EXP}") + done +fi + +[[ ${#GPU_BDFS[@]} -gt 0 ]] || die "No unlockable GPUs to verify" + +failures=0 +printf "\n%-16s %-8s %-8s %-12s %-12s %s\n" "BDF" "PCI ID" "Variant" "Expect" "Actual" "Status" +for i in "${!GPU_BDFS[@]}"; do + bdf="${GPU_BDFS[$i]}" + devid="${GPU_DEVIDS[$i]}" + profile="${GPU_PROFILES[$i]}" + expected="${GPU_EXPECTED[$i]}" + actual="$(smi_memory_for_bus "${bdf}" || true)" + [[ -n "${actual}" ]] || actual="?" + + status="FAIL" + if is_unlocked_memory "${profile}" "${actual}"; then + status="OK" + ok "${bdf}: ${actual} MiB (unlocked ${profile})" + elif is_stock_memory "${profile}" "${actual}"; then + status="STOCK" + err "${bdf}: still stock ${actual} MiB (expect ~${expected})" + failures=$((failures + 1)) + elif [[ "${actual}" == "?" ]]; then + status="MISSING" + err "${bdf}: not found in nvidia-smi" + failures=$((failures + 1)) + else + status="UNEXPECTED" + err "${bdf}: unexpected ${actual} MiB (expect ~${expected} for ${profile})" + failures=$((failures + 1)) + fi + + printf "%-16s %-8s %-8s ~%-11s %-12s %s\n" "${bdf}" "${devid}" "${profile}" "${expected}" "${actual}" "${status}" +done + +echo "" +sec2_logs="$(dmesg 2>/dev/null | grep 'SEC2_DEBUG' || true)" +if [[ -n "${sec2_logs}" ]]; then + ok "dmesg contains SEC2_DEBUG unlock logs" + info "Sample:" + printf '%s\n' "${sec2_logs}" | tail -n 8 | sed 's/^/ /' +else + warn "No SEC2_DEBUG lines in dmesg (logs may have rotated; unlock can still be OK if memory is unlocked)" +fi + +echo "" +if [[ -r "${INSTALL_MOD_DIR}/card_profile" ]]; then + info "Installed profile: $(cat "${INSTALL_MOD_DIR}/card_profile") / geometry: $(cat "${INSTALL_MOD_DIR}/unlock_geometry" 2>/dev/null || echo '?')" +fi + +if (( failures > 0 )); then + echo "" + die "${failures} GPU(s) failed unlock verification. Cold reboot if modules were just installed." +fi + +echo "" +ok "All ${#GPU_BDFS[@]} unlockable GPU(s) report unlocked memory" + +if [[ -x "${SCRIPT_DIR}/tools/service.sh" ]]; then + echo "" + info "Checking negotiated PCIe generation" + if ! "${SCRIPT_DIR}/tools/service.sh" verify; then + warn "Memory unlock is healthy, but PCIe Gen2 is not active" + exit 1 + fi +fi +exit 0