summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-01 08:13:34 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-01 08:13:56 -0400
commit8f1187e6ebff5e2664d19e3e4c55af2e0f6205b4 (patch)
treeddf5075a63c5b0056b08c7577c8d8cab2e3bcfb8 /kernel
parentb0f76b3a2b38f9e231cb71c88446a8ba91d2c586 (diff)
downloadopen-iov-8f1187e6ebff5e2664d19e3e4c55af2e0f6205b4.tar.gz
open-iov-8f1187e6ebff5e2664d19e3e4c55af2e0f6205b4.zip
gsp-phys: fix the booter-unload handoff seed (correct scratch register/access)
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_phys_fwsec.c188
1 files changed, 171 insertions, 17 deletions
diff --git a/kernel/gsp_phys_fwsec.c b/kernel/gsp_phys_fwsec.c
index 3c63a95..1fda977 100644
--- a/kernel/gsp_phys_fwsec.c
+++ b/kernel/gsp_phys_fwsec.c
@@ -151,6 +151,35 @@ bool gsp_phys_fwsec_dryrun_enabled(void)
#define GSP_PHYS_SCRATCH14_HANDOFF_MASK 0x0f000000u
#define GSP_PHYS_SCRATCH14_HANDOFF_SHIFT 24
#define GSP_PHYS_SCRATCH14_BOOTER_VERSION_TU10X 0u
+/*
+ * Every BSI secure scratch has a companion privilege-level mask. The scratch
+ * is write-protected to level3 (the SEC2/HS context) by default, so a bare
+ * host write is silently dropped: opening WRITE_PROTECTION for the host level
+ * is required before seeding, and the mask is restored afterwards. The BSI
+ * scratch PLM is at PRIV_LEVEL_MASK(i) = 0x118080 + i*4; scratch 14 -> 0xb8.
+ */
+#define GSP_PHYS_BSI_VPR_SCRATCH14_PLM 0x001180b8
+#define GSP_PHYS_SCRATCH_PLM_ALL_LEVELS 0x000000ffu
+/*
+ * PFB MMU WPR window (host-portable port of the booter unlock) and the MMU
+ * privilege mask/fuse that gate host writes to it.
+ */
+#define GSP_PHYS_PFB_MMU_PRIV_LEVEL_MASK 0x00100cdc
+#define GSP_PHYS_PFB_MMU_WPR_ALLOW_READ 0x001fa814
+#define GSP_PHYS_PFB_MMU_WPR_ALLOW_WRITE 0x001fa818
+#define GSP_PHYS_PFB_MMU_WPR2_ALLOW_MASK 0x00000f00u
+#define GSP_PHYS_FUSE_SECURE_MMU_PLM_WR_SECURE 0x00021198
+#define GSP_PHYS_FALCON_SEC_CFGA(i) (0x001f9040u + (i) * 8u)
+#define GSP_PHYS_FALCON_SEC_CFGB(i) (0x001f9044u + (i) * 8u)
+#define GSP_PHYS_FALCON_SEC_SUBWPR_COUNT 8
+#define GSP_PHYS_FALCON_GSP_CFGA(i) (0x001f9080u + (i) * 8u)
+#define GSP_PHYS_FALCON_GSP_CFGB(i) (0x001f9084u + (i) * 8u)
+#define GSP_PHYS_FALCON_GSP_SUBWPR_COUNT 4
+#define GSP_PHYS_FALCON_NVDEC0_CFGA(i) (0x001f91c0u + (i) * 8u)
+#define GSP_PHYS_FALCON_NVDEC0_CFGB(i) (0x001f91c4u + (i) * 8u)
+#define GSP_PHYS_NVDEC0_SUBWPR_GSPRM_BOOTER_WPR2 3
+#define GSP_PHYS_SUBWPR_INVALID_LO 0xfffffff0u
+#define GSP_PHYS_SUBWPR_INVALID_HI 0x00000000u
#define GSP_PHYS_GFW_PLM 0x118128
#define GSP_PHYS_GFW_PLM_LEVEL0 BIT(0)
#define GSP_PHYS_RISCV_STATUS 0x111240
@@ -974,38 +1003,88 @@ static void gsp_phys_fwsec_ucode_free(struct pci_dev *pdev,
}
/*
+ * Open every privilege level on a PLM by setting its READ/WRITE protection
+ * nibbles; the caller restores the original value. Returns true when the
+ * readback shows the requested protection bits took (a bare write is dropped
+ * when the mask is fuse-locked, e.g. mmu_plm for the MMU PLM).
+ */
+static bool gsp_phys_plm_open(void __iomem *bar0, u32 addr, u32 *orig)
+{
+ u32 before = readl(bar0 + addr);
+ u32 after;
+
+ writel((before & ~GSP_PHYS_SCRATCH_PLM_ALL_LEVELS) |
+ GSP_PHYS_SCRATCH_PLM_ALL_LEVELS, bar0 + addr);
+ after = readl(bar0 + addr);
+ *orig = before;
+ return (after & GSP_PHYS_SCRATCH_PLM_ALL_LEVELS) ==
+ GSP_PHYS_SCRATCH_PLM_ALL_LEVELS;
+}
+
+/*
* Seed the VPR secure-scratch sequence state the Booter Unload ucode gates on
* at entry. In the reference lifecycle those bits are produced by Booter Load,
* AHESASC, Booter Reload and ACR Unload; a FWSEC-only WPR2 carve (stage 3c)
* never runs those ucodes, so the unload refuses with its sequence/handoff
* error (observed SEC2 M0=0x29) before it touches WPR2 and WPR2 stays up.
- * Setting the four handoff bits DONE and the Booter binary version to the
- * Turing build version (0) restores the state Booter Load would have left so
- * the unload can run; the unload itself clears both fields on completion.
- * The WPR meta is not consumed by the unload path at all (it only resets the
- * GSP engine, invalidates the sub-WPRs and locks WPR2 down).
+ * The scratch is write-protected to level3 by its companion PLM, so the host
+ * write only sticks after the PLM's WRITE_PROTECTION is opened; the original
+ * mask is restored before returning. The WPR meta is not consumed by the
+ * unload path at all (it only resets the GSP engine, invalidates the sub-WPRs
+ * and unlocks WPR2).
*/
-static void gsp_phys_booter_seed_sequence_state(void __iomem *bar0)
+static bool gsp_phys_booter_seed_sequence_state(void __iomem *bar0)
{
- u32 before, after;
+ u32 plm_addr = GSP_PHYS_BSI_VPR_SCRATCH14_PLM;
+ u32 scratch_addr = GSP_PHYS_BSI_VPR_SECURE_SCRATCH_14;
+ u32 plm_orig, before, after, readback;
+ bool opened, seeded;
+
+ before = readl(bar0 + scratch_addr);
+ opened = gsp_phys_plm_open(bar0, plm_addr, &plm_orig);
+ if (opened)
+ before = readl(bar0 + scratch_addr);
- before = readl(bar0 + GSP_PHYS_BSI_VPR_SECURE_SCRATCH_14);
after = before & ~(GSP_PHYS_SCRATCH14_BOOTER_VERSION_MASK |
GSP_PHYS_SCRATCH14_HANDOFF_MASK);
after |= GSP_PHYS_SCRATCH14_BOOTER_VERSION_TU10X <<
GSP_PHYS_SCRATCH14_BOOTER_VERSION_SHIFT;
after |= GSP_PHYS_SCRATCH14_HANDOFF_MASK;
- writel(after, bar0 + GSP_PHYS_BSI_VPR_SECURE_SCRATCH_14);
-
- pr_info("gsp-phys-fwsec: booter sequence seed: scratch14 0x%08x -> 0x%08x (booterVersion=%u want %u, handoffs 0x%x)\n",
- before, readl(bar0 + GSP_PHYS_BSI_VPR_SECURE_SCRATCH_14),
- (readl(bar0 + GSP_PHYS_BSI_VPR_SECURE_SCRATCH_14) &
- GSP_PHYS_SCRATCH14_BOOTER_VERSION_MASK) >>
+ if (opened)
+ writel(after, bar0 + scratch_addr);
+ readback = readl(bar0 + scratch_addr);
+
+ /* Restore the scratch's original access protection. */
+ writel(plm_orig, bar0 + plm_addr);
+ seeded = opened &&
+ (readback & (GSP_PHYS_SCRATCH14_BOOTER_VERSION_MASK |
+ GSP_PHYS_SCRATCH14_HANDOFF_MASK)) ==
+ (after & (GSP_PHYS_SCRATCH14_BOOTER_VERSION_MASK |
+ GSP_PHYS_SCRATCH14_HANDOFF_MASK));
+
+ pr_info("gsp-phys-fwsec: booter sequence seed: PLM@0x%x 0x%08x (host-open=%s, restored 0x%08x), scratch14 0x%08x -> 0x%08x (booterVersion=%u want %u, handoffs 0x%x) -> %s\n",
+ plm_addr, plm_orig, opened ? "yes" : "no",
+ readl(bar0 + plm_addr), before, readback,
+ (readback & GSP_PHYS_SCRATCH14_BOOTER_VERSION_MASK) >>
GSP_PHYS_SCRATCH14_BOOTER_VERSION_SHIFT,
GSP_PHYS_SCRATCH14_BOOTER_VERSION_TU10X,
- (readl(bar0 + GSP_PHYS_BSI_VPR_SECURE_SCRATCH_14) &
- GSP_PHYS_SCRATCH14_HANDOFF_MASK) >>
- GSP_PHYS_SCRATCH14_HANDOFF_SHIFT);
+ (readback & GSP_PHYS_SCRATCH14_HANDOFF_MASK) >>
+ GSP_PHYS_SCRATCH14_HANDOFF_SHIFT,
+ seeded ? "SEEDED" : "NOT SEEDED (host write blocked)");
+
+ if (!seeded)
+ pr_warn("gsp-phys-fwsec: scratch14 write protection is not host-open; the booter unload handoff gate cannot be satisfied in a FWSEC-only carve\n");
+ return seeded;
+}
+
+/*
+ * Invalidate one falcon sub-WPR (reference s_booterGetSubWprAddrVals with
+ * bValid=false: ADDR_LO=0xFFFFFFFF, ADDR_HI=0, allow masks 0).
+ */
+static void gsp_phys_sub_wpr_invalidate(void __iomem *bar0, u32 cfga, u32 cfgb)
+{
+ writel(GSP_PHYS_SUBWPR_INVALID_LO, bar0 + cfga);
+ writel(GSP_PHYS_SUBWPR_INVALID_HI, bar0 + cfgb);
}
/*
@@ -1078,6 +1157,76 @@ static int gsp_phys_booter_unload(void __iomem *bar0)
}
/*
+ * Last-resort host port of the booter unload's teardown actions, used when
+ * the handoff gate cannot pass (a FWSEC-only carve never runs Booter
+ * Load/AHESASC/Booter Reload/ACR Unload). It mirrors booterUnload_TU10X:
+ * full GSP reset, invalidate the SEC/GSP/NVDEC sub-WPRs, then
+ * booterUnlockWpr2_TU10X (WPR2 VAL invalid on both address halves and WPR2
+ * read/write allow = 0). The WPR registers sit behind the MMU privilege mask
+ * (fuse mmu_plm), so the mask is opened first and only written through when
+ * the readback proves the host is allowed; otherwise nothing is touched and
+ * the caller's fail-safe keeps the hand-back refused.
+ */
+static void gsp_phys_host_wpr2_unlock(void __iomem *bar0)
+{
+ u32 plm_addr = GSP_PHYS_PFB_MMU_PRIV_LEVEL_MASK;
+ u32 plm_orig, fuse, reg;
+ u32 i;
+ bool opened;
+
+ fuse = readl(bar0 + GSP_PHYS_FUSE_SECURE_MMU_PLM_WR_SECURE);
+ opened = gsp_phys_plm_open(bar0, plm_addr, &plm_orig);
+ pr_info("gsp-phys-fwsec: host WPR2 unlock: MMU PLM@0x%x 0x%08x -> 0x%08x (host-open=%s), fuse SECURE_MMU_PLM_WR_SECURE=0x%08x\n",
+ plm_addr, plm_orig, readl(bar0 + plm_addr),
+ opened ? "yes" : "no", fuse);
+ if (!opened) {
+ writel(plm_orig, bar0 + plm_addr);
+ pr_warn("gsp-phys-fwsec: MMU PLM write protection is fuse-locked (restored 0x%08x); not touching WPR2 (fail-safe stays in force, reboot with approval may be required)\n",
+ readl(bar0 + plm_addr));
+ return;
+ }
+
+ gsp_phys_falcon_reset(bar0, GSP_PHYS_GSP_BASE);
+
+ for (i = 0; i < GSP_PHYS_FALCON_SEC_SUBWPR_COUNT; i++)
+ gsp_phys_sub_wpr_invalidate(bar0,
+ GSP_PHYS_FALCON_SEC_CFGA(i),
+ GSP_PHYS_FALCON_SEC_CFGB(i));
+ for (i = 0; i < GSP_PHYS_FALCON_GSP_SUBWPR_COUNT; i++)
+ gsp_phys_sub_wpr_invalidate(bar0,
+ GSP_PHYS_FALCON_GSP_CFGA(i),
+ GSP_PHYS_FALCON_GSP_CFGB(i));
+ gsp_phys_sub_wpr_invalidate(bar0,
+ GSP_PHYS_FALCON_NVDEC0_CFGA(GSP_PHYS_NVDEC0_SUBWPR_GSPRM_BOOTER_WPR2),
+ GSP_PHYS_FALCON_NVDEC0_CFGB(GSP_PHYS_NVDEC0_SUBWPR_GSPRM_BOOTER_WPR2));
+
+ reg = readl(bar0 + GSP_PHYS_WPR2_ADDR_HI);
+ writel(reg & ~(GSP_PHYS_WPR2_VAL_MASK << GSP_PHYS_WPR2_VAL_SHIFT),
+ bar0 + GSP_PHYS_WPR2_ADDR_HI);
+ reg = readl(bar0 + GSP_PHYS_WPR2_ADDR_LO);
+ writel((reg & ~(GSP_PHYS_WPR2_VAL_MASK << GSP_PHYS_WPR2_VAL_SHIFT)) |
+ (GSP_PHYS_WPR2_VAL_MASK << GSP_PHYS_WPR2_VAL_SHIFT),
+ bar0 + GSP_PHYS_WPR2_ADDR_LO);
+ reg = readl(bar0 + GSP_PHYS_PFB_MMU_WPR_ALLOW_READ);
+ writel(reg & ~GSP_PHYS_PFB_MMU_WPR2_ALLOW_MASK,
+ bar0 + GSP_PHYS_PFB_MMU_WPR_ALLOW_READ);
+ reg = readl(bar0 + GSP_PHYS_PFB_MMU_WPR_ALLOW_WRITE);
+ writel(reg & ~GSP_PHYS_PFB_MMU_WPR2_ALLOW_MASK,
+ bar0 + GSP_PHYS_PFB_MMU_WPR_ALLOW_WRITE);
+
+ pr_info("gsp-phys-fwsec: host WPR2 unlock applied: WPR2_LO=0x%08x WPR2_HI=0x%08x ALLOW_READ=0x%08x ALLOW_WRITE=0x%08x\n",
+ readl(bar0 + GSP_PHYS_WPR2_ADDR_LO),
+ readl(bar0 + GSP_PHYS_WPR2_ADDR_HI),
+ readl(bar0 + GSP_PHYS_PFB_MMU_WPR_ALLOW_READ),
+ readl(bar0 + GSP_PHYS_PFB_MMU_WPR_ALLOW_WRITE));
+
+ /* Restore the board's original privilege mask (best effort). */
+ writel(plm_orig, bar0 + plm_addr);
+ pr_info("gsp-phys-fwsec: host WPR2 unlock: MMU PLM restored to 0x%08x\n",
+ readl(bar0 + plm_addr));
+}
+
+/*
* Teardown: FWSEC-SB (puts the pre-OS applications back) followed by the
* booter unload (removes WPR2). The final criterion is WPR2 down, not the
* individual ucode statuses.
@@ -1127,6 +1276,11 @@ static int gsp_phys_fwsec_teardown(void __iomem *bar0)
pr_err("gsp-phys-fwsec: booter unload failed: %d\n", ret);
if (gsp_phys_fwsec_wpr2_up(bar0)) {
+ pr_warn("gsp-phys-fwsec: booter unload left WPR2 up (its handoff gate is unsatisfiable after a FWSEC-only carve); trying the host-side WPR2 unlock\n");
+ gsp_phys_host_wpr2_unlock(bar0);
+ }
+
+ if (gsp_phys_fwsec_wpr2_up(bar0)) {
pr_err("gsp-phys-fwsec: teardown did not clear WPR2 (HI=0x%08x)\n",
readl(bar0 + GSP_PHYS_WPR2_ADDR_HI));
return -EIO;