From 14f195400a14034612fd9b1f69fb6296419eae6d Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Wed, 30 Sep 2026 05:28:13 -0400 Subject: guest-diag: pushbuffer pin survives PTI holes (full-range scan + bounded retries until complete) --- tools/guest-diag/semfix_uvmt.py | 31 ++++++++++++++++++++++++++----- tools/guest-diag/semfix_watch.py | 15 +++++++++++---- 2 files changed, 37 insertions(+), 9 deletions(-) (limited to 'tools/guest-diag') diff --git a/tools/guest-diag/semfix_uvmt.py b/tools/guest-diag/semfix_uvmt.py index fba465d..b9f3ac9 100755 --- a/tools/guest-diag/semfix_uvmt.py +++ b/tools/guest-diag/semfix_uvmt.py @@ -32,7 +32,7 @@ from semfix_watch import ( # noqa: E402 Qmp, QMP_SOCK, is_kern, is_gpu, diag_str, guest_trace, resolve_sem_pool, pin, pin_bulk, resolve_from_channel, resolve_from_pushbuffer, resolve_from_sem, resolve_pushbuffer_wide, - pin_pushbuffer, + pin_pushbuffer, PUSHBUF_MAX_PAGES, ) TIMELINE = os.environ.get("TIMELINE", "/tmp/opencode/uvmt_timeline.log") @@ -250,7 +250,7 @@ def main(): seen_sem_pin = set() seen_pb = set() seen_ch = set() - pinned_ranges = set() + pinned_ranges = {} last_state = {} last_seen_t = {} dseen = set() @@ -262,10 +262,22 @@ def main(): def pin_range_once(gpu_vas, cpu, gpa, size=0): key = tuple(gpu_vas), cpu, size - if key in pinned_ranges: + st = pinned_ranges.get(key) + if st is None: + st = pinned_ranges[key] = {"tries": 0, "done": False, + "args": (gpu_vas, cpu, gpa, size)} + if st["done"]: return - pinned_ranges.add(key) - pin_pushbuffer(q, gpu_vas, cpu, gpa, size) + st["tries"] += 1 + pinned = pin_pushbuffer(q, gpu_vas, cpu, gpa, size) + expected = min(max(size >> 12, 1) if size else PUSHBUF_MAX_PAGES, + PUSHBUF_MAX_PAGES) + if pinned >= expected: + st["done"] = True + elif st["tries"] >= 12: + st["done"] = True + log("semfix: pushbuf pin incomplete after %d tries: %d/%d pages" + % (st["tries"], pinned, expected)) while True: loop += 1 @@ -388,6 +400,15 @@ def main(): if DLOG: dmesg_releases(dseen) + # Retry incomplete push-buffer pins: gva2gpa of the kernel vmalloc + # alias fails while the sampled vCPU runs in user mode (PTI), so a + # pass can miss a run of pages that resolves a moment later. Only + # every 8th loop - a full pass costs thousands of QMP reads. + if loop % 8 == 0: + for st in list(pinned_ranges.values()): + if not st["done"] and st["tries"] > 0: + pin_range_once(*st["args"]) + # Pin the completion-counter page of a newly started client, before its # first GR pushes decode their releases. The pid comes from the guest # scanner; the guest-agent fallback is only a safety net. The exec can diff --git a/tools/guest-diag/semfix_watch.py b/tools/guest-diag/semfix_watch.py index 1c33de6..fa435ea 100644 --- a/tools/guest-diag/semfix_watch.py +++ b/tools/guest-diag/semfix_watch.py @@ -392,7 +392,14 @@ def resolve_sem_pool(q, sem): 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).""" + group ascending-contiguous runs into one bulk injection each). + + gva2gpa of a kernel vmalloc VA intermittently fails while the sampled + vCPU executes in user mode (page-table isolation hides the kernel half), + so a pass can miss a run of pages. Do not stop at the first hole: scan + the whole range (bailing only when essentially everything misses) and + return the pinned count, so the caller can retry the remainder later. + """ pages = min(max(size >> 12, 1) if size else PUSHBUF_MAX_PAGES, PUSHBUF_MAX_PAGES) pinned = 0 @@ -403,9 +410,9 @@ def pin_pushbuffer(q, gpu_vas, cpu, gpa, size=0): g = q.gva2gpa(cpu + i * 0x1000) if not g: miss += 1 - if miss >= 64: - print("semfix: pushbuf va=0x%x: %d unmapped pages at %d, stop (%d pinned)" - % (gv, miss, i, pinned), flush=True) + if miss >= pages: + print("semfix: pushbuf va=0x%x: no page resolved (%d " + "tried), wrong cpu base" % (gv, pages), flush=True) return pinned i += 1 continue -- cgit v1.2.3