summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--kernel/gsp_sema_service.c134
-rw-r--r--tools/guest-diag/guest_pin_stream.py419
-rw-r--r--tools/guest-diag/pin_gr_stream.py90
3 files changed, 614 insertions, 29 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 856947f..11549ce 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -1545,6 +1545,19 @@ struct sema_svc {
*/
#define SVC_SETUP_DEMAND_MAX 512
u64 setup_demand_vas[SVC_SETUP_DEMAND_MAX];
+ /*
+ * SESSION 153-CONTINUED-64: the held entry's exact read window.
+ * The armed setup hold records its entry page with (off = entry va's
+ * in-page offset, len = the GPFIFO entry length); every other page
+ * keeps len = 0. The guest pin resolver uses the pair to validate a
+ * candidate page RANGE against the exact bytes the host will read
+ * [va, va+len) with the host's own full method-stream parse, so a pin
+ * is only injected when the host read is guaranteed to parse (kills
+ * the content-heuristic family learn that served a wrong area in
+ * r1120: `first8=00040000 00001000 ... plausible=0`).
+ */
+ u8 setup_demand_off[SVC_SETUP_DEMAND_MAX];
+ u32 setup_demand_len[SVC_SETUP_DEMAND_MAX];
u32 setup_demand_count;
u32 setup_demand_head; /* ring write cursor (valid once full) */
u64 setup_demand_total; /* notes accepted (dedup hits excluded) */
@@ -1594,17 +1607,33 @@ static void svc_log_stalled_va(u64 va)
* aligned u64 on a 64-bit kernel, so the in-place overwrite cannot tear for
* the seq reader (worst case it sees the previous resident VA once more).
*/
-static void svc_setup_demand_record(u64 va)
+static void svc_setup_demand_record_ex(u64 va, u32 off, u32 len)
{
u32 i, n = svc.setup_demand_count;
if (!va)
return;
for (i = 0; i < n; i++)
- if (svc.setup_demand_vas[i] == va)
+ if (svc.setup_demand_vas[i] == va) {
+ /*
+ * SESSION 153-CONTINUED-64: upgrade a plain entry to the
+ * anchored (off,len) window when the hold re-records it.
+ * off/len publish order keeps the reader consistent for
+ * new slots; an in-place upgrade may be seen one pass
+ * torn (off new, len old) - the guest then validates the
+ * wrong window once and retries, which is harmless.
+ */
+ if (len && !svc.setup_demand_len[i]) {
+ svc.setup_demand_off[i] = (u8)off;
+ smp_wmb();
+ svc.setup_demand_len[i] = len;
+ }
return;
+ }
if (n < SVC_SETUP_DEMAND_MAX) {
svc.setup_demand_vas[n] = va;
+ svc.setup_demand_off[n] = (u8)off;
+ svc.setup_demand_len[n] = len;
smp_wmb();
svc.setup_demand_count = n + 1;
} else {
@@ -1612,6 +1641,8 @@ static void svc_setup_demand_record(u64 va)
u32 w = svc.setup_demand_head;
svc.setup_demand_vas[w] = va;
+ svc.setup_demand_off[w] = (u8)off;
+ svc.setup_demand_len[w] = len;
svc.setup_demand_head = (w + 1u) % SVC_SETUP_DEMAND_MAX;
svc.setup_demand_evicted++;
if (ring_log < 4u || !(svc.setup_demand_evicted % 4096u)) {
@@ -1624,6 +1655,11 @@ static void svc_setup_demand_record(u64 va)
svc.setup_demand_total++;
}
+static void svc_setup_demand_record(u64 va)
+{
+ svc_setup_demand_record_ex(va, 0, 0);
+}
+
/*
* SESSION 153-CONTINUED-59: cross-file entry for the GR launch builder (see
* gsp_sema_service.h). Records the page of a VA whose window/attr operand
@@ -1642,9 +1678,12 @@ void gsp_sema_demand_note(u64 va)
* Read-only view of the setup-hold demand set for the pin tool:
* # sema setup-hold demand VAs (page-aligned): N total=T evicted=E
* 0x4021000
+ * 0x4001004 0x4 60216 <- anchored entry: entry va, in-page offset, len
* ...
* `total` counts every accepted note (dedup hits excluded); `evicted` counts
- * ring overwrites of the oldest slot (SESSION 153-CONTINUED-60).
+ * ring overwrites of the oldest slot (SESSION 153-CONTINUED-60). An anchored
+ * line carries the exact [va, va+len) read window of the held GPFIFO entry
+ * (SESSION 153-CONTINUED-64).
*/
static int svc_setup_demand_show(struct seq_file *m, void *v)
{
@@ -1653,9 +1692,18 @@ static int svc_setup_demand_show(struct seq_file *m, void *v)
seq_printf(m, "# sema setup-hold demand VAs (page-aligned): %u total=%llu evicted=%llu\n",
n, (unsigned long long)svc.setup_demand_total,
(unsigned long long)svc.setup_demand_evicted);
- for (i = 0; i < n && i < SVC_SETUP_DEMAND_MAX; i++)
- seq_printf(m, "0x%llx\n",
- (unsigned long long)svc.setup_demand_vas[i]);
+ for (i = 0; i < n && i < SVC_SETUP_DEMAND_MAX; i++) {
+ u32 dl = svc.setup_demand_len[i];
+
+ if (dl)
+ seq_printf(m, "0x%llx 0x%x %u\n",
+ (unsigned long long)svc.setup_demand_vas[i] +
+ svc.setup_demand_off[i],
+ (unsigned)svc.setup_demand_off[i], dl);
+ else
+ seq_printf(m, "0x%llx\n",
+ (unsigned long long)svc.setup_demand_vas[i]);
+ }
return 0;
}
@@ -3725,6 +3773,37 @@ static void pin_store_tab(struct svc_pin *tab, u32 slots, u32 probe_max,
(*drops)++;
}
+/*
+ * SESSION 153-CONTINUED-64d: same store, but reports a probe-window
+ * exhaustion back to the caller so it can evict instead of losing the pin
+ * (see svc_ipin_store). Returns true when the slot was NOT stored.
+ */
+static bool pin_store_tab_nodrop(struct svc_pin *tab, u32 slots,
+ u32 probe_max, u32 *n, u64 *drops, u64 va,
+ u64 pa, int ap, u8 win)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u32 mask = slots - 1;
+ u32 h = (u32)((page >> SEMA_PAGE_SHIFT) * 2654435761u) & mask;
+ u32 probe;
+
+ for (probe = 0; probe < probe_max; probe++) {
+ struct svc_pin *p = &tab[(h + probe) & mask];
+
+ if (!p->va_page || p->va_page == page) {
+ if (!p->va_page)
+ (*n)++;
+ p->va_page = page;
+ p->pa_page = pa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ p->ap = ap;
+ p->win = win;
+ return false;
+ }
+ }
+ (*drops)++;
+ return true;
+}
+
static int svc_pin_lookup(u64 va, u64 *pa, int *ap)
{
return pin_lookup_tab(svc.pin, SVC_PIN_SLOTS, SVC_PIN_PROBE, va, pa, ap,
@@ -3939,8 +4018,32 @@ static void svc_ipin_store(u64 va, u64 pa, int ap)
(old_pa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
(pa & ~(u64)(SEMA_PAGE_SIZE - 1)))
svc_ipin_alt_store(va, old_pa, old_ap);
- pin_store_tab(svc.ipin, SVC_IPIN_SLOTS, SVC_IPIN_PROBE, &svc.ipin_n,
- &svc.ipin_drop, va, pa, ap, 0);
+ /*
+ * SESSION 153-CONTINUED-64d: the table SATURATES under client map
+ * churn (r1122: `ipins=32768/148777` - the old store DROPPED every
+ * pin past the 32768 slots, so the demand stream's setup-push pages
+ * could never land and the held read never flipped). Slots: 1<<15.
+ * When the probe window is exhausted, evict the home slot's
+ * occupant instead of dropping: the evicted pair is preserved in
+ * the alt table (where a reader may still fall back to it) and both
+ * the scanner's periodic re-assert and the demand resend cadences
+ * restore any evicted entry that is still needed. A dropped demand
+ * pin, by contrast, is unrecoverable within the hold window.
+ */
+ if (pin_store_tab_nodrop(svc.ipin, SVC_IPIN_SLOTS, SVC_IPIN_PROBE,
+ &svc.ipin_n, &svc.ipin_drop, va, pa, ap, 0)) {
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u32 mask = SVC_IPIN_SLOTS - 1;
+ u32 h = (u32)((page >> SEMA_PAGE_SHIFT) * 2654435761u) & mask;
+ struct svc_pin *p = &svc.ipin[h];
+
+ if (p->va_page && p->va_page != page)
+ svc_ipin_alt_store(p->va_page, p->pa_page, p->ap);
+ p->va_page = page;
+ p->pa_page = pa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ p->ap = ap;
+ p->win = 0;
+ }
/*
* A fresh injection supersedes every older translation for this
* page: drop the verified pool cache entry so a subsequent pool
@@ -14029,8 +14132,8 @@ static void svc_mme_census(const u8 *buf, u32 len, u64 push_va, u32 h_object)
* appends strictly in order; the generation only increments after a
* capture that appended, giving the booter a seqcheck against torn views.
*/
-#define SVC_MME_PRELUDE_WORDS 512u
-#define SVC_MME_PRELUDE_SIGS 256u
+#define SVC_MME_PRELUDE_WORDS 1024u
+#define SVC_MME_PRELUDE_SIGS 512u
static u32 svc_mme_prelude_buf[SVC_MME_PRELUDE_WORDS];
static u32 svc_mme_prelude_n;
static u32 svc_mme_prelude_gen;
@@ -22711,8 +22814,17 @@ static void svc_gr_setup_hold_arm(struct svc_channel *ch, u64 va, u32 idx,
* debugfs demand set the pin tool reads directly, so the
* demand no longer depends on the dmesg row surviving the
* ring rotation under vgsp-sema spam.
+ *
+ * SESSION 153-CONTINUED-64: the entry's own page (poff == 0)
+ * additionally carries (off, len) - the exact [va, va+len)
+ * window the host read will parse - so the guest resolver
+ * can validate a candidate page range with the host's own
+ * full method-stream parse before injecting the pin.
*/
- svc_setup_demand_record(page + poff);
+ svc_setup_demand_record_ex(page + poff,
+ poff == 0 ?
+ (u32)(va & (SEMA_PAGE_SIZE - 1)) : 0u,
+ poff == 0 ? len : 0u);
}
}
diff --git a/tools/guest-diag/guest_pin_stream.py b/tools/guest-diag/guest_pin_stream.py
index e4dd0ba..d1595b4 100644
--- a/tools/guest-diag/guest_pin_stream.py
+++ b/tools/guest-diag/guest_pin_stream.py
@@ -421,6 +421,356 @@ def resolve_va_by_content(kc, va, out):
return False
+# SESSION 153-CONTINUED-64: candidate adjacencies for the strict window scan.
+_STRICT_ADJ = (0, 1, -1, 2, -2, 3, -3, 4, -4, 6, -6, 8, -8, 14, -14, 16, -16)
+
+
+def _stream_parse_full(buf):
+ """Exact port of the host's svc_stream_parse (gsp_sema_service.c), plus
+ the MME-setup content requirements the bare parse lacks.
+
+ Zero words are NOP padding (skipped anywhere in the stream), ops 1/3/5
+ are INC/NON_INC/ONE_INC runs with a 12-bit count (1..0xfff) and count
+ data words, ops 0/2/4/7 are header-only, anything else fails. The buffer
+ parses only when at least one method was seen and the walk lands exactly
+ on the end (trailing zero padding allowed).
+
+ SESSION 153-CONTINUED-64b: the bare exact-end parse accepted a GMMU
+ page-table window - PTEs are {pfn<<12, flags} u32 pairs whose values sit
+ below 0x20000000, so every word reads as op 0 (header-only) and the walk
+ lands exactly on the end (r1122 pinned pa=0x11dbb0000: all 32 words of
+ every page op 0, zero data runs, host read `sema-bigrun hdr=0x1dbb7000`).
+ A window is now only accepted when it carries at least one data run AND
+ at least one run addressing an MME-setup method (0x114..0x124 / 0x234 /
+ 0x550..0x568 / 0x3400..0x34fc - the same windows the host's
+ svc_mme_setup_run_addr classifies with). A page table can never satisfy
+ either; the held setup push always carries both (the r1116 capture's
+ first run `20020047` = INC run @0x11c).
+ """
+ off = 0
+ methods = 0
+ data_runs = 0
+ mme_hits = 0
+ code_words = 0
+ n = len(buf)
+ while off + 4 <= n:
+ hdr = struct.unpack_from('<I', buf, off)[0]
+ if hdr == 0:
+ off += 4
+ continue
+ op = (hdr >> 29) & 7
+ count = (hdr >> 16) & 0xFFF
+ if op in (1, 3, 5):
+ if not count or off + 4 + count * 4 > n:
+ return False
+ data_runs += 1
+ addr = (hdr & 0x1FFF) << 2
+ if (0x114 <= addr <= 0x124 or addr == 0x234 or
+ 0x550 <= addr <= 0x568 or 0x3400 <= addr <= 0x34FC):
+ mme_hits += 1
+ if addr == 0x114 and count >= 1:
+ code_words += count
+ off += 4 + count * 4
+ elif op in (0, 2, 4, 7):
+ off += 4
+ else:
+ return False
+ methods += 1
+ if methods > 8192:
+ return False
+ while off < n and buf[off] == 0:
+ off += 1
+ return (methods > 0 and off == n and data_runs > 0 and mme_hits > 0
+ and code_words >= 8)
+
+
+def _prefix_walk_ok(buf, off):
+ """Cheap first-page pre-filter for a strict window candidate.
+
+ Walks the method shapes from `off` to the end of `buf` WITHOUT requiring
+ the end to land on a boundary (runs may cross into the next page and a
+ run header near the page end declares data we cannot see yet). Fails on
+ the first header no method shape can produce; this kills arbitrary data
+ pages after a few words so the full parse only runs on survivors.
+ """
+ n = len(buf)
+ while off + 4 <= n:
+ hdr = struct.unpack_from('<I', buf, off)[0]
+ if hdr == 0:
+ off += 4
+ continue
+ op = (hdr >> 29) & 7
+ count = (hdr >> 16) & 0xFFF
+ if op in (1, 3, 5):
+ if not count:
+ return False
+ off += 4 + count * 4
+ elif op in (0, 2, 4, 7):
+ off += 4
+ else:
+ return False
+ return True
+
+
+def _read_window(kc, cpu, off, ln):
+ """Read [cpu+off, cpu+off+ln) page-by-page through kcore.
+
+ A vmap area's pages are contiguous in the kernel VA but arbitrary in
+ physical memory, so the window is assembled one page at a time.
+ """
+ out = b""
+ a = cpu + off
+ left = ln
+ while left > 0:
+ n = min(left, 0x1000 - (a & 0xFFF))
+ d = kc.read(a, n)
+ if not d:
+ return None
+ out += d
+ a += n
+ left -= n
+ return out
+
+
+def _mme_run_in_page(d):
+ """True when a 4K page contains a run addressing an MME-setup method.
+
+ The strict-window prefilter for client-mapping candidates (cheap: one
+ page read rejects the ~511/512 wrong shifts before the full parse).
+ """
+ n = len(d)
+ off = 0
+ while off + 4 <= n:
+ hdr = struct.unpack_from('<I', d, off)[0]
+ if hdr == 0:
+ off += 4
+ continue
+ op = (hdr >> 29) & 7
+ count = (hdr >> 16) & 0xFFF
+ if op in (1, 3, 5):
+ if not count:
+ off += 4
+ continue
+ addr = (hdr & 0x1FFF) << 2
+ if (0x114 <= addr <= 0x124 or addr == 0x234 or
+ 0x550 <= addr <= 0x568 or 0x3400 <= addr <= 0x34FC):
+ return True
+ off += 4 + count * 4
+ else:
+ off += 4
+ return False
+
+
+def resolve_va_client_mem(pid, ent_va, off, ln, out):
+ """Strict pin for a setup push whose pages live in CLIENT sysmem.
+
+ SESSION 153-CONTINUED-64c: when the setup push's allocation is UVM
+ sysmem it has NO kernel vmap (r1122: no nv_vm_map_pages area held the
+ `a0100045` 0x114 upload runs anywhere on the boot - the content exists
+ only in the client's `rw-s` /dev/nvidia mappings, and the host's
+ GPU-page-table walk answered an unrelated page-table buffer for the
+ held VA). The client's CPU mappings + /proc/<pid>/pagemap are the only
+ ground truth for those pages. Scan the client's readable+writable
+ mappings: for each candidate page shift, read the exact [va, va+len)
+ window via /proc/<pid>/mem, validate with the host's own full parse,
+ and emit pins for every page of the validated window from the
+ pagemap. The cheap per-page MME-run prefilter rejects wrong shifts.
+ """
+ if not pid:
+ return False
+ try:
+ pfd = os.open('/proc/%d/pagemap' % pid, os.O_RDONLY)
+ mfd = os.open('/proc/%d/mem' % pid, os.O_RDONLY)
+ except OSError:
+ return False
+ try:
+ gl_base = next((lo for lo, hi in PB_GL_FAMILIES
+ if lo <= (ent_va & ~0xFFF) < hi), None)
+ k = ((ent_va & ~0xFFF) - gl_base) >> 12 if gl_base is not None else None
+ if ln <= 0 or ln > 0x40000:
+ return False
+ need = (off + ln + 0xFFF) // 0x1000
+ if need > 64:
+ return False
+ try:
+ maps = open('/proc/%d/maps' % pid).read().splitlines()
+ except OSError:
+ return False
+ tried = 0
+ for line in maps:
+ parts = line.split()
+ if len(parts) < 5 or 'rw' not in parts[1]:
+ continue
+ # SESSION 153-CONTINUED-64e: any rw mapping may carry the
+ # sysmem push (observed as pathless rw-s /dev/nvidia mappings;
+ # memfd-backed buffers cannot be excluded either - the strict
+ # parse + MME prefilter are the safety gates, not the path).
+ try:
+ lo, hi = [int(x, 16) for x in parts[0].split('-')]
+ except ValueError:
+ continue
+ if hi - lo < (need + 4) * 0x1000 or hi - lo > (64 << 20):
+ continue
+ pages = (hi - lo) >> 12
+ if k is None:
+ # Unknown family (SESSION 153-CONTINUED-64e: the hold moved
+ # to the 0x3200000 window): no identity page index to key
+ # on, so sweep every page start of the mapping (bounded).
+ adjs = range(0, max(1, pages - need + 1))
+ else:
+ adjs = (0, 1, -1, 2, -2, 4, -4, 8, -8, 16, -16, 32, -32,
+ 64, -64, 128, -128, 256, -256)
+ for adj in adjs:
+ idx = (k + adj) if k is not None else adj
+ if idx < 0 or idx + need > pages:
+ continue
+ win = lo + idx * 0x1000 + off
+ tried += 1
+ if tried > (8192 if k is None else 192):
+ break
+ try:
+ first = os.pread(mfd, 0x1000 - off, win)
+ except OSError:
+ continue
+ if not first or not _mme_run_in_page(first):
+ continue
+ buf = b''
+ left = ln
+ a = win
+ ok = True
+ while left > 0:
+ n = min(left, 0x1000 - (a & 0xFFF))
+ try:
+ d = os.pread(mfd, n, a)
+ except OSError:
+ ok = False
+ break
+ if not d or len(d) != n:
+ ok = False
+ break
+ buf += d
+ a += n
+ left -= n
+ if not ok or not _stream_parse_full(buf):
+ continue
+ pas = []
+ for j in range(need):
+ vpg = (ent_va & ~0xFFF) + j * 0x1000
+ cpg = lo + (idx + j) * 0x1000
+ os.lseek(pfd, (cpg >> 12) * 8, 0)
+ e = int.from_bytes(os.read(pfd, 8), 'little')
+ if not (e >> 63) & 1:
+ pas = None
+ break
+ pas.append(((e & ((1 << 55) - 1)) << 12))
+ if not pas:
+ continue
+ for j, pa in enumerate(pas):
+ out.append("0x%x,0x%x,1\n"
+ % ((ent_va & ~0xFFF) + j * 0x1000, pa))
+ dbg("demand-va: 0x%x -> pa=0x%x (strict client-mem parse"
+ " %u B ok pages=%u map=0x%x idx=%d)"
+ % (ent_va & ~0xFFF, pas[0], ln, need, lo, idx))
+ return True
+ if tried > 192:
+ break
+ return False
+ finally:
+ os.close(pfd)
+ os.close(mfd)
+
+
+def resolve_va_strict(kc, ent_va, off, ln, out, pid=None):
+ """Demand pin validated by the host's exact full-parse of [ent_va, +ln).
+
+ SESSION 153-CONTINUED-64: the content heuristic learned a GL family from
+ ONE page's weak 0x200-byte prefix test and then served every family page
+ from that (area, adj) with NO content check at all; r1120 pinned
+ 0x4001000 -> a page whose bytes (`00040000 00001000...`) failed the
+ host's stream parse for the whole boot (`plausible=0 unreadable=1`), so
+ the setup push was never captured and the prelude gate deferred every
+ launch. This path runs the HOST'S OWN full parse on the exact bytes the
+ host will read before any pin is emitted, then pins EVERY page of the
+ validated window and learns the family from it. A family is never
+ served from an unvalidated learn.
+ """
+ page = ent_va & ~0xFFF
+ gl_base = next((lo for lo, hi in PB_GL_FAMILIES if lo <= page < hi), None)
+ if ln <= 0 or ln > 0x40000:
+ return False
+ if gl_base is None:
+ # SESSION 153-CONTINUED-64e: an anchor outside the known families
+ # (r1124: the hold moved to a 153600-byte push at 0x3200000) has no
+ # identity page index; only the client-mapping sweep can resolve it.
+ try:
+ if resolve_va_client_mem(pid, ent_va, off, ln, out):
+ return True
+ except Exception:
+ pass
+ dbg("demand-va: 0x%x strict unknown-family client-mem failed" % page)
+ return False
+ gl_hi = next(hi for lo, hi in PB_GL_FAMILIES if lo <= page < hi)
+ k = (page - gl_base) >> 12
+ need = (off + ln + 0xFFF) // 0x1000
+ if need > 64 or page + need * 0x1000 > gl_hi:
+ return False
+ areas = _nv_vmap_areas(kc)
+ cands = []
+ fam = _gl_fam.get(gl_base)
+ if fam is not None and any(a_lo == fam[0] for (a_lo, _h, _p) in areas):
+ cands.append(fam)
+ for (lo, _hi, pages) in areas:
+ for adj in _STRICT_ADJ:
+ c = (lo, adj)
+ if c not in cands:
+ cands.append(c)
+ tried = 0
+ for (lo, adj) in cands:
+ pages = next(p for (a_lo, _h, p) in areas if a_lo == lo)
+ idx = k + adj
+ if idx < 0 or idx + need > pages:
+ continue
+ tried += 1
+ if tried > 128:
+ break
+ cpu = lo + idx * 0x1000
+ first = kc.read(cpu, 0x1000)
+ if not first or not any(first):
+ continue
+ if not _prefix_walk_ok(first, off):
+ continue
+ buf = _read_window(kc, cpu, off, ln)
+ if buf is None or not _stream_parse_full(buf):
+ continue
+ pas = []
+ for j in range(need):
+ vpa = kc.va_to_pa(cpu + j * 0x1000)
+ if vpa is None:
+ pas = None
+ break
+ pas.append(vpa)
+ if not pas:
+ continue
+ for j, vpa in enumerate(pas):
+ out.append("0x%x,0x%x,1\n" % (page + j * 0x1000, vpa))
+ _area_off[lo] = adj
+ _gl_fam[gl_base] = (lo, adj)
+ dbg("demand-va: 0x%x -> pa=0x%x (strict parse %u B ok pages=%u"
+ " area=0x%x adj=%+d)" % (page, pas[0], ln, need, lo, adj))
+ return True
+ dbg("demand-va: 0x%x strict parse failed (%u B, %u windows tried)"
+ % (page, ln, tried))
+ # SESSION 153-CONTINUED-64c: no kernel vmap held the content - the
+ # allocation is UVM sysmem visible only in the client's own mappings.
+ try:
+ if resolve_va_client_mem(pid, ent_va, off, ln, out):
+ return True
+ except Exception:
+ pass
+ dbg("demand-va: 0x%x strict client-mem parse failed too" % page)
+ return False
+
+
def resolve_mem_pins(kc, mem, out, default_pages=16, va_lo=0, va_hi=0,
page_off=0):
"""uvm_rm_mem kptr -> GPU-VA/GPA runs (direct map or page-table walk).
@@ -878,7 +1228,8 @@ PB_CE_HI = 0x121000000
# original families did not cover, so the content resolver answered
# nothing and the read lottery could never be won for it (the
# 0x4020000-family hold re-armed for its whole 20 s window).
-PB_GL_FAMILIES = ((0x4000000, 0x4100000), (0x4200000, 0x4210000),
+PB_GL_FAMILIES = ((0x3200000, 0x3300000), (0x4000000, 0x4100000),
+ (0x4200000, 0x4210000),
(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;
@@ -1744,7 +2095,7 @@ def main():
# deinit), so the set deliberately persists across client switches.
pool_pages = {}
pool_reemit_next = 0.0
- _want_state = {"mtime": None, "done": set()}
+ _want_state = {"mtime": None, "done": set(), "strict_pending": {}}
loop = 0
while True:
pid = cdt_pid()
@@ -1860,6 +2211,65 @@ def main():
# a bad token is skipped, never fatal.
text = fw.read()
_want_state["mtime"] = st_d.st_mtime
+ # SESSION 153-CONTINUED-64: strict demand anchors first.
+ # A line `S 0x<entry_va> 0x<off> <len>` (written by
+ # pin_gr_stream from the service's anchored debugfs
+ # demand record) asks for the exact [entry_va, +len)
+ # read window to be validated with the host's own full
+ # stream parse before a pin is injected. Loose lines
+ # (plain `0x<va>`) keep the content/pagemap paths, but
+ # a GL family with a pending anchor is deferred so its
+ # learn can only come from the strict validation.
+ strict = []
+ loose_tokens = []
+ for line in text.splitlines():
+ ms = re.match(
+ r"\s*S\s+0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)"
+ r"\s+(\d+)\s*$", line)
+ if ms:
+ try:
+ strict.append((int(ms.group(1), 16),
+ int(ms.group(2), 16),
+ int(ms.group(3))))
+ except ValueError:
+ pass
+ continue
+ loose_tokens += re.findall(r"0x[0-9a-fA-F]+", line)
+ now_d = time.time()
+ for epage in list(_want_state["strict_pending"]):
+ if now_d - _want_state["strict_pending"][epage] > 300.0:
+ del _want_state["strict_pending"][epage]
+ for (ev, eoff, eln) in reversed(strict):
+ epage = ev & ~0xFFF
+ if epage in _want_state["done"]:
+ continue
+ got = []
+ try:
+ ok = resolve_va_strict(kc, ev, eoff, eln, got,
+ pid)
+ except Exception:
+ ok = False
+ got = []
+ if ok and got:
+ lines.extend(got)
+ for g in got:
+ try:
+ _want_state["done"].add(
+ int(g.split(",", 1)[0], 16))
+ except ValueError:
+ pass
+ _want_state["done"].add(epage)
+ _want_state["strict_pending"].pop(epage, None)
+ else:
+ _want_state["strict_pending"].setdefault(
+ epage, now_d)
+ # Loose VAs in a family whose strict anchor is still
+ # pending are deferred to the strict path only.
+ pend_bases = set()
+ for epage in _want_state["strict_pending"]:
+ for lo, hi in PB_GL_FAMILIES:
+ if lo <= epage < hi:
+ pend_bases.add((lo, hi))
# 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
@@ -1871,7 +2281,7 @@ def main():
# 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)):
+ for t in reversed(loose_tokens):
try:
v = int(t, 16)
except ValueError:
@@ -1883,6 +2293,9 @@ def main():
for wv in wvas:
if wv in _want_state["done"]:
continue
+ if any(lo <= (wv & ~0xFFF) < hi
+ for lo, hi in pend_bases):
+ continue
got = []
if POOL_SEM_LO <= (wv & ~0xFFF) < POOL_SEM_HI:
# m165 (D-F1a): pool/waiter VAs have no method
diff --git a/tools/guest-diag/pin_gr_stream.py b/tools/guest-diag/pin_gr_stream.py
index 7a9bb66..35c2ea3 100644
--- a/tools/guest-diag/pin_gr_stream.py
+++ b/tools/guest-diag/pin_gr_stream.py
@@ -131,11 +131,29 @@ def _demand_send(vas):
pass
-def _start_cmd():
+def _push_guest_script():
+ """Push the scanner into the guest in argv-safe chunks.
+
+ SESSION 153-CONTINUED-64: the one-shot `echo <b64> | base64 -d >` form
+ put the whole ~135 KB base64 of the (grown) scanner on vgpu_vm.py's
+ argv, and the host's MAX_ARG_STRLEN (128 KB) rejected the exec before
+ it ever reached the guest ('OSError: Argument list too long'), so no
+ scanner started and the boot streamed zero pins. Push 60 KB chunks and
+ print the guest-side md5 for verification.
+ """
with open(GUEST_SCRIPT) as f:
b64 = subprocess.run(["base64", "-w0"], stdin=f,
capture_output=True, text=True,
- timeout=10).stdout
+ timeout=10).stdout.strip()
+ vexec("rm -f /tmp/gps.b64", 30)
+ for off in range(0, len(b64), 60000):
+ vexec("printf %%s '%s' >> /tmp/gps.b64" % b64[off:off + 60000], 30)
+ return vexec("base64 -d /tmp/gps.b64 > /tmp/guest_pin_stream.py && "
+ "rm -f /tmp/gps.b64 && md5sum /tmp/guest_pin_stream.py "
+ "&& echo PUSH_OK", 60) or ""
+
+
+def _start_cmd():
prefixes = os.environ.get("CLIENT_PREFIXES",
"cdt,va,victory,Xorg,X,glxgears,gzdoom")
vma_every = os.environ.get("STREAM_VMA_EVERY", "1")
@@ -151,14 +169,13 @@ def _start_cmd():
# failed silently (r149).
return ("P=$(printf '^python3 /tmp/guest_pin_%%s.py' stream); "
"sudo pkill -f \"$P\" 2>/dev/null; sleep 1; "
- "echo '%s' | base64 -d > /tmp/guest_pin_stream.py; "
"sudo rm -f /tmp/guest_pin_stream.pid; "
"sudo setsid nohup env CLIENT_PREFIXES='%s' "
"STREAM_VMA_EVERY='%s' "
"python3 /tmp/guest_pin_stream.py %d %d %s %s %s "
">/tmp/guest_pin_stream.log 2>&1 & sleep 2; "
"sudo cat /tmp/guest_pin_stream.pid 2>/dev/null"
- % (b64, prefixes, vma_every, 2, PORT, LO, HI, PAMAX))
+ % (prefixes, vma_every, 2, PORT, LO, HI, PAMAX))
def install_and_start():
@@ -167,6 +184,14 @@ def install_and_start():
# a boot without the scanner streams no pins and the client then hangs.
for attempt in range(5):
_heartbeat()
+ try:
+ out_push = _push_guest_script()
+ if "PUSH_OK" not in (out_push or ""):
+ log("WARN: scanner push attempt %d incomplete: %s"
+ % (attempt + 1, (out_push or "")[-120:]))
+ except Exception as e:
+ log("WARN: scanner push attempt %d failed: %s"
+ % (attempt + 1, e))
out = vexec(_start_cmd(), 60)
if any(ch.isdigit() for ch in out):
return True
@@ -230,6 +255,13 @@ def _demand_loop():
expiry) as the stalled-VA class.
"""
track = {} # va -> [first_seen, last_sent]
+ # SESSION 153-CONTINUED-64: strict demand anchors (entry va -> [first,
+ # last_sent]) from the anchored debugfs lines. The guest validates the
+ # exact [entry_va, +len) read window with the host's full stream parse
+ # before pinning, so the setup-push pin can never land on a page whose
+ # bytes the host's read will reject. Resent fast (2 s) while the hold
+ # is up; the loose cadence (20 s) is too slow for the client's churn.
+ strict_track = {}
it = 0
while True:
# SESSION 153-CONTINUED-57: the 3 s pass was the losing half of the
@@ -296,30 +328,58 @@ def _demand_loop():
"/sys/kernel/debug/vgsp/sema_setup_demand"],
capture_output=True, text=True, timeout=10)
if rr.returncode == 0:
- for m in re.finditer(r"0x[0-9a-f]+", rr.stdout or ""):
- sva = int(m.group(0), 16) & ~0xfff
- if sva:
- track.setdefault(sva, [now, 0.0])
+ for line in (rr.stdout or "").splitlines():
+ ma = re.match(
+ r"\s*0x([0-9a-f]+)\s+0x([0-9a-f]+)\s+(\d+)\s*$",
+ line)
+ if ma:
+ ev = int(ma.group(1), 16)
+ off = int(ma.group(2), 16)
+ ln = int(ma.group(3))
+ if ev and 0 < ln <= 0x40000:
+ strict_track.setdefault(ev, [now, 0.0,
+ off, ln])
+ continue
+ for m in re.finditer(r"0x[0-9a-f]+", line):
+ sva = int(m.group(0), 16) & ~0xfff
+ if sva:
+ track.setdefault(sva, [now, 0.0])
except Exception:
pass
for va in list(track):
first, last = track[va]
if now - first > 300.0:
del track[va]
+ for ev in list(strict_track):
+ first, last, off, ln = strict_track[ev]
+ if now - first > 300.0:
+ del strict_track[ev]
+ cmds = []
todo = [va for va, (f, l) in track.items() if now - l > 20.0]
- if not todo:
+ if todo:
+ todo = todo[:48]
+ cmds.append(_demand_write_cmd(todo))
+ stodo = [ev for ev, (f, l, o, ln) in strict_track.items()
+ if now - l > 2.0]
+ if stodo:
+ stodo = stodo[:16]
+ cmds.append("; ".join(
+ "echo S 0x%x 0x%x %d >> /tmp/guest_want_vas.txt"
+ % (ev, strict_track[ev][2], strict_track[ev][3])
+ for ev in stodo))
+ if not cmds:
continue
- # CONT-51: bound one batch's command size (a page-expanded push
- # range can add many pages; the remainder goes next cycle).
- todo = todo[:48]
- cmd = _demand_write_cmd(todo)
try:
- vexec(cmd, 30)
+ vexec("; ".join(cmds), 30)
except Exception:
continue
for va in todo:
track[va][1] = now
- log("demand: sent %d stalled VA(s) to guest" % len(todo))
+ for ev in stodo:
+ strict_track[ev][1] = now
+ log("demand: sent %d stalled VA(s)%s to guest" %
+ (len(todo),
+ " + %d strict anchor(s)" % len(stodo) if stodo else ""))
_DEMAND_STARTED = False