From a3d801e394a1c57833c816edc3d3cee22dc4a78a Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 5 Nov 2026 11:24:16 -0500 Subject: 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. --- kernel/gsp_phys_booter.c | 143 ++++++++++++++++++++++++++++++++++++++++++++-- kernel/gsp_phys_booter.h | 9 +++ kernel/gsp_sema_service.c | 16 ++++++ 3 files changed, 163 insertions(+), 5 deletions(-) (limited to 'kernel') 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; @@ -20381,6 +20464,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 @@ -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); @@ -21929,6 +22023,26 @@ if (gsp_phys_gr_slot_kept(j, sa_mask, mme_hold_fe_busy = true; } } + /* + * 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 @@ -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); @@ -22242,6 +22357,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: @@ -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; -- cgit v1.2.3