diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-18 15:40:11 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-18 15:41:56 -0400 |
| commit | 97f3973aa310c3f3387c4e69c274c5075f2f779e (patch) | |
| tree | f60225c061b1a39e4742b89fda57e70f38ec5dc9 | |
| parent | 54a41db23985a82654ad4daeca283682e2b14b1e (diff) | |
| download | open-iov-97f3973aa310c3f3387c4e69c274c5075f2f779e.tar.gz open-iov-97f3973aa310c3f3387c4e69c274c5075f2f779e.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).
(cherry picked from commit 4d420373e50be777e967b0c671a8ff037ef92a45)
| -rw-r--r-- | kernel/gsp_sema_service.c | 171 |
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 + |
