summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-12 12:13:10 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-12 12:13:10 -0400
commit1612492982479bc4e1f722c8e52d22ef37b0280f (patch)
treed3816aaf013a5b7611c11089469868908b3cf6b1 /tools
parent9521339338f096d76fe22c3496ff435a48dffba9 (diff)
downloadopen-iov-1612492982479bc4e1f722c8e52d22ef37b0280f.tar.gz
open-iov-1612492982479bc4e1f722c8e52d22ef37b0280f.zip
vgsp-sema: pair CE push-buffer vmap pages with the right offset; validated rescues repair injections
The CE push-buffer uvm_rm_mem is vmalloc'd as a 4097-page area: one leading page plus the 4096-page (16 MB) GPU data window, so GPU data page k lives at vmap page k+1. The kcore chain resolver paired page k with vmap page k, pinning the adjacent push for every page; the pump then read the wrong push, decode failed, and the host fell into the content rescue which crawled one entry per ~2 s (r115: rescue_run=4202, entry_va=0x12001b18c repeating). Verified against five independent content-validated rescue anchors (k=20..28, all delta +1), and the vmap area boundary confirms the extra page (nv_vm_map_pages 4097 pages). Five fixes: - resolve_mem_pins() takes page_off; the CE chain and uvbp push-buffer paths pass mem_page_off() (1 when the vmap is exactly 16 MB + 1 page; 0 for direct-mapped mems like the semaphore pool). - a content-validated rescue page now also replaces the injected-pin entry (svc_rescue_try / svc_rescue_run_try), so a stale or wrong injection can no longer shadow the validated page and re-run the rescue on every pass. Evidence: r115 rescue anchors va 0x120014000->pa 0x12a2e2000 .. va 0x12001c000->pa 0x12a2de000 all at vmap offset k+1; the candidate pages decode as the adjacent push (wrong semaphore payload) before the fix.
Diffstat (limited to 'tools')
-rw-r--r--tools/guest-diag/guest_pin_stream.py40
1 files changed, 33 insertions, 7 deletions
diff --git a/tools/guest-diag/guest_pin_stream.py b/tools/guest-diag/guest_pin_stream.py
index 35cbfb9..2c1a1a3 100644
--- a/tools/guest-diag/guest_pin_stream.py
+++ b/tools/guest-diag/guest_pin_stream.py
@@ -266,11 +266,17 @@ def _vmalloc_pages_for(kc, va):
return 0
-def resolve_mem_pins(kc, mem, out, default_pages=16, va_lo=0, va_hi=0):
+def resolve_mem_pins(kc, mem, out, default_pages=16, va_lo=0, va_hi=0,
+ page_off=0):
"""uvm_rm_mem kptr -> GPU-VA/GPA runs (direct map or page-table walk).
va_lo/va_hi optionally restrict the emitted GPU VA range; the kcore
- chain resolver targets the CE push-buffer window only.
+ chain resolver targets the CE push-buffer window only. page_off shifts
+ the vmap page paired with GPU data page k (the CE push-buffer vmap is
+ a 4097-page area = 1 header page + 4096 data pages, so data page k
+ lives at vmap page k+1; pairing k with k pinned the previous page and
+ every entry read returned the adjacent push - the second-session
+ content-rescue crawl).
"""
if not is_kern_ptr(mem):
return 0
@@ -297,7 +303,7 @@ def resolve_mem_pins(kc, mem, out, default_pages=16, va_lo=0, va_hi=0):
runs = []
cur = None
for k in range(npages):
- pa = kc.va_to_pa(cpu + k * 0x1000)
+ pa = kc.va_to_pa(cpu + (k + page_off) * 0x1000)
if pa is None:
if cur:
runs.append(cur)
@@ -318,6 +324,23 @@ def resolve_mem_pins(kc, mem, out, default_pages=16, va_lo=0, va_hi=0):
return emitted
+def mem_page_off(kc, mem):
+ """vmap-page offset for a uvm_rm_mem's GPU data pages.
+
+ The CE push-buffer allocation's vmap is one page longer than its GPU
+ data window (16 MB of data + one leading page), so GPU data page k is
+ vmap page k+1. Content-validated against the host's rescue anchors
+ (five independent (va,pa) pairs, k=20..28, all delta +1).
+ """
+ vas = [kc.read64(mem + 0x10 + i * 8) for i in range(16)]
+ cpu = next((v for v in vas if is_kern_ptr(v)), None)
+ if not cpu:
+ return 0
+ if _direct_map_pa(kc, cpu) is not None:
+ return 0
+ return 1 if _vmalloc_pages_for(kc, cpu) == 4097 else 0
+
+
def _emit_ring_pins(kc, gpu_va, entries_cpu, entries, out):
"""Pin the GPFIFO ring pages (and the pages after it, where the GPPUT
buffer usually sits) from the ring's CPU mapping."""
@@ -444,7 +467,8 @@ def resolve_pb_pins(kc, pb, out):
mem = kc.read64(pb + 8)
if not mem:
return 0
- return resolve_mem_pins(kc, mem, out, default_pages=64)
+ return resolve_mem_pins(kc, mem, out, default_pages=64,
+ page_off=mem_page_off(kc, mem))
# The CE push-buffer window is the one UVM region the client never maps into
@@ -510,12 +534,14 @@ def resolve_pb_chain(kc, sem, out):
continue
RESOLVED_KC_PB.add(mem)
n = resolve_mem_pins(kc, mem, out, default_pages=64,
- va_lo=PB_CE_LO, va_hi=PB_CE_HI)
+ va_lo=PB_CE_LO, va_hi=PB_CE_HI,
+ page_off=mem_page_off(kc, mem))
try:
with open('/tmp/guest_pin_stream.log', 'a') as f:
f.write('%s kc-chain CE push-buffer mem=0x%x sem=0x%x '
- 'pin-runs=%d\n'
- % (time.strftime('%T'), mem, sem, n))
+ 'pin-runs=%d off=%d\n'
+ % (time.strftime('%T'), mem, sem, n,
+ mem_page_off(kc, mem)))
except OSError:
pass
return found