diff options
Diffstat (limited to 'tools/guest-diag')
| -rw-r--r-- | tools/guest-diag/semfix_watch.py | 97 |
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: |
