summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-31 02:04:46 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-31 02:04:46 -0400
commit5620ed076c87be100be87432c6d5b3035a46ed6f (patch)
tree1c0b88ddd10388325395057c9aa680ad8ef3b16c /tools
parentc8efdb1e22012d8168b15fd137887a1e5706fa1f (diff)
downloadopen-iov-5620ed076c87be100be87432c6d5b3035a46ed6f.tar.gz
open-iov-5620ed076c87be100be87432c6d5b3035a46ed6f.zip
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
Diffstat (limited to 'tools')
-rw-r--r--tools/guest-diag/guest_pin_stream.py120
-rw-r--r--tools/guest-diag/pin_gr_stream.py4
2 files changed, 101 insertions, 23 deletions
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):