summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-29 03:33:49 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-29 03:33:49 -0400
commit5d02d09fdb42bcb1952a94e75d8cb63b25e1aeb9 (patch)
tree85234f87b492b30d87713fe3f0b6216dc2d2d41e /kernel
parent75e993d9cabf07da3ebe7adc707bd49f0ce7bc30 (diff)
downloadopen-iov-5d02d09fdb42bcb1952a94e75d8cb63b25e1aeb9.tar.gz
open-iov-5d02d09fdb42bcb1952a94e75d8cb63b25e1aeb9.zip
vgsp: GL section arrival ledger (sema-glass) + denied-copy serving (sema_serve_denied); no new GL content after init measured (SESSION 153 r939)
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_sema_service.c240
1 files changed, 239 insertions, 1 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 7f3a4b2..f095adc 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -7137,6 +7137,85 @@ static void svc_gl_cap_store(u64 push_va, u64 hash)
svc_gl_cap_hist[i].push_va = push_va;
svc_gl_cap_hist[i].hash = hash;
}
+/*
+ * SESSION 153 CONT.6 (task 1): the per-frame GL section-arrival ledger.
+ * svc_gl_flush is the ONLY point where a C597 section's whole push buffer
+ * (content included) reaches the GL machinery, so every arrival is
+ * recorded here: (push_va, content hash, arrival count). The question the
+ * ledger answers deterministically is whether a NEW (different-content)
+ * section ever arrives after the driver's init - the wall evidence says
+ * the X server's 1524 B section is byte-identical every capture ("replay
+ * already captured" by content hash), but that evidence only covered the
+ * budgeted replay/skip logs. Now: first arrival, every content change
+ * (with the first words and the bound-site words), and a periodic
+ * liveness row per push VA. A hash never seen before on ANY push VA is
+ * NEW-CONTENT - the moment a genuinely new section (a draw stream)
+ * appears, it is on record with its words.
+ */
+#define SVC_GL_ARRIVAL_MAX 64
+static struct {
+ u64 push_va;
+ u64 hash;
+ u32 arrivals;
+} svc_gl_arrivals[SVC_GL_ARRIVAL_MAX];
+static u32 svc_gl_arrival_n;
+static u64 svc_gl_arrival_total;
+static u32 svc_gl_arrival_log; /* bounded new/changed/periodic rows */
+
+static void svc_gl_arrival_note(u64 push_va, const u8 *buf, u32 len)
+{
+ u64 ph = svc_push_hash(buf, len);
+ u32 i, slot = SVC_GL_ARRIVAL_MAX;
+ bool known_hash = false;
+
+ for (i = 0; i < svc_gl_arrival_n; i++) {
+ if (svc_gl_arrivals[i].push_va == push_va) {
+ slot = i;
+ break;
+ }
+ if (svc_gl_arrivals[i].hash == ph)
+ known_hash = true;
+ }
+ svc_gl_arrival_total++;
+ if (slot < SVC_GL_ARRIVAL_MAX) {
+ bool changed = svc_gl_arrivals[slot].hash != ph;
+
+ svc_gl_arrivals[slot].arrivals++;
+ if (!changed &&
+ svc_gl_arrivals[slot].arrivals % 1024 != 0)
+ return;
+ if (svc_gl_arrival_log < 512) {
+ svc_gl_arrival_log++;
+ pr_info("sema-glass: %s h=0x%x push=0x%llx len=%u hash=0x%llx n=%u total=%llu first=%08x %08x %08x %08x\n",
+ changed ? "CONTENT-CHANGED" : "arrival",
+ 0, push_va, len, ph,
+ svc_gl_arrivals[slot].arrivals,
+ svc_gl_arrival_total,
+ len >= 4 ? get_unaligned_le32(buf) : 0,
+ len >= 8 ? get_unaligned_le32(buf + 4) : 0,
+ len >= 12 ? get_unaligned_le32(buf + 8) : 0,
+ len >= 16 ? get_unaligned_le32(buf + 12) : 0);
+ }
+ svc_gl_arrivals[slot].hash = ph;
+ return;
+ }
+ if (svc_gl_arrival_n < SVC_GL_ARRIVAL_MAX) {
+ slot = svc_gl_arrival_n++;
+ svc_gl_arrivals[slot].push_va = push_va;
+ svc_gl_arrivals[slot].hash = ph;
+ svc_gl_arrivals[slot].arrivals = 1;
+ if (svc_gl_arrival_log < 512) {
+ svc_gl_arrival_log++;
+ pr_info("sema-glass: %s h=0x%x push=0x%llx len=%u hash=0x%llx n=1 total=%llu first=%08x %08x %08x %08x\n",
+ known_hash ? "KNOWN-CONTENT-OTHER-VA" : "NEW-CONTENT",
+ 0, push_va, len, ph, svc_gl_arrival_total,
+ len >= 4 ? get_unaligned_le32(buf) : 0,
+ len >= 8 ? get_unaligned_le32(buf + 4) : 0,
+ len >= 12 ? get_unaligned_le32(buf + 8) : 0,
+ len >= 16 ? get_unaligned_le32(buf + 12) : 0);
+ }
+ }
+}
static u32 svc_gl_rep_log; /* bounded replay-already-captured witness */
static u32 svc_gl_prog_log; /* R16 bounded program-read witness */
static u32 svc_gl_up_log; /* R17 bounded section-upload-apply witness */
@@ -7452,6 +7531,13 @@ static void svc_gl_flush(struct nv_vgpu_device *vgpu,
const char *blob_src = "none";
const char *op_src = "stream";
+ /*
+ * SESSION 153 CONT.6 (task 1): record every section arrival (any
+ * trusted or replay state, before any early exit) so a content
+ * change after init cannot hide behind the bounded capture logs.
+ */
+ if (gsp_sema_dbg)
+ svc_gl_arrival_note(push_va, buf, len);
svc_gl_prog_apply_param();
if (!svc_gl.seen || !trusted)
goto out;
@@ -12018,6 +12104,135 @@ static void svc_notify2d_keepalive(struct nv_vgpu_device *vgpu,
}
}
+/*
+ * SESSION 153 CONT.6 (task 2): serve a DENIED DMA copy.
+ *
+ * The X server's GL loop re-pushes transfer-shaped launches whose copy
+ * lands on a pass the trust/replay rules deny (witness:
+ * `sema-dmatrace: denied h=0x1000013 push=0x12001c64c dma=0x0000030d ...
+ * in=0x122281200 out=0x120350000 ... replay=1 eng=0`). Both operands of
+ * this class sit inside the identity guest-RAM window (< svc_pa_max()),
+ * so the emulator can execute the copy itself: read the source GPA, write
+ * the destination GPA, verify the landing bytes. The copy is exactly the
+ * work the hardware copy engine was asked to perform - refusing it only
+ * stalls whatever consumes the destination (the driver's own fence/state
+ * progression). Exactly-once per (push_va, content hash); a refused
+ * shape (operand outside guest RAM, oversized, or failing the readback)
+ * falls through to the existing deny/hold machinery unchanged.
+ */
+#define SVC_SERVE_MAX_BYTES (4u << 20)
+#define SVC_SERVE_HIST 64
+static bool gsp_sema_serve_denied = true;
+module_param_named(sema_serve_denied, gsp_sema_serve_denied, bool, 0644);
+MODULE_PARM_DESC(sema_serve_denied,
+ "Serve a denied transfer-shaped DMA copy between guest-RAM operands by executing it in the emulator (default 1; session 153 CONT.6)");
+static struct {
+ u64 push_va;
+ u64 hash;
+} svc_serve_hist[SVC_SERVE_HIST];
+static u32 svc_serve_hist_next;
+static u32 svc_serve_log; /* bounded served witness */
+static u32 svc_serve_ref_log; /* bounded refusal witness */
+static u64 svc_serve_bytes;
+static u64 svc_serve_ops;
+
+static bool svc_serve_seen(u64 push_va, u64 hash)
+{
+ u32 i;
+
+ for (i = 0; i < SVC_SERVE_HIST; i++)
+ if (svc_serve_hist[i].push_va == push_va &&
+ svc_serve_hist[i].hash == hash)
+ return true;
+ return false;
+}
+
+static void svc_serve_store(u64 push_va, u64 hash)
+{
+ u32 i = svc_serve_hist_next++ % SVC_SERVE_HIST;
+
+ svc_serve_hist[i].push_va = push_va;
+ svc_serve_hist[i].hash = hash;
+}
+
+static bool svc_serve_denied_copy(struct nv_vgpu_device *vgpu,
+ struct svc_channel *ch,
+ const struct svc_push_state *st, u32 dma,
+ u64 push_va, u64 pass_hash)
+{
+ u8 bounce[256];
+ u8 head[32];
+ u64 total = st->line_length;
+ u64 off, bytes = 0;
+ u64 hash = 1469598103934665603ULL;
+ int rc = 0;
+
+ if (!total || total > SVC_SERVE_MAX_BYTES)
+ return false;
+ if (st->offset_in + total > svc_pa_max() ||
+ st->offset_out + total > svc_pa_max()) {
+ if (gsp_sema_dbg && svc_serve_ref_log < 16) {
+ svc_serve_ref_log++;
+ pr_info("sema-serve: refused h=0x%x push=0x%llx in=0x%llx out=0x%llx line=%u dma=0x%08x (operand outside guest RAM)\n",
+ ch->h_object, push_va, st->offset_in,
+ st->offset_out, st->line_length, dma);
+ }
+ return false;
+ }
+ for (off = 0; off < total; off += sizeof(bounce)) {
+ size_t n = (size_t)min_t(u64, sizeof(bounce), total - off);
+ size_t b;
+
+ rc = svc_read(vgpu, st->offset_in + off, bounce, n);
+ if (rc)
+ break;
+ for (b = 0; b < n; b++) {
+ hash ^= bounce[b];
+ hash *= 1099511628211ULL;
+ }
+ rc = svc_write(vgpu, st->offset_out + off, bounce, n);
+ if (rc)
+ break;
+ bytes += n;
+ }
+ if (rc) {
+ if (gsp_sema_dbg && svc_serve_ref_log < 16) {
+ svc_serve_ref_log++;
+ pr_info("sema-serve: refused h=0x%x push=0x%llx in=0x%llx out=0x%llx line=%u rc=%d (read/write failed)\n",
+ ch->h_object, push_va, st->offset_in,
+ st->offset_out, st->line_length, rc);
+ }
+ return false;
+ }
+ /*
+ * Verified landing: read the destination back and compare its first
+ * bytes with the source's. A mismatch means the GPA model of the
+ * operand pair is wrong for this push - say so and do not latch it
+ * as served (the deny path then keeps its historical behaviour).
+ */
+ rc = svc_read(vgpu, st->offset_in, bounce, sizeof(head));
+ if (!rc)
+ rc = svc_read(vgpu, st->offset_out, head, sizeof(head));
+ if (rc || memcmp(head, bounce, sizeof(head))) {
+ if (gsp_sema_dbg && svc_serve_ref_log < 16) {
+ svc_serve_ref_log++;
+ pr_info("sema-serve: declined h=0x%x push=0x%llx out=0x%llx (readback mismatch)\n",
+ ch->h_object, push_va, st->offset_out);
+ }
+ return false;
+ }
+ svc_serve_store(push_va, pass_hash);
+ svc_serve_bytes += bytes;
+ svc_serve_ops++;
+ if (svc_serve_log < 128) {
+ svc_serve_log++;
+ pr_info("sema-serve: served h=0x%x push=0x%llx dma=0x%08x in=0x%llx out=0x%llx bytes=%llu hash=0x%llx verify=ok n=%llu\n",
+ ch->h_object, push_va, dma, st->offset_in,
+ st->offset_out, bytes, hash, svc_serve_ops);
+ }
+ return true;
+}
+
static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
u32 len, u64 page_pa, bool allow_ops,
struct svc_channel *ch, u32 entry_idx,
@@ -13292,6 +13507,7 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
u32 dma = v0;
u32 transfer = DMA_DATA_TRANSFER_TYPE(dma);
u32 n_ops_before = n_ops;
+ bool dma_served = false;
if (gsp_sema_dbg &&
svc.lda_log < (gsp_sema_ops_log > 48 ? gsp_sema_ops_log : 48)) {
@@ -13643,6 +13859,28 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
}
/*
+ * SESSION 153 CONT.6 (task 2): execute the copy a
+ * denied pass would otherwise refuse (see
+ * svc_serve_denied_copy). Placement: after the
+ * allow-path builder (a built op means the copy is
+ * already owned by svc_run_ops), before the owed/
+ * pending marks. A successful serve means nothing is
+ * owed - the dstpend note and the op_tr_pending mark
+ * below are suppressed via dma_served so the slot
+ * completes and later passes classify as replays.
+ */
+ if (gsp_sema_serve_denied && transfer != 0 &&
+ n_ops == n_ops_before && trusted && !replay &&
+ !ch->engine_type && st.line_length && st.have_out &&
+ st.have_in && !DMA_REMAP_ENABLE(dma) &&
+ !svc_serve_seen(push_va, svc_pass_hash) &&
+ svc_serve_denied_copy(vgpu, ch, &st, dma, push_va,
+ svc_pass_hash)) {
+ dma_served = true;
+ svc_dstpend_clear(st.offset_out);
+ }
+
+ /*
* r567: a trusted, transfer-shaped push that decoded
* NO op this pass (its operands are not resolved yet:
* the r569 mail b-copy needed have_in, which the
@@ -13815,7 +14053,7 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
* (non-replay) denied pass marks pending exactly as
* before.
*/
- if (!allow_ops && trusted && !replay &&
+ if (!allow_ops && trusted && !replay && !dma_served &&
!ch->engine_type && transfer != 0 &&
st.line_length && st.have_out && st.have_in &&
!DMA_REMAP_ENABLE(dma) &&