From 5620ed076c87be100be87432c6d5b3035a46ed6f Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Sat, 31 Oct 2026 02:04:46 -0400 Subject: vgsp+diag: resolver GL pushbuffer families (0x4000000/0x42000000 learn-once byte-exact, raw-tail newest-first scan), setup-hold demand cap 0x8000->0x10000 (60216B push=16 pages), pin_gr_stream harvest 2MB - r1089 target reached: setup push readable (unreadable=0), MME prelude captured words=511; beef context still tears down (submissions=0); demand races dmesg ring (manual demand fixed r1090); host / was 100%, freed ~6G - SESSION 153 r1088-r1090 --- tools/guest-diag/guest_pin_stream.py | 120 +++++++++++++++++++++++++++++------ tools/guest-diag/pin_gr_stream.py | 4 +- 2 files changed, 101 insertions(+), 23 deletions(-) (limited to 'tools') diff --git a/tools/guest-diag/guest_pin_stream.py b/tools/guest-diag/guest_pin_stream.py index 3db61ba..644aa50 100644 --- a/tools/guest-diag/guest_pin_stream.py +++ b/tools/guest-diag/guest_pin_stream.py @@ -306,6 +306,12 @@ def _nv_vmap_areas(kc): # instead of walking the whole +/-16 neighbourhood. _area_off = {} +# SESSION 153-CONTINUED-55: per-GL-family learned {family_base: (area_lo, +# adj)}. Once one page of a GL push-buffer family is content-verified the +# whole family resolves through the learned area/offset (continuation and +# zero pages included); cleared when the area disappears (context rebuild). +_gl_fam = {} + def resolve_va_by_content(kc, va, out): """Demand pin: find the page carrying GPU VA 'va' by content match. @@ -320,6 +326,12 @@ def resolve_va_by_content(kc, va, out): lottery. Only the requested VA is pinned, so a wrong candidate cannot poison other VAs. + SESSION 153-CONTINUED-55: the same rule now covers the GL push-buffer + families (PB_GL_FAMILIES). The setup-push holds (va=0x4001xxx) and the + draw pushes (0x402xxxx) are nv_vm_map_pages allocations exactly like the + CE window; without this they were rejected here silently and the held + setup push was never pinned, so its 4K pages read zeros at the host. + 2026-10-16b: the offset search window was +/-1 page. Pushes of a client whose pushbuffer vmap carries a larger per-allocation header (observed with multi-MB CE staging: pushes at 0x120000f58+ never resolved and the @@ -328,11 +340,47 @@ def resolve_va_by_content(kc, va, out): vmap area, because consecutive pushes advance sequentially inside the same allocation and share the offset. """ - if not (PB_CE_LO <= va < PB_CE_HI): - return False - off = (va - PB_CE_LO) & 0xFFFFFF - k = off >> 12 + if PB_CE_LO <= va < PB_CE_HI: + k = ((va - PB_CE_LO) & 0xFFFFFF) >> 12 + gl_base = None + else: + gl_base = next((lo for lo, hi in PB_GL_FAMILIES if lo <= va < hi), + None) + if gl_base is None: + return False + k = (va - gl_base) >> 12 areas = _nv_vmap_areas(kc) + # SESSION 153-CONTINUED-55: a GL push-buffer family is one allocation + # whose vmap offset is IDENTITY with the family offset (verified: the + # setup push's bytes at va 0x4001004 read back byte-exact at the vmap + # page's +0x1004). Once one page of the family resolves, every page of + # the family is known: continuation pages of a stream do NOT start with + # a method header (the 60216-byte setup push crosses page boundaries + # mid-run, so pages 2/3/5/7 fail the prefix test) and unwritten pages + # read all zero (pages 8..15 of the same push) yet are real translations + # the host read still needs. Use the learned (area, adj) for the whole + # family; only the FIRST learn pays the content test. + learned_fam = _gl_fam.get(gl_base) if gl_base else None + if learned_fam is not None: + lo, adj = learned_fam + if any(a_lo == lo for (a_lo, _h, _p) in areas): + idx = k + adj + pages = next(p for (a_lo, _h, p) in areas if a_lo == lo) + if 0 <= idx < pages: + cpu = lo + idx * 0x1000 + pa = kc.va_to_pa(cpu) + d = kc.read(cpu, 64) if pa is not None else None + if pa is not None and d is not None: + if gl_base is not None or (_push_prefix_ok(d) and + any(d[:32])): + out.append("0x%x,0x%x,1\n" % (va & ~0xFFF, pa)) + dbg("demand-va: 0x%x -> pa=0x%x (gl family learned" + " area=0x%x adj=%+d)" + % (va & ~0xFFF, pa, lo, adj)) + return True + # The learned area is gone (context rebuilt): fall through to a + # fresh content match and re-learn. + _gl_fam.pop(gl_base, None) for (lo, _hi, pages) in areas: learned = _area_off.get(lo) if learned is not None: @@ -345,18 +393,28 @@ def resolve_va_by_content(kc, va, out): if idx < 0 or idx >= pages: continue cpu = lo + idx * 0x1000 - d = kc.read(cpu, 64) - if not d or not any(d[:32]): - continue - if not _push_prefix_ok(d): - continue pa = kc.va_to_pa(cpu) if pa is None: continue + # 0x200 (not 64): a GL family's page 0 can carry its first + # method header at offset ~0x68 behind a zero-filled prefix + # (observed on the 60216 B setup push), invisible to a 64-byte + # read. + d = kc.read(cpu, 0x200) + if not d or not any(d): + continue + if not _push_prefix_ok(d): + continue _area_off[lo] = adj + if gl_base is not None: + # First content-verified page of this GL family: the + # identity mapping (area, adj) now serves every page of + # the family, including continuation and zero pages. + _gl_fam[gl_base] = (lo, adj) out.append("0x%x,0x%x,1\n" % (va & ~0xFFF, pa)) - dbg("demand-va: 0x%x -> pa=0x%x (area off=%d adj=%+d)" - % (va & ~0xFFF, pa, idx, adj)) + dbg("demand-va: 0x%x -> pa=0x%x (area off=%d adj=%+d%s)" + % (va & ~0xFFF, pa, idx, adj, + " gl-learn" if gl_base is not None else "")) return True dbg("demand-va: 0x%x not found by content (%d areas)" % (va & ~0xFFF, len(areas))) @@ -810,6 +868,12 @@ def resolve_pb_pins(kc, pb, out): # hold the ring-entry pushes is chosen (content anchor, _mem_content_ok). PB_CE_LO = 0x120000000 PB_CE_HI = 0x121000000 +# SESSION 153-CONTINUED-55: GL push-buffer VA families. The GL client's +# setup/MME-prelude push buffers (observed hold va=0x4001004 len=60216) and +# the draw pushes (observed va=0x4021004) are separate nv_vm_map_pages +# allocations in two fixed windows; the same family-relative page index +# rule applies: k = (va - base) >> 12. +PB_GL_FAMILIES = ((0x4000000, 0x4100000), (0x42000000, 0x43000000)) # m165 (D-F1a): UVM semaphore-pool page range. The pool is a 4 KB sysmem # uvm_rm_mem placed by the UVM arena right after the 16 MB CE push buffer; # the client's tracker words (0x121000000 + 4*idx) live in its first page. @@ -1788,15 +1852,29 @@ def main(): # main(), stopping ALL new pins (the observed # pool-pin latency). Extract every 0x token instead; # a bad token is skipped, never fatal. - wvas = [] - for t in re.findall(r"0x[0-9a-fA-F]+", - fw.read()): - try: - wvas.append(int(t, 16)) - except ValueError: - pass + text = fw.read() _want_state["mtime"] = st_d.st_mtime - for wv in wvas[-64:]: + # SESSION 153-CONTINUED-55: process the NEWEST VAs first, + # not the deduped head. The file is append-only and grows + # to hundreds of lines (every stalled-VA row is appended + # each demand cycle, ~48/4 s under load), so both the + # literal tail (`wvas[-64:]`) and a first-occurrence + # dedupe sorted older pending VAs out of the window (the + # 16-page GL setup-push demand fell out between its + # appends and the next mtime change). Walk the raw tail + # backwards, newest instruction first, skipping resolved + # VAs, up to a bounded batch. + wvas = [] + for t in reversed(re.findall(r"0x[0-9a-fA-F]+", text)): + try: + v = int(t, 16) + except ValueError: + continue + if v not in _want_state["done"] and v not in wvas: + wvas.append(v) + if len(wvas) >= 256: + break + for wv in wvas: if wv in _want_state["done"]: continue got = [] @@ -1833,8 +1911,8 @@ def main(): # page: resolve it through the client's own # pagemap. Without this the demand for those # VAs was a silent no-op (resolve_va_by_content - # rejects anything outside PB_CE_LO..PB_CE_HI) - # and the fills kept dropping them. + # rejects anything outside its windows) and the + # fills kept dropping them. if not ok and not got and pid: try: pfd = os.open('/proc/%d/pagemap' % pid, diff --git a/tools/guest-diag/pin_gr_stream.py b/tools/guest-diag/pin_gr_stream.py index 0bd6759..e224988 100644 --- a/tools/guest-diag/pin_gr_stream.py +++ b/tools/guest-diag/pin_gr_stream.py @@ -258,7 +258,7 @@ def _demand_loop(): # cannot, the guest content resolver is the last source - # harvest the VA so it resolves and injects the real page. r"|sema-pushzero: h=0x[0-9a-f]+ va=(0x[0-9a-f]+)", - (r.stdout or "")[-200000:]): + (r.stdout or "")[-2000000:]): va = int(m.group(1) or m.group(2) or m.group(3), 16) track.setdefault(va, [now, 0.0]) # SESSION 153-CONTINUED-51: the pushzero line now carries the @@ -268,7 +268,7 @@ def _demand_loop(): # whole push at once (bulk demand instead of one page per cycle). for m in re.finditer( r"sema-pushzero: h=0x[0-9a-f]+ va=(0x[0-9a-f]+) len=(\d+)", - (r.stdout or "")[-200000:]): + (r.stdout or "")[-2000000:]): zva = int(m.group(1), 16) zlen = int(m.group(2)) for zoff in range(0, min(zlen, 0x8000), 0x1000): -- cgit v1.2.3