summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-16 20:54:49 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-16 20:54:49 -0400
commitb90ced5aa52d202e678918093d1bb497ec1efd2b (patch)
tree56d7f2d81803484165ed9994a50f57e8301d9202
parent9ae857d0fa7e9123928dc3cc189d19691b5deb32 (diff)
downloadopen-iov-b90ced5aa52d202e678918093d1bb497ec1efd2b.tar.gz
open-iov-b90ced5aa52d202e678918093d1bb497ec1efd2b.zip
vgsp: m163 lost-H2D copy integrity (drain injected pins before CE op resolution; hold failed device-copy ops) + m164 CLASS-2 GR no-capture entry hold + m165 pool release applies at fresh-pin time + m166 timeout-ladder warm-sem fail-fast; scanner: first-class pool VA resolution + periodic re-assert + demand routing
-rw-r--r--kernel/gsp_phys_booter.c27
-rw-r--r--kernel/gsp_sema_service.c197
-rw-r--r--kernel/gsp_sema_service.h12
-rw-r--r--kernel/main.c18
-rw-r--r--tools/guest-diag/guest_pin_stream.py99
-rw-r--r--tools/guest-diag/pin_gr_stream.py11
6 files changed, 351 insertions, 13 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index f54a0dc..d21c5ea 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -17192,6 +17192,7 @@ int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req,
u8 *qmd_dst_mem = NULL;
u32 qmd_rel_off = 0, ch_rel_off = 0;
u32 poll = 0, w0 = 0, w1 = 0, grpoll_n = 0;
+ bool sem_gated = false;
static u32 sked_prev[11];
static u32 sked_sample[11];
static u32 sked_tick;
@@ -18277,6 +18278,32 @@ gr_page_unmapped:
rr);
}
/*
+ * m166: liveness gate before spending the fresh-entry
+ * retries. The m156 warm sem push (the stage-4 GR sem
+ * block) is the only proven "engine alive" probe; if
+ * it times out, the engine is wedged for the boot and
+ * every further retry is futile - fail fast instead of
+ * burning ~20 s with the single-inflight slot held
+ * (r167 memset2d: 8 internal ladder timeouts over 31 s
+ * for an entry the front-end never fetched). The probe
+ * itself is bounded by the sem push's own poll.
+ */
+ if (!sem_gated) {
+ int sp_ret;
+
+ sem_gated = true;
+ mutex_lock(&gsp_phys_rm_lock);
+ sp_ret = gsp_phys_rmcall_gr_sem_push_locked(bar0);
+ mutex_unlock(&gsp_phys_rm_lock);
+ if (sp_ret) {
+ gsp_phys_gr_sem_wedged++;
+ pr_info("vgsp-gr: timeout ladder: warm sem WEDGED (rc=%d) - failing fast instead of fresh-entry retries\n",
+ sp_ret);
+ break;
+ }
+ pr_info("vgsp-gr: timeout ladder: warm sem ALIVE - continuing fresh-entry retries\n");
+ }
+ /*
* The re-arm above only replays the schedule verbs; if the
* entry was already fetched or the front-end never latched
* it, that cannot make the QMD run. Re-publish a FRESH ring
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index c9c36b3..d6b9617 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -355,6 +355,12 @@ struct svc_channel {
* the capture; forcing it lets a deferred
* launch be forwarded once the engine's
* in-flight work completes) */
+ u32 gr_nocap_idx; /* m164 CLASS-2: slot in the no-capture hold
+ * (a readable launch push whose decode saw
+ * QMD words but built no candidate) */
+ u32 gr_nocap_replay;/* 1 = window opened on a replay decode */
+ u64 gr_nocap_va; /* push VA the window was opened for */
+ unsigned long gr_nocap_since; /* absolute first-seen stamp */
};
/*
@@ -775,6 +781,11 @@ struct sema_svc {
* expectation advanced, write queued */
u64 gpfifo_held; /* passes an entry was left pending
* for a translation/grace window */
+ u64 gr_nocap_held; /* m164: GR holds opened for a
+ * launch push whose decode saw QMD
+ * words but built no candidate */
+ u64 gr_nocap_consumed; /* m164: no-capture windows that
+ * expired and consumed the entry */
u64 gpfifo_starved; /* entries consumed without a
* deliverable exact slot */
u64 counter_rel_applied; /* releases to registered counters */
@@ -1194,6 +1205,7 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
memset(ch, 0, sizeof(*ch));
ch->gr_hold_idx = ~0u; /* m156: no hold armed yet */
ch->gr_rehold_idx = ~0u;
+ ch->gr_nocap_idx = ~0u; /* m164: no no-capture window armed */
if (same)
bitmap_copy(ch->rel_done, keep_rel_done,
SVC_GPFIFO_MAX_BITS);
@@ -2699,6 +2711,29 @@ static inline bool svc_pool_hold_active(void)
}
/*
+ * m165 (D-F2a): the blanket pool hold exists to stop a release decoded
+ * before the guest's own injection lands from being applied through a
+ * stale translation. Once a host-verified injected pin for THIS VA is
+ * present, that premise is gone: the pin source is the resolver's first
+ * choice (read-back proven) and the ipin table is wiped at session flush,
+ * so it is this session's ground truth. r156 measured every pool release
+ * waiting the full registration+5.00 s with the pin ready all along.
+ */
+static bool svc_pool_hold_active_va(u64 va)
+{
+ u64 ipa = 0;
+ int iap = 0;
+
+ if (!svc_pool_hold_active())
+ return false;
+ if (!gsp_sema_pool_hold_fresh)
+ return true;
+ if (svc_ipin_lookup(va, &ipa, &iap) == 0)
+ return false;
+ return true;
+}
+
+/*
* Verified pool-semaphore translations, keyed by page VA. An entry is only
* created from an injected pin or from a targeted rm_mem resolve whose page
* was read back successfully; the learned cache and the discovered map are
@@ -3591,11 +3626,12 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu)
svc.false_trans, svc.gpfifo_skipped, svc.gpfifo_rescued,
svc.releases_faked,
svc.releases_recovered, svc.counter_rel_applied);
- pr_info("sema-svc: drain held=%llu starved=%llu forwarded=%llu faked=%llu rec=%llu ops_once=%llu reident=%llu opdef=%llu/%llu/%llu\n",
+ pr_info("sema-svc: drain held=%llu starved=%llu forwarded=%llu faked=%llu rec=%llu ops_once=%llu reident=%llu opdef=%llu/%llu/%llu gr_nocap=%llu/%llu\n",
svc.gpfifo_held, svc.gpfifo_starved, svc.releases_forwarded,
svc.releases_faked, svc.releases_recovered,
svc.ops_once_skipped, svc.ops_reident,
- svc.opdef_held, svc.opdef_applied, svc.opdef_expired);
+ svc.opdef_held, svc.opdef_applied, svc.opdef_expired,
+ svc.gr_nocap_held, svc.gr_nocap_consumed);
pr_info("sema-svc: counter pub_calls=%llu gr_rel=%llu reresolve=%llu pushpg_fb=%llu altw=%llu residue=%llu\n",
svc.counter_pub_calls, svc.gr_rel_applied,
svc.sem_write_reresolve, svc.pushpg_fallback,
@@ -4836,6 +4872,11 @@ static int svc_grce_inline_write(struct nv_vgpu_device *vgpu, u64 dst_va,
return 0;
}
+/* defined below (svc_handle_map_add); the CE decode path drains the queued
+ * scanner injections before resolving copy operands, exactly like the GR
+ * path's m143 drain. */
+static void svc_handle_map_add(void);
+
/* Execute a decoded operation list (fills and copies). `ops_done` (may be
* NULL) is the per-entry bitmap of ops that already ran for the entry's
* identity; `ops_base` is the sequence number of ops[0] within the entry. */
@@ -4846,6 +4887,17 @@ static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
static u64 bigop_log;
svc.cur_ch = ch_h;
+ /*
+ * m163: apply every queued scanner injection before resolving this
+ * pass's copy operands. Without this, a CE copy decoded in the window
+ * between the client's push and its pins being processed resolves
+ * through the previous client's stale translation (or fails), while
+ * its release is still applied - the client's cudaMemcpy "completes"
+ * with no data and the next GR launch computes from the stale vidmem
+ * page (streams2 r163: a1 held the previous client's h_a; the vadd
+ * output was a_stale + b_fresh).
+ */
+ svc_handle_map_add();
for (i = 0; i < n_ops; i++) {
struct svc_memset_op *m = &ops[i];
u32 seq = ops_base + i;
@@ -4917,9 +4969,18 @@ static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
* (observed r72 c2) must not withhold a release that gates a GR
* launch with a 5 s poll deadline. On expiry the release is
* flushed and the op stays skipped (the pre-r62 fallback).
+ * m163 extends the hold to copy ops whose destination is a
+ * device buffer: a lost small H2D that silently "completes"
+ * corrupts the next launch's input (streams2 r163: the 128 KB
+ * a1 copy's release was applied with no copy op ever run; the
+ * vadd computed a_stale + b and the client read the wrong c).
+ * A small failing device copy now costs a bounded hold and the
+ * retried decode runs it once the pins land - never silent
+ * corruption. Small failing memsets keep the old trade.
*/
if (rc != 0 && rc != SVC_COPY_FORWARDED &&
- m->bytes >= (256u << 10))
+ (m->bytes >= (256u << 10) ||
+ (m->type == 1 && m->dst >= SVC_VAS_FLOOR)))
svc.op_tr_pending = 1;
if (gsp_sema_dbg && m->bytes == 256 && svc.sem256_log < 64) {
svc.sem256_log++;
@@ -5906,6 +5967,15 @@ static void svc_fwd_rel_direct_note(u32 h, u64 va, u32 payload)
* mapped to its register offset. Each completed QMD resets the accumulator,
* so a push may carry more than one launch.
*/
+/*
+ * m164: per-decode QMD witness for the CLASS-2 no-capture hold. Set to the
+ * max number of QMD words any accumulator saw during the current
+ * svc_decode_stream() call; reset at its top and read by the entry loop's
+ * no-capture check. Service-thread only.
+ */
+static u32 svc_gr_qmd_seen;
+static u64 svc_gr_nocap_log;
+
static void svc_qmd_accum(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
struct svc_push_state *st, u32 addr, u32 count,
const u8 *data, const u8 *push, u32 push_len,
@@ -5935,6 +6005,8 @@ static void svc_qmd_accum(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
}
}
}
+ if (st->qmd_n > svc_gr_qmd_seen)
+ svc_gr_qmd_seen = st->qmd_n;
if (st->qmd_have == 3)
st->qmd_dev_va = (((u64)st->qmd_hi << 32) | st->qmd_lo) << 8;
if (st->qmd_n == SVC_QMD_WORDS) {
@@ -6001,6 +6073,9 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
u32 ops_seq = 0; /* ops appended this pass (sequence numbers) */
u32 off = 0;
+ /* m164: fresh CLASS-2 QMD witness for this decode pass. */
+ svc_gr_qmd_seen = 0;
+
/*
* A false VA->PA pair (the discovered map is content-scanned) can
* point a ring entry's push VA at an unrelated page - observed:
@@ -7440,7 +7515,7 @@ static void svc_apply_pending(struct nv_vgpu_device *vgpu)
* a pool VA the base may not have landed: applying while
* the word is still 0 is absorbed, so hold and retry.
*/
- if (svc_pool_sem_va(p->va) && svc_pool_hold_active()) {
+ if (svc_pool_sem_va(p->va) && svc_pool_hold_active_va(p->va)) {
p->tries++;
svc_pending_note_starved(p);
i++;
@@ -9339,6 +9414,27 @@ bool gsp_sema_counter_derive = true;
unsigned gsp_sema_gr_grace_ms = 8000;
/*
+ * m164 CLASS-2: hold window (ms) for a readable GR launch push whose decode
+ * saw QMD words but built no launch candidate for its channel (a replay-
+ * suppressed capture, a partial/split QMD, or a push page the pin stream has
+ * not reached yet). The entry is left unconsumed for this window so the next
+ * pass re-decodes it with a forced fresh capture (gr_rehold_idx); on expiry
+ * the historical consume applies. Without this, such entries were consumed
+ * silently and the client's launch never ran (streams2 second vadd: 56
+ * consumes / 2 captures on one channel; the client read zeros).
+ */
+unsigned gsp_sema_gr_nocap_hold_ms = 6000;
+
+/*
+ * m165 (D-F2a): when set (default), a pool release is only deferred by the
+ * post-registration hold while NO host-verified injected pin exists for its
+ * VA; a fresh pin is this session's ground truth and lets the apply land at
+ * pin time (~0.5-2 s) instead of registration+5 s. Set 0 to restore the
+ * legacy blanket 5 s hold.
+ */
+bool gsp_sema_pool_hold_fresh = 1;
+
+/*
* CE release fallback: when a push carrying a tracking-semaphore release
* cannot be read (and the content rescue failed), advance the channel's
* tracking semaphore to the expected payload directly. The push's data
@@ -14776,6 +14872,84 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
}
}
/*
+ * m164 CLASS-2 no-capture hold: a
+ * readable GR launch push whose
+ * decode saw QMD words but built
+ * no launch candidate for this
+ * channel (replay-suppressed
+ * capture, partial/split QMD, or
+ * a page the pin stream has not
+ * reached) must not be consumed
+ * at the tail - consuming drops
+ * the client's launch and only
+ * the RAM sweep's release reaches
+ * it (r161/r167 streams2 second
+ * vadd: 56 consumes / 2 captures
+ * on channel 0x5c000023). Hold
+ * the entry and force a fresh
+ * capture on the next pass
+ * (gr_rehold_idx makes the replay
+ * decode rebuild the candidate);
+ * one bounded window per
+ * (slot, push VA), then fall
+ * through to the historical
+ * consume so the channel can
+ * never livelock.
+ */
+ if (!ch->engine_type && va && len &&
+ gsp_phys_gr_forward_enabled() &&
+ svc_gr_qmd_seen > 0 &&
+ !(svc_gr_cand.valid &&
+ svc_gr_cand.h_object ==
+ ch->h_object &&
+ svc_gr_cand.push_va == va)) {
+ if (ch->gr_nocap_idx !=
+ (u32)idx ||
+ ch->gr_nocap_va != va) {
+ ch->gr_nocap_idx =
+ (u32)idx;
+ ch->gr_nocap_va = va;
+ ch->gr_nocap_replay =
+ entry_replay ?
+ 1 : 0;
+ ch->gr_nocap_since =
+ jiffies;
+ }
+ if (gsp_sema_gr_nocap_hold_ms &&
+ time_before(jiffies,
+ ch->gr_nocap_since +
+ msecs_to_jiffies(
+ gsp_sema_gr_nocap_hold_ms))) {
+ /* Force a fresh capture on
+ * the retry: a replay decode
+ * would suppress it. */
+ ch->gr_rehold_idx =
+ (u32)idx;
+ svc.gpfifo_held++;
+ svc.gr_nocap_held++;
+ if (svc_gr_nocap_log < 16) {
+ svc_gr_nocap_log++;
+ pr_info("sema-chan: GR no-capture hold h=0x%x entry=%u va=0x%llx len=%u replay=%d qmd_seen=%u (forcing capture; window %u ms)\n",
+ ch->h_object,
+ (u32)idx, va,
+ len,
+ ch->gr_nocap_replay,
+ svc_gr_qmd_seen,
+ gsp_sema_gr_nocap_hold_ms);
+ }
+ kfree(pb);
+ break;
+ }
+ /* Window expired: consume
+ * exactly as the pre-change
+ * tail does, with a proof
+ * line. */
+ svc.gr_nocap_consumed++;
+ pr_info_ratelimited("sema-chan: GR no-capture hold expired h=0x%x entry=%u va=0x%llx len=%u (consuming without a launch)\n",
+ ch->h_object,
+ (u32)idx, va, len);
+ }
+ /*
* A CE push that should carry this
* channel's next tracking release but
* decoded none read from a
@@ -15355,6 +15529,12 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
ch->last_get = (idx + 1) % ch->gpfifo_entries;
ch->rescue_fail = 0;
ch->defer_since = 0;
+ /* m164: the entry was consumed (or fell through the
+ * no-capture window); clear the hold identity so a
+ * future push on this slot opens a fresh window. */
+ ch->gr_nocap_idx = ~0u;
+ ch->gr_nocap_since = 0;
+ ch->gr_nocap_va = 0;
ch->unread_va = 0;
ch->unread_until = 0;
@@ -15472,6 +15652,15 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
*/
svc.chan[k].gr_hold_until = ch->gr_hold_until;
svc.chan[k].gr_hold_idx = ch->gr_hold_idx;
+ /* m164: the CLASS-2 no-capture window is snapshot
+ * state too - without the write-back every pass
+ * would re-arm it and rehold the entry forever
+ * (the m150 lesson). */
+ svc.chan[k].gr_nocap_idx = ch->gr_nocap_idx;
+ svc.chan[k].gr_nocap_replay =
+ ch->gr_nocap_replay;
+ svc.chan[k].gr_nocap_va = ch->gr_nocap_va;
+ svc.chan[k].gr_nocap_since = ch->gr_nocap_since;
svc.chan[k].pushpg_va = ch->pushpg_va;
svc.chan[k].pushpg_n = ch->pushpg_n;
svc.chan[k].content_max = ch->content_max;
diff --git a/kernel/gsp_sema_service.h b/kernel/gsp_sema_service.h
index c1dd763..ede8fa0 100644
--- a/kernel/gsp_sema_service.h
+++ b/kernel/gsp_sema_service.h
@@ -263,4 +263,16 @@ extern unsigned gsp_sema_push_hold_ms;
*/
extern unsigned gsp_sema_gr_grace_ms;
+/*
+ * m164 CLASS-2: hold window (ms) for a GR launch push whose decode saw QMD
+ * words but built no launch candidate; 0 disables the hold.
+ */
+extern unsigned gsp_sema_gr_nocap_hold_ms;
+
+/*
+ * m165 (D-F2a): skip the post-registration pool hold when a verified
+ * injected pin exists for the VA (default on); 0 restores the blanket 5 s.
+ */
+extern bool gsp_sema_pool_hold_fresh;
+
#endif /* _GSP_SEMA_SERVICE_H_ */
diff --git a/kernel/main.c b/kernel/main.c
index 257638a..c4e4792 100644
--- a/kernel/main.c
+++ b/kernel/main.c
@@ -2771,6 +2771,24 @@ module_param_named(sema_gr_grace_ms, gsp_sema_gr_grace_ms, uint, 0644);
MODULE_PARM_DESC(sema_gr_grace_ms, "GR pending-entry grace window in ms (default 8000)");
/*
+ * m164 CLASS-2: hold window (ms) for a readable GR launch push whose decode
+ * saw QMD words but built no launch candidate (replay-suppressed capture,
+ * partial/split QMD, or an unreadable push page). The entry stays unconsumed
+ * for this window and the next pass forces a fresh capture; on expiry the
+ * historical consume applies. 0 disables.
+ */
+module_param_named(sema_gr_nocap_hold_ms, gsp_sema_gr_nocap_hold_ms, uint, 0644);
+MODULE_PARM_DESC(sema_gr_nocap_hold_ms, "GR no-capture entry hold in ms (default 6000, 0 disables)");
+
+/*
+ * m165 (D-F2a): skip the post-registration pool-release hold when a
+ * host-verified injected pin for the VA exists (default on). 0 restores the
+ * legacy blanket 5 s hold.
+ */
+module_param_named(sema_pool_hold_fresh, gsp_sema_pool_hold_fresh, bool, 0644);
+MODULE_PARM_DESC(sema_pool_hold_fresh, "apply pool releases at fresh-pin time instead of registration+5s (default 1)");
+
+/*
* Debug: inject a verified {GPU VA -> guest PA} translation into the
* semaphore service (used when the client's mapping cannot be discovered
* from guest structures):
diff --git a/tools/guest-diag/guest_pin_stream.py b/tools/guest-diag/guest_pin_stream.py
index 6012dd1..b308136 100644
--- a/tools/guest-diag/guest_pin_stream.py
+++ b/tools/guest-diag/guest_pin_stream.py
@@ -734,6 +734,49 @@ def resolve_sem_pins(kc, sem, out):
return resolve_mem_pins(kc, mem, out, default_pages=16)
+def resolve_pool_va(kc, va, out, sems):
+ """Pool/waiter VA -> PA via tracker-sem -> page -> mem; NO content match.
+
+ The word at POOL base + 4*idx is a slot in a UVM semaphore-pool page: a
+ sysmem uvm_rm_mem whose PA is only reachable through its kernel CPU
+ alias (the client pagemap has no present entry for it). Same kcore walk
+ the CE pushbuffer uses; the chain is shorter (sem+0 = page, page+0 =
+ mem). The page sits 16 MB above the CE push buffer base, outside
+ resolve_sem_pins' default_pages window, which is why the host saw
+ 'pending release starved' for it on every client's first seconds.
+ """
+ page = va & ~0xFFF
+ if not (POOL_SEM_LO <= page < POOL_SEM_HI):
+ return False
+ for sem in list(sems):
+ if not is_kern_ptr(sem):
+ continue
+ try:
+ pg = kc.read64(sem)
+ if not is_kern_ptr(pg):
+ continue
+ mem = kc.read64(pg)
+ if not is_kern_ptr(mem):
+ continue
+ got = []
+ resolve_mem_pins(kc, mem, got, default_pages=1,
+ va_lo=page, va_hi=page + 0x1000)
+ except Exception:
+ continue
+ for line in got:
+ try:
+ lva = int(line.split(',')[0], 16)
+ except Exception:
+ continue
+ if lva == page:
+ out.append(line)
+ dbg('pool-va: 0x%x -> %s (mem=0x%x)'
+ % (page, line.strip(), mem))
+ return True
+ dbg('pool-va: 0x%x unresolved (%d known sems)' % (page, len(sems)))
+ return False
+
+
def resolve_pb_pins(kc, pb, out):
mem = kc.read64(pb + 8)
if not mem:
@@ -767,6 +810,14 @@ 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
+# 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.
+# The client pagemap has no present entry for it, so only the kcore
+# tracker-sem -> page -> mem walk can translate it.
+POOL_SEM_LO = 0x121000000
+POOL_SEM_HI = 0x122000000
+KC_POOL_REEMIT_S = 2.0 # re-assert the pool page pair this often too
KC_CHAIN_BACK = 0x2000
KC_CHAIN_SPAN = 0xA000
KC_CHAIN_MAX = 400
@@ -1569,6 +1620,11 @@ def main():
last_log = 0.0
scrub_next = 0.0
demand_next = 0.0
+ # m165 (D-F1a): pool page VAs learned from demand, re-asserted every
+ # KC_POOL_REEMIT_S. Boot-stable (UVM frees pool pages only at GPU
+ # deinit), so the set deliberately persists across client switches.
+ pool_pages = {}
+ pool_reemit_next = 0.0
_want_state = {"mtime": None, "done": set()}
loop = 0
while True:
@@ -1669,16 +1725,49 @@ def main():
if wv in _want_state["done"]:
continue
got = []
- try:
- ok = resolve_va_by_content(kc, wv, got)
- except Exception:
- ok = False
- got = []
+ if POOL_SEM_LO <= (wv & ~0xFFF) < POOL_SEM_HI:
+ # m165 (D-F1a): pool/waiter VAs have no method
+ # stream content and the CE offset math does not
+ # apply; route them to the structural tracker-sem
+ # walk instead.
+ try:
+ ok = resolve_pool_va(kc, wv, got,
+ seen_sems)
+ except Exception:
+ ok = False
+ got = []
+ dbg('demand-pool-va: 0x%x %s'
+ % (wv, 'ok' if ok else 'pending'))
+ if ok and got:
+ pool_pages.setdefault(wv & ~0xFFF, True)
+ else:
+ try:
+ ok = resolve_va_by_content(kc, wv, got)
+ except Exception:
+ ok = False
+ got = []
if ok and got:
lines.extend(got)
_want_state["done"].add(wv)
except OSError:
pass
+ # m165 (D-F1a): re-assert the pool page pair every KC_POOL_REEMIT_S.
+ # The pool page is boot-stable (UVM frees it only at GPU deinit) but
+ # the host pin table is wiped at every session flush, so a one-shot
+ # pin leaves later clients starving until the slow structural walks
+ # answer; the periodic re-assert heals the wipe within seconds (the
+ # same argument as the CE mem sweep above).
+ if kc_ok and pool_pages and time.time() >= pool_reemit_next:
+ pool_reemit_next = time.time() + KC_POOL_REEMIT_S
+ for pv in list(pool_pages):
+ got = []
+ try:
+ ok = resolve_pool_va(kc, pv, got, seen_sems)
+ except Exception:
+ ok = False
+ got = []
+ if ok and got:
+ lines.extend(got)
# PMA scrubber buffer discovery (throttled): resolve the finish/host-
# semaphore word PAs and stream them for host registration.
if kc_ok and time.time() >= scrub_next:
diff --git a/tools/guest-diag/pin_gr_stream.py b/tools/guest-diag/pin_gr_stream.py
index e1f5195..dff2d2d 100644
--- a/tools/guest-diag/pin_gr_stream.py
+++ b/tools/guest-diag/pin_gr_stream.py
@@ -145,7 +145,7 @@ def _demand_loop():
except Exception:
continue
now = time.time()
- for m in re.finditer(r"stalled VA #\d+: (0x[0-9a-f]+)",
+ for m in re.finditer(r"(?:stalled VA #\d+|pending release starved va)=(0x[0-9a-f]+)",
(r.stdout or "")[-200000:]):
va = int(m.group(1), 16)
track.setdefault(va, [now, 0.0])
@@ -258,14 +258,17 @@ def _flush(runs, seen):
"""
todo = []
for va, pa, n in runs:
- # The CE window (0x120000000..0x121000000) is re-asserted by the
+ # The CE window (0x120000000..0x121000000) and the UVM semaphore
+ # pool page range (0x121000000..0x122000000) are re-asserted by the
# guest every couple of seconds on purpose: the host's session flush
# wipes the whole pin table when the previous client's last channel
# closes, and a pin batch that raced in for the new client must be
# allowed to land again even when the pair is unchanged. Skipping
# repeats there stranded the second client's CE pushes (the r144/
- # r145 stall: sema-probe "NO ENTRY" for the live push pages).
- ce_win = 0x120000000 <= va < 0x121000000
+ # r145 stall: sema-probe "NO ENTRY" for the live push pages) and the
+ # pool/tracker page (pending release starved, every client's first
+ # seconds).
+ ce_win = 0x120000000 <= va < 0x122000000
if not ce_win and seen.get(va) == pa and n == 1:
continue
todo.append((va, pa, n))