summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-01 13:07:05 -0500
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-01 13:07:05 -0500
commitd4a10ced7a4e8fa8ab65fdab1596190f5d567775 (patch)
tree5bd6a9af160cc45be8eca575f0b5e3152a4f48ea
parent5451c5845a2636fdad60627d244fddc1d1270c4b (diff)
downloadopen-iov-d4a10ced7a4e8fa8ab65fdab1596190f5d567775.tar.gz
open-iov-d4a10ced7a4e8fa8ab65fdab1596190f5d567775.zip
vgsp: CONT-93 - sema_gl_sph_8k + pair-line (PTE0=program page PTE1=family+0x1000 single fetch line); sema_sph_version_val forced SET_SPH_VERSION A/B; sema_gr_park_recover (TSG-preempt+resched re-arm on warm-sem wedge); gsp-phys-pgraph-rw debugfs (live PGRAPH snapshot + bounded writes 0x400000-0x410000); sema_sph_region_legacy (0x1608/0x160c pair) - refutations vs per-window latch: region 'write lands' refuted (same push ds=a6000e20 vs a6000e66 not region-caused); 8K tail no change (SPH-PAIR line=0x620000 hpa0=0x14444f000 hpa1=0x10a418000 + INLINE-APPLY-ALIAS -> ds=a4000e20); version windows 00040004/00050005/00060006/00010003 all a6000e20; PD filter pgraph-rw 0x00406020 57610001->17610001 no change; bind scope: client's own program also rejected (no SPH-BIND emitted still a4000e20); park not recoverable (WEDGE-REARM preempt status=0x0 rpc=0x0 ret=0 probe rc=-110); next: emit SET_SHADER_LOCAL_MEMORY_A/B/C/D (0x790-0x79c) download-state push, only missing client channel-init class, SPH carries LOCAL_MEMORY_LOW/HIGH_SIZE fields DS validates, capture from guest-RAM scrape like init block; pgraph-rw as live oracle - SESSION 153 r1311-r1320
-rw-r--r--kernel/gsp_phys_booter.c372
1 files changed, 371 insertions, 1 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 15a344d..ef882cc 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -17932,6 +17932,19 @@ MODULE_PARM_DESC(sema_gl_sph_version,
"Emit the Turing channel-init SET_SPH_VERSION/CHECK_SPH_VERSION pair (0x16a4/0x16a8 <- 0x00040004) ahead of each replayed 3D window (default 1; A/B with 0 reproduces the Shader Program Header 9/11/18 fault)");
/*
+ * SESSION 153-CONTINUED-93: forced SPH version-window value. 0 = auto (the
+ * blob-derived window, see the emission site); nonzero = emit exactly this
+ * 32-bit value for both 0x16a4/0x16a8. Live A/B for the DS version-window
+ * hypothesis: the emitted 4/4 window may reject the fetched SPHs while a
+ * wider/older window (e.g. 0x00040003) accepts them.
+ */
+static u64 gsp_phys_rmcall_gr_sph_version_val;
+module_param_named(sema_sph_version_val, gsp_phys_rmcall_gr_sph_version_val,
+ ullong, 0644);
+MODULE_PARM_DESC(sema_sph_version_val,
+ "Forced SET_SPH_VERSION/CHECK_SPH_VERSION value (0 = auto from the loaded blobs)");
+
+/*
* SESSION 153-CONTINUED-90: SET_SHADER_EXCEPTIONS (0x1528 <- 0).
*
* NVIDIA's own Turing 3D channel init (_nv3dInitChannelTuring) pushes this
@@ -17997,6 +18010,17 @@ MODULE_PARM_DESC(sema_sph_region_base,
"Program-region base override (0 = launch program family page; nonzero = explicit engine-side base)");
/*
+ * SESSION 153-CONTINUED-93: additionally emit the legacy program-region
+ * pair (0x1608/0x160c) beside the MME shadow pair (see the emission site).
+ * Default 0 (A/B; the DS snapshot registers share the legacy pair's names).
+ */
+static bool gsp_phys_rmcall_gr_sph_region_legacy;
+module_param_named(sema_sph_region_legacy, gsp_phys_rmcall_gr_sph_region_legacy,
+ bool, 0644);
+MODULE_PARM_DESC(sema_sph_region_legacy,
+ "Also emit the legacy SET_PROGRAM_REGION_A/B (0x1608/0x160c) pair with the same base (default 0)");
+
+/*
* SESSION 153-CONTINUED-87: the missing TESS_CONTROL/PIXEL program binds.
* The client's real channel state binds every pipeline stage once per
* context; the captured init-push image (guest RAM scrape) carries
@@ -18816,8 +18840,11 @@ static u32 gsp_phys_rmcall_build_gr_launch(u32 *pb,
*/
u32 bv = (u32)gsp_sema_gl_sph_blob_version();
u32 val = 0x00040004u;
+ u64 ov = READ_ONCE(gsp_phys_rmcall_gr_sph_version_val);
- if (bv >= 1u && bv <= 15u)
+ if (ov)
+ val = (u32)ov;
+ else if (bv >= 1u && bv <= 15u)
val = ((bv < 4u ? 4u : bv) << 16) | bv;
*p++ = ((u32)GSP_PHYS_RMCALL_METHOD_IMM << 29) |
@@ -18867,6 +18894,41 @@ static u32 gsp_phys_rmcall_build_gr_launch(u32 *pb,
}
}
/*
+ * SESSION 153-CONTINUED-93: the LEGACY program-region pair
+ * (0x1608/0x160c = the pre-Volta SET_PROGRAM_REGION_A/B whose
+ * names the DS's own snapshot registers
+ * DS_HWW_ESR_SPH_INFO_PROGRAM_REGION_A/B carry). Those
+ * snapshots read 0/0 on every SPH-faulting window while the
+ * MME shadow pair (0x78/0x79) is provably emitted, so the
+ * DS's validation region may be the legacy pair's state, not
+ * the MME scratch. The methods are "obsolete" on Volta+ (not
+ * in the c597 class table), so this is an A/B: a PBDMA that
+ * refuses them faults the launch visibly, an accepting one
+ * tells us via the latch. Knob sema_sph_region_legacy
+ * (default 0). Emitted beside the MME pair with the same
+ * base.
+ */
+ if (READ_ONCE(gsp_phys_rmcall_gr_sph_region) && req->prog_va &&
+ READ_ONCE(gsp_phys_rmcall_gr_sph_region_legacy)) {
+ static u32 sph_reg_leg_log;
+ u64 base = READ_ONCE(gsp_phys_rmcall_gr_sph_region_base);
+
+ if (!base)
+ base = req->prog_va &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1);
+ *p++ = ((u32)GSP_PHYS_RMCALL_METHOD_IMM << 29) |
+ (1u << 16) | (0x1608u >> 2);
+ *p++ = (u32)(base >> 32) & 0xffu;
+ *p++ = ((u32)GSP_PHYS_RMCALL_METHOD_IMM << 29) |
+ (1u << 16) | (0x160cu >> 2);
+ *p++ = (u32)base;
+ if (sph_reg_leg_log < 16u) {
+ sph_reg_leg_log++;
+ pr_info("vgsp-gr: SPH-REGION-LEGACY emitted base=0x%llx (0x1608/0x160c) h=0x%x push=0x%llx\n",
+ base, req->h_object, req->push_va);
+ }
+ }
+ /*
* SESSION 153-CONTINUED-84: SET_MME_VERSION=1 (0x234).
*
* The Turing channel-init defaults push
@@ -22124,6 +22186,149 @@ static u64 gsp_phys_gr_sph_canary_hpa(void)
}
/*
+ * SESSION 153-CONTINUED-93: 8K program-pair shadow line (mission task 2
+ * fallback). The DS validates a bound program through its fetched SPH; a
+ * per-page shadow line maps exactly one 4 KB page at PTE0 and leaves the
+ * line's PTE1 invalid, so any read past the first 4 KB of the program page
+ * (the SASS tail of a bind whose delivered length crosses 0x1000, or any
+ * DS full-program walk) leaves the authored bytes behind. Map the program
+ * family page (PTE0) and family+0x1000 (PTE1) into ONE fresh 128 B fetch
+ * line BEFORE the engine can fetch it (a line's contents are frozen once
+ * fetched), so a contiguous 8 KB is engine-visible from its first window
+ * on. The line is cached per hpa0 (pair cache) so every later window reuses
+ * the identical never-rewritten mapping; the alias cache is re-pointed at
+ * the pair line so every other consumer (program binds, code-fetch
+ * operands) sees the same 8 KB view. Live A/B via sema_gl_sph_8k. Called
+ * with gsp_phys_rm_lock held.
+ */
+static bool gsp_phys_rmcall_gr_sph_8k;
+module_param_named(sema_gl_sph_8k, gsp_phys_rmcall_gr_sph_8k, bool, 0644);
+MODULE_PARM_DESC(sema_gl_sph_8k,
+ "Map the program family page and family+0x1000 in one contiguous 8K shadow line (fresh fetch line; default 0)");
+#define GSP_PHYS_RMCALL_GR_PAIR_CACHE 64u
+static u64 gsp_phys_gr_pair_hpa[GSP_PHYS_RMCALL_GR_PAIR_CACHE];
+static u64 gsp_phys_gr_pair_va[GSP_PHYS_RMCALL_GR_PAIR_CACHE];
+static u32 gsp_phys_gr_pair_n;
+
+/*
+ * Resolve the HPA of `page0 + 4K` through the same sources the relocation
+ * chain uses: the launch's own page set (the pages the copy-in authored),
+ * the tracked data recs, the sema service's trusted resolver, then the
+ * late-resolved gap pages. 0 = not resolvable.
+ */
+static u64 gsp_phys_rmcall_gr_prog_next_hpa_locked(
+ const struct gsp_phys_gr_submit *req, u64 page0)
+{
+ u64 page1 = page0 + GSP_PHYS_PAGE_SIZE;
+ u32 i;
+
+ for (i = 0; i < req->n_pages; i++)
+ if ((req->pages[i].guest_va &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1)) == page1 &&
+ req->pages[i].hpa)
+ return req->pages[i].hpa;
+ for (i = 0; i < req->n_data; i++)
+ if ((req->data[i].va &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1)) == page1 &&
+ req->data[i].hpa)
+ return req->data[i].hpa;
+ {
+ struct gsp_sema_gr_reloc r;
+
+ if (!gsp_sema_gr_reloc_resolve(page1, &r) && r.hpa)
+ return r.hpa;
+ }
+ for (i = 0; i < gsp_phys_gr_gap_n; i++)
+ if ((gsp_phys_gr_gap[i].va &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1)) == page1 &&
+ gsp_phys_gr_gap[i].hpa)
+ return gsp_phys_gr_gap[i].hpa;
+ return 0;
+}
+
+static void gsp_phys_rmcall_gr_alias_cache_set_locked(u64 hpa, u64 va)
+{
+ u32 i;
+
+ for (i = 0; i < boot_stage.rmcall_gr_alias_count; i++)
+ if (boot_stage.rmcall_gr_alias_hpa[i] == hpa) {
+ boot_stage.rmcall_gr_alias_va[i] = va;
+ return;
+ }
+ if (boot_stage.rmcall_gr_alias_count < GSP_PHYS_RMCALL_GR_ALIAS_CACHE) {
+ u32 ci = boot_stage.rmcall_gr_alias_count;
+
+ boot_stage.rmcall_gr_alias_hpa[ci] = hpa;
+ boot_stage.rmcall_gr_alias_va[ci] = va;
+ boot_stage.rmcall_gr_alias_count = ci + 1u;
+ }
+}
+
+/*
+ * Allocate (or reuse) the pair line. Returns the line base VA on success,
+ * 0 when the second half cannot be resolved or the pool is exhausted (the
+ * caller then falls through to the single-page path). The fresh line is
+ * allocated on first use for an hpa0; the pair cache reuses it afterwards
+ * (the PTEs are identical on every window by construction).
+ */
+static u64 gsp_phys_rmcall_gr_sph_pair_locked(
+ const struct gsp_phys_gr_submit *req, u64 page0, u64 hpa0, u32 slot)
+{
+ struct gsp_phys_boot_chunk *sh = &boot_stage.rmcall_gr_shadow;
+ u32 i, line, page, line_in_page;
+ u64 hpa1, va0;
+ u8 *pt;
+ int ret;
+
+ for (i = 0; i < gsp_phys_gr_pair_n; i++)
+ if (gsp_phys_gr_pair_hpa[i] == hpa0)
+ return gsp_phys_gr_pair_va[i];
+ hpa1 = gsp_phys_rmcall_gr_prog_next_hpa_locked(req, page0);
+ if (!hpa1)
+ return 0;
+ if (!sh->pages) {
+ ret = gsp_phys_chunk_alloc(boot_stage.pdev, sh,
+ GSP_PHYS_RMCALL_GR_SHADOW_PAGES);
+ if (ret)
+ return 0;
+ gsp_phys_chunk_zero(sh);
+ }
+ if (slot >= sh->pages || !boot_stage.rmcall_va_guest_pt)
+ return 0;
+ line = boot_stage.rmcall_guest_pt_next_line;
+ if (line >= GSP_PHYS_RMCALL_GUEST_PT_TOTAL_LINES)
+ return 0;
+ page = line / GSP_PHYS_RMCALL_GUEST_PT_LINES_PER_PAGE;
+ line_in_page = line % GSP_PHYS_RMCALL_GUEST_PT_LINES_PER_PAGE;
+ if (gsp_phys_rmcall_gr_scratch_win_install_locked(page))
+ return 0;
+ pt = gsp_phys_rmcall_guest_pt_page_va(page);
+ if (!pt)
+ return 0;
+ put_unaligned_le64(gsp_phys_rmcall_gr_pte_leaf(hpa0),
+ pt + (size_t)(GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES *
+ line_in_page) * 8);
+ put_unaligned_le64(gsp_phys_rmcall_gr_pte_leaf(hpa1),
+ pt + (size_t)(GSP_PHYS_RMCALL_GUEST_PT_CACHE_PTES *
+ line_in_page + 1u) * 8);
+ dma_wmb();
+ boot_stage.rmcall_guest_pt_next_line = line + 1u;
+ va0 = gsp_phys_rmcall_guest_pt_win_va(page) +
+ ((u64)line_in_page << 16);
+ gsp_phys_rmcall_gr_alias_cache_set_locked(hpa0, va0);
+ gsp_phys_rmcall_gr_alias_cache_set_locked(hpa1,
+ va0 + GSP_PHYS_PAGE_SIZE);
+ if (gsp_phys_gr_pair_n < GSP_PHYS_RMCALL_GR_PAIR_CACHE) {
+ gsp_phys_gr_pair_hpa[gsp_phys_gr_pair_n] = hpa0;
+ gsp_phys_gr_pair_va[gsp_phys_gr_pair_n] = va0;
+ gsp_phys_gr_pair_n++;
+ }
+ pr_info("vgsp-gr: SPH-PAIR line=0x%llx hpa0=0x%llx hpa1=0x%llx (8K contiguous program page) h=0x%x\n",
+ va0, hpa0, hpa1, req->h_object);
+ return va0;
+}
+
+/*
* SESSION 153-CONTINUED-92: identity-fetch keep test (see the
* sema_gl_prog_guest_va knob above). Resolve the operand's page (the
* launch's own page set first - the pages the copy-in authored - then the
@@ -22292,6 +22497,32 @@ static bool gsp_phys_rmcall_gr_3d_reloc(u32 *up_w, u32 *lo_w,
continue;
if (*n_shadow >= GSP_PHYS_RMCALL_GR_SHADOW_PAGES)
return false;
+ /*
+ * SESSION 153-CONTINUED-93: 8K program-pair line (see the
+ * pair helper). Only the program family page rides this
+ * path; an unresolvable second half falls through to the
+ * single-page map below.
+ */
+ if (READ_ONCE(gsp_phys_rmcall_gr_sph_8k) && req->prog_va &&
+ page == (req->prog_va &
+ ~(u64)(GSP_PHYS_PAGE_SIZE - 1))) {
+ u64 pv = gsp_phys_rmcall_gr_sph_pair_locked(
+ req, page, req->pages[i].hpa, *n_shadow);
+
+ if (pv) {
+ (*n_shadow)++;
+ sh = pv | (va &
+ (GSP_PHYS_PAGE_SIZE - 1));
+ gsp_phys_gr_wit_note(page, req->pages[i].hpa);
+ gsp_phys_gr_sph_alias_receipt(page, va, pv,
+ req->pages[i].hpa, "pair");
+ *lo_w = (*lo_w & 0x3u) |
+ ((u32)sh & 0xfffffffcu);
+ *up_w = (*up_w & ~0xffu) |
+ ((u32)(sh >> 32) & 0xffu);
+ return true;
+ }
+ }
if (gsp_phys_rmcall_gr_map_shadow_locked(*n_shadow,
req->pages[i].hpa,
&sh, &line))
@@ -22492,6 +22723,25 @@ module_param_named(sema_gr_wedge_recover, gsp_phys_gr_wedge_recover, bool,
MODULE_PARM_DESC(sema_gr_wedge_recover,
"On the 0xbadf priv-error poison in the GR FIFO status, run one bounded TSG-preempt+resched hard re-arm per launch (default 1)");
+/*
+ * SESSION 153-CONTINUED-93: parked-engine recovery on the warm-sem ladder
+ * gate. The r1311-r1313 class: after the first few DS-validated draws the
+ * front-end stops fetching (grfifo=0x00000001, no poison, no trap) and the
+ * warm GR semaphore probe times out on every later launch - the ladder
+ * then fails fast and the whole boot is dead for bisection. When this knob
+ * is set, a wedged probe triggers ONE bounded hard re-arm (the proven
+ * r445 TSG-preempt bWait=1 + resched + probe) before giving up; an ALIVE
+ * probe after the re-arm lets the fresh-entry retries resume, and the MME
+ * prelude latch is cleared so the re-preload is owed (the preempt reset
+ * the context). Bounded: one per launch; all RPCs keep their timeouts.
+ * Default 1; A/B with 0 restores the fail-fast ladder.
+ */
+static bool gsp_phys_gr_park_recover = true;
+module_param_named(sema_gr_park_recover, gsp_phys_gr_park_recover, bool,
+ 0644);
+MODULE_PARM_DESC(sema_gr_park_recover,
+ "On a wedged warm-sem probe in the launch timeout ladder, run one bounded TSG-preempt+resched hard re-arm per launch and retry (default 1)");
+
static char *gsp_phys_rmcall_gr_win_drop;
module_param_named(sema_gl_win_drop, gsp_phys_rmcall_gr_win_drop, charp, 0644);
MODULE_PARM_DESC(sema_gl_win_drop,
@@ -26332,6 +26582,36 @@ gr_page_unmapped:
mutex_lock(&gsp_phys_rm_lock);
sp_ret = gsp_phys_rmcall_gr_sem_push_locked(bar0);
mutex_unlock(&gsp_phys_rm_lock);
+ /*
+ * SESSION 153-CONTINUED-93: parked-engine
+ * recovery (see the sema_gr_park_recover
+ * knob). A wedged probe on a poison-free,
+ * trap-free front-end is the r1311-r1313
+ * park class: the context is gone and only a
+ * context reload brings it back. One bounded
+ * hard re-arm per launch; ALIVE afterwards
+ * resumes the fresh-entry retries.
+ */
+ if (sp_ret && bar0 &&
+ READ_ONCE(gsp_phys_gr_park_recover)) {
+ static u32 park_rec_n;
+
+ mutex_lock(&gsp_phys_rm_lock);
+ sp_ret =
+ gsp_phys_gr_wedge_hard_rearm_locked(bar0);
+ mutex_unlock(&gsp_phys_rm_lock);
+ park_rec_n++;
+ if (!sp_ret) {
+ WRITE_ONCE(gsp_phys_gr_mme_prelude_gen, 0);
+ WRITE_ONCE(gsp_phys_gr_mme_pre_emit_off, 0);
+ pr_warn("vgsp-gr: PARK-RECOVER #%u warm sem ALIVE after TSG preempt + resched; fresh-entry retries resume push=0x%llx\n",
+ park_rec_n, req->push_va);
+ } else {
+ pr_warn("vgsp-gr: PARK-RECOVER #%u still wedged after hard re-arm (rc=%d) push=0x%llx\n",
+ park_rec_n, sp_ret,
+ req->push_va);
+ }
+ }
if (sp_ret) {
gsp_phys_gr_sem_wedged++;
pr_info("vgsp-gr: timeout ladder: warm sem WEDGED (rc=%d) - failing fast instead of fresh-entry retries\n",
@@ -30260,6 +30540,87 @@ static const struct file_operations gsp_phys_ds_spy_fops = {
.release = single_release,
};
+/*
+ * SESSION 153-CONTINUED-93: live PGRAPH register lab (debugfs
+ * gsp-phys-pgraph-rw). Read: a fixed snapshot of the DS/PD/FIFO registers
+ * the SPH investigation reads. Write: "0xADDR [0xVALUE]" (hex) inside the
+ * PGRAPH PRI window [0x400000,0x410000) - reads the old value, writes the
+ * new one (when given) and reads back, logging both. Diagnostic only;
+ * development bring-up tool. Writes outside the window are refused.
+ */
+static int gsp_phys_pgraph_show(struct seq_file *m, void *unused)
+{
+ void __iomem *bar0 = gsp_phys_gpu_bar0();
+
+ if (!bar0)
+ return -ENODEV;
+ seq_printf(m, "# PGRAPH register snapshot\n");
+ seq_printf(m, "PD_CYA 0x00406020 %08x\n", readl(bar0 + 0x00406020u));
+ seq_printf(m, "PD_HWW_ESR 0x00406018 %08x\n", readl(bar0 + 0x00406018u));
+ seq_printf(m, "DS_HWW_ESR 0x00405840 %08x\n", readl(bar0 + 0x00405840u));
+ seq_printf(m, "DS_HWW_ESR_2 0x00405848 %08x\n", readl(bar0 + 0x00405848u));
+ seq_printf(m, "DS_REPORT_MASK 0x00405844 %08x\n", readl(bar0 + 0x00405844u));
+ seq_printf(m, "DS_REPORT_MASK2 0x0040584c %08x\n", readl(bar0 + 0x0040584cu));
+ seq_printf(m, "DS_SPH_REGION_A 0x00405c1c %08x\n", readl(bar0 + 0x00405c1cu));
+ seq_printf(m, "DS_SPH_REGION_B 0x00405c20 %08x\n", readl(bar0 + 0x00405c20u));
+ seq_printf(m, "DS_SPH_STAGE1 0x00405c04 %08x\n", readl(bar0 + 0x00405c04u));
+ seq_printf(m, "DS_SPH_STAGE2 0x00405c08 %08x\n", readl(bar0 + 0x00405c08u));
+ seq_printf(m, "DS_SPH_STAGE5 0x00405c14 %08x\n", readl(bar0 + 0x00405c14u));
+ seq_printf(m, "PGRAPH_STATUS 0x00400700 %08x\n", readl(bar0 + 0x00400700u));
+ seq_printf(m, "GR_FIFO_STATUS 0x00400504 %08x\n", readl(bar0 + 0x00400504u));
+ return 0;
+}
+
+static int gsp_phys_pgraph_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gsp_phys_pgraph_show, NULL);
+}
+
+static ssize_t gsp_phys_pgraph_write(struct file *file,
+ const char __user *ubuf, size_t len,
+ loff_t *off)
+{
+ char buf[64];
+ unsigned long long addr = 0, val = 0;
+ void __iomem *bar0 = gsp_phys_gpu_bar0();
+ int n, has_val;
+ u32 old, now;
+
+ if (!bar0)
+ return -ENODEV;
+ if (len == 0 || len >= sizeof(buf))
+ return -EINVAL;
+ if (copy_from_user(buf, ubuf, len))
+ return -EFAULT;
+ buf[len] = '\0';
+ n = sscanf(buf, "%llx %llx", &addr, &val);
+ if (n < 1)
+ return -EINVAL;
+ has_val = (n >= 2);
+ if (addr < 0x00400000ull || addr >= 0x00410000ull)
+ return -EINVAL;
+ old = readl(bar0 + addr);
+ if (has_val) {
+ writel((u32)val, bar0 + addr);
+ now = readl(bar0 + addr);
+ pr_warn("vgsp-boot: pgraph-rw 0x%08llx old=%08x new=%08x (wrote %08llx)\n",
+ addr, old, now, val);
+ } else {
+ pr_info("vgsp-boot: pgraph-rw 0x%08llx = %08x (read)\n",
+ addr, old);
+ }
+ return (ssize_t)len;
+}
+
+static const struct file_operations gsp_phys_pgraph_fops = {
+ .owner = THIS_MODULE,
+ .open = gsp_phys_pgraph_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+ .write = gsp_phys_pgraph_write,
+};
+
int gsp_phys_boot_debugfs_init(void)
{
struct dentry *file;
@@ -30431,6 +30792,15 @@ int gsp_phys_boot_debugfs_init(void)
&gsp_phys_ds_spy_fops);
if (IS_ERR_OR_NULL(file))
pr_warn("vgsp-boot: debugfs file gsp-phys-ds-spy creation failed (DS SPH fault spy unavailable)\n");
+ /*
+ * SESSION 153-CONTINUED-93: live PGRAPH register lab (read snapshot +
+ * bounded writes inside the PGRAPH PRI window). Diagnostic only.
+ */
+ file = debugfs_create_file("gsp-phys-pgraph-rw", 0644,
+ gsp_phys_debugfs_root, NULL,
+ &gsp_phys_pgraph_fops);
+ if (IS_ERR_OR_NULL(file))
+ pr_warn("vgsp-boot: debugfs file gsp-phys-pgraph-rw creation failed (PGRAPH register lab unavailable)\n");
pr_info("vgsp-boot: debugfs files /sys/kernel/debug/vgsp/{gsp-phys-state read-only, gsp-phys-log-dump read-only, gsp-phys-status-drain read-only, gsp-phys-reg-probe read-only, gsp-phys-ds-spy read-only, gsp-phys-unload-rpc write-only, gsp-phys-rm-probe write-only, gsp-phys-rm-call write-only, gsp-phys-guest-gpa write-only, gsp-phys-guest-ram write-only} ready\n");
return 0;
}