summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/guest-diag/semfix_watch.py97
1 files changed, 72 insertions, 25 deletions
diff --git a/tools/guest-diag/semfix_watch.py b/tools/guest-diag/semfix_watch.py
index 7730e86..400fafe 100644
--- a/tools/guest-diag/semfix_watch.py
+++ b/tools/guest-diag/semfix_watch.py
@@ -34,6 +34,7 @@
import json
import os
import re
+import select
import socket
import subprocess
import sys
@@ -47,8 +48,8 @@ QMP_SOCK = os.environ.get("QMP_SOCK", "/tmp/qemu-qmp.sock")
GPU_VA_LO = 0x100000000
GPU_VA_HI = 0x2000000000
PUSHBUF_VA_LO = int(os.environ.get("PUSHBUF_VA_LO", "0x120000000"), 16)
-PUSHBUF_VA_HI = int(os.environ.get("PUSHBUF_VA_HI", "0x120400000"), 16)
-PUSHBUF_MAX_PAGES = 1024
+PUSHBUF_VA_HI = int(os.environ.get("PUSHBUF_VA_HI", "0x124000000"), 16)
+PUSHBUF_MAX_PAGES = int(os.environ.get("PUSHBUF_MAX_PAGES", "4096"))
CHAN_WINDOW_BACK = int(os.environ.get("CHAN_WINDOW_BACK", "0x4000"), 16)
CHAN_MAX_CANDS = int(os.environ.get("CHAN_MAX_CANDS", "256"))
@@ -63,11 +64,27 @@ class Qmp:
self._cache = {}
def _rd(self):
- time.sleep(0.03)
- try:
- return self.s.recv(1 << 20).decode()
- except Exception:
- return ""
+ """Read one QMP reply (JSON object ends with a newline). Poll with a
+ short timeout instead of a fixed sleep: the fixed 30 ms made batch
+ page translations (4096 per pushbuffer) take two minutes."""
+ buf = b""
+ deadline = time.time() + 10
+ while time.time() < deadline:
+ r, _, _ = select.select([self.s], [], [], 0.05)
+ if not r:
+ if buf:
+ break
+ continue
+ try:
+ chunk = self.s.recv(1 << 20)
+ except Exception:
+ break
+ if not chunk:
+ break
+ buf += chunk
+ if buf.endswith(b"\n"):
+ break
+ return buf.decode()
def cmd(self, name, **args):
try:
@@ -128,8 +145,11 @@ def guest_trace():
Read *before* clearing: the create-time probes fire once per client and
would be wiped by a clear that races them.
"""
- cmd = ("sudo sh -c 'grep -E \"uvmt|uvch|uvbp|uvpbf\" /sys/kernel/tracing/trace "
- "| tail -400; echo > /sys/kernel/tracing/trace'")
+ inner = ("grep -E \"uvmt|uvch|uvbp|uvpbf\" /sys/kernel/tracing/trace "
+ "| tail -400")
+ if not os.environ.get("KEEP_TRACE"):
+ inner += "; echo > /sys/kernel/tracing/trace"
+ cmd = "sudo sh -c '%s'" % inner
try:
out = subprocess.run([VMEXEC, "exec", cmd], capture_output=True,
text=True, timeout=90).stdout
@@ -162,7 +182,8 @@ def vas_from_mem(q, mem, d):
if not gpa:
diag_add(d, "no_gpa")
return None
- return gpu_vas, cpu, gpa
+ size = q.read64(mem + 0x60)
+ return gpu_vas, cpu, gpa, (size[0] if size else 0)
def pushbuffer_of_manager(q, mgr, d):
@@ -310,6 +331,17 @@ def pin(va, pa):
return False
+def pin_bulk(va, pa, nbytes):
+ cmd = "printf '0x%x,0x%x,0x%x' > %s/sema_map_add" % (va, pa, nbytes, PARAM)
+ for _ in range(40):
+ r = subprocess.run(["sudo", "-n", "sh", "-c", cmd],
+ capture_output=True, text=True)
+ if r.returncode == 0 and "timed out" not in (r.stderr or ""):
+ return True
+ time.sleep(0.05)
+ return False
+
+
def resolve_sem_pool(q, sem):
page = q.read64(sem)
if not page or not page[0]:
@@ -328,28 +360,43 @@ def resolve_sem_pool(q, sem):
return [(gv, gpa) for gv in gpu_vas]
-def pin_pushbuffer(q, gpu_vas, cpu, gpa):
- """Pin the whole pushbuffer with *per-page* translations (the layout is
- not linear across the allocation: page 32 jumped on a real client)."""
+def pin_pushbuffer(q, gpu_vas, cpu, gpa, size=0):
+ """Pin the pushbuffer pages (the UVM allocation is 16 x 1 MB chunks =
+ 16 MB = 4096 pages; the page layout is only piecewise contiguous, so
+ group ascending-contiguous runs into one bulk injection each)."""
+ pages = min(max(size >> 12, 1) if size else PUSHBUF_MAX_PAGES,
+ PUSHBUF_MAX_PAGES)
pinned = 0
for gv in gpu_vas:
miss = 0
- for i in range(PUSHBUF_MAX_PAGES):
+ i = 0
+ while i < pages:
g = q.gva2gpa(cpu + i * 0x1000)
if not g:
miss += 1
- if miss >= 32:
- print("semfix: pushbuf va=0x%x: 32 unmapped pages at %d, stop (%d pinned)"
- % (gv, i, pinned), flush=True)
+ if miss >= 64:
+ print("semfix: pushbuf va=0x%x: %d unmapped pages at %d, stop (%d pinned)"
+ % (gv, miss, i, pinned), flush=True)
return pinned
+ i += 1
continue
miss = 0
- if not pin(gv + i * 0x1000, g):
- print("semfix: pushbuf va=0x%x: pin failed at page %d" % (gv, i), flush=True)
+ j = i + 1
+ last = g
+ while j < pages:
+ gj = q.gva2gpa(cpu + j * 0x1000)
+ if gj != last + 0x1000:
+ break
+ last = gj
+ j += 1
+ n = j - i
+ if not pin_bulk(gv + i * 0x1000, g, n * 0x1000):
+ print("semfix: pushbuf va=0x%x: bulk pin failed at page %d" % (gv, i), flush=True)
return pinned
- pinned += 1
- print("semfix: pushbuf va=0x%x cpu=0x%x gpa=0x%x: pinned %d page(s)"
- % (gv, cpu, gpa, pinned), flush=True)
+ pinned += n
+ i = j
+ print("semfix: pushbuf va=0x%x cpu=0x%x gpa=0x%x size=0x%x: pinned %d page(s)"
+ % (gv, cpu, gpa, size, pinned), flush=True)
return pinned
@@ -361,12 +408,12 @@ def main():
seen_ch = set()
pinned_ranges = set()
- def pin_range_once(gpu_vas, cpu, gpa):
- key = tuple(gpu_vas), cpu
+ def pin_range_once(gpu_vas, cpu, gpa, size=0):
+ key = tuple(gpu_vas), cpu, size
if key in pinned_ranges:
return
pinned_ranges.add(key)
- pin_pushbuffer(q, gpu_vas, cpu, gpa)
+ pin_pushbuffer(q, gpu_vas, cpu, gpa, size)
print("semfix: watching (interval %.1fs)" % interval, flush=True)
while True: