diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-11-04 17:40:47 -0500 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-11-04 17:40:47 -0500 |
| commit | b6c8e74164716acdcdb3d34e05966deec4f9f35b (patch) | |
| tree | ec10cd351feca570a7b256a3279f78f566c57f0e | |
| parent | 70d8f709224ed4d2064bff8d176db3dbdb74fce5 (diff) | |
| download | open-iov-b6c8e74164716acdcdb3d34e05966deec4f9f35b.tar.gz open-iov-b6c8e74164716acdcdb3d34e05966deec4f9f35b.zip | |
vgsp-diag: whole-vidmem page-hash bracket - sema_gl_vidmem_bracket hashes every 4KB page of the BAR1 shadow before the GR publish and diffs after the output-retirement wait, reporting any changed page outside the sampled output set; vidmem_hash_snap/vidmem_hash_diff manual module params; default 0 is a strict no-op
| -rw-r--r-- | kernel/gsp_phys_booter.c | 25 | ||||
| -rw-r--r-- | kernel/gsp_sema_service.c | 138 | ||||
| -rw-r--r-- | kernel/gsp_sema_service.h | 3 | ||||
| -rw-r--r-- | kernel/main.c | 32 |
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 |
