From a8d4f128df384a4b5fb2fea2e2860112e38f9307 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Tue, 6 Oct 2026 13:47:57 -0400 Subject: vgsp: install the Tier-2 scratch-pool window spine via PDE_UPDATE + IQ2M sentinel diagnostic - The Tier-2 shadow VAs (0x200000 + page<<21 + line<<16) had no PTE chain in the engine's table view on the shared-management VAS: pdb_build's client PD0 spine for the guest scratch PT pool is discarded by the server's root migration, so every param/program/release/alias leg the launch rewrites to a shadow VA was unreachable by the engine. Install each scratch pool window lazily through DMA_UPDATE_PDE_2 (same control shape as the Tier-1 installer, pool chunk as the leaf PT) the first time a shadow line in it is consumed; shared VAS only, idempotent via rmcall_guest_pt_win_mask. m17 confirmed the install is accepted (pdeIndex=1 VA 0x200000 status 0x0). - IQ2M sentinel: write 0xdeadbeef to the inline-QMD destination word 0 after the prewrite and report iq2m=ran/not-run in the post-mortem so 'engine stalled before the inline window' is distinguishable from 'QMD deposited but not executed' without a server capture. --- kernel/gsp_phys_booter.c | 106 +++++++++++++++++++++++++++++++++++++++++------ kernel/gsp_phys_booter.h | 9 ++++ 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c index 989c91f..60b57f2 100644 --- a/kernel/gsp_phys_booter.c +++ b/kernel/gsp_phys_booter.c @@ -1873,6 +1873,7 @@ static struct gsp_phys_boot_stage { bool rmcall_gr_exec_ok; /* a real GR submission proved out */ u32 rmcall_gr_submit_epoch; /* per-submission mapper epoch */ u32 rmcall_gr_pt_used; /* Tier-1 window pool slots used */ + u32 rmcall_guest_pt_win_mask; /* Tier-2 scratch windows installed */ struct gsp_phys_boot_chunk rmcall_gr_pt[GSP_PHYS_RMCALL_GR_PT_PAGES]; struct gsp_phys_boot_chunk rmcall_gr_shadow; /* Tier-2 pages */ struct { @@ -2501,6 +2502,7 @@ static void gsp_phys_rmcall_reset(void) boot_stage.rmcall_gr_exec_ok = false; boot_stage.rmcall_gr_submit_epoch = 0; boot_stage.rmcall_gr_pt_used = 0; + boot_stage.rmcall_guest_pt_win_mask = 0; memset(boot_stage.rmcall_gr_pt, 0, sizeof(boot_stage.rmcall_gr_pt)); memset(&boot_stage.rmcall_gr_shadow, 0, sizeof(boot_stage.rmcall_gr_shadow)); @@ -13693,6 +13695,68 @@ static int gsp_phys_rmcall_gr_map_va_locked(void __iomem *bar0, u64 guest_va, return 0; } +static void __iomem *gsp_phys_gr_scratch_bar0; + +/* + * Tier-2 spine: install the guest scratch PT pool's 2 MiB VA window through + * the server walker (DMA_UPDATE_PDE_2) the first time a shadow line in that + * pool page is consumed. On the shared-management VAS the client PD0 entries + * built by pdb_build are not preserved by the server's root migration, so + * without this install the shadow VAs (rmcall_va_guest_pt + page<<21 + ...) + * have no PTE chain in the engine's table view and every Tier-2 leg (param, + * program, release, pointer/indirect pages the QMD was rewritten to) is + * unreachable: the QMD can never execute even though the ring entry is + * fetched. The control shape matches the Tier-1 installer; the PT page is the + * pool chunk itself, whose PTEs gr_map_shadow(_contig)_locked writes. On an + * externally-owned VAS the client PDB spine is preserved, so the install is + * skipped there. Called with gsp_phys_rm_lock held. + */ +static int gsp_phys_rmcall_gr_scratch_win_install_locked(u32 page) +{ + struct gsp_phys_rmcall_update_pde2_params p; + struct gsp_phys_boot_chunk *pt; + u32 rpc_result = 0, status = 0, pde_index; + int ret; + + if (page >= GSP_PHYS_RMCALL_GUEST_PT_PAGES) + return -EINVAL; + if (!gsp_phys_rmcall_vas_shared_value()) + return 0; + if (boot_stage.rmcall_guest_pt_win_mask & BIT(page)) + return 0; + pt = &boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_GUEST_PT0 + page]; + if (!pt->pages) + return -EIO; + if (!boot_stage.rmcall_va_guest_pt) + return -EIO; + pde_index = (u32)((boot_stage.rmcall_va_guest_pt >> 21) + page); + memset(&p, 0, sizeof(p)); + p.pde_index = cpu_to_le32(pde_index); + p.flags = cpu_to_le32(GSP_PHYS_RMCALL_PDE2_FLAG_FLUSH); + p.pt[0].phys_addr = cpu_to_le64((u64)gsp_phys_chunk_page_dma(pt, 0)); + p.pt[0].aperture = cpu_to_le32(GSP_PHYS_RMCALL_PDE2_APERTURE_SYS_COH); + p.pt[1].aperture = cpu_to_le32(GSP_PHYS_RMCALL_PDE2_APERTURE_INVALID); + p.h_va_space = cpu_to_le32(boot_stage.rmcall_vas); + p.pde_buffer = 0; + p.sub_device_id = 0; + ret = gsp_phys_rmcall_control(gsp_phys_gr_scratch_bar0, + boot_stage.rmcall_device, + GSP_PHYS_RMCALL_CTRL_DMA_UPDATE_PDE_2, + &p, sizeof(p), sizeof(p), &p, sizeof(p), + &rpc_result, &status, "GR forward scratch PDE_UPDATE"); + if (ret) { + pr_err("vgsp-gr: scratch PDE install refused pdeIndex=%u (VA 0x%llx) status=0x%x rpc_result=0x%x ret=%d; Tier-2 shadow legs stay unreachable in the engine's table view\n", + pde_index, (u64)pde_index << 21, status, rpc_result, + ret); + return ret; + } + boot_stage.rmcall_guest_pt_win_mask |= BIT(page); + pr_info("vgsp-gr: scratch window installed pdeIndex=%u (VA 0x%llx) ptPa=0x%llx status=0x%x (Tier-2 shadow legs now reachable through the server walker)\n", + pde_index, (u64)pde_index << 21, + (u64)gsp_phys_chunk_page_dma(pt, 0), status); + return 0; +} + /* * Tier-2 map: consume one never-reused 128 B fetch line from the guest * scratch PT pool (PTE0 only) for a ladder-owned shadow page and return the @@ -13726,6 +13790,9 @@ static int gsp_phys_rmcall_gr_map_shadow_locked(u32 slot, u64 hpa, return -ENOSPC; page = line / GSP_PHYS_RMCALL_GUEST_PT_LINES_PER_PAGE; line_in_page = line % GSP_PHYS_RMCALL_GUEST_PT_LINES_PER_PAGE; + ret = gsp_phys_rmcall_gr_scratch_win_install_locked(page); + if (ret) + return ret; pt = &boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_GUEST_PT0 + page]; if (!pt->pages) return -EIO; @@ -13756,6 +13823,7 @@ static int gsp_phys_rmcall_gr_map_shadow_contig_locked(u32 line, u32 pte, struct gsp_phys_boot_chunk *pt; u32 page, line_in_page; u8 *pt_va; + int ret; if (pte >= GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES) return -EINVAL; @@ -13764,6 +13832,9 @@ static int gsp_phys_rmcall_gr_map_shadow_contig_locked(u32 line, u32 pte, return -ENOSPC; page = line / GSP_PHYS_RMCALL_GUEST_PT_LINES_PER_PAGE; line_in_page = line % GSP_PHYS_RMCALL_GUEST_PT_LINES_PER_PAGE; + ret = gsp_phys_rmcall_gr_scratch_win_install_locked(page); + if (ret) + return ret; pt = &boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_GUEST_PT0 + page]; if (!pt->pages) return -EIO; @@ -13851,6 +13922,7 @@ int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req, ret = -EAGAIN; goto out; } + gsp_phys_gr_scratch_bar0 = bar0; sem_va = gsp_phys_chunk_page_va(sem, 0); if (!sem_va || !gsp_phys_chunk_page_va(pb, 0)) { gsp_phys_gr_refused++; @@ -14223,9 +14295,11 @@ gr_page_unmapped: */ if (qmd_dst_mem) { memcpy(qmd_dst_mem, qmd, GSP_PHYS_GR_QMD_WORDS * 4); + put_unaligned_le32(GSP_PHYS_GR_QMD_SENTINEL, qmd_dst_mem); wmb(); - pr_info("vgsp-gr: QMD prewritten to dest 0x%llx (%u words)\n", - qmd_dst, GSP_PHYS_GR_QMD_WORDS); + pr_info("vgsp-gr: QMD prewritten to dest 0x%llx (%u words, w0 sentinel=0x%08x)\n", + qmd_dst, GSP_PHYS_GR_QMD_WORDS, + GSP_PHYS_GR_QMD_SENTINEL); } words = gsp_phys_rmcall_build_gr_launch(methods, req, qmd, @@ -14309,22 +14383,30 @@ gr_page_unmapped: u32 q0 = 0, q1 = 0, q23 = 0, q24 = 0, q25 = 0; u32 relw = 0; - if (req->qmd_hpa) { - u8 *qp = (u8 *)__va(req->qmd_hpa); - - q0 = get_unaligned_le32(qp); - q1 = get_unaligned_le32(qp + 4); - q23 = get_unaligned_le32(qp + 92); - q24 = get_unaligned_le32(qp + 96); - q25 = get_unaligned_le32(qp + 100); + { + u8 *qp = qmd_dst_mem ? qmd_dst_mem : + (req->qmd_hpa ? + (u8 *)__va(req->qmd_hpa) : + NULL); + + if (qp) { + q0 = get_unaligned_le32(qp); + q1 = get_unaligned_le32(qp + 4); + q23 = get_unaligned_le32(qp + 92); + q24 = get_unaligned_le32(qp + 96); + q25 = get_unaligned_le32(qp + 100); + } } if (qmd_rel_hpa) relw = READ_ONCE( *(u32 *)((u8 *)__va(qmd_rel_hpa) + qmd_rel_off)); - pr_info("vgsp-gr: post-mortem gpget=%u->%u qmd_page w0=%08x w1=%08x w23..25=%08x %08x %08x relw=%08x\n", + pr_info("vgsp-gr: post-mortem gpget=%u->%u qmd_page w0=%08x w1=%08x w23..25=%08x %08x %08x relw=%08x iq2m=%s\n", sub.gpget, gpget_post, q0, q1, q23, q24, - q25, relw); + q25, relw, + q0 == GSP_PHYS_GR_QMD_SENTINEL ? + "not-run (sentinel intact)" : + "ran (page overwritten)"); } poll = w1; ret = -ETIMEDOUT; diff --git a/kernel/gsp_phys_booter.h b/kernel/gsp_phys_booter.h index 9a0331f..67d5d5c 100644 --- a/kernel/gsp_phys_booter.h +++ b/kernel/gsp_phys_booter.h @@ -335,6 +335,15 @@ void gsp_phys_backend_relay_counters(u64 *requested, u64 *ok, u64 *verify_fail, #define GSP_PHYS_GR_MAX_INLINE 8u #define GSP_PHYS_GR_INLINE_BYTES 64u #define GSP_PHYS_GR_QMD_WORDS 64u +/* + * IQ2M sentinel: written to the inline-QMD destination's word 0 after the + * prewrite. If the engine processes the inline window, the copy overwrites + * the page (word 0 -> the inline value, 0) and the post-mortem sees the page + * changed; if the sentinel survives, the engine stalled before the inline + * window and the page was never touched. Distinguishes "IQ2M never ran" from + * "QMD deposited but not executed" without a server-side capture. + */ +#define GSP_PHYS_GR_QMD_SENTINEL 0xdeadbeefu #define GSP_PHYS_GR_QMD_WORD_NONE 0xffffffffu #define GSP_PHYS_GR_RAW_WORDS 256u -- cgit v1.2.3