summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--kernel/gsp_phys_booter.c25
-rw-r--r--kernel/gsp_sema_service.c138
-rw-r--r--kernel/gsp_sema_service.h3
-rw-r--r--kernel/main.c32
4 files changed, 198 insertions, 0 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 94d5f5f..9b1294a 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -18092,6 +18092,20 @@ MODULE_PARM_DESC(sema_gl_sdm_drop,
"Drop single-word SDM-type entries (SEC_OP 0/2/6) from 3D window replays, the MME prelude and the attr prelude (default 1)");
/*
+ * Whole-vidmem page-hash bracket for the forwarded GR launch path. When set,
+ * hash every 4 KB page of the first open device's BAR1 shadow immediately
+ * before the publish, then re-hash once the output retirement wait returns
+ * and log the pages whose content changed. This proves whether the engine
+ * wrote any vidmem page, not just the output pages the copy-out samples.
+ * Default 0: no allocation, no hashing and no change to the submit path.
+ */
+static bool gsp_phys_gr_vidmem_bracket;
+module_param_named(sema_gl_vidmem_bracket, gsp_phys_gr_vidmem_bracket, bool,
+ 0644);
+MODULE_PARM_DESC(sema_gl_vidmem_bracket,
+ "Hash every vidmem page before and after a forwarded GR launch to report writes outside the sampled output pages (default 0)");
+
+/*
* SESSION 153-CONTINUED-53: MME context-setup prelude. The r1073-r1077
* in-flight capture proved the draw-window replay raises a real MME
* ILLEGAL_OPCODE on CALL_MME_MACRO(2)=0x3810 (mme_pc=0, mme_ir=1, mme_mac
@@ -28941,6 +28955,14 @@ gr_page_unmapped:
*/
gsp_phys_gr_wit_capture(req);
+ if (gsp_phys_gr_vidmem_bracket) {
+ int hret = gsp_sema_service_vidmem_hash_snapshot();
+
+ if (hret)
+ pr_warn_ratelimited("vgsp-gr: VIDMEM-BRACKET snapshot failed rc=%d\n",
+ hret);
+ }
+
ret = gsp_phys_rmcall_publish_locked(bar0, boot_stage.rmcall_va_pb,
words, &sub);
mutex_unlock(&gsp_phys_rm_lock);
@@ -30146,6 +30168,9 @@ gr_page_unmapped:
u32 wrote = gsp_phys_gr_output_retire_wait(req, bounce_csum_in,
&n_tracked);
+ if (gsp_phys_gr_vidmem_bracket)
+ gsp_sema_service_vidmem_hash_diff("launch", 64);
+
/* CONT-73: where did the draw write? bounce count above;
* direct guest stores (relocated alias/shadow operands and
* MME shadow-scratch destinations) here. */
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 64139f0..498198d 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -41074,6 +41074,138 @@ int gsp_sema_service_find_vidmem(u64 pattern, u64 max_hits)
}
/*
+ * Whole-vidmem page-hash bracket. Snapshot a hash of every 4 KB page of the
+ * first open device's BAR1 shadow, then re-hash later and report the pages
+ * whose content changed. This proves whether an engine wrote any page of the
+ * emulated vidmem, including pages the output copy-out does not sample.
+ * The hash array is allocated once per shadow size and kept until the
+ * service stops.
+ */
+static DEFINE_MUTEX(gsp_sema_vidmem_hash_lock);
+static u64 *gsp_sema_vidmem_hash;
+static unsigned int gsp_sema_vidmem_hash_pages;
+
+static inline u64 gsp_sema_vidmem_fnv1a64(const u8 *p, size_t len)
+{
+ u64 h = 0xcbf29ce484222325ULL;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ h ^= p[i];
+ h *= 0x100000001b3ULL;
+ }
+ return h;
+}
+
+/*
+ * Resolve the shadow to bracket: the first mdev a guest has open, the same
+ * resolution the vidmem debug helpers use. Only whole 4 KB pages inside
+ * bars[1] are hashed.
+ */
+static int gsp_sema_vidmem_hash_target(u8 **base, u64 *size)
+{
+ struct nv_vgpu_device *vgpu = NULL;
+ struct nv_vgpu_device *d;
+
+ mutex_lock(&nv_vgpu_driver.lock);
+ list_for_each_entry(d, &nv_vgpu_driver.devices, list) {
+ if (atomic_read(&d->vfio_dev_open_count) > 0) {
+ vgpu = d;
+ break;
+ }
+ }
+ mutex_unlock(&nv_vgpu_driver.lock);
+ if (!vgpu || !vgpu->bars[1].vaddr || !vgpu->bars[1].size)
+ return -ENODEV;
+ *base = vgpu->bars[1].vaddr;
+ *size = vgpu->bars[1].size;
+ return 0;
+}
+
+int gsp_sema_service_vidmem_hash_snapshot(void)
+{
+ u8 *base;
+ u64 size;
+ unsigned int pages, i;
+ int ret;
+
+ ret = gsp_sema_vidmem_hash_target(&base, &size);
+ if (ret)
+ return ret;
+ pages = (unsigned int)(size >> SEMA_PAGE_SHIFT);
+ if (!pages)
+ return -ENODEV;
+
+ mutex_lock(&gsp_sema_vidmem_hash_lock);
+ if (!gsp_sema_vidmem_hash || gsp_sema_vidmem_hash_pages != pages) {
+ u64 *hashes;
+
+ hashes = vmalloc((size_t)pages * sizeof(*hashes));
+ if (!hashes) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
+ vfree(gsp_sema_vidmem_hash);
+ gsp_sema_vidmem_hash = hashes;
+ gsp_sema_vidmem_hash_pages = pages;
+ }
+ for (i = 0; i < pages; i++)
+ gsp_sema_vidmem_hash[i] = gsp_sema_vidmem_fnv1a64(
+ base + ((u64)i << SEMA_PAGE_SHIFT), SEMA_PAGE_SIZE);
+ ret = 0;
+out_unlock:
+ mutex_unlock(&gsp_sema_vidmem_hash_lock);
+ return ret;
+}
+
+int gsp_sema_service_vidmem_hash_diff(const char *tag, unsigned int max_pages)
+{
+ static DEFINE_RATELIMIT_STATE(detail_rs, 5 * HZ, 4);
+ u8 *base;
+ u64 size;
+ unsigned int pages, i, changed = 0;
+ const char *name = tag ? tag : "diff";
+ bool detail;
+ int ret;
+
+ ret = gsp_sema_vidmem_hash_target(&base, &size);
+ if (ret)
+ return ret;
+ pages = (unsigned int)(size >> SEMA_PAGE_SHIFT);
+ if (!pages)
+ return -ENODEV;
+ if (max_pages > 256)
+ max_pages = 256;
+
+ mutex_lock(&gsp_sema_vidmem_hash_lock);
+ if (!gsp_sema_vidmem_hash || gsp_sema_vidmem_hash_pages != pages) {
+ mutex_unlock(&gsp_sema_vidmem_hash_lock);
+ return -EINVAL;
+ }
+ detail = __ratelimit(&detail_rs);
+ for (i = 0; i < pages; i++) {
+ const u8 *p = base + ((u64)i << SEMA_PAGE_SHIFT);
+ u64 h = gsp_sema_vidmem_fnv1a64(p, SEMA_PAGE_SIZE);
+
+ if (h == gsp_sema_vidmem_hash[i])
+ continue;
+ changed++;
+ if (detail && changed <= max_pages) {
+ u64 w0;
+
+ memcpy(&w0, p, sizeof(w0));
+ pr_info("vgsp-gr: VIDMEM-BRACKET page=0x%x off=0x%llx w0=0x%016llx hash=0x%016llx\n",
+ i, (unsigned long long)i << SEMA_PAGE_SHIFT,
+ w0, h);
+ }
+ }
+ mutex_unlock(&gsp_sema_vidmem_hash_lock);
+ pr_info_ratelimited("vgsp-gr: VIDMEM-BRACKET %s changed=%u/%u\n",
+ name, changed, pages);
+ return (int)changed;
+}
+
+/*
* Register a channel's GPU completion counter (guest PA of the u32 the GPU
* would advance as pushes complete). Called from the sema_counter debug
* parameter; plain state update, so it runs in the caller's context.
@@ -41813,6 +41945,12 @@ void gsp_sema_service_stop(void)
svc_setup_demand_debugfs_fini();
/* A fresh session must not inherit shadowed bytes from the last one. */
svc_vas_free_all();
+ /* Drop the bracket hash array; a later session allocates it again. */
+ mutex_lock(&gsp_sema_vidmem_hash_lock);
+ vfree(gsp_sema_vidmem_hash);
+ gsp_sema_vidmem_hash = NULL;
+ gsp_sema_vidmem_hash_pages = 0;
+ mutex_unlock(&gsp_sema_vidmem_hash_lock);
}
/*
diff --git a/kernel/gsp_sema_service.h b/kernel/gsp_sema_service.h
index 1e0dac6..f63b9fa 100644
--- a/kernel/gsp_sema_service.h
+++ b/kernel/gsp_sema_service.h
@@ -149,6 +149,9 @@ int gsp_sema_service_dump_vidmem(u64 addr, u64 len);
int gsp_sema_service_xw_mirror(u64 cnt_pa, u64 mem_pa);
int gsp_sema_service_xw_comp(u64 comp_pa);
int gsp_sema_service_find_vidmem(u64 pattern, u64 max_hits);
+int gsp_sema_service_vidmem_hash_snapshot(void);
+int gsp_sema_service_vidmem_hash_diff(const char *tag,
+ unsigned int max_pages);
int gsp_sema_service_pdb_discover(u64 ring_va, u64 ring_pa);
int gsp_sema_service_chan_rewind(u32 h_object);
int gsp_sema_service_counter_add(u32 h_object, u64 pa);
diff --git a/kernel/main.c b/kernel/main.c
index c96abf8..458ba36 100644
--- a/kernel/main.c
+++ b/kernel/main.c
@@ -3660,6 +3660,38 @@ module_param_cb(dump_vidmem, &nv_vgpu_dump_vidmem_ops, NULL, 0200);
MODULE_PARM_DESC(dump_vidmem, "Dump emulated vidmem range (hex offset,hex len)");
/*
+ * Whole-vidmem page-hash bracket: snapshot every 4 KB page hash of the
+ * emulated vidmem, then re-hash and report the pages whose content changed.
+ * echo 1 > .../parameters/vidmem_hash_snap
+ * echo 1 > .../parameters/vidmem_hash_diff
+ */
+static int nv_vgpu_vidmem_hash_snap_set(const char *val,
+ const struct kernel_param *kp)
+{
+ return gsp_sema_service_vidmem_hash_snapshot();
+}
+
+static int nv_vgpu_vidmem_hash_diff_set(const char *val,
+ const struct kernel_param *kp)
+{
+ return gsp_sema_service_vidmem_hash_diff("manual", 64);
+}
+
+static const struct kernel_param_ops nv_vgpu_vidmem_hash_snap_ops = {
+ .set = nv_vgpu_vidmem_hash_snap_set,
+};
+
+static const struct kernel_param_ops nv_vgpu_vidmem_hash_diff_ops = {
+ .set = nv_vgpu_vidmem_hash_diff_set,
+};
+
+module_param_cb(vidmem_hash_snap, &nv_vgpu_vidmem_hash_snap_ops, NULL, 0200);
+MODULE_PARM_DESC(vidmem_hash_snap, "Snapshot whole-vidmem page hashes");
+module_param_cb(vidmem_hash_diff, &nv_vgpu_vidmem_hash_diff_ops, NULL, 0200);
+MODULE_PARM_DESC(vidmem_hash_diff,
+ "Diff whole-vidmem page hashes (tag manual, first 64 pages)");
+
+/*
* Debug: hex-dump or scan the BAR0 register shadow. Guest MMIO writes that go
* through the mapped shadow never reach the write callback, so this is the
* only way to observe registers the guest writes directly (e.g. the