summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-01 13:49:48 -0500
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-01 13:49:48 -0500
commit0c18f09e8aba7d1c4a37e4bddaa6915d8a21b431 (patch)
tree303cf825c88f0290e7f4f36dc6d50c8e352e97c2
parentbf5710a6a14fcaa277e3cac28da3eef976bb9e9b (diff)
downloadopen-iov-0c18f09e8aba7d1c4a37e4bddaa6915d8a21b431.tar.gz
open-iov-0c18f09e8aba7d1c4a37e4bddaa6915d8a21b431.zip
vgsp: CONT-95 - PROMOTE_CTX recorder (gsp_sema_service_ctx_promote_note/get w/ bInitialize from emulated promote loop) + CTX-BIND launch receipt (client MAIN/PATCH vs engine ctx + first-words readback) + gsp-phys-ctx-dump + engine-ctx CE readback (CE forbidden on GR ladder channel) - DECISIVE: r1323 CTX-BIND client-main=seen pa=0x114e00000 va=0x1200d0000 size=0x133000 attr=0x6 binit=1; client-patch=seen pa=0x12dcc8000 binit=1; replay-ctx pa=0x1f4000000 size=0x133000 fbmem=1 va=0x2200000 srv_ctxva=0; installed=0 (engine ctx is ladder's buffer NOT client promote); client-main content[0..7]=0, host read 100% zero bytes -> client promoted GR ctx carries no state; fw DS handler decoded (vaddr 0x52b8290 per-bit reporter: esr=a6000e20->0x73 SPH5; validation HW-side) - next: read engine ctx buffer VA 0x2200000/FBMEM 0x1f4000000 via secondary stage-3 COPY0 channel on same VAS (0xc5b5 copy block, dump gsp-phys-ctx-dump); structured -> drive fill, zeros -> reproduce GSP golden-ctx init - SESSION 153 r1322-r1323
-rw-r--r--kernel/gsp_phys_booter.c229
-rw-r--r--kernel/gsp_rm_emulation.c14
-rw-r--r--kernel/gsp_sema_service.c101
-rw-r--r--kernel/gsp_sema_service.h13
4 files changed, 356 insertions, 1 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 027c81c..0fd78a4 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -46,6 +46,7 @@
#include <linux/vgaarb.h>
#include <linux/acpi.h>
#include <linux/mm.h>
+#include <linux/vmalloc.h>
#include <asm/barrier.h>
#include <asm/unaligned.h>
@@ -23780,6 +23781,190 @@ static void gsp_phys_gr_pb_copy(const u32 *methods, u32 words)
}
}
+/*
+ * SESSION 153-CONTINUED-95: CTX-BIND launch receipt - is the client's
+ * promoted GR context buffer engine-visible on the replay channel?
+ *
+ * The guest driver promotes its per-channel context buffers through
+ * NV2080_CTRL_CMD_GPU_PROMOTE_CTX entries (MAIN bufferId 0 and PATCH
+ * bufferId 2 carry bInitialize=1 on a fresh context). The emulated GSP-RM
+ * answers SUCCESS and records the statements (gsp_sema_service_ctx_
+ * promote_note), but it does not install them anywhere: the replay
+ * channel's engine context is the buffer the kernel's own ladder built
+ * (server FBMEM MAIN, mapped at GSP_PHYS_RMCALL_VA_GR_CTX and committed by
+ * the mode-35 lane promote). This receipt logs, at the first launches of a
+ * boot, the exact promote statements next to the engine's binding so the
+ * "context state never replayed" hypothesis is decided by bytes: the
+ * client's MAIN/PATCH pa/va/size/bInitialize, and the first words of the
+ * client MAIN buffer read through the GPA->HPA map (a zeroed buffer is the
+ * expected state before a server-side ctx init that the emulation never
+ * performs).
+ */
+static void gsp_phys_ctx_bind_receipt(void)
+{
+ static u32 receipt_n;
+ u64 cpa = 0, cva = 0, csz = 0, ppa = 0, pva = 0, psz = 0;
+ u32 cattr = 0, cinit = 0, pattr = 0, pinit = 0;
+ bool have_main, have_patch;
+
+ if (receipt_n >= 4u)
+ return;
+ receipt_n++;
+ have_main = gsp_sema_service_ctx_promote_get(GSP_PHYS_RMCALL_ENGINE_GR0,
+ 0u, &cpa, &cva, &csz,
+ &cattr, &cinit) == 0;
+ have_patch = gsp_sema_service_ctx_promote_get(GSP_PHYS_RMCALL_ENGINE_GR0,
+ 2u, &ppa, &pva, &psz,
+ &pattr, &pinit) == 0;
+ pr_info("vgsp-gr: CTX-BIND client-main=%s pa=0x%llx va=0x%llx size=0x%llx attr=0x%x binit=%u; client-patch=%s pa=0x%llx va=0x%llx size=0x%llx binit=%u; replay-ctx pa=0x%llx size=0x%x fbmem=%u va=0x%llx srv_ctxva=%u; installed=0 (engine ctx is the ladder's buffer, NOT the client promote)\n",
+ have_main ? "seen" : "none", cpa, cva, csz, cattr, cinit,
+ have_patch ? "seen" : "none", ppa, pva, psz, pinit,
+ boot_stage.rmcall_ctx_pa, boot_stage.rmcall_ctx_size,
+ boot_stage.rmcall_ctx_fbmem ? 1u : 0u,
+ boot_stage.rmcall_ctx_va, boot_stage.rmcall_srv_ctxva ? 1u : 0u);
+ if (have_main && cpa) {
+ u64 hpa = 0;
+
+ if (gsp_phys_gpa_to_hpa_vgpu(NULL, cpa & ~(u64)(GSP_PHYS_PAGE_SIZE - 1), &hpa) == 0 &&
+ pfn_valid(hpa >> PAGE_SHIFT)) {
+ const u32 *w = (const u32 *)__va(hpa);
+
+ pr_info("vgsp-gr: CTX-BIND client-main content[0..7]=%08x %08x %08x %08x %08x %08x %08x %08x (gpa=0x%llx hpa=0x%llx)\n",
+ get_unaligned_le32((const u8 *)&w[0]),
+ get_unaligned_le32((const u8 *)&w[1]),
+ get_unaligned_le32((const u8 *)&w[2]),
+ get_unaligned_le32((const u8 *)&w[3]),
+ get_unaligned_le32((const u8 *)&w[4]),
+ get_unaligned_le32((const u8 *)&w[5]),
+ get_unaligned_le32((const u8 *)&w[6]),
+ get_unaligned_le32((const u8 *)&w[7]),
+ cpa, hpa);
+ } else {
+ pr_info("vgsp-gr: CTX-BIND client-main content: GPA 0x%llx -> HPA translation unavailable (no guest RAM registration yet)\n",
+ cpa);
+ }
+ }
+}
+
+/*
+ * SESSION 153-CONTINUED-95: engine context readback (gsp-phys-ctx-dump).
+ *
+ * What the DS validates SPHs against is the *engine's* context buffer, not
+ * this host's view of anything the client promoted. The replay channel's
+ * context is the ladder's buffer (server FBMEM MAIN) mapped into the
+ * channel VAS at GSP_PHYS_RMCALL_VA_GR_CTX with VIDMEM PTEs. The host has
+ * no aperture to FBMEM (no BAR1 window), so the copy engine reads it: one
+ * 4 KB CE copy per page from the ctx VA to a host page placed on a fresh
+ * never-reused scratch fetch line, polled on the module semaphore exactly
+ * like the P6a guest copies. Runs once per boot at the first launch (the
+ * channel is idle: single-inflight work item), while the ladder's channel
+ * is not server-memory and the identity DMA domain holds. Bounded: stops
+ * at the first refused/timed-out page and records how many bytes landed.
+ */
+#define GSP_PHYS_CTX_RB_BYTES (64u * 1024u)
+
+static u8 *gsp_phys_ctx_rb_buf;
+static u32 gsp_phys_ctx_rb_len;
+static bool gsp_phys_ctx_rb_tried;
+
+static const u8 *gsp_phys_ctx_readback_get(u32 *len)
+{
+ if (len)
+ *len = READ_ONCE(gsp_phys_ctx_rb_len);
+ return gsp_phys_ctx_rb_buf;
+}
+
+static void gsp_phys_ctx_readback_try(void)
+{
+ struct gsp_phys_boot_chunk *sem =
+ &boot_stage.rmcall_chunks[GSP_PHYS_RMCALL_BUF_SEM];
+ void __iomem *bar0;
+ struct page *pg;
+ u64 hpa;
+ u32 i, bytes = 0;
+
+ if (READ_ONCE(gsp_phys_ctx_rb_tried))
+ return;
+ if (!boot_stage.rmcall_ctx_valid || !boot_stage.rmcall_ctx_pa ||
+ !boot_stage.rmcall_channel ||
+ boot_stage.rmcall_channel_server_mem)
+ return;
+ /*
+ * CE submits are legal on this GR0 ladder (the ladder itself copies
+ * through the CE after the GR channel exists), but
+ * gsp_phys_backend_ladder_ready() refuses them on a GR0 ladder by
+ * design (gsp_phys_rmcall_gr_ce_refuse is about forwarding guest CE
+ * work, not the ladder's own copy). Gate the readback on the same
+ * pieces the submission needs: claimed, RM init-done, not unloading,
+ * a live token.
+ */
+ if (gsp_phys_backend_state() != GSP_PHYS_BACKEND_READY)
+ return;
+ if (!gsp_phys_gpu_claimed() || !boot_stage.rm_init_done ||
+ boot_stage.rm_unload_submitted || !boot_stage.rmcall_token)
+ return;
+ if (gsp_phys_dma_domain_identity() != 1)
+ return;
+ bar0 = gsp_phys_gpu_bar0();
+ if (!bar0)
+ return;
+ if (!gsp_phys_ctx_rb_buf) {
+ gsp_phys_ctx_rb_buf = vmalloc(GSP_PHYS_CTX_RB_BYTES);
+ if (!gsp_phys_ctx_rb_buf)
+ return;
+ }
+ pg = alloc_page(GFP_KERNEL);
+ if (!pg)
+ return;
+ hpa = (u64)page_to_phys(pg);
+ for (i = 0; i < GSP_PHYS_CTX_RB_BYTES / GSP_PHYS_PAGE_SIZE; i++) {
+ u64 src_va = 0, dst_va = 0, sem_va = 0;
+ u32 poll = 0;
+ unsigned long deadline;
+ int ret;
+
+ mutex_lock(&gsp_phys_rm_lock);
+ ret = gsp_phys_rmcall_guest_map_locked(hpa, hpa, 0, &src_va,
+ &dst_va, &sem_va);
+ if (ret) {
+ mutex_unlock(&gsp_phys_rm_lock);
+ break;
+ }
+ memset((u8 *)__va(hpa), 0xab, GSP_PHYS_PAGE_SIZE);
+ ret = gsp_phys_rmcall_ce_submit_locked(bar0,
+ GSP_PHYS_RMCALL_VA_GR_CTX +
+ (u64)i * GSP_PHYS_PAGE_SIZE,
+ dst_va, GSP_PHYS_RMCALL_VA_SEM,
+ GSP_PHYS_PAGE_SIZE,
+ GSP_PHYS_RMCALL_SEM_PAYLOAD, false, NULL);
+ mutex_unlock(&gsp_phys_rm_lock);
+ if (ret)
+ break;
+ deadline = jiffies + msecs_to_jiffies(250);
+ for (;;) {
+ poll = READ_ONCE(*(u32 *)gsp_phys_chunk_page_va(sem, 0));
+ if (poll == GSP_PHYS_RMCALL_SEM_PAYLOAD)
+ break;
+ if (time_after_eq(jiffies, deadline)) {
+ poll = 0;
+ break;
+ }
+ gsp_phys_rmcall_wait_quantum();
+ }
+ if (poll != GSP_PHYS_RMCALL_SEM_PAYLOAD)
+ break;
+ memcpy(gsp_phys_ctx_rb_buf + (size_t)i * GSP_PHYS_PAGE_SIZE,
+ (const u8 *)__va(hpa), GSP_PHYS_PAGE_SIZE);
+ bytes += GSP_PHYS_PAGE_SIZE;
+ }
+ __free_page(pg);
+ WRITE_ONCE(gsp_phys_ctx_rb_len, bytes);
+ WRITE_ONCE(gsp_phys_ctx_rb_tried, true);
+ pr_info("vgsp-gr: CTX-READBACK engine ctx pa=0x%llx copied=%u/%u bytes (CE copies from VA 0x%llx; server/own buffer, not the client promote; WPR2 untouched)\n",
+ boot_stage.rmcall_ctx_pa, bytes, GSP_PHYS_CTX_RB_BYTES,
+ GSP_PHYS_RMCALL_VA_GR_CTX);
+}
+
int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req,
struct gsp_phys_backend_copy_result *out)
{
@@ -23862,6 +24047,14 @@ int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req,
sked_tick = 0;
sked_first = true;
guest_param_pg = NULL;
+ /*
+ * SESSION 153-CONTINUED-95: prove the client's promoted GR context
+ * buffer binding at the first launches, then read the engine's own
+ * context buffer back for byte-level inspection (both bounded, both
+ * diagnostic; no behavior change to the launch itself).
+ */
+ gsp_phys_ctx_bind_receipt();
+ gsp_phys_ctx_readback_try();
u32 qmd_win_pde = 0, qmd_win_installed = 0;
int qmd_win_ret = 0;
bool qmd_win_diag = false;
@@ -30669,6 +30862,29 @@ static const struct file_operations gsp_phys_init_push_fops = {
.release = single_release,
};
+/*
+ * SESSION 153-CONTINUED-95: gsp-phys-ctx-dump - raw binary readback of the
+ * engine context buffer (see gsp_phys_ctx_readback_try). Empty until the
+ * first launch of the boot has run the CE readback.
+ */
+static ssize_t gsp_phys_ctx_dump_read(struct file *filp, char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ const u8 *buf;
+ u32 len = 0;
+
+ buf = gsp_phys_ctx_readback_get(&len);
+ if (!buf || !len)
+ return 0;
+ return simple_read_from_buffer(ubuf, count, ppos, buf, len);
+}
+
+static const struct file_operations gsp_phys_ctx_dump_fops = {
+ .owner = THIS_MODULE,
+ .read = gsp_phys_ctx_dump_read,
+ .llseek = default_llseek,
+};
+
int gsp_phys_boot_debugfs_init(void)
{
struct dentry *file;
@@ -30860,7 +31076,18 @@ int gsp_phys_boot_debugfs_init(void)
&gsp_phys_init_push_fops);
if (IS_ERR_OR_NULL(file))
pr_warn("vgsp-boot: debugfs file gsp-phys-init-push creation failed (init-push capture 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");
+ /*
+ * SESSION 153-CONTINUED-95: raw binary dump of the engine context
+ * buffer readback (64 KB from GSP_PHYS_RMCALL_VA_GR_CTX, taken once
+ * per boot at the first launch). 0444; empty until the first launch
+ * happens. Read with `dd if=...gsp-phys-ctx-dump of=/tmp/ctx.bin`.
+ */
+ file = debugfs_create_file("gsp-phys-ctx-dump", 0444,
+ gsp_phys_debugfs_root, NULL,
+ &gsp_phys_ctx_dump_fops);
+ if (IS_ERR_OR_NULL(file))
+ pr_warn("vgsp-boot: debugfs file gsp-phys-ctx-dump creation failed (engine ctx readback 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-ctx-dump 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;
}
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index 99f394c..d0f9e61 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -4395,6 +4395,20 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
*/
gsp_sema_service_promote_add(eva, pa, esz,
attr, bid);
+ /*
+ * SESSION 153-CONTINUED-95: keep the latest
+ * MAIN (0) / PATCH (2) statement per engine
+ * for the launch-time CTX-BIND receipt (the
+ * bInitialize flag is the client's "server,
+ * initialize this buffer" request; the
+ * emulation answers OK but never installs
+ * the buffer on the engine channel - the
+ * receipt makes that visible).
+ */
+ gsp_sema_service_ctx_promote_note(engine_type,
+ bid, pa, eva,
+ esz, attr,
+ e[30]);
}
}
out_status = 0;
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index c30cc22..234932c 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -3586,6 +3586,107 @@ static int svc_promo_lookup(u64 va, u64 *addr, int *ap)
}
/*
+ * SESSION 153-CONTINUED-95: per-(engine,bufferId) record of the guest
+ * driver's GR context-buffer promote statements, for the launch-time
+ * CTX-BIND receipt. The response-free sema promote table above is keyed by
+ * VA and answers translations; this one keeps the latest PROMOTE_CTX entry
+ * for the context buffers the mission asks about - MAIN (bufferId 0) and
+ * PATCH (bufferId 2) - including the bInitialize flag, so a launch can log
+ * whether the client's promoted GR context buffer is installed on the
+ * engine's replay channel (it is not: the channel's context is the
+ * server/own buffer the ladder built; this record proves which buffer the
+ * client believes is its context, with pa/va/size/init as sent).
+ */
+#define SVC_CTXP_MAX 8
+struct svc_ctxp_rec {
+ u32 engine;
+ u32 buffer_id;
+ u64 pa;
+ u64 va;
+ u64 size;
+ u32 phys_attr;
+ u32 b_init;
+ unsigned long seq;
+ bool valid;
+};
+static struct svc_ctxp_rec svc_ctxp[SVC_CTXP_MAX];
+static unsigned long svc_ctxp_seq;
+static DEFINE_SPINLOCK(svc_ctxp_lock);
+
+void gsp_sema_service_ctx_promote_note(u32 engine, u32 buffer_id, u64 pa,
+ u64 va, u64 size, u32 phys_attr,
+ u32 b_init)
+{
+ struct svc_ctxp_rec *rec = NULL, *free_rec = NULL, *oldest = NULL;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&svc_ctxp_lock, flags);
+ for (i = 0; i < SVC_CTXP_MAX; i++) {
+ if (svc_ctxp[i].valid &&
+ svc_ctxp[i].engine == engine &&
+ svc_ctxp[i].buffer_id == buffer_id) {
+ rec = &svc_ctxp[i];
+ break;
+ }
+ if (!svc_ctxp[i].valid && !free_rec)
+ free_rec = &svc_ctxp[i];
+ if (svc_ctxp[i].valid &&
+ (!oldest || svc_ctxp[i].seq < oldest->seq))
+ oldest = &svc_ctxp[i];
+ }
+ if (!rec)
+ rec = free_rec ? free_rec : oldest;
+ if (rec) {
+ rec->engine = engine;
+ rec->buffer_id = buffer_id;
+ rec->pa = pa;
+ rec->va = va;
+ rec->size = size;
+ rec->phys_attr = phys_attr;
+ rec->b_init = b_init;
+ rec->seq = ++svc_ctxp_seq;
+ rec->valid = true;
+ }
+ spin_unlock_irqrestore(&svc_ctxp_lock, flags);
+}
+
+int gsp_sema_service_ctx_promote_get(u32 engine, u32 buffer_id, u64 *pa,
+ u64 *va, u64 *size, u32 *phys_attr,
+ u32 *b_init)
+{
+ unsigned long flags;
+ u64 best_seq = 0;
+ int rc = -ENOENT;
+ int i;
+
+ spin_lock_irqsave(&svc_ctxp_lock, flags);
+ for (i = 0; i < SVC_CTXP_MAX; i++) {
+ const struct svc_ctxp_rec *rec = &svc_ctxp[i];
+
+ if (!rec->valid || rec->engine != engine ||
+ rec->buffer_id != buffer_id)
+ continue;
+ if (best_seq && rec->seq <= best_seq)
+ continue;
+ best_seq = rec->seq;
+ if (pa)
+ *pa = rec->pa;
+ if (va)
+ *va = rec->va;
+ if (size)
+ *size = rec->size;
+ if (phys_attr)
+ *phys_attr = rec->phys_attr;
+ if (b_init)
+ *b_init = rec->b_init;
+ rc = 0;
+ }
+ spin_unlock_irqrestore(&svc_ctxp_lock, flags);
+ return rc;
+}
+
+/*
* VA shadow: page-granular backing for device VAs whose VA->PA translation
* cannot be derived from the guest's page tables.
*
diff --git a/kernel/gsp_sema_service.h b/kernel/gsp_sema_service.h
index 24e1094..f2a40ee 100644
--- a/kernel/gsp_sema_service.h
+++ b/kernel/gsp_sema_service.h
@@ -108,6 +108,19 @@ void gsp_sema_service_vaspace_del(u32 h_vaspace);
void gsp_sema_service_promote_add(u64 va, u64 pa, u64 size, u32 phys_attr,
u32 buffer_id);
/*
+ * SESSION 153-CONTINUED-95: latest PROMOTE_CTX statement per
+ * (engine, bufferId), kept for the launch-time CTX-BIND receipt. MAIN is
+ * bufferId 0, PATCH bufferId 2 (NV2080_CTRL_GPU_PROMOTE_CTX_BUFFER_ID_*).
+ * The note side never fails; the get side returns -ENOENT until the guest
+ * driver has promoted that buffer.
+ */
+void gsp_sema_service_ctx_promote_note(u32 engine, u32 buffer_id, u64 pa,
+ u64 va, u64 size, u32 phys_attr,
+ u32 b_init);
+int gsp_sema_service_ctx_promote_get(u32 engine, u32 buffer_id, u64 *pa,
+ u64 *va, u64 *size, u32 *phys_attr,
+ u32 *b_init);
+/*
* r496j: park a page-directory candidate (from COPY_SERVER_PDES) until a
* content-validated walk proves it belongs to a live client; only then is
* it registered (a blind registration let stale kernel directories answer