summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/04-implementation/04-real-render-walls.md34
-rw-r--r--kernel/gsp_sema_service.c175
-rw-r--r--tools/guest-diag/fenceword_assist.py91
3 files changed, 298 insertions, 2 deletions
diff --git a/docs/04-implementation/04-real-render-walls.md b/docs/04-implementation/04-real-render-walls.md
index f6ea39d..4298772 100644
--- a/docs/04-implementation/04-real-render-walls.md
+++ b/docs/04-implementation/04-real-render-walls.md
@@ -185,3 +185,37 @@ gdb leaves the VM stopped (recover with a detach-only connect);
`stepi` loops over the stub freeze the guest's userspace for minutes -
prefer breakpoints; every probe run with the caps-fill breakpoint changes
the driver's path, so keep the fill in each experiment.
+
+## 8. UPDATE 2026-10-25 #3 (SESSION 126): the fence fold landed - the service authors the Vulkan fence releases from the driver's own push bytes; the compute-dispatch shape decoded (direct C5C0 method stream, no inline QMD)
+
+**The fence wall is closed.** The client's per-frame fence wait polls a
+16 KB BO word that a submit-push INC-5 semaphore group targets
+(`[2005c017][0x45ba090][0][payload 1][0][0x02100001]`). The group sits in a
+GPFIFO entry beyond the driver's kick boundary (GP_PUT=11 while the ring
+already carries the entries it pre-built for the next submits, idx12..46),
+so the GP_PUT-bounded pump walk never decoded it and a host-side assist had
+to write the word. The service now sweeps that pre-built continuation
+itself: `sema_gr_ahead` (default 64) walks entries beyond GP_PUT through the
+ring's validated translation, accepts only pushes that pass the strict
+method-stream gate, extracts the addr-0x5c RELEASE groups and applies them
+forward-only through the standard pending ledger. Live evidence: the sweep
+finds the groups and the ledger applies the fence word once per frame;
+`VKFRAME seq=1..3/DONE` completes with no assist. The frames are still
+black - the shader work itself is unexecuted.
+
+**The compute-dispatch shape (why nothing executes).** The workload is a
+single compute dispatch (640x480 storage image). Its push is a DIRECT
+method-stream programming of the Turing compute class: `SET_OBJECT 0xC5C0`
+on subchannel 1, then the context windows (`0x7b4` local-memory window B,
+`0x2a4` shared-memory window B, `0x270`), an immediate-config block, and
+the code/const data runs (`0x3800`/`0x39C8` pairs plus a subchannel-0 data
+port stream of `0x11c`/`0x114` runs in a separate 17 KB push). There is NO
+inline-QMD write (no `0x318/0x31c/0x320..0x41c` run), so the launch capture
+that feeds the physical-engine forward path never fires - and the CE-shaped
+ops path is not even consulted for GR-class channels by construction. Real
+pixels therefore require a translator that recognizes the C5C0 section,
+builds the vendor launch the physical channel expects (the same prelude/QMD
+shape the CUDA path already executes) from the section's fields and data
+runs, and forwards it with the existing operand/relocation machinery. The
+method vocabulary overlaps the physical prelude's existing knowledge
+(`0x7b0/0x7b4`, `0x2a4`), so the landmarks for that translator are known.
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index c50832e..13ad648 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -23671,6 +23671,165 @@ static void svc_gr_stale_wedge_check(struct nv_vgpu_device *vgpu,
put, get, ch->submissions, waited, reason);
}
+/*
+ * ---------------------------------------------------------------------
+ * SESSION 126 - FENCE FOLD: channel-anchored sweep of a GR-class
+ * channel's PRE-BUILT ring continuation.
+ *
+ * The Vulkan client's fence waits never clear unaided because the
+ * driver's kick boundary (GP_PUT) stops at the first batch while the ring
+ * already carries the continuation entries it pre-built for the next
+ * submits - and the fence's own INC-5 RELEASE group (observed
+ * [2005c017][0x45ba090][0][payload 1][0][0x02100001]) lives in one of
+ * those entries, so the pump's GP_PUT-bounded walk never decodes it
+ * (r715/r716: put=11, entries 12..47 carried content, no dmesg row ever
+ * mentioned 0x45ba). The host-side fenceword_assist wrote the wait word
+ * directly; this fold makes the service author it from the driver's own
+ * push bytes.
+ *
+ * Doctrine: unlike the disabled page-content sweep (svc_apply_page_data,
+ * off since the false-positive corruption incidents), this sweep is
+ * CHANNEL-ANCHORED: it only walks entries reachable through the ring's
+ * validated GPFIFO translation, only accepts entries whose bytes pass the
+ * strict method-stream gate, and only applies the semaphore release
+ * groups it finds - forward-only, through the standard pending ledger
+ * (svc_pending_add). No consumption bookkeeping is touched (last_get,
+ * put_prev, slot_done_epoch and the replay state stay as the pump left
+ * them), so a later real kick of the same entries decodes them normally;
+ * repeated sweeps are idempotent (the pending ledger dedups (va,payload)
+ * and the apply path is forward-only).
+ *
+ * Gated by the same live-epoch arm as the counter publish
+ * (ch->gr_pub_armed) so a residual ring from a previous session cannot
+ * author releases before the session switch is proven.
+ * ---------------------------------------------------------------------
+ */
+unsigned gsp_sema_gr_ahead_max = 64;
+module_param_named(sema_gr_ahead, gsp_sema_gr_ahead_max, uint, 0644);
+MODULE_PARM_DESC(sema_gr_ahead,
+ "GR-class channels: max GPFIFO entries beyond GP_PUT whose channel-anchored pushes are swept for semaphore releases (0=off)");
+
+static u32 svc_gr_ahead_log;
+static u64 svc_gr_ahead_entries;
+static u64 svc_gr_ahead_rels;
+static u64 svc_gr_ahead_skips;
+
+/*
+ * Extract the semaphore release groups from one coherent push and queue
+ * them forward-only. Mirrors the decode sites' group shape exactly:
+ * addr 0x5c, count >= 5, op RELEASE (1) or REDUCTION (6); the address is
+ * w0/w1 as a 40-bit VA and the payload is w2.
+ */
+static u32 svc_gr_ahead_push(struct svc_channel *ch, const u8 *buf, u32 len)
+{
+ u32 off = 0, nrel = 0;
+
+ while (off + 4 <= len) {
+ u32 hdr, op, addr, count;
+
+ memcpy(&hdr, buf + off, 4);
+ if (!hdr) {
+ off += 4;
+ continue;
+ }
+ op = (hdr >> 29) & 0x7;
+ addr = (hdr & 0x1fff) << 2;
+ count = (hdr >> 16) & 0xfff;
+ if (op == 0 || op == 2 || op == 4 || op == 7) {
+ off += 4;
+ continue;
+ }
+ if (op != 1 && op != 3 && op != 5) {
+ off += 4;
+ continue;
+ }
+ if (!count || count > 0xfff || off + 4 + count * 4 > len) {
+ off += 4;
+ continue;
+ }
+ if (addr == HOST_M_SEM_ADDR_LO && count >= 5) {
+ u32 w0, w1, w2, w4;
+ u64 va;
+
+ memcpy(&w0, buf + off + 4, 4);
+ memcpy(&w1, buf + off + 8, 4);
+ memcpy(&w2, buf + off + 12, 4);
+ memcpy(&w4, buf + off + 20, 4);
+ va = ((u64)(w1 & 0xff) << 32) |
+ (u64)(w0 & 0xfffffffc);
+ op = w4 & 0x7;
+ if ((op == HOST_SEM_OP_RELEASE ||
+ op == HOST_SEM_OP_REDUCTION) && w2 &&
+ va >= SVC_LOW_REL_VA_LO && va < SEMA_VA_HI) {
+ svc_pending_add(va, w2,
+ op == HOST_SEM_OP_REDUCTION ? 1 : 0);
+ nrel++;
+ }
+ }
+ off += 4 + count * 4;
+ }
+ (void)ch;
+ return nrel;
+}
+
+static void svc_gr_ahead_sweep(struct nv_vgpu_device *vgpu,
+ struct svc_channel *ch, u32 put)
+{
+ static u8 abuf[2048];
+ u32 i, maxent, nrel = 0, swept = 0, nskip = 0;
+
+ if (!put || !ch->gpfifo_entries)
+ return;
+ maxent = min_t(u32, gsp_sema_gr_ahead_max, 512);
+ for (i = 0; i < maxent; i++) {
+ u32 idx = (put + i) % ch->gpfifo_entries;
+ u8 ent[SVC_GPFIFO_ENTRY_SIZE];
+ u32 e0, e1, len, rlen;
+ u64 va;
+
+ if (svc_read_va(vgpu,
+ ch->gpfifo_va +
+ (u64)idx * SVC_GPFIFO_ENTRY_SIZE,
+ ent, sizeof(ent)))
+ break;
+ memcpy(&e0, ent, 4);
+ memcpy(&e1, ent + 4, 4);
+ va = ((u64)(e1 & 0xff) << 32) | (u64)(e0 & 0xfffffffc);
+ len = ((e1 >> 10) & 0x1fffff) << 2;
+ if (!va && !len)
+ break; /* unwritten slot: end of the continuation */
+ if (!va || !len || len > SVC_PUSH_MAX)
+ break;
+ if (va < SVC_LOW_REL_VA_LO || va >= SEMA_VA_HI)
+ break;
+ rlen = min_t(u32, len, sizeof(abuf));
+ if (svc_read_va_push(vgpu, ch, va, abuf, rlen)) {
+ /*
+ * Unreadable here (pre-anchor translation) is a
+ * reason to stop this pass: the continuation is not
+ * proven coherent, so nothing is authored.
+ */
+ nskip++;
+ break;
+ }
+ if (!svc_stream_full_ok(abuf, rlen)) {
+ nskip++;
+ continue;
+ }
+ swept++;
+ nrel += svc_gr_ahead_push(ch, abuf, rlen);
+ }
+ svc_gr_ahead_entries += swept;
+ svc_gr_ahead_rels += nrel;
+ svc_gr_ahead_skips += nskip;
+ if (swept && (nrel || svc_gr_ahead_log < 64)) {
+ svc_gr_ahead_log++;
+ pr_info("sema-ahead: h=0x%x put=%u swept=%u rels=%u skipped=%u (total rels=%llu)\n",
+ ch->h_object, put, swept, nrel, nskip,
+ svc_gr_ahead_rels);
+ }
+}
+
static void svc_pump_channels(struct nv_vgpu_device *vgpu)
{
if (svc_pdb_disc_pending) {
@@ -24198,6 +24357,17 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
ch->put_prev = put;
/*
+ * SESSION 126 fence fold: sweep the ring continuation beyond
+ * GP_PUT for semaphore releases (see svc_gr_ahead_sweep).
+ * GR-class channels only, live-armed epochs only, and only
+ * with the ring translation proven through the channel
+ * object (rrc == 0).
+ */
+ if (!ch->engine_type && ch->gr_pub_armed &&
+ gsp_sema_gr_ahead_max && !rrc && ring_pa)
+ svc_gr_ahead_sweep(vgpu, ch, put);
+
+ /*
* STALLFREE r225: stale-epoch / stale-replay wedge watchdog.
* Runs before the entry loop so a wedged session epoch is
* recovered (and logged) instead of spinning: see
@@ -24695,7 +24865,7 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
* Budget: 512 rows per client (reset in
* svc_session_flush).
*/
- if (gsp_sema_dbg && svc_pushdec_log < 512) {
+ if (gsp_sema_dbg && svc_pushdec_log < 2048) {
svc_pushdec_log++;
pr_info("sema-pushdec: h=0x%x va=0x%llx len=%u src=%u pa=0x%llx plausible=%d injected=%d ipin=%d zero_nop=%d displaced=%d unreadable=%d first8=%08x %08x\n",
ch->h_object, va, len,
@@ -26847,12 +27017,13 @@ static void svc_handle_chan_dump(struct nv_vgpu_device *vgpu)
svc_read_ap(vgpu, ch->notifier_pa + 16 + 8,
ch->notifier_as, &info1, 4);
}
- pr_info("sema-chan-dump: h=0x%x eng=%u gpfifo=0x%llx ent=%u userd=0x%llx as=%u ok=%d get=%u sub=%llu exp=%u rel=0x%llx crel=0x%llx notif=0x%llx as=%u slot0{info=0x%x st=0x%x} slot1{info=0x%x st=0x%x} notified=%llu\n",
+ pr_info("sema-chan-dump: h=0x%x eng=%u gpfifo=0x%llx ent=%u userd=0x%llx as=%u ok=%d get=%u sub=%llu exp=%u rel=0x%llx crel=0x%llx armed=%d notif=0x%llx as=%u slot0{info=0x%x st=0x%x} slot1{info=0x%x st=0x%x} notified=%llu\n",
ch->h_object, ch->engine_type, ch->gpfifo_va,
ch->gpfifo_entries, ch->userd_pa, ch->userd_as,
ch->userd_ok, ch->last_get, ch->submissions,
ch->expected_sem_payload, ch->last_rel_va,
ch->counter_pa,
+ ch->gr_pub_armed,
ch->notifier_pa,
ch->notifier_as, info0, st0, info1, st1,
ch->notified);
diff --git a/tools/guest-diag/fenceword_assist.py b/tools/guest-diag/fenceword_assist.py
new file mode 100644
index 0000000..9e207e2
--- /dev/null
+++ b/tools/guest-diag/fenceword_assist.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: BSD-3-Clause
+# fenceword_assist.py - keep the Vulkan fence wait word of a live vk_frame120
+# client released (session 125).
+#
+# SUPERSEDED (SESSION 126, commit 95e7f57e): the service now authors these
+# releases itself. The `sema_gr_ahead` fence fold sweeps the channel's
+# pre-built ring continuation beyond GP_PUT through the ring's validated
+# GPFIFO translation, decodes the INC-5 RELEASE groups it finds (addr 0x5c
+# count>=5) and queues them in the standard pending ledger; r717 evidence:
+# `sema-ahead: h=0xbeec0100 put=11 swept=10 rels=4` followed 30 ms later by
+# `sema-svc: deferred release va=0x45ba090 payload=1 (was cur=0)`, with
+# VKFRAME seq/DONE and NO assist running (see
+# backups/evidence-20261002-victory/r717-fencefold/). Keep this tool only as
+# the A/B fallback for boots that predate the fold (or with
+# sema_gr_ahead=0).
+#
+# The client's fence wait polls a word in a 16 KB /dev/nvidiactl BO mapped
+# read-write into the client: the init wait word lives at +0x80 (the t122
+# "BW" chaser writes it once) and the per-submit fence word at +0x90 of the
+# SAME BO (GPU VA <base>+0x90, delivered by the submit push's INC-5 semaphore
+# group). While the emulation does not apply that group, this host assist
+# writes 1 to the +0x90 slot whenever it reads 0 - the direct predecessor of
+# the service-side fold (see Handoff SESSION 125/126).
+#
+# Usage (host, while a t122-driven client runs):
+# tools/guest-diag/fenceword_assist.py [seconds] [guest-pid]
+import os
+import subprocess
+import sys
+import time
+
+VMEXEC = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)),
+ "..", "..", "scripts", "vgpu_vm.py"))
+PY = r'''
+import struct, sys, time
+pids = [p for p in sys.argv[1:] if p]
+maps_of = {}
+def vma(pid, addr):
+ for line in open("/proc/%d/maps" % pid):
+ rng, perms = line.split(None, 2)[:2]
+ a, b = [int(x, 16) for x in rng.split("-")]
+ if a <= addr < b:
+ return line.strip()
+ return None
+found = {}
+for pid in pids:
+ try:
+ maps = [l for l in open("/proc/%d/maps" % pid) if "nvidia" in l and "rw-s" in l]
+ except OSError:
+ continue
+ for l in maps:
+ rng, perms, off = l.split(None, 3)[:3]
+ a, b = [int(x, 16) for x in rng.split("-")]
+ if b - a != 0x4000:
+ continue
+ try:
+ mem = open("/proc/%d/mem" % pid, "r+b", 0)
+ mem.seek(a + 0x80); init = mem.read(4)
+ mem.seek(a + 0x90); fw = mem.read(4)
+ except Exception:
+ continue
+ if init == b"\x01\x00\x00\x00" and fw == b"\x00\x00\x00\x00":
+ found[pid] = (a + 0x90, mem)
+ print("found pid=%s word=%#x" % (pid, a + 0x90), flush=True)
+end = time.time() + float(sys.argv[1])
+while time.time() < end:
+ for pid, (w, mem) in list(found.items()):
+ try:
+ mem.seek(w)
+ if mem.read(4) == b"\x00\x00\x00\x00":
+ mem.seek(w); mem.write(struct.pack("<I", 1)); mem.flush()
+ print("released pid=%s word=%#x" % (pid, w), flush=True)
+ except Exception:
+ del found[pid]
+ time.sleep(0.5)
+print("assist done", flush=True)
+'''
+
+
+def main():
+ dur = sys.argv[1] if len(sys.argv) > 1 else "300"
+ pid = sys.argv[2] if len(sys.argv) > 2 else ""
+ b64 = __import__("base64").b64encode(PY.encode()).decode()
+ subprocess.run([VMEXEC, "exec", "--timeout", str(int(dur) + 60),
+ "echo %s | base64 -d > /tmp/fwa.py; sudo python3 /tmp/fwa.py %s %s"
+ % (b64, dur, pid)])
+
+
+if __name__ == "__main__":
+ main()