summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-05 11:24:16 -0500
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-05 11:24:16 -0500
commita3d801e394a1c57833c816edc3d3cee22dc4a78a (patch)
treefe906759f83571dfd81ea4e9b88afe5111ce0879 /kernel
parent7d88261d01100e46a5160319b94b1e79b5eb1117 (diff)
downloadopen-iov-a3d801e394a1c57833c816edc3d3cee22dc4a78a.tar.gz
open-iov-a3d801e394a1c57833c816edc3d3cee22dc4a78a.zip
vgsp-gr: hold calls on a stream-damaged push so a dropped chunk cannot run LOAD_CONSTANT_BUFFER without its selector state
A chunked GL draw whose earlier chunk was dropped (prelude LOAD-ONLY or the r1447 arming window hold) replayed the remaining chunks' calls alone; the constant-buffer selector (0x2380) lived in the dropped chunk, so the bug2548370 macro's LOAD_CONSTANT_BUFFER(0x2390) class-errored (ErrorCode 3) and the engine poisoned (badf1201 sentinel: pc=513/mac=0xf1 are its artifact, not a macro). Propagate the stream-relative chunk offset through the candidate and submit path, keep a per-push STREAM-DAMAGE record of the lowest dropped chunk, emit later chunks calls-held (the proven-safe shape), never arm the arming window-hold on a damaged stream, and start a fresh section at stream_rel==0. r1487: the exact r1485 damaging shape now retires clean (events=5, no poison) and the next chunk is captured; r1486 (holds off) still poisons because the first launch runs against a partially packed prelude, confirming the dropped chunk is the cause, not the hold release.
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_phys_booter.c143
-rw-r--r--kernel/gsp_phys_booter.h9
-rw-r--r--kernel/gsp_sema_service.c16
3 files changed, 163 insertions, 5 deletions
diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c
index 657a418..6f2a858 100644
--- a/kernel/gsp_phys_booter.c
+++ b/kernel/gsp_phys_booter.c
@@ -20316,6 +20316,88 @@ bool gsp_phys_gr_mme_fe_defer_wanted(u32 h_object, u32 win3d_n)
return true;
}
+/*
+ * r1487 STREAM-DAMAGE: a client 3D section is sliced into method-aligned
+ * chunks replayed as separate launches (svc_gl_stream_chunks). The channel
+ * state accumulates across those launches, so a chunk that is *dropped*
+ * (MME-LOAD-ONLY carrier while the prelude is not resident, or the r1447
+ * arming window-hold) permanently removes that slice of the method stream.
+ * A later chunk of the same push then executes CALL_MME_MACRO runs against a
+ * channel missing the dropped state: r1485's 81-word tail called
+ * LOAD_CONSTANT_BUFFER_BUG2548370 after chunks 1..3 had been dropped, the
+ * macro emitted method 0x2390 (LOAD_CONSTANT_BUFFER(0)) without a valid
+ * constant-buffer selector, the 3D class raised
+ * "Offset 00002390, Data bf800000, ErrorCode 00000003", and the FECS
+ * priv-error sentinel 0xbadf1201 wedged the lane (pc=513/mac=0xf1 are
+ * artifacts of that sentinel: 0xbadf1201 & 0xfff == 0x201).
+ *
+ * Fail closed per stream: remember the lowest dropped stream-relative offset
+ * for a push; while a later chunk of that push arrives, force the calls-held
+ * emission (CALL runs elided, the proven-safe shape) so no macro can run
+ * against the incomplete state. A chunk at stream_rel 0 is the start of a
+ * fresh client section and clears the record, so the next frame replays its
+ * whole window in order and draws normally.
+ */
+#define GSP_PHYS_GR_STREAM_DAMAGE_SLOTS 8
+struct gsp_phys_gr_stream_damage {
+ u64 push_va;
+ u32 rel;
+ bool valid;
+};
+static struct gsp_phys_gr_stream_damage
+ gsp_phys_gr_stream_damage[GSP_PHYS_GR_STREAM_DAMAGE_SLOTS];
+static u32 gsp_phys_gr_stream_damage_next;
+static u32 gsp_phys_gr_stream_damage_hits;
+static u32 gsp_phys_gr_stream_damage_notes;
+
+static bool gsp_phys_gr_stream_damaged(u64 push_va, u32 rel)
+{
+ u32 i;
+
+ for (i = 0; i < GSP_PHYS_GR_STREAM_DAMAGE_SLOTS; i++) {
+ struct gsp_phys_gr_stream_damage *d =
+ &gsp_phys_gr_stream_damage[i];
+
+ if (d->valid && d->push_va == push_va)
+ return rel > d->rel;
+ }
+ return false;
+}
+
+static void gsp_phys_gr_stream_damage_note(u64 push_va, u32 rel)
+{
+ u32 i;
+
+ for (i = 0; i < GSP_PHYS_GR_STREAM_DAMAGE_SLOTS; i++) {
+ struct gsp_phys_gr_stream_damage *d =
+ &gsp_phys_gr_stream_damage[i];
+
+ if (d->valid && d->push_va == push_va) {
+ if (rel < d->rel)
+ d->rel = rel;
+ return;
+ }
+ }
+ i = gsp_phys_gr_stream_damage_next++ %
+ GSP_PHYS_GR_STREAM_DAMAGE_SLOTS;
+ gsp_phys_gr_stream_damage[i].push_va = push_va;
+ gsp_phys_gr_stream_damage[i].rel = rel;
+ gsp_phys_gr_stream_damage[i].valid = true;
+}
+
+static void gsp_phys_gr_stream_damage_clear(u64 push_va)
+{
+ u32 i;
+
+ for (i = 0; i < GSP_PHYS_GR_STREAM_DAMAGE_SLOTS; i++) {
+ struct gsp_phys_gr_stream_damage *d =
+ &gsp_phys_gr_stream_damage[i];
+
+ if (d->valid && d->push_va == push_va)
+ d->valid = false;
+ }
+}
+
static u32 gsp_phys_rmcall_build_gr_launch(u32 *pb,
const struct gsp_phys_gr_submit *req,
const u32 *qmd, u64 qmd_va,
@@ -20362,6 +20444,7 @@ static u32 gsp_phys_rmcall_build_gr_launch(u32 *pb,
bool mme_hold_fe_busy = false;
bool mme_load_only = false;
bool latch_before = false;
+ bool stream_damaged = false;
u32 pre_words_out = 0;
u32 win_emit_off = 0;
const u32 *pl = NULL;
@@ -20382,6 +20465,16 @@ static u32 gsp_phys_rmcall_build_gr_launch(u32 *pb,
if (block_cap > GSP_PHYS_RMCALL_GR_METHOD_WORDS - 128u)
block_cap = GSP_PHYS_RMCALL_GR_METHOD_WORDS - 128u;
/*
+ * r1487 STREAM-DAMAGE: stream_rel 0 starts a fresh client
+ * section, so any dropped-chunk record for this push is
+ * stale; a later chunk keeps the record and is emitted
+ * calls-held below (see the table comment).
+ */
+ if (req->stream_rel == 0u)
+ gsp_phys_gr_stream_damage_clear(req->push_va);
+ stream_damaged = gsp_phys_gr_stream_damaged(req->push_va,
+ req->stream_rel);
+ /*
* SESSION 153-CONTINUED-63: MME START-ADDRESS RAM completion.
* The prelude capture is capped at 512 words, so the client's
* SA table (macro slot -> IR address) may be incomplete; on a
@@ -21854,6 +21947,7 @@ if (gsp_phys_gr_slot_kept(j, sa_mask,
*/
if (settle && latch_after && !pre_words_out &&
!(rg == pl_gen && rh == req->h_object) &&
+ !stream_damaged &&
READ_ONCE(
gsp_phys_rmcall_gr_mme_hold_window)) {
WARN_ON_ONCE(mme_load_only);
@@ -21930,6 +22024,26 @@ if (gsp_phys_gr_slot_kept(j, sa_mask,
}
}
/*
+ * r1487 STREAM-DAMAGE: an earlier chunk of this same push was
+ * dropped (LOAD-ONLY carrier or the arming window hold), so
+ * the channel no longer holds the state that chunk carried.
+ * Run this chunk's CALL_MME_MACRO/DATA runs held (elided) -
+ * the same proven-safe shape as the prelude-hold path - so no
+ * macro can emit LOAD_CONSTANT_BUFFER against the incomplete
+ * state and class-error into the FECS priv-error poison. The
+ * window's non-CALL state methods still execute, and the next
+ * client section (stream_rel 0) clears the record.
+ */
+ if (stream_damaged && !mme_load_only) {
+ if (!mme_hold_calls && gsp_phys_gr_stream_damage_hits < 32u) {
+ gsp_phys_gr_stream_damage_hits++;
+ pr_info("vgsp-gr: STREAM-DAMAGE h=0x%x push=0x%llx rel=%u window=%u (calls held: earlier chunk dropped)\n",
+ req->h_object, req->push_va,
+ req->stream_rel, win3d_n);
+ }
+ mme_hold_calls = true;
+ }
+ /*
* SESSION 153-CONTINUED-107: IRAM readback gate (see the
* gsp_phys_gr_mme_iram_gate comment). The calls would run this
* launch; verify the live IRAM actually holds the packed image
@@ -22227,7 +22341,8 @@ if (gsp_phys_gr_slot_kept(j, sa_mask,
* (the r1443b shape); the strict grace budget restarts with the
* next generation cycle.
*/
- if (mme_hold_window && !mme_load_only && !mme_hold_calls) {
+ if (mme_hold_window && !mme_load_only &&
+ (!mme_hold_calls || stream_damaged)) {
WRITE_ONCE(gsp_phys_gr_mme_window_hold_gen, 0);
WRITE_ONCE(gsp_phys_gr_mme_window_hold_h, 0);
WRITE_ONCE(gsp_phys_gr_mme_strict_holds, 0);
@@ -22243,6 +22358,23 @@ if (gsp_phys_gr_slot_kept(j, sa_mask,
win_emit_off = (u32)(p - pb);
if (mme_load_only || mme_hold_window) {
/*
+ * r1487 STREAM-DAMAGE: this window slice never reaches
+ * the engine. Record the push/offset so the remaining
+ * chunks of the same client section hold their CALL
+ * runs instead of executing a macro against the lost
+ * state (see the damage table comment).
+ */
+ gsp_phys_gr_stream_damage_note(req->push_va,
+ req->stream_rel);
+ if (gsp_phys_gr_stream_damage_notes < 32u) {
+ gsp_phys_gr_stream_damage_notes++;
+ pr_info("vgsp-gr: STREAM-DAMAGE-NOTE h=0x%x push=0x%llx rel=%u window=%u (%s)\n",
+ req->h_object, req->push_va,
+ req->stream_rel, win3d_n,
+ mme_hold_window ? "window-hold" :
+ "load-only");
+ }
+ /*
* r1465 PAIR-ORPHAN-DROP: the FE consumed none of
* this window, so the pair projection is a no-op:
* restore the launch-entry snapshot the fixer took
@@ -22257,9 +22389,9 @@ if (gsp_phys_gr_slot_kept(j, sa_mask,
READ_ONCE(gsp_phys_gr_mme_pair_open_entry_split));
}
if (mme_hold_window && !mme_load_only) {
- pr_info_ratelimited("vgsp-gr: MME-WINDOW-HOLD gen=%u h=%#x window=%u dropped (r1447 arming hold) push=0x%llx\n",
+ pr_info_ratelimited("vgsp-gr: MME-WINDOW-HOLD gen=%u h=%#x window=%u rel=%u dropped (r1447 arming hold) push=0x%llx\n",
pl_gen, req->h_object, win3d_n,
- req->push_va);
+ req->stream_rel, req->push_va);
} else {
/*
* SESSION 153-CONTINUED-107: no window on a
@@ -22274,9 +22406,10 @@ if (gsp_phys_gr_slot_kept(j, sa_mask,
load_only_log++;
if (load_only_log <= 24u)
- pr_info("vgsp-gr: MME-LOAD-ONLY carrier h=0x%x gen=%u window=%u dropped until the IRAM gate verifies (log %u) push=0x%llx\n",
+ pr_info("vgsp-gr: MME-LOAD-ONLY carrier h=0x%x gen=%u window=%u rel=%u dropped until the IRAM gate verifies (log %u) push=0x%llx\n",
req->h_object, pl_gen,
- win3d_n, load_only_log,
+ win3d_n, req->stream_rel,
+ load_only_log,
req->push_va);
}
} else if (mme_hold_calls) {
diff --git a/kernel/gsp_phys_booter.h b/kernel/gsp_phys_booter.h
index 6689771..f821a85 100644
--- a/kernel/gsp_phys_booter.h
+++ b/kernel/gsp_phys_booter.h
@@ -495,6 +495,15 @@ struct gsp_phys_gr_submit {
*/
const u32 *stream;
u32 stream_n; /* dwords */
+ /* Stream-relative dword offset of this window inside the client's
+ * 3D section (0 = the section's first chunk). Used to detect that an
+ * earlier chunk of the same push was dropped by a LOAD-ONLY carrier
+ * or the r1447 window hold: the remaining chunks must then elide
+ * their CALL runs (the lost chunk's state - e.g. the constant-buffer
+ * selector - is no longer on the channel, so a macro that emits
+ * LOAD_CONSTANT_BUFFER would class-error -> FECS priv-error poison).
+ */
+ u32 stream_rel;
/*
* SESSION 153-CONTINUED-87: the first program VA the captured
* window binds (SET_PIPELINE_PROGRAM_ADDRESS pair; 0 when none).
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 327a932..1a8efd6 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -7516,6 +7516,10 @@ struct svc_gr_cand_rec {
bool stream_3d;
u32 stream_off; /* dword offset of the window inside raw[] */
u32 stream_n; /* dwords of the replayable window */
+ u32 stream_rel; /* stream-relative dword offset of this
+ * window (0 = the section's first chunk); the
+ * backend uses it to detect a dropped earlier
+ * chunk of the same push */
/*
* SESSION 153-CONTINUED-87: every SET_PIPELINE_PROGRAM_ADDRESS pair
* the captured window itself binds (VA + pipeline slot), copied from
@@ -9569,6 +9573,13 @@ static void svc_gl_stream_chunks(struct svc_channel *ch, const u8 *buf,
c->stream_3d = true;
c->stream_off = cur / 4u;
c->stream_n = (end - cur) / 4u;
+ /*
+ * Stream-relative offset for the backend's dropped-chunk
+ * detection. cur walks from svc_gl.start_off, so the first
+ * chunk of the section is 0 and each later chunk is exactly
+ * the number of words already sliced off.
+ */
+ c->stream_rel = (cur - svc_gl.start_off) / 4u;
n++;
cur = end;
}
@@ -28153,6 +28164,8 @@ struct svc_fwd_req {
bool gr_stream_3d;
u32 gr_stream_off;
u32 gr_stream_n;
+ u32 gr_stream_rel; /* stream-relative dword offset of this
+ * window (0 = the section's first chunk) */
u32 gr_raw[SVC_GR_PUSH_WORDS];
u64 gr_qmd_dev_va;
/*
@@ -28322,6 +28335,7 @@ static bool svc_gr_missre_arm(const struct svc_fwd_rel *rel, u8 nrel)
c->stream_3d = svc_fwd_req.gr_stream_3d;
c->stream_off = svc_fwd_req.gr_stream_off;
c->stream_n = svc_fwd_req.gr_stream_n;
+ c->stream_rel = svc_fwd_req.gr_stream_rel;
memcpy(c->raw, svc_fwd_req.gr_raw, sizeof(c->raw));
c->qmd_dev_va = svc_fwd_req.gr_qmd_dev_va;
memcpy(c->qmd, svc_fwd_req.gr_qmd, sizeof(c->qmd));
@@ -30975,6 +30989,7 @@ static void svc_fwd_gr_work(void)
if (svc_fwd_req.gr_stream_3d) {
gs.stream = svc_fwd_req.gr_raw + svc_fwd_req.gr_stream_off;
gs.stream_n = svc_fwd_req.gr_stream_n;
+ gs.stream_rel = svc_fwd_req.gr_stream_rel;
}
gs.prog_va = svc_fwd_req.gr_prog_va; /* CONT-87: family binds */
gs.n_prog_binds = svc_fwd_req.gr_n_prog_binds;
@@ -33569,6 +33584,7 @@ static int svc_gr_req_fill(struct nv_vgpu_device *vgpu,
svc_fwd_req.gr_stream_3d = svc_gr_cand.stream_3d;
svc_fwd_req.gr_stream_off = svc_gr_cand.stream_off;
svc_fwd_req.gr_stream_n = svc_gr_cand.stream_n;
+ svc_fwd_req.gr_stream_rel = svc_gr_cand.stream_rel;
svc_fwd_req.gr_n_prog_binds = svc_gr_cand.n_prog_binds;
{
u32 pb;