summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--kernel/gsp_sema_service.c7
-rw-r--r--tools/guest-diag/guest_pmap_win.py47
-rw-r--r--tools/guest-diag/pin_gr_window.py184
-rwxr-xr-xtools/guest-diag/push_guest_tools.sh1
4 files changed, 237 insertions, 2 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index b9e7db3..1296f81 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -178,9 +178,12 @@ static u32 svc_map_hash[SEMA_MAP_MAX * 2];
#define SVC_PIN_PROBE 128
/*
* Host-injected translations (debug `sema_map_add`): authoritative, never
- * overwritten by self-discovery, checked before every other source.
+ * overwritten by self-discovery, checked before every other source. The GR
+ * push-buffer window pins thousands of client pages per boot (one entry per
+ * 4 KB page), so the table must hold them plus the CE push-buffer region
+ * (4096 pages) without dropping inserts.
*/
-#define SVC_IPIN_SLOTS (1u << 13)
+#define SVC_IPIN_SLOTS (1u << 15)
#define SVC_IPIN_PROBE 64
struct svc_pin {
diff --git a/tools/guest-diag/guest_pmap_win.py b/tools/guest-diag/guest_pmap_win.py
new file mode 100644
index 0000000..a9b74a7
--- /dev/null
+++ b/tools/guest-diag/guest_pmap_win.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+# guest_pmap_win.py - dump present pages of a process's VA range through the
+# guest's own pagemap (root in the guest). Batched reads (512 entries per
+# pread) so a 256 MB window takes about a second, not minutes.
+#
+# Usage: sudo python3 guest_pmap_win.py <pid> <lo-hex> <hi-hex> [max-pairs] [pa-max-hex]
+# Output: one "0x<va>,0x<pa>" line per present page (ascending VA), capped at
+# max-pairs. PAs of 0 are skipped; pages above pa-max are skipped unless 0.
+import os
+import sys
+
+pid = int(sys.argv[1])
+lo = int(sys.argv[2], 16) & ~0xFFF
+hi = int(sys.argv[3], 16)
+maxp = int(sys.argv[4]) if len(sys.argv) > 4 else 20000
+pa_max = int(sys.argv[5], 16) if len(sys.argv) > 5 else 0
+
+BATCH = 512
+out = []
+fd = os.open('/proc/%d/pagemap' % pid, os.O_RDONLY)
+try:
+ va = lo
+ while va < hi:
+ n = min(BATCH, (hi - va) // 0x1000)
+ if n <= 0:
+ break
+ os.lseek(fd, (va // 4096) * 8, 0)
+ data = os.read(fd, n * 8)
+ got = len(data) // 8
+ for i in range(got):
+ e = int.from_bytes(data[i * 8:i * 8 + 8], 'little')
+ if not ((e >> 63) & 1):
+ continue
+ pfn = e & ((1 << 55) - 1)
+ pa = pfn << 12
+ if not pa:
+ continue
+ if pa_max and pa >= pa_max:
+ continue
+ out.append('0x%x,0x%x' % (va + i * 0x1000, pa))
+ if len(out) >= maxp:
+ print('\n'.join(out))
+ sys.exit(0)
+ va += got * 0x1000
+finally:
+ os.close(fd)
+print('\n'.join(out))
diff --git a/tools/guest-diag/pin_gr_window.py b/tools/guest-diag/pin_gr_window.py
new file mode 100644
index 0000000..4a968c9
--- /dev/null
+++ b/tools/guest-diag/pin_gr_window.py
@@ -0,0 +1,184 @@
+#!/usr/bin/env python3
+# pin_gr_window.py - pin the CUDA client's GR push-buffer pages from guest
+# truth, per boot (host-side dev tool, no guest changes beyond the helper).
+#
+# Why: the compute (GR) channels' GPFIFO entries point at pushes that live in
+# the client's GPU VA window (observed one allocation per channel starting at
+# 0x200400000 + stride). The pushes are sysmem pages mapped into the client
+# process, so the guest's /proc/<pid>/pagemap gives their GPA. Injecting those
+# translations (sema_map_add) is what lets the pump read the GR pushes and
+# apply the tracking-semaphore releases they carry - the wall documented in
+# PART 61 (pushes_nomap / deferred releases).
+#
+# The tool re-scans on an interval so pages mapped after the first pass are
+# caught, and re-injects when a new client pid appears (pins are per-process
+# pages; the same VA usually has a different PA on the next client/boot).
+#
+# Usage: tools/guest-diag/pin_gr_window.py [interval-seconds]
+# Env:
+# PINWIN_LO/PINWIN_HI VA window to scan (default 0x200400000..0x206000000)
+# PINWIN_MAX max present pages per pass (default 20000)
+# PINWIN_PA_MAX ignore PAs at/above this (skip device BARs)
+# (default 0x180000000)
+# PINWIN_VERIFY 1 = probe the first pins on the first batch (default)
+
+import os
+import re
+import subprocess
+import sys
+import time
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+VMEXEC = os.path.normpath(os.path.join(HERE, "..", "..", "scripts", "vgpu_vm.py"))
+PARAM = "/sys/module/open_iov_vfio/parameters"
+HELPER = os.path.join(HERE, "guest_pmap_win.py")
+
+LO = int(os.environ.get("PINWIN_LO", "0x200400000"), 16)
+HI = int(os.environ.get("PINWIN_HI", "0x206000000"), 16)
+MAXP = int(os.environ.get("PINWIN_MAX", "20000"))
+PAMAX = int(os.environ.get("PINWIN_PA_MAX", "0x180000000"), 16)
+VERIFY = os.environ.get("PINWIN_VERIFY", "1") == "1"
+
+
+def log(msg):
+ print("[%s] %s" % (time.strftime("%H:%M:%S"), msg), flush=True)
+
+
+def vexec(cmd, timeout=60):
+ try:
+ r = subprocess.run([VMEXEC, "exec", "--timeout", str(timeout - 5), cmd],
+ capture_output=True, text=True, timeout=timeout)
+ return r.stdout
+ except Exception:
+ return ""
+
+
+def push_helper():
+ with open(HELPER) as f:
+ b64 = subprocess.run(["base64", "-w0"], stdin=f,
+ capture_output=True, text=True).stdout
+ for _ in range(5):
+ vexec("echo '%s' | base64 -d > /tmp/guest_pmap_win.py" % b64, 40)
+ out = vexec("test -s /tmp/guest_pmap_win.py && echo PUSHED", 20)
+ if "PUSHED" in out:
+ return True
+ time.sleep(1)
+ return False
+
+
+def cdt_pid():
+ out = vexec("pgrep -x cdt", 25)
+ m = re.search(r"^\s*(\d+)\s*$", out, re.M)
+ return int(m.group(1)) if m else None
+
+
+def scan(pid):
+ out = vexec("sudo python3 /tmp/guest_pmap_win.py %d 0x%x 0x%x %d 0x%x"
+ % (pid, LO, HI, MAXP, PAMAX), 90)
+ pairs = []
+ for line in out.splitlines():
+ m = re.match(r"^(0x[0-9a-f]+),(0x[0-9a-f]+)$", line.strip())
+ if m:
+ pairs.append((int(m.group(1), 16), int(m.group(2), 16)))
+ return pairs
+
+
+def inject(va, pa, ln):
+ cmd = "printf '0x%x,0x%x,0x%x,1' > %s/sema_map_add" % (va, pa, ln, PARAM)
+ for _ in range(60):
+ r = subprocess.run(["sudo", "-n", "sh", "-c", cmd],
+ capture_output=True, text=True)
+ if r.returncode == 0 and "denied" not in (r.stderr or ""):
+ return True
+ time.sleep(0.05)
+ return False
+
+
+def runs_of(pairs):
+ """Group (va,pa) pairs into contiguous VA+PA runs."""
+ pairs = sorted(set(pairs))
+ runs = []
+ cur_va, cur_pa, cur_n = pairs[0][0], pairs[0][1], 1
+ for va, pa in pairs[1:]:
+ if va == cur_va + cur_n * 0x1000 and pa == cur_pa + cur_n * 0x1000:
+ cur_n += 1
+ continue
+ runs.append((cur_va, cur_pa, cur_n * 0x1000))
+ cur_va, cur_pa, cur_n = va, pa, 1
+ runs.append((cur_va, cur_pa, cur_n * 0x1000))
+ return runs
+
+
+def probe(va):
+ subprocess.run(["sudo", "-n", "sh", "-c",
+ "printf '0x%x,2' > %s/sema_probe" % (va, PARAM)],
+ capture_output=True, text=True)
+ time.sleep(0.4)
+ r = subprocess.run(["sudo", "-n", "dmesg"], capture_output=True, text=True)
+ tail = r.stdout.splitlines()[-14:]
+ vals = []
+ for line in tail:
+ m = re.search(r"sema-probe:.*\[(\d+)\] 0x([0-9a-f]+)", line)
+ if m:
+ vals.append((int(m.group(1)), int(m.group(2), 16)))
+ return vals
+
+
+def main():
+ interval = float(sys.argv[1]) if len(sys.argv) > 1 else 2.0
+ if not push_helper():
+ log("FATAL: could not push guest helper")
+ return 1
+ log("watching window 0x%x..0x%x (pamax 0x%x, max %d, interval %.1fs)"
+ % (LO, HI, PAMAX, MAXP, interval))
+ seen = {}
+ pid = None
+ verified = False
+ while True:
+ p = cdt_pid()
+ if not p:
+ pid = None
+ time.sleep(interval)
+ continue
+ if p != pid:
+ pid = p
+ seen = {}
+ log("new cdt pid %d" % pid)
+ try:
+ pairs = scan(pid)
+ except Exception as e:
+ log("scan failed: %s" % e)
+ time.sleep(interval)
+ continue
+ new = [(va, pa) for va, pa in pairs if seen.get(va) != pa]
+ if new:
+ runs = runs_of(new)
+ npages = 0
+ nfail = 0
+ for va, pa, ln in runs:
+ if inject(va, pa, ln):
+ npages += ln // 0x1000
+ else:
+ nfail += 1
+ for i in range(ln // 0x1000):
+ if inject(va + i * 0x1000, pa + i * 0x1000, 0x1000):
+ npages += 1
+ else:
+ nfail += 1
+ for va, pa in new:
+ seen[va] = pa
+ log("pid=%d present=%d new=%d pinned=%dp/%dr fail=%d"
+ % (pid, len(pairs), len(new), npages, len(runs), nfail))
+ for va, pa in new[:4]:
+ log(" va=0x%x -> pa=0x%x" % (va, pa))
+ if VERIFY and not verified and new:
+ verified = True
+ for va, _pa in new[:2]:
+ vals = probe(va)
+ log(" probe va=0x%x: %s" % (va, " ".join(
+ "0x%x" % v for _, v in vals) or "(no reply)"))
+ time.sleep(interval)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tools/guest-diag/push_guest_tools.sh b/tools/guest-diag/push_guest_tools.sh
index a96fe68..34866d2 100755
--- a/tools/guest-diag/push_guest_tools.sh
+++ b/tools/guest-diag/push_guest_tools.sh
@@ -33,6 +33,7 @@ push_b64 "$HERE/sem_counters.py" /tmp/sem_counters.py
push_b64 "$HERE/sem_slots.py" /tmp/sem_slots.py
push_b64 "$HERE/read_va.py" /tmp/read_va.py
push_b64 "$HERE/guest_pmap.py" /tmp/guest_pmap.py
+push_b64 "$HERE/guest_pmap_win.py" /tmp/guest_pmap_win.py
push_b64 "$HERE/track_waits.sh" /tmp/track_waits.sh
push_b64 "$HERE/guest_kprobes.sh" /tmp/kprobes.sh