summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-18 15:40:11 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-18 15:41:21 -0400
commit4d420373e50be777e967b0c671a8ff037ef92a45 (patch)
treef60225c061b1a39e4742b89fda57e70f38ec5dc9
parent54a41db23985a82654ad4daeca283682e2b14b1e (diff)
downloadopen-iov-4d420373e50be777e967b0c671a8ff037ef92a45.tar.gz
open-iov-4d420373e50be777e967b0c671a8ff037ef92a45.zip
vgsp: channel-reuse reconciliation + first-attempt readiness (fresh on 54a41db2)
Net port of the stallfree work onto master without the reverted hint-stop: - Channel-reuse reconciliation, provably safe: svc_chan_registration marks a genuinely new binding (!same_gen) with wd_chanswitch; the first pump pass drops opdef slots bound to the reused handle ONLY when their first-pending stamp predates the reconcile instant (time_before), so a slot the fresh binding creates (after registration, at/after the reconcile) can never be touched; logs reason=stale-entry-chanswitch when the ring already carries content. Captures stay enabled (the fresh client's own entries may already be at slot 0). - First-attempt readiness (r229/r231: attempt 1 stalls, attempt 2 works once the 10 s rebuild cadence caught up): a genuinely new binding gets a 3 s freshness window (wd_newbind_until, SVC_GR_NEWBIND_MS) in which the pump (a) does NOT silently consume a GR entry that decoded nothing (no release/QMD/ops, non-replay) - it holds it and requests an immediate rebuild (svc.rebuild_now, consumed at the top of the service loop before the next pump), and (b) does the same for a GR entry whose page is unmapped. The entry re-evaluates with resolved pages inside the window, so the launch executes on the FIRST attempt; after the window entries consume as before (<=3 s extra on first contact). - Token: 'vgsp-gr: GR-STALL-pending-map h=... va=... entry=... action=rebuild reason=stale-entry-newbind' (bounded 16/session). - No hint-stop (master's revert already removed it; kept absent - the re-post window is the delivery bridge for late-registered events). - f0789007 kill-recovery, the r220 hold guard and the m181 arm rule are untouched (already present on this base). make kernel green (0 errors, pre-existing warnings only).
-rw-r--r--kernel/gsp_sema_service.c171
1 files changed, 170 insertions, 1 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 9de9038..02c100f 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -422,6 +422,15 @@ struct svc_channel {
u32 wd_nocap_put; /* force-replay (no capture) for slots below
* this while the stale range is re-walked; 0 =
* none */
+ bool wd_chanswitch; /* STALLFREE: registered; first pump pass
+ * must reconcile the binding before the walk
+ * (drop dead opdef slots whose since predates
+ * the binding; log the transition) */
+ unsigned long wd_newbind_until; /* STALLFREE: freshness window of a
+ * new binding; inside it an
+ * unmapped/undecoded GR entry is held
+ * (not silently consumed) and an
+ * immediate rebuild is requested */
};
/*
@@ -1011,6 +1020,15 @@ struct sema_svc {
* stale-replay wedge watchdog (zero on a green run).
*/
u64 gr_stale_wedge;
+ /*
+ * STALLFREE r229/r230: set by the pump when a new binding's GR entry
+ * is unmapped/empty; the service loop consumes it by running
+ * svc_rebuild() before the next pump pass, so the page translations
+ * land within ~1 s and the pending entry re-evaluates.
+ */
+ bool rebuild_now;
+ u32 gr_pending_map; /* GR-STALL-pending-map lines
+ * emitted (bounded proof) */
u64 fwd_gr_hold_exp; /* GRFIX FIX1: data-stalled holds
* that reached the hard bound and
* were consumed with the software
@@ -1308,6 +1326,18 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
ch->gr_rehold_idx = ~0u;
ch->gr_nocap_idx = ~0u; /* m164: no no-capture window armed */
ch->session_gen = svc_sess_gen; /* m176 */
+ /*
+ * STALLFREE: a genuinely new binding - a fresh channel or a
+ * same-handle rebinding across a session/context boundary
+ * (!same_gen) - must reconcile on its first pump pass: drop
+ * opdef slots a dead context left under this handle (only
+ * ones whose first-pending stamp predates the binding, so a
+ * slot the fresh binding creates can never be touched) and
+ * log the transition when the ring already carries content.
+ * A same-generation RM re-issue preserves state deliberately
+ * (m148/m176) and is not reconciled.
+ */
+ ch->wd_chanswitch = !same_gen;
if (same_gen)
bitmap_copy(ch->rel_done, keep_rel_done,
SVC_GPFIFO_MAX_BITS);
@@ -13403,6 +13433,31 @@ static void svc_opdef_drop_gone(struct nv_vgpu_device *vgpu,
}
}
+/*
+ * STALLFREE: drop every opdef slot bound to one channel handle whose
+ * first-pending stamp PREDATES `before` (the rebinding's reconcile instant).
+ * A context switch/teardown invalidates the handle's withheld sets - their
+ * waiters are gone and the releases must not ride the reused channel (the
+ * opdef-hold class). The `before` filter is the provable-safety rule: a slot
+ * the fresh binding creates after its registration (the reconcile runs at
+ * the top of the first pump pass, before that pass's walk) always has
+ * since >= before and is NEVER touched; only the dead context's slots are
+ * dropped. Counted as a LOSS when the slot still held undelivered releases.
+ */
+static void svc_opdef_drop_channel(struct nv_vgpu_device *vgpu, u32 h_object,
+ unsigned long before)
+{
+ int i;
+
+ for (i = 0; i < SVC_OPDEF_SLOTS; i++) {
+ struct svc_opdef_ent *e = &svc_opdef[i];
+
+ if (e->active && e->vgpu == vgpu && e->h_object == h_object &&
+ time_before(e->since, before))
+ svc_opdef_drop_counted(e);
+ }
+}
+
static void svc_opdef_reset_all(void)
{
int i;
@@ -16527,6 +16582,18 @@ static void svc_userd_sweep(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
* only the rewind re-opening of a consumed window produces.
*/
#define SVC_GR_STALE_WEDGE_MS 3000u
+/*
+ * STALLFREE r229/r230: freshness window after a genuinely new channel
+ * binding. Within it, a GR entry whose page is unmapped or whose decode
+ * yields nothing is NOT silently consumed: the pump requests an immediate
+ * rebuild/pin pass and leaves the entry pending, so the first launch of a
+ * fresh client executes on its FIRST attempt instead of being dropped
+ * before the page translations land (r229/r231: attempt 1 stalls, attempt
+ * 2 works because the rebuild caught up). Stale-free cost: at most this
+ * window of extra latency on first contact.
+ */
+#define SVC_GR_NEWBIND_MS 3000u
+
static void svc_gr_stale_wedge_check(struct nv_vgpu_device *vgpu,
struct svc_channel *ch, u32 put, u32 get)
{
@@ -17089,6 +17156,36 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
svc_gr_stale_wedge_check(vgpu, ch, put, get);
/*
+ * STALLFREE r229: first-use reconciliation after a genuinely
+ * new binding (see svc_chan_registration). The rebinding's
+ * ring cursor starts at 0 (fresh sequence); drop the dead
+ * context's opdef slots under this handle (their waiters are
+ * gone and the sets must not ride the reused channel, the
+ * r228 opdef-hold class), and when the ring already carries
+ * content (put > 1) log the reconcile so the transition is
+ * visible. Captures stay enabled: the fresh client's own
+ * entries may already be at slot 0.., and suppressing them
+ * would lose exactly the first launch this must protect; a
+ * stale launch re-walk is bounded by the submit budget and
+ * the m181 put-drop recovery (same precedent).
+ */
+ if (ch->wd_chanswitch) {
+ ch->wd_chanswitch = false;
+ svc_opdef_drop_channel(vgpu, ch->h_object, jiffies);
+ ch->wd_newbind_until = jiffies +
+ msecs_to_jiffies(SVC_GR_NEWBIND_MS);
+ if (put > 1) {
+ svc.gr_stale_wedge++;
+ pr_warn_ratelimited("vgsp-gr: GR-STALL-stale-wedge h=0x%x va=0x%llx last=%u put=%u get=%u sub=%llu waited_ms=0 action=recover reason=stale-entry-chanswitch reset=0 rewind=0 entry=0 cpa=0x%llx rpa=0x%llx\n",
+ ch->h_object, ch->gpfifo_va,
+ (u32)ch->last_get, put, get,
+ ch->submissions,
+ ch->counter_pa,
+ ch->release_pa);
+ }
+ }
+
+ /*
* m136 (SA-STALL M2): liveness watchdog for a held CE entry.
* When this channel's entry loop is not re-entered (early
* continue paths above, or no fresh pushes), a deferred entry
@@ -17572,6 +17669,41 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
if (rehold)
ch->gr_rehold_idx = ~0u;
/*
+ * STALLFREE r230: a fresh
+ * binding's entry that decoded
+ * NOTHING (no release, no QMD,
+ * no ops) is the r229 attempt-1
+ * class: the page content read
+ * before the client's real
+ * writes/translations landed, so
+ * consuming it would drop the
+ * launch. Hold it for the short
+ * new-binding window and ask for
+ * an immediate rebuild; after the
+ * window it consumes as before, so
+ * the cost is bounded.
+ */
+ if (!ch->engine_type &&
+ !entry_replay &&
+ svc_rel_count == rel_n_iter &&
+ svc_gr_qmd_seen == 0 &&
+ ch->wd_newbind_until &&
+ time_before(jiffies,
+ ch->wd_newbind_until)) {
+ if (!ch->defer_since)
+ ch->defer_since =
+ jiffies;
+ svc.rebuild_now = true;
+ if (svc.gr_pending_map < 16) {
+ svc.gr_pending_map++;
+ pr_warn("vgsp-gr: GR-STALL-pending-map h=0x%x va=0x%llx entry=%u action=rebuild reason=stale-entry-newbind\n",
+ ch->h_object, va,
+ (u32)idx);
+ }
+ kfree(pb);
+ break;
+ }
+ /*
* Record what ran for this entry
* identity before any hold path can
* break out: a held entry is decoded
@@ -18628,6 +18760,30 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
!ch->defer_since)
ch->defer_since =
jiffies;
+ /*
+ * STALLFREE r230: a fresh
+ * binding's unmapped entry is
+ * the host-readiness race -
+ * request the immediate rebuild
+ * so the pages resolve inside
+ * this grace instead of the
+ * 10 s cadence (r229/r231:
+ * attempt 1 lost exactly
+ * here, attempt 2 won after
+ * the rebuild caught up).
+ */
+ if (!ch->engine_type &&
+ ch->wd_newbind_until &&
+ time_before(jiffies,
+ ch->wd_newbind_until)) {
+ svc.rebuild_now = true;
+ if (svc.gr_pending_map < 16) {
+ svc.gr_pending_map++;
+ pr_warn("vgsp-gr: GR-STALL-pending-map h=0x%x va=0x%llx entry=%u action=rebuild reason=stale-entry-newbind\n",
+ ch->h_object, va,
+ (u32)idx);
+ }
+ }
svc.gpfifo_nomap++;
kfree(pb);
break;
@@ -18857,6 +19013,8 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
ch->wd_rewind_off_until;
svc.chan[k].wd_epoch_done = ch->wd_epoch_done;
svc.chan[k].wd_nocap_put = ch->wd_nocap_put;
+ svc.chan[k].wd_chanswitch = ch->wd_chanswitch;
+ svc.chan[k].wd_newbind_until = ch->wd_newbind_until;
svc.chan[k].pushpg_va = ch->pushpg_va;
svc.chan[k].pushpg_n = ch->pushpg_n;
svc.chan[k].content_max = ch->content_max;
@@ -19743,7 +19901,18 @@ static int svc_thread(void *arg)
* svc_rebuild() publishes (svc.last_build).
*/
sess = svc_guest_get(vgpu, true);
- if (svc_sess_flush || !svc.direct_base ||
+ /*
+ * STALLFREE r230: a pending rebuild request from the
+ * pump (a fresh binding's entry hit an unmapped page)
+ * forces the rebuild now instead of waiting for the
+ * 10 s cadence, so the first attempt's pages resolve.
+ */
+ if (svc.rebuild_now) {
+ svc.rebuild_now = false;
+ svc_rebuild(vgpu);
+ if (sess)
+ sess->last_build = svc.last_build;
+ } else if (svc_sess_flush || !svc.direct_base ||
!sess ||
time_after(jiffies,
(unsigned long)(sess->last_build +