summaryrefslogtreecommitdiffstats
path: root/kernel/gsp_sema_service.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/gsp_sema_service.c')
-rw-r--r--kernel/gsp_sema_service.c8454
1 files changed, 8092 insertions, 362 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 561ebf0..53e2140 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -162,6 +162,16 @@ struct sema_map_entry {
struct svc_channel {
bool valid;
+ u32 session_gen; /* m176: session generation this entry was
+ * created in; learned PA state (counter_pa/
+ * release_pa) is only inherited on a re-
+ * registration within the same generation, so
+ * a recycled handle from a previous client can
+ * never keep the dead session's write targets */
+ struct nv_vgpu_device *vgpu; /* owning mdev (guest session); the
+ * table is keyed by (vgpu, h_object)
+ * because two guests' stock allocators
+ * choose identical handles */
u32 h_object;
u32 engine_type;
u32 cid; /* RM channel id (doorbell token vector) */
@@ -184,6 +194,28 @@ struct svc_channel {
* reset (PUT dropping below put_prev) resyncs
* last_get to slot 0 so the live first push is
* processed instead of skipped. */
+ bool gr_pub_armed; /* mm2 (r207/r208 second-client hang): a GR
+ * channel's completion word may only receive a
+ * GP_PUT publish after the channel has been
+ * proven to belong to the CURRENT session. A
+ * GR ring/USERD surviving in its fixed VRAM
+ * slot from the previous session reads a
+ * residual put at discovery (e.g. 109/112);
+ * publishing that residual put into the
+ * channel's own word (8c634144 made the target
+ * release_pa - the client's waited word) while
+ * the stale ring is consumed poisons the fresh
+ * client's UVM tracking semaphore: its
+ * completed_value observes a far-forward
+ * payload, the later reset/release sequence is
+ * a >MAX_JUMP (2*entries) downward observation,
+ * and the fresh session's first waits wedge
+ * (matmul: one GR push per channel then 242 s
+ * silent). Armed when a GR channel is seen with
+ * put <= 1 (pristine ring) or when the fresh-
+ * put-drop rewind proves a session switch; the
+ * rewind also voids any publish state the
+ * stale pass advanced. */
bool logged; /* one state log per channel */
bool pool_rel_seen; /* ISSUE-0002 r70: a pool release word for
* this channel has been read through a
@@ -228,6 +260,10 @@ struct svc_channel {
* window (observed: one 8 s grace per push,
* serializing the runtime init) */
DECLARE_BITMAP(slot_done_epoch, SVC_GPFIFO_MAX_BITS);
+ u8 slot_pend_tries[SVC_GPFIFO_MAX_BITS]; /* fail4 r308: bounded
+ * re-holds of a denied device-dst copy slot */
+ u32 counter_gen; /* r311: session generation that authored the
+ * completion word (guards the residue zero) */
/* GPFIFO slots already consumed in the current
* put epoch (the client's put value since it
* last wrapped). A pin-batch rewind replays
@@ -298,6 +334,10 @@ struct svc_channel {
* publish targets counter_pa instead, so both
* families of the counter page get written
* (SA-COUNTER FORENSICS-grv7-counter F1) */
+ u64 release_va; /* r378: the release VA release_pa was learned
+ * from, kept so a late write (stale-recover
+ * zero) can re-validate the target through
+ * gsp_sema_write_guard before it lands */
u32 counter_last; /* last value published to counter_pa */
u64 pushpg_va; /* last push page logged as pin-vs-window
* divergent (rate limit: one line per page) */
@@ -339,6 +379,58 @@ struct svc_channel {
* slot a bounded number of times, while a
* clobbered slot loops forever (storm
* detector) */
+ unsigned long gr_hold_until; /* m150: GR data-arg retry window expiry */
+ u32 gr_hold_idx; /* GPFIFO slot the window was armed for (identity:
+ * an entry holds at most once, then is consumed
+ * even if still unresolved - bounded zeros, never
+ * a hang); m156: re-held while the window is
+ * live so a busy forward gets its real retry */
+ unsigned long gr_hold_deadline; /* GRFIX FIX1b: hard expiry of the data-
+ * stalled hold for gr_hold_dead_va.
+ * Fixed once on the first hold of that
+ * launch push VA and NEVER moved while
+ * the launch stays held; when it passes
+ * the launch proceeds (pre-8b058884
+ * behaviour), so the guest can never
+ * wait unboundedly. Keyed by the launch
+ * VA, not the GPFIFO slot: pin-batch
+ * rewinds re-decode one launch at
+ * different slot indices (r213:
+ * entry 107 -> 107 -> 99), which
+ * re-armed a slot-keyed deadline on
+ * every pass. */
+ u64 gr_hold_dead_va; /* launch push VA the bound belongs to */
+ unsigned long gr_hold_dead_since; /* first-hold stamp (waited_ms) */
+ bool gr_hold_exp_logged; /* one GRFILL-EXPIRED line per launch */
+ u64 opdef_hold_dead_va; /* r361: launch push VA the opdef bound
+ * belongs to (slot-keyed `since` is
+ * defeated by rewind replays) */
+ unsigned long opdef_hold_deadline; /* absolute opdef hold expiry */
+ bool opdef_hold_exp_logged; /* one OPDEF-EXPIRED line per launch */
+ u32 gr_rehold_idx; /* m156: slot whose next decode must force a
+ * fresh launch capture (a held entry is
+ * re-decoded as a replay, which suppresses
+ * the capture; forcing it lets a deferred
+ * launch be forwarded once the engine's
+ * in-flight work completes) */
+ u32 gr_nocap_idx; /* m164 CLASS-2: slot in the no-capture hold
+ * (a readable launch push whose decode saw
+ * QMD words but built no candidate) */
+ u32 gr_nocap_replay;/* 1 = window opened on a replay decode */
+ u64 gr_nocap_va; /* push VA the window was opened for */
+ unsigned long gr_nocap_since; /* absolute first-seen stamp */
+ /*
+ * STALLFREE r225: stale-epoch / stale-replay wedge watchdog. A
+ * channel that cannot make forward progress (last_get pinned behind
+ * put, or a residual session put that never drops) is detected here
+ * and recovered instead of spinning. All fields MUST be in the pump
+ * snapshot write-back list (GRFIX NOTE).
+ */
+ u64 wd_last_get; /* last_get at the window start */
+ u64 wd_put; /* put at the window start */
+ u64 wd_sub; /* submissions at the window start */
+ unsigned long wd_since; /* window start (0 = no window) */
+ unsigned long wd_rewind_off_until; /* pin-batch rewind cooldown */
};
/*
@@ -444,11 +536,136 @@ static u32 svc_ring_n;
* client spins). One clean boot always works, so make the next session look
* like one: when the channel set empties, request a flush and let the next
* rebuild start from the same empty state as a fresh boot.
+ *
+ * Dual-guest staging (goal 1.0): the discovery tables are still shared
+ * between guests (per-guest learned tables are the staged G4 remainder), so
+ * the flush is requested only when the channel set is globally empty. The
+ * active decision is made per guest (svc_guests below): one guest closing
+ * its last channel never flushes while another guest still has live
+ * channels - otherwise the departing guest would wipe the live guest's
+ * pins/maps and stall it. Single-guest behaviour is unchanged: the one
+ * guest's last-channel close is also the global empty transition.
*/
static u8 svc_sess_flush;
+static u8 svc_guest_reset_req; /* m177: guest RM reload; wiped on svc thread */
static u8 svc_sess_active; /* a channel of a session has registered (until flush) */
+static u32 svc_sess_gen; /* m176: bumped by every executed session flush;
+ * stamps channels/scrubbers so per-session learned
+ * state is never inherited by a recycled handle */
static struct completion svc_sess_flush_done; /* a pending flush has executed */
static unsigned long svc_ring_done; /* CE ranks whose ring is pinned (session state) */
+
+/* Per-guest session records (one per open mdev; see SVC_MAX_CHANNELS for the channel cap). */
+#define SVC_MAX_GUESTS 8
+struct svc_guest_sess {
+ struct nv_vgpu_device *vgpu; /* NULL = free slot */
+ bool active; /* a channel of this guest is
+ * registered until this guest's
+ * last channel closes */
+ u64 last_build; /* per-guest svc_rebuild freshness */
+};
+static struct svc_guest_sess svc_guests[SVC_MAX_GUESTS];
+static DEFINE_SPINLOCK(svc_guest_lock);
+
+/*
+ * Find (or, with create, allocate) the session record for one mdev. The
+ * records are pointer-keyed; a removed mdev's record is freed by
+ * gsp_sema_service_vgpu_cleanup() so the pointer can never match a newly
+ * allocated device.
+ */
+static struct svc_guest_sess *svc_guest_get(struct nv_vgpu_device *vgpu,
+ bool create)
+{
+ struct svc_guest_sess *sess = NULL;
+ unsigned long flags;
+ int i;
+
+ if (!vgpu)
+ return NULL;
+ spin_lock_irqsave(&svc_guest_lock, flags);
+ for (i = 0; i < SVC_MAX_GUESTS; i++) {
+ if (svc_guests[i].vgpu == vgpu) {
+ sess = &svc_guests[i];
+ break;
+ }
+ if (create && !sess && !svc_guests[i].vgpu)
+ sess = &svc_guests[i];
+ }
+ if (sess && create && !sess->vgpu) {
+ memset(sess, 0, sizeof(*sess));
+ sess->vgpu = vgpu;
+ }
+ spin_unlock_irqrestore(&svc_guest_lock, flags);
+ return sess;
+}
+
+/*
+ * CeUtils scrubber channel (the guest PMA's internal CE channel created at
+ * driver init). When the PMA must hand out scrubbed pages it waits on the
+ * channel's finish payload (channel buffer offset finishPayloadOffset =
+ * channelPbSize + GPFIFO_SIZE + 4; for the regular scrubber = 0x6c004),
+ * which the physical CE would advance as the scrub work completes. In this
+ * emulation nothing authors it, so the third+ CUDA session's cudaMalloc
+ * freezes in scrubCheckAndWaitForSize.
+ *
+ * The scrubber channel is identified by its GPFIFO entry count
+ * (NUM_COPY_BLOCKS = 4096; client channels use 1024); its USERD PA is known
+ * from the channel ALLOC registration. The channel buffer's PA cannot be
+ * resolved through the global VA map (the kernel VA space collides
+ * numerically with the client's CE push-buffer window), so the guest
+ * scanner (vmallocinfo + kcore page-table walk) registers it through
+ * sema_scrubbuf_add. The service then authors, from the channel's own
+ * GP_PUT: USERD GP_GET = put, host semaphore = put (so the guest's
+ * channelWaitForFreeEntry never blocks), and finish payload = submitted
+ * block count (so the scrub wait completes). Idempotent, forward-only.
+ */
+struct svc_scrub {
+ struct nv_vgpu_device *vgpu; /* owning mdev (NULL = free slot) */
+ bool userd_ok; /* a 4096-entry channel was registered */
+ u32 h_object; /* handle of the registered scrubber channel */
+ u64 userd_pa;
+ u32 userd_as;
+ u64 gpfifo_va;
+ u32 entries;
+ bool pa_ok; /* scanner registered fin/hsem PAs */
+ u64 fin_pa; /* finish payload word (u32) */
+ u64 hsem_pa; /* channel host semaphore word (u32) */
+ bool seeded;
+ u32 put_last;
+ u64 submitted; /* blocks submitted since seeding */
+ u64 authored; /* last authored finish value */
+ u32 pa_gen; /* scanner registration generation (logged) */
+ u32 session_gen; /* m176: session generation of the last registration */
+ u32 log_budget;
+};
+/*
+ * One scrubber record per guest: every guest's PMA has its own 4096-entry
+ * scrubber channel, and the authored words must be written through the
+ * owning guest's address space (writing guest A's PA through guest B's VFIO
+ * device would corrupt B). Keyed by vgpu pointer, freed by
+ * gsp_sema_service_vgpu_cleanup().
+ */
+static struct svc_scrub svc_scrubs[SVC_MAX_GUESTS];
+
+static struct svc_scrub *svc_scrub_get(struct nv_vgpu_device *vgpu, bool create)
+{
+ struct svc_scrub *s = NULL;
+ int i;
+
+ if (!vgpu)
+ return NULL;
+ for (i = 0; i < SVC_MAX_GUESTS; i++) {
+ if (svc_scrubs[i].vgpu == vgpu)
+ return &svc_scrubs[i];
+ if (create && !s && !svc_scrubs[i].vgpu)
+ s = &svc_scrubs[i];
+ }
+ if (s) {
+ memset(s, 0, sizeof(*s));
+ s->vgpu = vgpu;
+ }
+ return s;
+}
static u8 svc_pb_passes;
static u64 svc_pb_last;
/*
@@ -587,6 +804,10 @@ struct sema_svc {
u64 op_tr_log; /* operand targeted-resolve budget */
u32 op_tr_pending; /* an operand's targeted scan is in progress */
u64 copy_watch_log; /* sema_copy_watch log budget */
+ u64 copy_dst_hold; /* m169: device-dst copies held for
+ * want of a pin (stale-table guard) */
+ u64 copy_dst_relaxed; /* m169b: guards released without
+ * a pin after the bounded wait */
u64 copy_pat_log; /* sema_copy_pat log budget */
u64 copy_dst_log; /* sema_copy_dst log budget */
u32 op_tr_src; /* provenance of last svc_op_translate */
@@ -594,9 +815,30 @@ struct sema_svc {
u64 push_pat_log; /* push-content pattern budget */
u64 dma_skip_log; /* unrecognized-DMA budget */
u64 xfer_raw_log; /* operandless transfer raw-dump budget */
+ u64 fill_ram_log; /* r361 physical-RAM fill build budget */
+ u64 pushtrace_all; /* fail4: de-windowed push decode
+ * trace (every push, bounded) */
+ u64 opadd_log; /* fail4: every op appended */
+ u64 dmatrace_log; /* fail4: denied-transfer trace */
+ u64 pend_dbg_log; /* fail4 r305: pending/hold witness */
+ u64 snap_gate_log; /* fail4 r311: capture-gate witness */
+ u64 copyent_log; /* fail4: svc_copy entry trace */
+ u64 bigrun_log; /* fail4: big method-data run headers */
+ u64 lid_hdr_log; /* fail4: LOAD_INLINE_DATA headers */
+ u64 skiprun_log; /* fail4: silently skipped data runs */
+ u64 mystery_log; /* fail4: moderate op=1 runs on big pushes */
+ u64 next_hdr_log; /* fail4: header after each big LID run */
u64 grce_log; /* GRCE inline-write budget */
u32 cur_ch; /* channel of the op being run */
u64 entry_log; /* per-push summary budget */
+ /*
+ * m151: bounded log for CE pushes that were READABLE (nonzero first
+ * bytes) yet decoded no release. These are the entries the pump holds
+ * for the 8 s grace; naming their decode state (have_sem, rel) and
+ * head bytes is the Class-2 diagnostic (r128: correct content, right
+ * sem block at +0xC0 with RELEASE_ONE_WORD, yet rel_seen false).
+ */
+ u64 entry_norel_log;
u64 ops_denied; /* work refused: implausible stream */
u64 false_trans; /* resolvable VA mapped to non-push */
u64 sem_write_reresolve; /* writes re-resolved after read-back */
@@ -631,10 +873,16 @@ struct sema_svc {
* expectation advanced, write queued */
u64 gpfifo_held; /* passes an entry was left pending
* for a translation/grace window */
+ u64 gr_nocap_held; /* m164: GR holds opened for a
+ * launch push whose decode saw QMD
+ * words but built no candidate */
+ u64 gr_nocap_consumed; /* m164: no-capture windows that
+ * expired and consumed the entry */
u64 gpfifo_starved; /* entries consumed without a
* deliverable exact slot */
u64 counter_rel_applied; /* releases to registered counters */
u64 counter_rel_dbg; /* rel-apply diagnostic budget */
+ u64 crel_entry_dbg; /* C1: rel-apply entry budget, 512/session */
u64 counter_noslot_dbg; /* "no slot registered" budget,
* separate so it cannot hide the
* written/skip lines */
@@ -656,9 +904,13 @@ struct sema_svc {
u64 opdef_held;
u64 opdef_applied;
u64 opdef_expired;
+ u64 opdef_delivered_late; /* stall fix: set delivered at consume */
+ u64 opdef_drop_lost; /* withheld releases dropped unapplied */
u64 vidmem_writes; /* semaphores written in vidmem */
u64 gpfifo_log; /* channel-state log budget */
u64 gpfifo_entry_log; /* per-entry log budget */
+ u64 walk_dbg; /* C4: GR put/walk witness budget */
+ u64 gr_rel_auth_log; /* F1: GR-REL-AUTHOR budget, 256/session */
u64 doorbell_count; /* NV_VIRTUAL_FUNCTION_DOORBELL pokes */
u32 doorbell_token; /* last token written */
u32 doorbell_log; /* token log budget */
@@ -759,6 +1011,7 @@ struct sema_svc {
u64 fwd_gr_ok;
u64 fwd_gr_fallback;
u64 fwd_gr_refused;
+ u64 fwd_gr_defer; /* m150: held for data-arg retry */
u64 fwd_gr_timeout;
u64 fwd_gr_fault;
u64 fwd_gr_bridge;
@@ -769,6 +1022,22 @@ struct sema_svc {
* abort, or a release0 page the backend could not observe).
*/
u64 fwd_gr_sw_rel;
+ /*
+ * STALLFREE: failed forwarded launches the pass-top outcome check
+ * completed in software because no hold owned their release (the
+ * bounded-completion guarantee; zero on a green run).
+ */
+ u64 fwd_gr_stall_swrel;
+ /*
+ * STALLFREE r225: GR channels recovered by the stale-epoch /
+ * stale-replay wedge watchdog (zero on a green run).
+ */
+ u64 gr_stale_wedge;
+ u64 fwd_gr_hold_exp; /* GRFIX FIX1: data-stalled holds
+ * that reached the hard bound and
+ * were consumed with the software
+ * release (proof the bound fires
+ * instead of a stall) */
/* Distinct stalled VA tracking for rate-limited logging. */
#define SVC_STALLED_MAX 256
@@ -928,6 +1197,21 @@ static int svc_write_ap(struct nv_vgpu_device *vgpu, u64 addr, u32 aperture,
static bool svc_gr_content_rearm;
/*
+ * m149 second-client stall evidence: the CE push-buffer window pins are the
+ * translation source the second session's GPFIFO entries need; log the
+ * first few per session (reset on channel registration) so a boot shows
+ * whether they arrived.
+ */
+static u32 svc_cepin_log;
+
+/*
+ * m154: injected pins dropped because they collide with a live channel's
+ * GPFIFO ring range (see svc_handle_map_add). Counted + logged so the
+ * defensive drop is visible in the boot log.
+ */
+static u32 svc_ring_pin_skips;
+
+/*
* m148 (second-client wedge): re-arm the per-rank one-shot ring/push-buffer
* latches for a (re-)registered channel. The guest driver rebuilds the GSP
* session for every client context and RM reuses the same object handles and
@@ -970,8 +1254,11 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
if (!desc || !desc->h_object || !desc->gpfifo_va ||
desc->gpfifo_entries < 2)
return;
- if (!desc->engine_type)
+ if (!desc->engine_type) {
WRITE_ONCE(svc_gr_content_rearm, true);
+ svc_cepin_log = 0;
+ svc_ring_pin_skips = 0;
+ }
/*
* ISSUE-0002: a session-end flush may still be pending on svc_thread.
@@ -995,7 +1282,8 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
}
spin_lock_irqsave(&svc.chan_lock, flags);
for (i = 0; i < SVC_MAX_CHANNELS; i++) {
- if (svc.chan[i].valid && svc.chan[i].h_object == desc->h_object) {
+ if (svc.chan[i].valid && svc.chan[i].vgpu == desc->vgpu &&
+ svc.chan[i].h_object == desc->h_object) {
ch = &svc.chan[i];
break;
}
@@ -1006,9 +1294,21 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
int k;
u64 keep_counter_pa = 0;
u64 keep_release_pa = 0;
+ u64 keep_release_va = 0;
u8 keep_gr_index = 0, keep_ce_index = 0;
+ bool keep_pub_armed = false;
+ u32 keep_counter_gen = 0;
DECLARE_BITMAP(keep_rel_done, SVC_GPFIFO_MAX_BITS);
- bool same = ch->valid && ch->h_object == desc->h_object;
+ bool same = ch->valid && ch->vgpu == desc->vgpu &&
+ ch->h_object == desc->h_object;
+ /* m176: only a same-generation re-registration may inherit the
+ * learned write targets. After a session flush the same numeric
+ * handle belongs to a fresh client whose counter/release slots
+ * live in a brand-new page; inheriting the dead session's PAs
+ * made every release land on the old page (and be skipped
+ * against its stale contents) while the new client polled its
+ * own slot forever. */
+ bool same_gen = same && ch->session_gen == svc_sess_gen;
/*
* A re-registration of the same channel must not lose the
@@ -1017,32 +1317,60 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
* counter layout does not change). Same for the increment
* release marks: losing them would re-apply pushes.
*/
- if (same) {
+ if (same_gen) {
keep_counter_pa = ch->counter_pa;
keep_release_pa = ch->release_pa;
+ keep_release_va = ch->release_va;
keep_gr_index = ch->gr_index;
keep_ce_index = ch->ce_index;
+ /*
+ * r311: a same-session re-registration must not
+ * forget that this session already proved itself
+ * (gr_pub_armed) - losing it re-arms the residual
+ * watchdog, and its recovery zeroes a completion
+ * word this live session authored, which the
+ * client's UVM reads as a downward jump and never
+ * completes on (r311 vector_add: pub-adv/zero
+ * 3 s cycles with the client polling forever).
+ */
+ keep_pub_armed = ch->gr_pub_armed;
+ keep_counter_gen = ch->counter_gen;
bitmap_copy(keep_rel_done, ch->rel_done,
SVC_GPFIFO_MAX_BITS);
}
memset(ch, 0, sizeof(*ch));
- if (same)
+ ch->gr_hold_idx = ~0u; /* m156: no hold armed yet */
+ ch->gr_hold_dead_va = 0; /* GRFIX: no launch bound armed */
+ ch->gr_hold_exp_logged = false;
+ ch->opdef_hold_dead_va = 0; /* r361: no opdef bound armed */
+ ch->opdef_hold_exp_logged = false;
+ ch->gr_rehold_idx = ~0u;
+ ch->gr_nocap_idx = ~0u; /* m164: no no-capture window armed */
+ ch->session_gen = svc_sess_gen; /* m176 */
+ if (same_gen) {
bitmap_copy(ch->rel_done, keep_rel_done,
SVC_GPFIFO_MAX_BITS);
+ ch->gr_pub_armed = keep_pub_armed;
+ ch->counter_gen = keep_counter_gen;
+ }
/*
- * Rank among the live CE channels. The RM hands out object
- * handles in creation order and the guest's UVM allocates one
- * push buffer, ring and tracking semaphore per channel in that
- * same order, so the rank is what ties a channel to its ring
- * offset and its push-buffer base (see svc_ring_autopin /
- * svc_pb_pair).
+ * Rank among the live CE channels OF THIS GUEST. The RM hands
+ * out object handles in creation order and the guest's UVM
+ * allocates one push buffer, ring and tracking semaphore per
+ * channel in that same order, so the rank is what ties a
+ * channel to its ring offset and its push-buffer base (see
+ * svc_ring_autopin / svc_pb_pair). The rank must not count the
+ * other guest's channels: their handles are independent and
+ * their allocators interleave.
*/
if (desc->engine_type) {
u8 rank = 0;
for (k = 0; k < SVC_MAX_CHANNELS; k++)
- if (svc.chan[k].valid && svc.chan[k].engine_type &&
+ if (svc.chan[k].valid &&
+ svc.chan[k].vgpu == desc->vgpu &&
+ svc.chan[k].engine_type &&
svc.chan[k].h_object < desc->h_object)
rank++;
ch->ce_index = rank;
@@ -1050,13 +1378,16 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
u8 rank = 0;
for (k = 0; k < SVC_MAX_CHANNELS; k++)
- if (svc.chan[k].valid && !svc.chan[k].engine_type &&
+ if (svc.chan[k].valid &&
+ svc.chan[k].vgpu == desc->vgpu &&
+ !svc.chan[k].engine_type &&
svc.chan[k].h_object < desc->h_object)
rank++;
ch->gr_index = rank;
}
ch->valid = true;
svc_sess_active = 1;
+ ch->vgpu = desc->vgpu;
ch->h_object = desc->h_object;
ch->engine_type = desc->engine_type;
ch->cid = desc->cid;
@@ -1071,9 +1402,18 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
if (same) {
ch->counter_pa = keep_counter_pa;
ch->release_pa = keep_release_pa;
+ ch->release_va = keep_release_va;
ch->gr_index = keep_gr_index;
ch->ce_index = keep_ce_index;
}
+ /* Per-guest session flag: this guest has a live channel. */
+ {
+ struct svc_guest_sess *sess =
+ svc_guest_get(desc->vgpu, true);
+
+ if (sess)
+ sess->active = true;
+ }
}
spin_unlock_irqrestore(&svc.chan_lock, flags);
@@ -1086,6 +1426,48 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
*/
svc_chan_rearm_rank(ch);
svc.last_chan_reg = jiffies;
+ /*
+ * CeUtils scrubber channel: NUM_COPY_BLOCKS = 4096 GPFIFO
+ * entries (client channels use 1024). Stash its USERD and ring
+ * geometry for the scrub pass; the channel itself is left
+ * entirely alone (no rank change, no pump touch changes) - the
+ * pass authors the finish payload from the USERD alone. A
+ * colliding numeric-handle FREE may still unregister the
+ * channel; the stashed geometry survives, so the scrub path
+ * does not depend on the channel-table identity at all.
+ */
+ if (desc->gpfifo_entries >= 4096 && desc->gpfifo_va &&
+ desc->userd_pa) {
+ struct svc_scrub *s = svc_scrub_get(desc->vgpu, true);
+
+ if (!s)
+ goto scrub_done; /* table full: max guests */
+ /* Re-seed the counting on a fresh scrubber object or a
+ * new client session (m176: the counter/submitted state
+ * is per-session; a recycled scrubber handle after a
+ * flush must start from the new channel's GPPut, not
+ * carry the dead session's counts). */
+ if (!s->userd_ok ||
+ s->session_gen != svc_sess_gen ||
+ s->h_object != desc->h_object ||
+ s->gpfifo_va != desc->gpfifo_va) {
+ s->seeded = false;
+ s->submitted = 0;
+ }
+ s->session_gen = svc_sess_gen;
+ s->userd_ok = true;
+ s->h_object = desc->h_object;
+ s->userd_pa = desc->userd_pa;
+ s->userd_as = desc->userd_as ? desc->userd_as
+ : SVC_APERTURE_SYS;
+ s->gpfifo_va = desc->gpfifo_va;
+ s->entries = desc->gpfifo_entries;
+ pr_info("sema-svc: scrubber channel 0x%x detected (gpfifo=0x%llx entries=%u userd=0x%llx as=%u)\n",
+ desc->h_object, desc->gpfifo_va,
+ desc->gpfifo_entries, desc->userd_pa,
+ s->userd_as);
+ }
+scrub_done:
pr_info("sema-svc: channel 0x%x registered gpfifo=0x%llx entries=%u userd=0x%llx eng=%u cid=%u notif=0x%llx\n",
desc->h_object, desc->gpfifo_va, desc->gpfifo_entries,
desc->userd_pa, desc->engine_type, desc->cid,
@@ -1097,7 +1479,8 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
}
/* P6b wave-1.5 hold state is defined with the forwarding section below. */
-static void svc_fwd_hold_abort(u32 h_object);
+static void svc_fwd_hold_abort(struct nv_vgpu_device *vgpu, u32 h_object);
+static void svc_fwd_hold_clear(void); /* m176: session flush drops a stale hold */
/*
* P6b wave-2 inline software fallback for a withheld (skipped) candidate;
* defined with the forwarding section below. Service-thread only.
@@ -1115,8 +1498,23 @@ static bool svc_opdef_release(struct nv_vgpu_device *vgpu,
static void svc_opdef_drop(struct nv_vgpu_device *vgpu,
struct svc_channel *ch, u32 entry_idx);
static void svc_opdef_reset_all(void);
-
-void gsp_sema_service_channel_del(u32 h_object)
+static void svc_opdef_finish_ch(struct svc_channel *ch, u32 idx);
+static u32 svc_opdef_log; /* m176: forward decl for the session flush */
+/*
+ * One-cycle GAP-1/GAP-2 instrumentation budgets (agent-datapath plan C1-C10):
+ * small, reset per session where they are service-thread state, so a later
+ * client's strand is never blacked out by the first client's spend.
+ */
+static u32 svc_relx_log; /* C2: deferred-launch apply entry witness */
+static u32 svc_opdef_clr_log; /* C3: opdef-clear caller witness */
+static u32 svc_busy_defer_log; /* C8: GR busy-defer timeout witness */
+static u32 svc_hold_fb_log; /* C7: hold failure/timeout fallback witness */
+static u32 svc_cand_ovw_log; /* C9: candidate overwrite witness */
+static u32 svc_inline_recs_log; /* C10: inline-recs digest witness */
+static u32 svc_gr_await_log; /* r351: launch-held-for-trust witness */
+static u32 svc_gr_retry_log; /* r351: retry-forward/proceed witness */
+
+void gsp_sema_service_channel_del(struct nv_vgpu_device *vgpu, u32 h_object)
{
unsigned long flags;
int i;
@@ -1126,7 +1524,8 @@ void gsp_sema_service_channel_del(u32 h_object)
spin_lock_irqsave(&svc.chan_lock, flags);
for (i = 0; i < SVC_MAX_CHANNELS; i++) {
- if (svc.chan[i].valid && svc.chan[i].h_object == h_object) {
+ if (svc.chan[i].valid && svc.chan[i].vgpu == vgpu &&
+ svc.chan[i].h_object == h_object) {
svc.chan[i].valid = false;
pr_info("sema-svc: channel 0x%x unregistered (submissions=%llu)\n",
h_object, svc.chan[i].submissions);
@@ -1136,19 +1535,27 @@ void gsp_sema_service_channel_del(u32 h_object)
/*
* ISSUE-0002: when the last channel closes, the next client is a new
* session in a new UVM VA space; its discovery state must not inherit
- * this session's map/pins/page-directory latch. Request the flush
- * here; it executes at the next rebuild (the only writer of that
- * state) just like a fresh boot would start empty. Only the transition
- * from "a session existed" to "empty" may request it: redundant
- * deletes for handles that never registered would otherwise re-flush
- * (and wipe) a session that started after the real one ended.
+ * this session's map/pins/page-directory latch. The active decision is
+ * per guest (svc_guests) and the flush request is raised only when NO
+ * guest has a live channel left: the discovery tables are still shared
+ * between guests (staged G4), so flushing while another guest is live
+ * would wipe that guest's pins and stall it. One guest: the last-channel
+ * close IS the global empty transition, so behaviour is unchanged.
*/
if (svc_sess_active) {
+ struct svc_guest_sess *sess = svc_guest_get(vgpu, false);
+ bool any = false, own = false;
+
for (i = 0; i < SVC_MAX_CHANNELS; i++) {
- if (svc.chan[i].valid)
- break;
+ if (!svc.chan[i].valid)
+ continue;
+ any = true;
+ if (svc.chan[i].vgpu == vgpu)
+ own = true;
}
- if (i == SVC_MAX_CHANNELS) {
+ if (sess && !own)
+ sess->active = false;
+ if (!any) {
svc_sess_active = 0;
svc_sess_flush = 1;
}
@@ -1163,9 +1570,10 @@ void gsp_sema_service_channel_del(u32 h_object)
* P6b wave 1.5: a deleted channel's held releases must not wait out
* the timeout; mark the hold aborted so the pump applies them (or
* drops them if the snapshot already lost the channel) on its next
- * pass.
+ * pass. The hold is matched per (vgpu, handle): the other guest's
+ * identical handle must not abort this guest's hold.
*/
- svc_fwd_hold_abort(h_object);
+ svc_fwd_hold_abort(vgpu, h_object);
}
/*
@@ -1420,8 +1828,18 @@ static int svc_gmmu_walk(struct nv_vgpu_device *vgpu, u64 pdb, u32 entries,
return -ENOENT;
}
- /* PD0: one dual entry per index (big half then small half). */
+ /*
+ * PD0: one dual entry per index (big half then small half).
+ * Geometry per the driver's own GMMU format (gp10x family):
+ * PD3 [48:47], PD2 [46:38], PD1 [37:29], PD0 [28:21], PT
+ * [20:12]. PD0 is 8 bits - an earlier 0x1ff experiment here was
+ * reverted; the observed +2 MB client-page phase is NOT a PD0
+ * index error.
+ */
idx = (va >> 21) & 0xff;
+ if (trace)
+ pr_info("sema-walk: PD0 table=0x%llx idx=%u va=0x%llx tap=%d\n",
+ table, idx, va, tap);
if (svc_read_ap(vgpu, table + 16 * idx,
tap == SVC_APERTURE_FB ? SVC_APERTURE_FB
: SVC_APERTURE_SYS,
@@ -1431,6 +1849,9 @@ static int svc_gmmu_walk(struct nv_vgpu_device *vgpu, u64 pdb, u32 entries,
: SVC_APERTURE_SYS,
&small, sizeof(small)))
return -ENOENT;
+ if (trace)
+ pr_info("sema-walk: PD0 big=0x%016llx small=0x%016llx\n",
+ big, small);
/* Small half: 4 KB page table under it. */
sap = svc_pde_aperture(((small >> 1) & 0x3));
@@ -1442,6 +1863,12 @@ static int svc_gmmu_walk(struct nv_vgpu_device *vgpu, u64 pdb, u32 entries,
*addr += va & 0xfff;
return 0;
}
+ if (trace)
+ pr_info("sema-walk: small miss sap=%d t=0x%llx\n",
+ sap, t);
+ } else if (trace) {
+ pr_info("sema-walk: small skip sap=%d small&1=%llu\n",
+ sap, small & 1);
}
/* Big half: a 2 MB page here, or a large (64 KB) page table. */
@@ -1474,6 +1901,9 @@ static int svc_gmmu_walk(struct nv_vgpu_device *vgpu, u64 pdb, u32 entries,
return 0;
}
}
+ if (trace)
+ pr_info("sema-walk: no leaf va=0x%llx big=0x%llx small=0x%llx\n",
+ va, big, small);
return -ENOENT;
}
@@ -1701,13 +2131,133 @@ static void svc_pd_discover(struct nv_vgpu_device *vgpu)
svc_pd_cands, svc_pd_best, n);
}
+/*
+ * m177c: a SECOND discovered-directory slot for the UVM SYS VA space (the
+ * 0x2xxxxxxx family: GR rings/pushes, QMD windows, counter pages). The
+ * primary slot (above) is validated against rm_mem-content pairs - the
+ * 0x1xxxxxxx pool family - and cannot walk the UVM space, so without pins
+ * the counter page (0x20400fXXX) never resolved: the guest's KMD/UVM
+ * epoch waited on completions that could never be written and re-ran its
+ * ~103 s retry loop forever (r194/r195: 'rel-apply ... slot pending' x8,
+ * then silence). Samples for this slot are content-VERIFIED
+ * (push_va -> push_pa) pairs from the GR content discovery and the CE
+ * push pairing: the push bytes themselves are the proof, and they live in
+ * exactly the space this directory maps.
+ */
+#define SVC_PD_RING_SAMPLES 8
+static u64 svc_pd_r_v[SVC_PD_RING_SAMPLES];
+static u64 svc_pd_r_pa[SVC_PD_RING_SAMPLES];
+static u32 svc_pd_r_n;
+static u64 svc_pd_r_base;
+static int svc_pd_r_ap;
+static u8 svc_pd_r_found;
+static u8 svc_pd_r_attempts;
+static u64 svc_pd_r_ev_hash;
+
+static void svc_pd_ring_note(u64 va, u64 pa)
+{
+ u32 i;
+
+ if (!va || !pa)
+ return;
+ va &= ~0xfffULL;
+ pa &= ~0xfffULL;
+ for (i = 0; i < svc_pd_r_n; i++)
+ if (svc_pd_r_v[i] == va)
+ return;
+ if (svc_pd_r_n < SVC_PD_RING_SAMPLES) {
+ svc_pd_r_v[svc_pd_r_n] = va;
+ svc_pd_r_pa[svc_pd_r_n] = pa;
+ svc_pd_r_n++;
+ }
+}
+
+static void svc_pd_ring_discover(struct nv_vgpu_device *vgpu)
+{
+ u64 chunk = 64 * 1024;
+ u8 *buf;
+ u64 gpa;
+ u64 ev = svc_pd_r_n;
+ u32 i;
+
+ if (svc_pd_r_found || svc_pd_r_attempts >= SVC_PD_ATTEMPTS_MAX)
+ return;
+ if (svc_pd_r_n < SVC_PD_MIN_HITS)
+ return;
+ for (i = 0; i < svc_pd_r_n; i++)
+ ev = (ev + svc_pd_r_v[i] + (svc_pd_r_pa[i] << 12)) * 1000003;
+ if (svc_pd_r_attempts && ev == svc_pd_r_ev_hash)
+ return;
+ svc_pd_r_ev_hash = ev;
+ svc_pd_r_attempts++;
+
+ buf = kmalloc(chunk, GFP_KERNEL);
+ if (!buf)
+ return;
+ for (gpa = 0; gpa < SEMA_PA_MAX && !svc_pd_r_found; gpa += chunk) {
+ u32 len = (u32)min_t(u64, chunk, SEMA_PA_MAX - gpa);
+ u64 off;
+
+ if (svc_read(vgpu, gpa, buf, len))
+ continue;
+ for (off = 0; off + 8 <= len && !svc_pd_r_found;
+ off += SEMA_PAGE_SIZE) {
+ u64 e;
+ u64 a = 0;
+ int eap = 0;
+ u32 hits = 1;
+
+ memcpy(&e, buf + off, sizeof(e));
+ if ((e & 1) || !((e >> 1) & 0x3))
+ continue;
+ /* First sample rejects almost every candidate. */
+ if (svc_gmmu_walk(vgpu, gpa + off, 4, svc_pd_r_v[0],
+ SVC_APERTURE_SYS, &a, &eap, false) ||
+ (a & ~0xfffULL) != svc_pd_r_pa[0])
+ continue;
+ for (i = 1; i < svc_pd_r_n; i++) {
+ if (!svc_gmmu_walk(vgpu, gpa + off, 4,
+ svc_pd_r_v[i],
+ SVC_APERTURE_SYS, &a, &eap,
+ false) &&
+ (a & ~0xfffULL) == svc_pd_r_pa[i])
+ hits++;
+ }
+ if (hits >= SVC_PD_MIN_HITS) {
+ svc_pd_r_base = gpa + off;
+ svc_pd_r_ap = SVC_APERTURE_SYS;
+ svc_pd_r_found = 1;
+ pr_info("sema-svc: discovered UVM ring-space page directory 0x%llx (ram), %u/%u content-verified samples\n",
+ svc_pd_r_base, hits, svc_pd_r_n);
+ }
+ }
+ }
+ kfree(buf);
+}
+
static int svc_walk_discovered(struct nv_vgpu_device *vgpu, u64 va,
u64 *addr, int *ap)
{
- if (!svc_pd_found)
- return -ENOENT;
- return svc_gmmu_walk(vgpu, svc_pd_base, svc_pd_entries, va,
- svc_pd_ap, addr, ap, false);
+ int rc = -ENOENT;
+
+ if (svc_pd_found)
+ rc = svc_gmmu_walk(vgpu, svc_pd_base, svc_pd_entries, va,
+ svc_pd_ap, addr, ap, false);
+ if (rc && svc_pd_r_found)
+ rc = svc_gmmu_walk(vgpu, svc_pd_r_base, 4, va,
+ svc_pd_r_ap, addr, ap, false);
+ /*
+ * m177c: the ring-space directory discovery runs on new samples
+ * only (fingerprint + attempt cap inside). A first call with
+ * enough verified ring/push samples performs one bounded scan;
+ * after that this is a flag check.
+ */
+ if (rc && !svc_pd_r_found && svc_pd_r_n >= SVC_PD_MIN_HITS)
+ svc_pd_ring_discover(vgpu);
+ if (rc && svc_pd_r_found)
+ rc = svc_gmmu_walk(vgpu, svc_pd_r_base, 4, va,
+ svc_pd_r_ap, addr, ap, false);
+ return rc;
}
/* Learned translations: a walk costs a handful of guest reads, so cache the
@@ -2471,6 +3021,29 @@ static inline bool svc_pool_hold_active(void)
}
/*
+ * m165 (D-F2a): the blanket pool hold exists to stop a release decoded
+ * before the guest's own injection lands from being applied through a
+ * stale translation. Once a host-verified injected pin for THIS VA is
+ * present, that premise is gone: the pin source is the resolver's first
+ * choice (read-back proven) and the ipin table is wiped at session flush,
+ * so it is this session's ground truth. r156 measured every pool release
+ * waiting the full registration+5.00 s with the pin ready all along.
+ */
+static bool svc_pool_hold_active_va(u64 va)
+{
+ u64 ipa = 0;
+ int iap = 0;
+
+ if (!svc_pool_hold_active())
+ return false;
+ if (!gsp_sema_pool_hold_fresh)
+ return true;
+ if (svc_ipin_lookup(va, &ipa, &iap) == 0)
+ return false;
+ return true;
+}
+
+/*
* Verified pool-semaphore translations, keyed by page VA. An entry is only
* created from an injected pin or from a targeted rm_mem resolve whose page
* was read back successfully; the learned cache and the discovered map are
@@ -2494,6 +3067,16 @@ static u32 svc_pool_pa_n;
static u32 svc_pool_pa_next;
static u64 svc_pool_pa_logged[SVC_POOL_PA_LOG_MAX];
static u32 svc_pool_pa_logged_n;
+/*
+ * fail4: the completion-hint window was 10 s. It outlived short-lived
+ * clients: after a client freed its RM events the window kept re-posting
+ * hint events every 0.5 s, and a post already queued in the status ring
+ * could be read after the guest's own free processing - the guest ISR
+ * then ran a freed RC callback and the guest panicked in interrupt
+ * (rm_run_rc_callback+0x0). 1.5 s still covers a waiter that arrives
+ * after the edge while shrinking the post-free exposure ~7x.
+ */
+#define SVC_POST_HINT_MS 1500
static u64 svc_pool_pa_miss_log;
static u32 svc_pool_pa_hit_log;
static u32 svc_pool_wr_log;
@@ -2542,6 +3125,14 @@ static void svc_pool_wr_note(u32 h, u64 va, u64 pa, const char *src,
}
}
+/* r372: landing-table entries repaired from a fresh pin (stale-ledger class). */
+static u64 svc_ledger_pin_repairs;
+/* r372: pool alt fan-outs skipped because the primary pin moved on. */
+static u64 svc_pool_alt_skipped;
+/* r372: bounded page-note resolution diagnostics (va/pa/ap/branch). */
+static u32 svc_gr_pagenote_log;
+static u32 svc_fbhpa_log;
+
/*
* Fan a pool release out to the last displaced translation of the same page
* (see svc_ipin_alt): the primary target may be the stale one when two host
@@ -2565,6 +3156,27 @@ static void svc_pool_alt_apply(struct nv_vgpu_device *vgpu, u32 h, u64 va,
return;
if ((pa & ~(u64)(SEMA_PAGE_SIZE - 1)) == page)
return; /* never write an identity translation */
+ /*
+ * r372 (stale-ledger corruption class): fan out ONLY while the
+ * current primary pin for this VA still names the displaced page.
+ * Once a newer pin has displaced it, the old page belongs to a
+ * buffer the guest has freed - writing it lands on whatever the
+ * guest kernel reused the page for. A missing current pin is
+ * equally unprovable: skip. The primary release write already
+ * succeeded either way, so this is a lost best-effort, not a
+ * failure.
+ */
+ {
+ u64 cpa = 0;
+ int cap = SVC_APERTURE_SYS;
+
+ if (svc_ipin_lookup(va, &cpa, &cap) ||
+ ((cpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1)))) {
+ svc_pool_alt_skipped++;
+ return;
+ }
+ }
if (svc_read_ap(vgpu, pa, svc_ap_of(ap), &cur, sizeof(cur)))
return;
if (cur >= val)
@@ -2597,6 +3209,12 @@ static inline bool svc_pool_pa_identity(u64 va, u64 pa)
(va & ~(u64)(SEMA_PAGE_SIZE - 1));
}
+static void svc_pool_boot_store(u64 va, u64 pa, int ap); /* m168 */
+static bool svc_dst_guard_hold(u64 va); /* m169b */
+static bool svc_dst_guard_pending(u64 va); /* r231 */
+static u32 svc_read_order_log; /* r231 budget */
+static u32 svc_atrace_log; /* r234 A-TRACE budget */
+
static int svc_pool_pa_lookup(u64 va, u64 *pa, int *ap)
{
u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
@@ -2627,6 +3245,8 @@ static void svc_pool_pa_store(u64 va, u64 pa, int ap)
/* Never cache an identity translation (see svc_pool_pa_identity). */
if (svc_pool_pa_identity(page, pa))
return;
+ /* m168: every proven resolution also feeds the boot-scoped cache. */
+ svc_pool_boot_store(va, pa, ap);
for (i = 0; i < svc_pool_pa_n; i++) {
if (svc_pool_pa[i].va_page == page) {
svc_pool_pa[i].pa_page =
@@ -2656,6 +3276,97 @@ static void svc_pool_pa_forget(u64 va)
}
/*
+ * m168 (D-F2c): boot-scoped verified pool-page cache. Within one guest boot
+ * the UVM semaphore pool (gpu->semaphore_pool, a 4 KB sysmem rm_mem at
+ * 0x121000000 whose 1024 words are the per-channel tracker slots) belongs
+ * to the shared GPU object; clients of the same boot reuse the pages while
+ * their per-process vaspaces come and go. The session flush wipes
+ * svc_pool_pa deliberately (a VA may name a new allocation for the next
+ * client), but wiping the POOL page forces every new client through the
+ * slow resolver: r170's clients 4-6 starved 2+ minutes on
+ * "pending release starved ... pool/waiter page" while the scanner learned
+ * the live tracker sems. This cache survives the flush; it is filled ONLY
+ * from read-back-proven resolutions (pin/cache/walk/vaspaces/targeted) and
+ * every use re-proves the exact offset readable, so a page that moved
+ * fails the proof and is dropped. A fresh injected pin always overwrites
+ * it (pins are the resolver's first source and run before this cache).
+ */
+struct svc_pool_boot_pa {
+ u64 va_page;
+ u64 pa_page;
+ int ap;
+};
+
+static struct svc_pool_boot_pa svc_pool_boot[SVC_POOL_PA_MAX];
+static u32 svc_pool_boot_n;
+static u32 svc_pool_boot_next;
+static u32 svc_pool_boot_conflicts;
+static u32 svc_pool_boot_hits;
+
+static int svc_pool_boot_lookup(u64 va, u64 *pa, int *ap)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u32 i;
+
+ for (i = 0; i < svc_pool_boot_n; i++) {
+ if (svc_pool_boot[i].va_page == page) {
+ if (svc_pool_pa_identity(page,
+ svc_pool_boot[i].pa_page))
+ return -ENOENT;
+ *pa = svc_pool_boot[i].pa_page |
+ (va & (SEMA_PAGE_SIZE - 1));
+ *ap = svc_pool_boot[i].ap;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+static void svc_pool_boot_store(u64 va, u64 pa, int ap)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u64 pa_page = pa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u32 i;
+
+ if (svc_pool_pa_identity(page, pa))
+ return;
+ for (i = 0; i < svc_pool_boot_n; i++) {
+ if (svc_pool_boot[i].va_page == page) {
+ if (svc_pool_boot[i].pa_page != pa_page) {
+ /* The page moved (or a source lied): a
+ * fresh proof wins, but the conflict is on
+ * record. */
+ svc_pool_boot_conflicts++;
+ pr_info("sema-svc: pool-boot conflict va=0x%llx cached=0x%llx new=0x%llx (replacing)\n",
+ page, svc_pool_boot[i].pa_page,
+ pa_page);
+ }
+ svc_pool_boot[i].pa_page = pa_page;
+ svc_pool_boot[i].ap = ap;
+ return;
+ }
+ }
+ if (svc_pool_boot_n < SVC_POOL_PA_MAX) {
+ i = svc_pool_boot_n++;
+ } else {
+ i = svc_pool_boot_next++ % SVC_POOL_PA_MAX;
+ }
+ svc_pool_boot[i].va_page = page;
+ svc_pool_boot[i].pa_page = pa_page;
+ svc_pool_boot[i].ap = ap;
+}
+
+static void svc_pool_boot_forget(u64 va)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u32 i;
+
+ for (i = 0; i < svc_pool_boot_n; i++)
+ if (svc_pool_boot[i].va_page == page)
+ svc_pool_boot[i].va_page = 0;
+}
+
+/*
* Resolve one pool-semaphore target the way the successful deferred apply did:
* injected pin (host-verified ground truth), the verified cache, then the
* targeted rm_mem record scan, whose result is read back before it is cached.
@@ -2697,11 +3408,35 @@ static int svc_pool_pa_resolve(struct nv_vgpu_device *vgpu, u32 h, u64 va,
if (src)
*src = "pin";
svc_pool_pa_note(h, va, pa, "pin");
+ svc_pool_boot_store(va, pa, a);
*addr = pa;
*ap = a;
return 0;
}
}
+ /*
+ * m168: boot-scoped page, survives the session flush. Every use
+ * re-proves the exact offset readable: a page that moved fails the
+ * proof and is dropped (the fresh pin path above always wins when a
+ * pin exists).
+ */
+ if (!svc_pool_boot_lookup(va, &pa, &a)) {
+ u32 bprobe = 0;
+
+ if (!svc_read_ap(vgpu, pa, svc_ap_of(a), &bprobe,
+ sizeof(bprobe))) {
+ svc_tr_clear_failure(page);
+ svc_pool_boot_hits++;
+ if (src)
+ *src = "bootcache";
+ *addr = pa;
+ *ap = a;
+ return 0;
+ }
+ pr_info("sema-svc: pool-boot stale va=0x%llx pa=0x%llx (read-back failed; dropping)\n",
+ va, pa);
+ svc_pool_boot_forget(va);
+ }
if (!svc_pool_pa_lookup(va, &pa, &a)) {
svc_tr_clear_failure(page);
if (svc_pool_pa_hit_log < SVC_POOL_PA_HIT_LOG_MAX) {
@@ -2876,6 +3611,17 @@ static int svc_sem_read(struct nv_vgpu_device *vgpu, u64 va, u32 *val)
}
static void svc_pending_add(u64 va, u32 payload, u32 op);
+/* r34x: defined with the pending ring / opdef slots (later in the file);
+ * the acquire gate drains/appraises withheld releases before it holds. */
+static void svc_apply_pending(struct nv_vgpu_device *vgpu);
+static bool svc_wait_pend_release(u64 va);
+
+/*
+ * F1: GR release-word authorship checks. Defined next to the svc_fwd_req
+ * slot; forward-declared because the sweep writer runs earlier in the file.
+ */
+static bool svc_gr_rel_match(u64 va, u32 *payload);
+static bool svc_gr_rel_inflight(u64 va, u32 *payload);
static int svc_sem_write(struct nv_vgpu_device *vgpu, u32 h, u64 va, u32 val)
{
@@ -3261,6 +4007,116 @@ static int svc_map_lookup_range(u64 gpu_va, u32 from, u32 to, u64 *pa)
static void svc_session_flush(void); /* defined next to the pending queue */
+/*
+ * Ledger sizing + counters hoisted above svc_rebuild so the periodic diag
+ * can report the table's fill state: a full table degrades the write/read
+ * authority silently (home replacement evicts another VA's landing), which
+ * is exactly the D2H "reads back prior content" window.
+ */
+#define SVC_LEDGER_MAX (1u << 14)
+#define SVC_LEDGER_MASK (SVC_LEDGER_MAX - 1u)
+#define SVC_LEDGER_PROBE 16u
+/*
+ * STALLFREE: eviction burst threshold for the cap-and-degrade reset. A
+ * cluster keeps 16 same-home VA pages; EXIT_RESET evictions inside one session
+ * means the table is thrashing (pin churn / leftover crashed clients), and
+ * the half-evicted table silently strands the evicted pages' landing
+ * authority. At the threshold the cache is cleared and rebuilt from live
+ * writes (uniform resolver fallback instead of silent partial authority).
+ */
+#define SVC_LEDGER_EVICT_RESET 256u
+static u32 svc_ledger_n;
+static u64 svc_ledger_diverges;
+static u64 svc_ledger_pinwins; /* r336: pin-backed divergence wins */
+static u32 svc_d2hmap_log; /* D2H-MAP per-page destination budget */
+/*
+ * r336/r34x: host-semaphore acquire (C56F SEM_EXECUTE op 0/2/3/4/5) wait
+ * state. The decoder captures EVERY acquire of the ring entry currently
+ * being decoded (svc_waits[], reset per entry at the top of
+ * svc_decode_stream), and svc_run_ops honors them before running any of
+ * the entry's work, so a copy cannot execute ahead of the dependency its
+ * push waits on (the streams2 readback-before-scale class). Per-entry
+ * capture is essential: the wait state used to leak across entries (a
+ * later launch's self-release wait, e.g. need=85 while the held push
+ * releases 84, gated an earlier entry that carries no acquire at all -
+ * the r340 over-hold). The re-decode re-parses the entry, so the state
+ * is refreshed on every pass.
+ *
+ * Semantics per op (class contract, see svc_wait_satisfied):
+ * 0 ACQUIRE = (v == p); 2 ACQ_STRICT_GEQ = v >= p; 3 ACQ_CIRC_GEQ =
+ * circular (s32)(v - p) >= 0; 4 ACQ_AND = (v & p) != 0;
+ * 5 ACQ_NOR = ~(v | p) != 0.
+ */
+#define SVC_WAIT_MAX 8
+/*
+ * r401: bounded acquire wait (fail-open), now a module parameter. The
+ * original 100 ms bound was shorter than a single GR launch's completion
+ * latency in this emulation (observed 0.5-1.6 s per launch), so a copy
+ * that waited on a launch's tracker (streams2: the D2H readback's
+ * ACQ_CIRC_GEQ on the scale channel's progress counter) gave up and ran
+ * BEFORE the launch wrote its output - the host read pre-scale data
+ * (bad=32767, c2 all wrong). 4 s covers several launch latencies; the
+ * bound still guarantees the entry is never parked forever (a genuinely
+ * unsatisfiable acquire fails open late, after the dependency's own work
+ * would have landed - late-but-correct instead of early-and-wrong).
+ */
+unsigned gsp_sema_wait_hold_ms = 4000;
+struct svc_wait_op {
+ u64 va;
+ u32 payload;
+ u8 op; /* C56F SEM_EXECUTE operation */
+ u32 off; /* method offset in the entry (order) */
+};
+static struct svc_wait_op svc_waits[SVC_WAIT_MAX];
+static u32 svc_wait_n; /* acquires captured this entry */
+static u32 svc_wait_ovf; /* acquires dropped past the array */
+static u64 svc_wait_held;
+static u64 svc_wait_pass;
+static u64 svc_wait_giveup;
+static u32 svc_wait_log; /* per-session held-row print budget */
+static u32 svc_wait_pass_log; /* per-session resolved-row budget */
+static u32 svc_remapconst_log; /* r343 FILL: SET_REMAP_CONST_A rows */
+static u32 svc_pitch_log; /* r402: 2D (pitched) op builds */
+static u32 svc_fill_raw_log; /* r365 FILL-RAW: raw dropped-fill dumps */
+static u32 svc_lowfill_log; /* r365b LOWFILL-WIT: below-floor fills */
+static u32 svc_tabcopy_log; /* r367 TABLE-COPY: table-page copies */
+static u32 svc_fill_noremap; /* r343 FILL: fail-closed holds */
+static u64 svc_wait_rel_va; /* this entry's own release (mirror) */
+static u32 svc_wait_rel_payload;
+static u32 svc_wait_rel_off; /* its method offset (order check) */
+static bool svc_wait_rel_have;
+/*
+ * Per-channel first-hold clock for the bounded wait: a gate evaluation
+ * that cannot satisfy the acquire arms a 100 ms budget per hold episode;
+ * expiry fails open (ops run) with a loud 'give-up' row. The table is
+ * tiny (a handful of channels), service-thread only, cleared per pass
+ * resolution / session flush.
+ */
+struct svc_wait_hold {
+ bool active;
+ u32 h;
+ unsigned long since;
+};
+#define SVC_WAIT_HOLD_MAX 16
+static struct svc_wait_hold svc_wait_holds[SVC_WAIT_HOLD_MAX];
+static u32 svc_dst_div_log; /* r402: copy-dst ledger/probe divergence */
+static u64 svc_ledger_stores;
+static u64 svc_ledger_evictions;
+static u64 svc_ledger_evict_since_reset;
+static u64 svc_ledger_evict_resets;
+static u64 svc_ledger_rewrites;
+static u64 svc_ledger_reloc_diverges;
+static u32 svc_ledger_store_log;
+/*
+ * STALLFREE: pending-ring overflow telemetry (the negative queue silently
+ * overwrote its oldest entry when full). Hoisted here so svc_rebuild's diag
+ * can print it; the ring itself is defined with the pending queue below.
+ */
+static u64 svc_pending_ovf;
+static u32 svc_pending_ovf_log;
+/* STALLFREE: rescue scans that hit their wall-clock cap (bounded service). */
+static u64 svc_rescue_scan_capped;
+
static void svc_rebuild(struct nv_vgpu_device *vgpu)
{
static u8 *buf;
@@ -3363,18 +4219,23 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu)
svc.false_trans, svc.gpfifo_skipped, svc.gpfifo_rescued,
svc.releases_faked,
svc.releases_recovered, svc.counter_rel_applied);
- pr_info("sema-svc: drain held=%llu starved=%llu forwarded=%llu faked=%llu rec=%llu ops_once=%llu reident=%llu opdef=%llu/%llu/%llu\n",
+ pr_info("sema-svc: drain held=%llu starved=%llu forwarded=%llu faked=%llu rec=%llu ops_once=%llu reident=%llu opdef=%llu/%llu/%llu gr_nocap=%llu/%llu gr_hold_exp=%llu gr_swrel=%llu gr_wedge=%llu pend_ovf=%llu\n",
svc.gpfifo_held, svc.gpfifo_starved, svc.releases_forwarded,
svc.releases_faked, svc.releases_recovered,
svc.ops_once_skipped, svc.ops_reident,
- svc.opdef_held, svc.opdef_applied, svc.opdef_expired);
- pr_info("sema-svc: counter pub_calls=%llu gr_rel=%llu reresolve=%llu pushpg_fb=%llu altw=%llu residue=%llu\n",
+ svc.opdef_held, svc.opdef_applied, svc.opdef_expired,
+ svc.gr_nocap_held, svc.gr_nocap_consumed,
+ svc.fwd_gr_hold_exp, svc.fwd_gr_stall_swrel,
+ svc.gr_stale_wedge, svc_pending_ovf);
+ pr_info("sema-svc: counter pub_calls=%llu gr_rel=%llu reresolve=%llu pushpg_fb=%llu altw=%llu residue=%llu wait=%llu/%llu giveup=%llu ovf=%u\n",
svc.counter_pub_calls, svc.gr_rel_applied,
svc.sem_write_reresolve, svc.pushpg_fallback,
- svc.pool_alt_applied, svc.pool_residue);
- if (svc.resc_run)
- pr_info("sema-svc: rescue_run=%llu (multi-page content pins)\n",
- svc.resc_run);
+ svc.pool_alt_applied, svc.pool_residue,
+ svc_wait_held, svc_wait_pass, svc_wait_giveup,
+ svc_wait_ovf);
+ if (svc.resc_run || svc_rescue_scan_capped)
+ pr_info("sema-svc: rescue_run=%llu scan_capped=%llu (multi-page content pins)\n",
+ svc.resc_run, svc_rescue_scan_capped);
pr_info("sema-svc: gpfifo done=%llu nomap=%llu vidmem_writes=%llu ring_auto=%u pb=%u/%u pins=%u/%llu ipins=%u/%llu\n",
svc.gpfifo_entries_done, svc.gpfifo_nomap,
svc.vidmem_writes, svc_ring_n, svc_pb_n, svc_pb_passes,
@@ -3383,6 +4244,19 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu)
svc_vas_n, SVC_VAS_SLOTS, svc.vas_writes, svc.vas_reads,
svc.vas_shadowed);
/*
+ * D2H-source authority visibility (r220): a full ledger degrades to
+ * home-slot replacement, which silently evicts another VA's landing
+ * and lets readers fall back to the resolver - the "reads back prior
+ * content" window. n approaching SVC_LEDGER_MAX or evict > 0 in a
+ * client's window is the tell.
+ */
+ pr_info("sema-svc: ledger n=%u/%u stores=%llu evict=%llu resets=%llu diverges=%llu pinwins=%llu reloc_div=%llu rewrites=%llu pinrep=%llu alt_skip=%llu\n",
+ svc_ledger_n, SVC_LEDGER_MAX, svc_ledger_stores,
+ svc_ledger_evictions, svc_ledger_evict_resets,
+ svc_ledger_diverges, svc_ledger_pinwins,
+ svc_ledger_reloc_diverges, svc_ledger_rewrites,
+ svc_ledger_pin_repairs, svc_pool_alt_skipped);
+ /*
* P6b wave 1: guest-forwarding summary. Printed whenever any counter
* moved (not only on requests) so a run that captured no candidate is
* still diagnosable from the log.
@@ -3440,6 +4314,9 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu)
#define CE_M_OFFSET_OUT_UPPER 0x408
#define CE_M_OFFSET_OUT_LOWER 0x40c
#define CE_M_LINE_LENGTH_IN 0x418
+#define CE_M_LINE_COUNT 0x41c
+#define CE_M_PITCH_IN 0x410
+#define CE_M_PITCH_OUT 0x414
#define CE_M_SET_REMAP_CONST_A 0x700
#define CE_M_SET_REMAP_CONST_B 0x704
#define CE_M_SET_REMAP_COMPONENTS 0x708
@@ -3514,6 +4391,19 @@ struct svc_memset_op {
u64 bytes; /* copy length */
u32 src_phys;
u32 src_aperture;
+ /*
+ * r402: 2D (pitched) transfers. rows > 1 selects row-wise mode:
+ * the op runs `rows` times with the destination advancing by
+ * dpitch (and the source by spitch) per row, each row carrying
+ * `bytes` (copy) or elem*count (fill) bytes. cudaMemset2D and
+ * cudaMemcpy2D issue exactly this shape (LINE_COUNT rows,
+ * PITCH_OUT/PITCH_IN in bytes); without it the fill/copy ran flat
+ * and the partial-width rows corrupted the neighbouring columns
+ * (victory_memset2d bad>12M, memcpy_roundtrip pitched class).
+ */
+ u32 rows;
+ u32 dpitch;
+ u32 spitch;
};
/* Decoder state carried across the methods of one push. */
@@ -3525,9 +4415,19 @@ struct svc_push_state {
u32 have_in, have_out;
u32 line_length;
u32 gr_line_count; /* GRCE LINE_COUNT */
+ /*
+ * r402: 2D transfer geometry (CE class): PITCH_IN/PITCH_OUT in
+ * bytes and LINE_COUNT rows. A pitched fill/copy whose pitch
+ * exceeds the row length must run row-wise; a flat transfer leaves
+ * pitch == line_length (or no pitch method at all).
+ */
+ u32 pitch_in, pitch_out;
+ u32 have_pitch_in, have_pitch_out;
+ u32 line_count;
u32 sem_va_hi, sem_va_lo, sem_payload;
u32 have_sem;
u32 have_remap;
+ u32 have_remap_a, have_remap_b, have_remap_c;
u32 release_dma;
u32 sem_inc;
u32 rs_upper, rs_lower, rs_payload;
@@ -3658,7 +4558,18 @@ static int svc_op_translate(struct nv_vgpu_device *vgpu, u64 va, u64 *addr,
* withholding the release corrupted the next session (r59: the late
* memset overwrote refilled data after the client had passed the
* release), so the two must move together.
+ *
+ * r383 (streams2 big-copy class): name the missed VA through the
+ * same stalled-VA demand channel the GR fill uses. The userspace pin
+ * stream watches the "stalled VA" row and pushes the VA to the
+ * in-guest scanner, so the pin lands on the next scanner pass
+ * instead of waiting out a full scan cadence. The hold itself is
+ * unchanged; this only shortens the window in which the entry sits
+ * op-pending (r377: a held H2D copy executed ~1.5 s late and read a
+ * staging page the client had since refilled with the next copy's
+ * bytes - the neighbor-data class).
*/
+ svc_log_stalled_va(va & ~(u64)(SEMA_PAGE_SIZE - 1));
svc.op_tr_pending = 1;
return -ENOENT;
}
@@ -3683,19 +4594,280 @@ static bool svc_dst_watched(u64 dst)
}
/*
+ * COPYAUDIT shared budget (see the svc_copy comment for the contract):
+ * every copy at/above SVC_COPY_AUDIT_MIN is logged once with want/done and
+ * gets a first/last-16-bytes destination readback vs the written bytes
+ * (COPY-MISMATCH). One budget covers every copy path in this file so a
+ * whole cycle stays visible.
+ */
+#define SVC_COPY_AUDIT_MIN (32u << 10) /* >32 KB copies audited */
+#define SVC_COPY_AUDIT_MAX 2048u /* per-boot line budget */
+#define SVC_CEAUDIT_MAX 4096u /* per-boot CEAUDIT budget */
+static u64 svc_copy_audit_n;
+static u64 svc_ceaudit_n;
+
+/* COPYAUDIT for a finished fill: log want/done and check the first 16
+ * destination bytes against the repeated pattern. The readback uses the
+ * accessor matching the aperture the fill wrote through. */
+static void svc_fill_audit(struct nv_vgpu_device *vgpu, u64 va0, u64 addr,
+ u32 aperture, size_t total, const u8 *pat, u32 elem)
+{
+ u8 exp[16], got[16] = {0};
+ size_t i;
+ bool ok = false;
+
+ if (total < SVC_COPY_AUDIT_MIN || svc_copy_audit_n >= SVC_COPY_AUDIT_MAX)
+ return;
+ /*
+ * The fill writes the 8-byte pattern group repeatedly (pat is the
+ * element zero-padded to 8 bytes), so the expectation mirrors that:
+ * byte i of the destination equals pat[i % 8].
+ */
+ for (i = 0; i < sizeof(exp); i++)
+ exp[i] = pat[i % 8];
+ if (aperture == CE_APERTURE_VID) {
+ u8 *base = vgpu->bars[1].vaddr;
+
+ if (base && addr + sizeof(got) <= svc_fb_shadow_size(vgpu)) {
+ memcpy(got, base + addr, sizeof(got));
+ ok = true;
+ }
+ } else if (!svc_read(vgpu, addr, got, sizeof(got))) {
+ ok = true;
+ }
+ svc_copy_audit_n++;
+ pr_info("vgsp: COPYAUDIT path=sema-fill va=0x%llx len=0x%llx done=0x%llx\n",
+ va0, (u64)total, (u64)total);
+ if (ok && memcmp(exp, got, sizeof(exp)))
+ pr_info_ratelimited("vgsp: COPY-MISMATCH va=0x%llx off=0x0 path=sema-fill want=%*phN got=%*phN\n",
+ va0, 16, exp, 16, got);
+}
+
+/*
* Write `count` elements of `value` (element size `elem`) starting at
* `addr`, in vidmem or guest RAM depending on the aperture DST_TYPE
* selected. Returns 0 on success.
*/
+static void svc_ledger_store(u64 va, u64 pa, u32 ap); /* R2 ledger, defined below */
+static int svc_ledger_lookup(u64 va, u64 *pa, u32 *ap); /* R2 ledger, defined below */
+static void svc_ledger_clear(void); /* STALLFREE: cap-and-degrade reset */
+
+/*
+ * r384 (the guest boot-corruption lottery, root-caused 2026-10-20):
+ * page-table pages of the guest kernel's direct map were found ZEROED
+ * (two adjacent 4 KiB PTE pages at gpa 0x12d3c2000/0x3000 => two 2 MiB
+ * holes in the guest physmap, kernel objects inside them unreachable,
+ * multi-oops panic). The only emulation writers of page-sized ZERO runs
+ * are the virtual FILL executor (svc_fill / CE memsets), whose large
+ * >= SVC_VAS_FLOOR destinations resolve through the walk/scan when no
+ * pin and no ledger answer exists - and that resolver is demonstrably
+ * non-deterministic (four executions of the same 8 KiB engine-window
+ * clear resolved to four different guest pages, all refused by the
+ * below-floor guard).
+ *
+ * Two nets, mirroring the m169/r378 copy guards:
+ * 1. svc_fill_guard_hold(): a LARGE (>= 64 KiB) virtual fill whose only
+ * authority is the walk/scan (no pin, no ledger) is held through a
+ * short bounded window and its VA pushed to the pin-demand channel
+ * (the userspace pin stream injects the guest-pagemap truth), so the
+ * retried decode resolves through a real pin. Fail-open after the
+ * bound with the historical behaviour, like the copy guard.
+ * 2. svc_fill_target_is_pt(): a ZERO fill about to land in guest RAM on
+ * a page whose first 64 qwords are all PTE-shaped (present, NX,
+ * physical address in range) is REFUSED loudly. A page table page is
+ * never a legitimate zero-fill destination; this directly blocks the
+ * observed corruption shape even when some other resolver approved
+ * the target. Pin-backed resolutions are exempt (the guest's own
+ * pagemap is truth - if it says the VA maps the page, obey it).
+ */
+unsigned gsp_sema_fill_guard_ms = 800;
+bool gsp_sema_fill_guard = true;
+#define SVC_FILL_GUARD_SLOTS 16
+static struct {
+ u64 va_page;
+ unsigned long first;
+} svc_fill_guard[SVC_FILL_GUARD_SLOTS];
+static u32 svc_fill_guard_next;
+static u32 svc_fill_held;
+static u32 svc_fill_pt_refused;
+static u32 svc_fill_exec_log; /* r384 FILL-EXEC witness budget */
+
+/* true = the guard may hold this fill; false = fail-open, run it. */
+static bool svc_fill_guard_hold(u64 va)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ unsigned long now = jiffies;
+ u32 i, slot = SVC_FILL_GUARD_SLOTS;
+
+ for (i = 0; i < SVC_FILL_GUARD_SLOTS; i++) {
+ if (svc_fill_guard[i].va_page == page) {
+ if (time_before(now, svc_fill_guard[i].first +
+ msecs_to_jiffies(
+ gsp_sema_fill_guard_ms)))
+ return true;
+ if (svc_fill_guard[i].first) {
+ svc_fill_guard[i].first = 0;
+ pr_info("sema-svc: fill guard released without a pin va=0x%llx (using the walk translation)\n",
+ page);
+ }
+ return false;
+ }
+ if (!svc_fill_guard[i].va_page)
+ slot = i;
+ }
+ if (slot == SVC_FILL_GUARD_SLOTS)
+ slot = svc_fill_guard_next++ % SVC_FILL_GUARD_SLOTS;
+ svc_fill_guard[slot].va_page = page;
+ svc_fill_guard[slot].first = now;
+ return true;
+}
+
+/*
+ * A guest-kernel page table page: the first SVC_PT_SCAN qwords are all
+ * present PTEs (bit 0), non-executable (bit 63 - kernel data mappings),
+ * with a physical address inside the 34-bit guest range. Random payload
+ * qwords pass all three conditions with probability far below the 75%
+ * threshold at SVC_PT_SCAN=64; a genuine table passes at 100%.
+ */
+#define SVC_PT_SCAN 64u
+#define SVC_PT_MIN 48u
+static bool svc_fill_target_is_pt(struct nv_vgpu_device *vgpu, u64 addr)
+{
+ u64 ents[SVC_PT_SCAN];
+ u32 i, hits = 0;
+
+ if (addr & (SEMA_PAGE_SIZE - 1))
+ return false; /* only a page-aligned table shape */
+ if (svc_read(vgpu, addr, ents, sizeof(ents)))
+ return false;
+ for (i = 0; i < SVC_PT_SCAN; i++) {
+ u64 e = ents[i];
+
+ if (!(e & 1ull))
+ continue;
+ if (!(e & (1ull << 63)))
+ continue;
+ if ((e & 0x000ffffffffff000ull) == 0)
+ continue;
+ if ((e & 0x000ffffffffff000ull) >= (1ull << 34))
+ continue;
+ hits++;
+ }
+ return hits >= SVC_PT_MIN;
+}
+
+/*
+ * r391 per-page destination resolution for a VIRTUAL fill (the r390
+ * guest-panic class). A device VA's pages are independent guest allocations
+ * - the pin stream shows split runs (va=0x7c36c7400000 -> 29 pages at
+ * 0x131b63000, the next run at 0x131a80000) - so resolving only the fill's
+ * first page and writing `total` bytes contiguously past it writes over
+ * whatever memory follows the first run. On r390 a 383620-byte zero fill
+ * did exactly that: the contiguous write covered the guest kernel's
+ * page-table pages (0x131ba1000/0x131ba2000/0x131bab000), the direct map
+ * grew holes, and the guest died with "Fatal exception in interrupt"
+ * (unmap_page_range / cpuacct / psi faults). svc_copy has always translated
+ * virtual operands per 4 KB page; the fill must do the same.
+ *
+ * Authority order mirrors the top-level resolution (and svc_copy's per-page
+ * dst): a fresh pin (guest pagemap truth) wins and repairs the landing
+ * table, then the landing table (the page a previous write of this VA
+ * landed on), then the resolver chain. A page with no resolution returns
+ * -ENOENT: the caller fails the fill loudly instead of writing through a
+ * contiguous guess.
+ */
+static int svc_fill_dst_page_pa(struct nv_vgpu_device *vgpu, u64 va,
+ u64 *pa_out, u32 *ap_out, u32 *prov_out)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u64 pa = 0, lpa = 0;
+ u32 aper = 0, lap = 0;
+
+ if (!svc_pin_lookup_any(page, &pa, &aper)) {
+ *prov_out = 1;
+ if (!svc_ledger_lookup(page, &lpa, &lap) &&
+ (((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) != page) ||
+ (u32)lap != (u32)aper)) {
+ svc_ledger_pin_repairs++;
+ if (svc_ledger_pin_repairs <= 32)
+ pr_info("sema-ledger: PIN-WINS fill-page va=0x%llx ledger=0x%llx/%u pin=0x%llx/%u\n",
+ page,
+ lpa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ lap, pa, aper);
+ svc_ledger_store(page, pa, aper);
+ }
+ *pa_out = pa;
+ *ap_out = aper;
+ return 0;
+ }
+ if (!svc_ledger_lookup(page, &pa, &aper)) {
+ *prov_out = 2;
+ *pa_out = pa;
+ *ap_out = aper;
+ return 0;
+ }
+ if (!svc_op_translate(vgpu, page, &pa, &aper)) {
+ *prov_out = 3;
+ *pa_out = pa;
+ *ap_out = aper;
+ return 0;
+ }
+ return -ENOENT;
+}
+
+static int svc_fill_1d(struct nv_vgpu_device *vgpu, u64 addr, u64 value,
+ u32 elem, u32 count, u32 phys, u32 aperture,
+ u64 *done_out);
+static int svc_copy_1d(struct nv_vgpu_device *vgpu, u64 dst, u64 src,
+ size_t total, u32 dst_phys, u32 dst_aperture,
+ u32 src_phys, u32 src_aperture, u64 *done_out);
+
+/*
+ * r402 row-wise wrapper: a 2D (pitched) fill runs the 1D fill once per
+ * row with the destination advancing by dpitch. Each row keeps the whole
+ * per-page resolution, ledger-store and guard machinery of the 1D path
+ * (no new resolution semantics), and a failed row's retry simply re-runs
+ * the already-written rows with the same pattern - idempotent for fills.
+ */
static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem,
- u32 count, u32 phys, u32 aperture)
+ u32 count, u32 phys, u32 aperture, u64 *done_out,
+ u32 rows, u32 dpitch)
+{
+ if (rows > 1 && dpitch) {
+ u64 done = 0;
+ int rc = 0;
+ u32 r;
+
+ for (r = 0; r < rows; r++) {
+ u64 row_done = 0;
+
+ rc = svc_fill_1d(vgpu, addr + (u64)r * dpitch,
+ value, elem, count, phys, aperture,
+ &row_done);
+ done += row_done;
+ if (rc)
+ break;
+ }
+ if (done_out)
+ *done_out = done;
+ return rc;
+ }
+ return svc_fill_1d(vgpu, addr, value, elem, count, phys, aperture,
+ done_out);
+}
+
+static int svc_fill_1d(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem,
+ u32 count, u32 phys, u32 aperture, u64 *done_out)
{
u8 pat[8];
u8 bounce[256];
size_t total = (size_t)elem * count;
size_t o;
bool shadow = false;
+ u64 va0 = addr; /* original destination VA (fills, C2) */
+ if (done_out)
+ *done_out = 0;
if (elem != 1 && elem != 2 && elem != 4 && elem != 8)
return -EINVAL;
if (!count || total > (256ULL << 20))
@@ -3706,21 +4878,151 @@ static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem,
if (!phys) {
/* Virtual destination: the VA shadow first (its pages stay
- * authoritative), then the pinned map and the VA->PA map. */
+ * authoritative), then a live pin (the guest's newest
+ * statement - r372: a re-pinned VA must not be written
+ * through a stale landing), then the unified landing table
+ * (the page a previous write of this VA landed on - always
+ * the authoritative location of the content), then the
+ * pinned map and the VA->PA map. */
u64 pa;
u32 aper;
+ u32 prov = 0; /* r384: 1 pin, 2 ledger, 3 walk/scan */
if (svc_vas_has(addr)) {
shadow = true;
+ } else if (!svc_pin_lookup_any(addr, &pa, &aper)) {
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ prov = 1;
+ /*
+ * r372 pin-wins: repair the landing table when it
+ * still names the pre-re-pin page, so every later
+ * reader follows the pin too.
+ */
+ if (!svc_ledger_lookup(addr, &lpa, &lap) &&
+ (((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ (u32)lap != (u32)aper)) {
+ svc_ledger_pin_repairs++;
+ if (svc_ledger_pin_repairs <= 32)
+ pr_info("sema-ledger: PIN-WINS fill-dst va=0x%llx ledger=0x%llx/%u pin=0x%llx/%u\n",
+ addr,
+ lpa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ lap, pa, aper);
+ svc_ledger_store(addr,
+ pa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ aper);
+ }
+ addr = pa;
+ aperture = (aper == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ } else if (!svc_ledger_lookup(addr, &pa, &aper)) {
+ prov = 2;
+ addr = pa;
+ aperture = (aper == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
} else if (svc_op_translate(vgpu, addr, &pa, &aper)) {
if (!svc_vas_can_shadow(addr))
return -ENOENT;
shadow = true;
svc.vas_shadowed++;
} else {
+ prov = 3;
addr = pa;
aperture = aper;
}
+ /*
+ * r365b LOWFILL-WIT: a below-floor virtual fill (the guest's
+ * vidmem-window page-table writes) is a corruption risk: its
+ * resolution must be proven before the op is allowed to run
+ * (r365: 16-byte and 32 KB zero fills landed in guest RAM and
+ * killed the guest kernel). Log every resolution bounded.
+ */
+ if (va0 < SVC_VAS_FLOOR && svc_lowfill_log < 64) {
+ svc_lowfill_log++;
+ pr_info("sema-lowfill: va=0x%llx shadow=%d resolved=0x%llx ap=%u elem=%u count=%u value=0x%llx\n",
+ va0, shadow, addr, aperture, elem, count,
+ value);
+ }
+ /*
+ * r369: a below-floor fill may ONLY land in vidmem. The
+ * 0x204020000 engine-window clear resolved to a DIFFERENT
+ * guest-RAM page on every execution (9 runs, 9 pages) and
+ * the guest died with its task_struct corrupted (gmain NULL
+ * deref in __fdget) - each wrong resolution zeroed 8 KB of
+ * guest kernel memory. Vidmem resolutions (the page-table
+ * region, ap=VID) are the emulation's own shadow and stay
+ * allowed; a SYS resolution for a window VA is refused and
+ * logged loudly.
+ */
+ if (va0 < SVC_VAS_FLOOR && !shadow &&
+ aperture != CE_APERTURE_VID) {
+ pr_err_ratelimited("vgsp-sema: LOWFILL-REFUSED va=0x%llx pa=0x%llx ap=%u elem=%u count=%u (below-floor fill resolved outside vidmem)\n",
+ va0, addr, aperture, elem, count);
+ return -ENOENT;
+ }
+ /*
+ * r384 net 1 (pin-demand hold for unproven large fills): a
+ * >= 64 KiB virtual fill whose only authority is the
+ * walk/scan resolver (no pin, no landing-table entry) AND
+ * which would land in GUEST RAM (SYS aperture) is the class
+ * that zeroed the guest's page-table pages (see the guard
+ * commentary above svc_fill_guard_hold). Demand the VA
+ * through the pin channel and hold the op through a short
+ * bounded window so the retried decode resolves through the
+ * guest's own pagemap; fail open with the historical
+ * behaviour after the bound. Vidmem-shadow resolutions are
+ * the emulation's own memory and stay immediate.
+ */
+ if (gsp_sema_fill_guard && va0 >= SVC_VAS_FLOOR && !shadow &&
+ total >= (64u << 10) && prov >= 3 &&
+ aperture == CE_APERTURE_SYS) {
+ svc_log_stalled_va(va0);
+ if (svc_fill_guard_hold(va0)) {
+ svc_fill_held++;
+ if (svc_fill_held <= 32)
+ pr_info("sema-svc: FILL-HOLD va=0x%llx resolved=0x%llx ap=%u total=%zu value=0x%llx (unproven large fill; pin demanded)\n",
+ va0, addr, aperture, total,
+ value);
+ return -ENOENT;
+ }
+ }
+ /*
+ * r384 net 2 (page-table-shape refusal): a ZERO fill about
+ * to land in guest RAM on a page whose first 64 qwords are
+ * all present+NX+in-range PTEs is refused loudly - a page
+ * table page is never a legitimate zero-fill destination.
+ * This blocks the observed corruption shape (two adjacent
+ * PTE pages zeroed => two 2 MiB holes in the guest physmap)
+ * even when some other resolver approved the target, and
+ * irrespective of how wrong the resolution is. Pin-backed
+ * resolutions are exempt: the guest's own pagemap is truth
+ * there.
+ */
+ if (gsp_sema_fill_guard && va0 >= SVC_VAS_FLOOR && !shadow &&
+ value == 0 && total >= SEMA_PAGE_SIZE &&
+ aperture == CE_APERTURE_SYS && prov != 1 &&
+ svc_fill_target_is_pt(vgpu, addr)) {
+ svc_fill_pt_refused++;
+ if (svc_fill_pt_refused <= 32)
+ pr_err("vgsp-sema: FILL-PT-REFUSED va=0x%llx pa=0x%llx prov=%u total=%zu (zero fill target is a page table)\n",
+ va0, addr, prov, total);
+ svc_log_stalled_va(va0);
+ return -ENOENT;
+ }
+ /*
+ * r384 witness: where every >= 4 KiB virtual fill actually
+ * lands (va, pa, aperture, resolution provenance, pattern) -
+ * the decisive line for the boot-corruption forensics (the
+ * large zero fills were invisible until now). Bounded.
+ */
+ if (va0 >= SVC_VAS_FLOOR && !shadow &&
+ total >= SEMA_PAGE_SIZE && svc_fill_exec_log < 64) {
+ svc_fill_exec_log++;
+ pr_info("sema-svc: FILL-EXEC va=0x%llx pa=0x%llx ap=%u prov=%u total=%zu value=0x%llx\n",
+ va0, addr, aperture, prov, total, value);
+ }
}
if (gsp_sema_dbg && !shadow && svc_dst_watched(addr) &&
@@ -3735,10 +5037,40 @@ static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem,
svc.op_tr_src, elem, pb, svc.cur_ch);
}
- if (shadow)
- return svc_vas_fill(addr, pat, elem, total);
+ if (shadow) {
+ int rc = svc_vas_fill(addr, pat, elem, total);
- if (aperture == CE_APERTURE_VID) {
+ if (!rc && done_out)
+ *done_out = (u64)total;
+ return rc;
+ }
+
+ /*
+ * C2 (data class) + m180 unification: a large virtual fill is a
+ * write/read pair with the memset's readers like any H2D copy -
+ * record its landing pages so the engine fill and the D2H read of
+ * this VA use exactly the page the fill wrote. Only the VID
+ * (shadow) case is contiguous; a SYS landing is recorded per page
+ * in the loop below (r391: the contiguous `addr + po` store also
+ * poisoned the table with pages the fill never wrote).
+ */
+ if (!phys && !shadow && va0 >= SVC_VAS_FLOOR &&
+ total >= (64u << 10) && aperture == CE_APERTURE_VID) {
+ u64 po;
+
+ for (po = 0; po < total; po += SEMA_PAGE_SIZE)
+ svc_ledger_store(va0 + po, addr + po,
+ SVC_APERTURE_FB);
+ }
+
+ /*
+ * r391: a virtual device destination is translated per 4 KB page
+ * (see svc_fill_dst_page_pa) - never written through the first
+ * page's PA contiguously. The whole-range shadow write below only
+ * applies to a resolution that is contiguous by construction.
+ */
+ if (aperture == CE_APERTURE_VID &&
+ !(va0 >= SVC_VAS_FLOOR && !phys && !shadow)) {
u8 *base = vgpu->bars[1].vaddr;
size_t size = svc_fb_shadow_size(vgpu);
@@ -3748,6 +5080,9 @@ static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem,
memcpy(base + addr + o, pat, 8);
if (o < total)
memcpy(base + addr + o, pat, total - o);
+ if (done_out)
+ *done_out = (u64)total;
+ svc_fill_audit(vgpu, va0, addr, aperture, total, pat, elem);
return 0;
}
@@ -3755,17 +5090,107 @@ static int svc_fill(struct nv_vgpu_device *vgpu, u64 addr, u64 value, u32 elem,
for (o = 0; o < total; ) {
size_t chunk = total - o;
size_t i;
+ u64 wpa = addr;
+ u32 wap = aperture;
+ bool per_page = !phys && !shadow && va0 >= SVC_VAS_FLOOR;
if (chunk > sizeof(bounce))
chunk = sizeof(bounce);
+ /*
+ * r391: resolve THIS page's own landing (see
+ * svc_fill_dst_page_pa) and clamp the chunk to the page
+ * boundary - a virtual VA's pages are independent guest
+ * allocations and must never be written through the first
+ * page's PA contiguously.
+ */
+ if (per_page) {
+ u32 off = (u32)((va0 + o) & (SEMA_PAGE_SIZE - 1));
+ u64 ppa = 0;
+ u32 pap = 0, prov = 0;
+ int prc;
+
+ chunk = min_t(size_t, chunk, SEMA_PAGE_SIZE - off);
+ prc = svc_fill_dst_page_pa(vgpu, va0 + o, &ppa,
+ &pap, &prov);
+ if (prc) {
+ if (done_out)
+ *done_out = (u64)o;
+ /*
+ * r391: demand the exact page through the pin
+ * channel (the userspace stream pushes this VA
+ * to the in-guest scanner) and hold the fill
+ * for a bounded retry - never write through a
+ * contiguous guess past a missing page.
+ */
+ svc_log_stalled_va((va0 + o) &
+ ~(u64)(SEMA_PAGE_SIZE - 1));
+ pr_err_ratelimited("vgsp: FILL-TRUNC va=0x%llx want=0x%llx done=0x%llx path=sema-fill (page 0x%llx unresolved)\n",
+ va0, (u64)total, (u64)o,
+ (va0 + o) &
+ ~(u64)(SEMA_PAGE_SIZE - 1));
+ return -ENOENT;
+ }
+ wap = (pap == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ wpa = (ppa & ~(u64)(SEMA_PAGE_SIZE - 1)) + off;
+ /*
+ * r391 net: the r384 page-table-shape refusal
+ * applied to EVERY page of a virtual zero fill.
+ * The r390 fill's first page was the client's
+ * buffer, the later pages were the guest's PTE
+ * pages - the first-page-only check could not see
+ * them. A page-table-shaped guest-RAM page is never
+ * a legitimate zero-fill target: skip it loudly
+ * instead of zeroing it. Pin-backed resolutions
+ * are exempt (the guest's pagemap is truth there).
+ */
+ if (wap == CE_APERTURE_SYS && value == 0 && prov != 1 &&
+ svc_fill_target_is_pt(vgpu,
+ wpa & ~(u64)(SEMA_PAGE_SIZE - 1))) {
+ svc_fill_pt_refused++;
+ if (svc_fill_pt_refused <= 32)
+ pr_err("vgsp-sema: FILL-PT-REFUSED va=0x%llx pa=0x%llx prov=%u total=%zu (zero fill page is a page table; page skipped)\n",
+ va0 + o, wpa, prov, total);
+ o += chunk;
+ continue;
+ }
+ }
for (i = 0; i + 8 <= chunk; i += 8)
memcpy(bounce + i, pat, 8);
if (i < chunk)
memcpy(bounce + i, pat, chunk - i);
- if (svc_write(vgpu, addr + o, bounce, chunk))
+ if (wap == CE_APERTURE_VID) {
+ u8 *base = vgpu->bars[1].vaddr;
+ size_t size = svc_fb_shadow_size(vgpu);
+
+ if (!base || wpa + chunk > size) {
+ if (done_out)
+ *done_out = (u64)o;
+ return -EINVAL;
+ }
+ memcpy(base + wpa, bounce, chunk);
+ } else if (svc_write(vgpu, wpa, bounce, chunk)) {
+ if (done_out)
+ *done_out = (u64)o;
+ if (total >= SVC_COPY_AUDIT_MIN)
+ pr_err_ratelimited("vgsp: COPY-TRUNC va=0x%llx want=0x%llx done=0x%llx path=sema-fill rc=%d\n",
+ va0, (u64)total, (u64)o, -EIO);
return -EIO;
+ }
+ /*
+ * r391: record this page's landing (the readers follow the
+ * table) - SYS and VID alike, exactly like the copy path.
+ */
+ if (per_page)
+ svc_ledger_store(va0 + o,
+ wpa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (wap == CE_APERTURE_VID) ?
+ SVC_APERTURE_FB : SVC_APERTURE_SYS);
o += chunk;
}
+ if (done_out)
+ *done_out = (u64)total;
+ svc_fill_audit(vgpu, va0, addr, aperture, total, pat, elem);
return 0;
}
@@ -3820,13 +5245,23 @@ static struct svc_fwd_cand svc_fwd_cand;
#define SVC_GR_POINTER_MAX 8
#define SVC_GR_PAGES_MAX 16
/*
+ * relfix PART 3 (fill candidate superset): maximum distinct candidate pages
+ * per collection pass. Raised from 16 because the full-page scans added
+ * below (aligned with the rewrite's own whole-page scan) can legitimately
+ * carry more pointer-shaped words than the argument region alone; the old
+ * cap silently dropped the extra words, which then resurfaced as RELOC-GAP
+ * at rewrite time. It remains a hard bound with the historical silent-drop
+ * semantics beyond it (n_cand is logged in the fill summary).
+ */
+#define SVC_GR_CAND_MAX 24u
+/*
* m99: guest data identity-map capacity. The forwarded kernel's global data
* pages (discovered by scanning the param/const-0 page for device VAs) are
- * resolved on the service thread and identity-mapped into the engine tree.
- * 16K pages = 64 MB of window coverage, comfortably above the demo buffers
- * (2 x 16 MB observed) with headroom.
+ * resolved on the service thread and relocated into the engine-visible
+ * region. B-2: 32K records = 2x the grown region's 16384 pages of headroom,
+ * so the hand-off array is never the binding limit.
*/
-#define SVC_GR_DATA_MAX 16384
+#define SVC_GR_DATA_MAX 32768
#define SVC_GR_PUSH_WORDS 256
#define SVC_GR_QMD_WORD_NONE 0xffffffffu
/* svc_gr_cand.rel_flags bits (decoded from qmd[24] and qmd[4]). */
@@ -3870,9 +5305,18 @@ struct svc_gr_data_win {
u64 reloc_base; /* relocated window base */
};
-struct svc_gr_cand {
+struct svc_gr_cand_rec {
bool valid;
bool offered; /* consumed by a request or a fallback */
+ /*
+ * STALLFREE: exactly-once latch for the candidate's QMD completion
+ * release when the launch is consumed without ever being forwarded
+ * (a data-stall/busy/nocap hold fell through). Zeroed with the rest
+ * of the candidate at capture.
+ */
+ bool rel_done;
+ unsigned long captured_jiffies; /* STALLFREE: capture stamp for the
+ * GR-STALL waited_ms accounting */
u32 h_object;
u64 push_va;
u32 push_len;
@@ -3897,7 +5341,105 @@ struct svc_gr_cand {
u32 pointer_word[SVC_GR_POINTER_MAX]; /* QMD word of the pair */
};
-static struct svc_gr_cand svc_gr_cand;
+/*
+ * fail4: the capture was a single slot; two concurrent launches (streams2's
+ * vadd pair on two channels) overwrote each other's candidate while the
+ * first was kept for a retry (the candidate-overwrite witness) and the
+ * engines ran crossed parameters (c1 == a2+b2). Keep up to four candidates
+ * side by side in a ring; the accessor macro addresses the current slot,
+ * so every existing use keeps working on the selected candidate, and
+ * capture/consume select the slot instead of clobbering it.
+ */
+#define SVC_GR_CAND_RING 4
+static struct svc_gr_cand_rec svc_gr_cand_ring[SVC_GR_CAND_RING];
+static u32 svc_gr_cand_pos;
+#define svc_gr_cand (svc_gr_cand_ring[svc_gr_cand_pos])
+/*
+ * r383: push-VA history of recent captures, beyond the (four-slot)
+ * candidate ring's lifetime. A replayed decode of a complete QMD asks
+ * "has this launch ever been captured?"; the ring alone can have rotated
+ * past it, and a wrong "no" would re-capture (and re-execute) an already
+ * forwarded launch. Bounded, service thread only.
+ */
+#define SVC_GR_CAP_HIST 8
+static u64 svc_gr_cap_hist[SVC_GR_CAP_HIST];
+static u32 svc_gr_cap_hist_pos;
+
+/*
+ * r401: robust session-scoped "already captured" memory for the r383
+ * replay-capture path. The 4-slot candidate ring + 8-VA history evicted a
+ * real capture within ~12 later captures, so a true replay of an
+ * already-forwarded launch (identical push VA and content) was
+ * re-captured and re-executed: the r399 soak log shows the same nine
+ * launch pushes re-submitted every ~10-11 s for minutes (idempotent
+ * kernels survive it; accumulators would not). The open-addressed table
+ * keeps the (push_va, push_hash) identity of every capture of the
+ * session, so a true replay always classifies as "seen" (LAUNCH-SKIP,
+ * exactly once) while a genuinely new push - even at the same VA with
+ * the same entry words - is captured through the normal
+ * `trusted && !replay` path and never consults this table. Cleared by
+ * svc_session_flush (a new client's identical-payload pushes restart
+ * their release counters, so a boot-scoped table would wrongly suppress
+ * them). Service-thread only.
+ */
+#define SVC_GR_SEEN_SLOTS 2048
+#define SVC_GR_SEEN_PROBE 8
+static struct {
+ u64 push_va;
+ u64 hash;
+} svc_gr_seen[SVC_GR_SEEN_SLOTS];
+static u64 svc_gr_seen_stale; /* probe-chain evictions (telemetry) */
+
+static u32 svc_gr_seen_mix(u64 push_va, u64 hash)
+{
+ u64 k = push_va * 0x9E3779B97F4A7C15ULL;
+
+ k ^= hash + 0x9E3779B97F4A7C15ULL + (k << 6) + (k >> 2);
+ return (u32)(k % SVC_GR_SEEN_SLOTS);
+}
+
+static bool svc_gr_seen_lookup(u64 push_va, u64 hash)
+{
+ u32 i, base = svc_gr_seen_mix(push_va, hash);
+
+ if (!push_va)
+ return false;
+ for (i = 0; i < SVC_GR_SEEN_PROBE; i++) {
+ u32 k = (base + i) % SVC_GR_SEEN_SLOTS;
+
+ if (!svc_gr_seen[k].push_va)
+ return false;
+ if (svc_gr_seen[k].push_va == push_va &&
+ svc_gr_seen[k].hash == hash)
+ return true;
+ }
+ return false;
+}
+
+static void svc_gr_seen_store(u64 push_va, u64 hash)
+{
+ u32 i, base = svc_gr_seen_mix(push_va, hash);
+
+ if (!push_va)
+ return;
+ for (i = 0; i < SVC_GR_SEEN_PROBE; i++) {
+ u32 k = (base + i) % SVC_GR_SEEN_SLOTS;
+
+ if (!svc_gr_seen[k].push_va ||
+ (svc_gr_seen[k].push_va == push_va &&
+ svc_gr_seen[k].hash == hash)) {
+ svc_gr_seen[k].push_va = push_va;
+ svc_gr_seen[k].hash = hash;
+ return;
+ }
+ }
+ /* Full probe chain: replace the tail (oldest-ish) entry. */
+ svc_gr_seen_stale++;
+ svc_gr_seen[(base + SVC_GR_SEEN_PROBE - 1) % SVC_GR_SEEN_SLOTS].push_va =
+ push_va;
+ svc_gr_seen[(base + SVC_GR_SEEN_PROBE - 1) % SVC_GR_SEEN_SLOTS].hash =
+ hash;
+}
static_assert(SVC_GR_QMD_WORDS == GSP_PHYS_GR_QMD_WORDS,
"the sema GR QMD word count must match the backend ABI");
@@ -3933,7 +5475,7 @@ static_assert(sizeof(struct svc_gr_page) == 24,
static u64 svc_push_hash(const u8 *p, u32 len);
/* QMD field decode: the exact shape the host CUDA bridge uses. */
-static void svc_gr_qmd_decode(struct svc_gr_cand *c)
+static void svc_gr_qmd_decode(struct svc_gr_cand_rec *c)
{
const u32 *q = c->qmd;
u32 i;
@@ -3972,13 +5514,50 @@ static void svc_gr_cand_capture(struct svc_channel *ch,
{
u32 i, n;
+ /*
+ * C9 (GAP-2 A4): a capture over a still-valid candidate (never
+ * offered) destroys an unforwarded launch - the single-slot
+ * overwrite hazard. Bounded, unconditional (gsp_sema_dbg is defined
+ * later in the file and this site may not reference it).
+ */
+ /*
+ * fail4: never overwrite a DIFFERENT launch's unconsumed candidate -
+ * move to a free ring slot instead (same-push re-captures still
+ * refresh in place).
+ */
+ if (svc_gr_cand.valid && !svc_gr_cand.offered &&
+ svc_gr_cand.push_va != push_va) {
+ u32 ri;
+
+ for (ri = 1; ri < SVC_GR_CAND_RING; ri++) {
+ u32 rp = (svc_gr_cand_pos + ri) % SVC_GR_CAND_RING;
+
+ if (!svc_gr_cand_ring[rp].valid ||
+ svc_gr_cand_ring[rp].offered) {
+ svc_gr_cand_pos = rp;
+ break;
+ }
+ }
+ }
+ if (svc_gr_cand.valid && svc_cand_ovw_log < 64) {
+ svc_cand_ovw_log++;
+ pr_info("sema-gr: CAND-OVERWRITE old_h=0x%x old_push=0x%llx new_push=0x%llx\n",
+ svc_gr_cand.h_object, svc_gr_cand.push_va, push_va);
+ }
memset(&svc_gr_cand, 0, sizeof(svc_gr_cand));
svc_gr_cand.valid = true;
+ svc_gr_cand.captured_jiffies = jiffies;
svc_gr_cand.h_object = ch->h_object;
svc_gr_cand.push_va = push_va;
svc_gr_cand.push_len = push_len;
+ /* r383: remember the identity beyond this ring slot's lifetime. */
+ svc_gr_cap_hist[svc_gr_cap_hist_pos % SVC_GR_CAP_HIST] = push_va;
+ svc_gr_cap_hist_pos++;
if (push && push_len)
svc_gr_cand.push_hash = svc_push_hash(push, push_len);
+ /* r401: the session-wide exactly-once memory (survives ring rotation;
+ * see svc_gr_seen_lookup). */
+ svc_gr_seen_store(push_va, svc_gr_cand.push_hash);
if (push && push_len >= 4) {
u32 total = push_len / 4;
u32 n = total < SVC_GR_PUSH_WORDS ? total : SVC_GR_PUSH_WORDS;
@@ -4071,8 +5650,14 @@ static bool svc_fwd_defer_arm(void)
{
if (!gsp_phys_guest_mem_enabled())
return false;
- if (svc_gr_cand.valid && gsp_phys_gr_forward_enabled())
- return true;
+ if (gsp_phys_gr_forward_enabled()) {
+ u32 ri;
+
+ for (ri = 0; ri < SVC_GR_CAND_RING; ri++)
+ if (svc_gr_cand_ring[ri].valid &&
+ !svc_gr_cand_ring[ri].offered)
+ return true;
+ }
return svc_fwd_cand.valid &&
gsp_phys_work_defer_release_enabled();
}
@@ -4131,8 +5716,358 @@ static bool svc_fwd_defer_armed;
#define SVC_COPY_FORWARDED 1
/* Byte copy between two CE addresses (src/dst decoding mirrors svc_fill). */
+/*
+ * m171 (AGENT-F R2): write/read consistency ledger. A large H2D write to a
+ * device VA records {va_page -> pa, aperture} for every page it wrote; the
+ * later read of the same VA (the launch data fill, or a D2H copy) prefers
+ * the recorded page whenever its own resolution disagrees. The pipeline
+ * then reads exactly what was written regardless of which of the two
+ * resolutions is stale - the r174/r175 first-page divergence (the write
+ * landed on pa X, the fill read a stale page Y, and the engine computed on
+ * old bytes) can no longer split a write/read pair. Session-scoped: cleared
+ * with the flush; zero cost while empty. Apertures are stored in the SVC_
+ * encoding, the canonical space for the svc accessors.
+ */
+struct svc_ledger_ent {
+ u64 va_page;
+ u64 pa_page;
+ u32 ap;
+};
+/*
+ * Unified VA->landing-page authority ("R2 ledger").
+ *
+ * m172 grew the original 256-entry ring to 2048, but a ring still evicts
+ * unrelated VAs wholesale: one 4 MiB H2D plus one launch's data-page
+ * record set is 4-6 K inserts, so an 8 MiB machine operand's early pages
+ * were evicted before the launch fill looked them up, and the fill fell
+ * back to its own (possibly stale) resolution - the exact "write path and
+ * read path pick different pages" class.
+ *
+ * m180: keyed hash table instead of a ring. One entry per VA page, O(1)
+ * update/lookup, no cross-VA eviction, and the insert of a page already
+ * present rewrites it in place (a write is a new landing and is always
+ * the truth). 16384 slots cover a full multi-buffer client's working set
+ * (8 MB in + 8 MB out + engine records ~ 12 K pages); hash collisions
+ * only occur between VA pages 64 MiB apart or across clients whose pages
+ * do not co-reside in time.
+ *
+ * COPYAUDIT r220: SVC_LEDGER_MAX/MASK/PROBE and the counters live above
+ * svc_rebuild (hoisted for the diag line); only the table and the home
+ * helper stay here.
+ */
+static struct svc_ledger_ent svc_ledger[SVC_LEDGER_MAX];
+
+static inline u32 svc_ledger_home(u64 page)
+{
+ return (u32)((page >> SEMA_PAGE_SHIFT) * 2654435761u) &
+ SVC_LEDGER_MASK;
+}
+
+static void svc_ledger_store(u64 va, u64 pa, u32 ap)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u64 pa_page = pa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u32 home = svc_ledger_home(page);
+ u32 p;
+
+ for (p = 0; p < SVC_LEDGER_PROBE; p++) {
+ struct svc_ledger_ent *e = &svc_ledger[(home + p) &
+ SVC_LEDGER_MASK];
+
+ if (e->va_page == page) {
+ /*
+ * A write path moving an existing page: its landing
+ * is the new truth. Logged (bounded) because a move
+ * is the write-side half of the DIVERGE class.
+ */
+ if (e->pa_page != pa_page) {
+ svc_ledger_rewrites++;
+ if (svc_ledger_rewrites <= 16)
+ pr_info("sema-ledger: rewrite va=0x%llx old=0x%llx new=0x%llx ap=%u\n",
+ page, e->pa_page, pa_page,
+ ap);
+ }
+ e->pa_page = pa_page;
+ e->ap = ap;
+ return;
+ }
+ if (!e->va_page) {
+ e->va_page = page;
+ e->pa_page = pa_page;
+ e->ap = ap;
+ svc_ledger_n++;
+ svc_ledger_stores++;
+ if (svc_ledger_store_log < 64) {
+ svc_ledger_store_log++;
+ pr_info("sema-ledger: store va=0x%llx pa=0x%llx ap=%u n=%u\n",
+ page, pa_page, ap, svc_ledger_n);
+ }
+ return;
+ }
+ }
+ /*
+ * Cluster full (16 same-home VA pages co-resident): replace the home
+ * slot. Bounded, rare, and only affects VAs whose pages hash to the
+ * identical home slot. r220: this is a SILENT loss of the evicted
+ * VA's landing authority (its later readers fall back to the
+ * resolver, possibly stale), so the first occurrences are logged and
+ * counted into the diag line.
+ *
+ * STALLFREE cap-and-degrade: a sustained eviction burst means the
+ * table is being thrashed (a pin storm / leftover crashed client
+ * churning VA pages faster than the working set), and a half-evicted
+ * table silently strands authority for exactly the evicted pages.
+ * Past SVC_LEDGER_EVICT_RESET evictions in one session the cache is
+ * cleared wholesale and rebuilt from live writes: every reader
+ * uniformly falls back to the resolver until its page is rewritten,
+ * which removes the stranded-authority class at a known, logged
+ * cost. The clear is a cache operation only (no control flow, no
+ * wait); the current store then lands in the empty table.
+ */
+ svc_ledger_evictions++;
+ svc_ledger_evict_since_reset++;
+ if (svc_ledger_evict_since_reset >= SVC_LEDGER_EVICT_RESET) {
+ svc_ledger_evict_since_reset = 0;
+ svc_ledger_evict_resets++;
+ pr_warn("sema-ledger: LEDGER-RESET evict=%llu resets=%llu (cluster thrash under churn; cache cleared, readers fall back to the resolver until rewritten)\n",
+ svc_ledger_evictions, svc_ledger_evict_resets);
+ svc_ledger_clear();
+ }
+ if (svc_ledger_evictions <= 32)
+ pr_warn("sema-ledger: EVICT home=%u old_va=0x%llx new_va=0x%llx (cluster full; old landing authority lost until rewritten)\n",
+ home, (u64)svc_ledger[home].va_page, page);
+ svc_ledger[home].va_page = page;
+ svc_ledger[home].pa_page = pa_page;
+ svc_ledger[home].ap = ap;
+ svc_ledger_stores++;
+}
+
+static int svc_ledger_lookup(u64 va, u64 *pa, u32 *ap)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u32 home = svc_ledger_home(page);
+ u32 p;
+
+ for (p = 0; p < SVC_LEDGER_PROBE; p++) {
+ const struct svc_ledger_ent *e =
+ &svc_ledger[(home + p) & SVC_LEDGER_MASK];
+
+ if (!e->va_page)
+ return -ENOENT;
+ if (e->va_page == page) {
+ *pa = e->pa_page | (va & (SEMA_PAGE_SIZE - 1));
+ *ap = e->ap;
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+static void svc_ledger_clear(void)
+{
+ memset(svc_ledger, 0, sizeof(svc_ledger));
+ svc_ledger_n = 0;
+ svc_ledger_rewrites = 0;
+ svc_ledger_reloc_diverges = 0;
+ svc_ledger_evict_since_reset = 0;
+}
+
+/*
+ * COPYAUDIT (2026-10-18 copy-completeness audit): every copy at/above
+ * SVC_COPY_AUDIT_MIN is logged once with its want/done byte counts, and
+ * its first/last 16 destination bytes are read back immediately and
+ * compared against the bytes just written (COPY-MISMATCH on difference).
+ * COPY-TRUNC marks a copy that failed after partially writing (want>done),
+ * so a partial transfer can never be silent. Pure diagnostics: no control
+ * flow, locking or timing change; the readback uses the same accessor the
+ * write just used. Definitions live with svc_fill (first user).
+ */
+static void svc_copy_audit_chunk(struct nv_vgpu_device *vgpu, u64 dst_va,
+ u64 o, const u8 *bounce, size_t chunk,
+ bool shadow, u64 pa_eff, u32 aper, bool tail)
+{
+ size_t n = chunk < 16 ? chunk : 16;
+ size_t so = tail ? chunk - n : 0; /* true end sample */
+ u8 rb[16] = {0};
+
+ if (shadow) {
+ if (svc_vas_read(dst_va + o + so, rb, n))
+ return;
+ } else if (aper == CE_APERTURE_VID) {
+ u8 *base = vgpu->bars[1].vaddr;
+
+ if (!base || pa_eff + so + n > svc_fb_shadow_size(vgpu))
+ return;
+ memcpy(rb, base + pa_eff + so, n);
+ } else if (svc_read(vgpu, pa_eff + so, rb, n)) {
+ return;
+ }
+ if (memcmp(rb, bounce + so, n)) {
+ pr_info_ratelimited("vgsp: COPY-MISMATCH va=0x%llx off=0x%llx path=ce-soft-copy want=%*phN got=%*phN\n",
+ dst_va + o + so, o + so, (int)n,
+ bounce + so, (int)n, rb);
+ pr_info_ratelimited("vgsp: CEAUDIT-MISMATCH op=copy va=0x%llx off=0x%llx path=ce-soft-copy want=%*phN got=%*phN\n",
+ dst_va + o + so, o + so, (int)n,
+ bounce + so, (int)n, rb);
+ }
+}
+
+#define SVC_COPY_TRUNC_RET(rc_) \
+ do { \
+ if (done_out) \
+ *done_out = (u64)o; \
+ if (total >= SVC_COPY_AUDIT_MIN) \
+ pr_err_ratelimited("vgsp: COPY-TRUNC va=0x%llx want=0x%llx done=0x%llx path=ce-soft-copy rc=%d\n", \
+ dst_orig, (u64)total, (u64)o, (rc_)); \
+ return (rc_); \
+ } while (0)
+
+/*
+ * fail4: pipeline-order source snapshot for held copies. The m169
+ * device-dst guard defers a staged H2D (host->device) for up to 3 s;
+ * the driver's ping-pong staging buffers are overwritten by the next
+ * chunk meanwhile, so the deferred read returns the content of a later
+ * chunk (vector_add: device a[i] pages held a[i+2 MB] - exactly the
+ * 2x1 MB staging stride). A held copy snapshots its source at hold
+ * time and the retried execution copies from the snapshot, matching
+ * the hardware's in-order read of the staging.
+ */
+#define SVC_COPY_SNAP_N 16
+#define SVC_COPY_SNAP_MAX (4u << 20)
+#define SVC_COPY_SNAP_TTL_MS 30000u
+struct svc_copy_snap {
+ u64 dst, src;
+ size_t total;
+ u8 *buf;
+ unsigned long stamp;
+};
+static struct svc_copy_snap svc_copy_snaps[SVC_COPY_SNAP_N];
+
+static u8 *svc_copy_snap_lookup(u64 dst, u64 src, size_t total)
+{
+ u32 i;
+
+ for (i = 0; i < SVC_COPY_SNAP_N; i++)
+ if (svc_copy_snaps[i].buf &&
+ svc_copy_snaps[i].dst == dst &&
+ svc_copy_snaps[i].src == src &&
+ svc_copy_snaps[i].total == total)
+ return svc_copy_snaps[i].buf;
+ return NULL;
+}
+
+static void svc_copy_snap_free(u64 dst, u64 src, size_t total)
+{
+ u32 i;
+
+ for (i = 0; i < SVC_COPY_SNAP_N; i++)
+ if (svc_copy_snaps[i].buf &&
+ svc_copy_snaps[i].dst == dst &&
+ svc_copy_snaps[i].src == src &&
+ svc_copy_snaps[i].total == total) {
+ kfree(svc_copy_snaps[i].buf);
+ svc_copy_snaps[i].buf = NULL;
+ return;
+ }
+}
+
+/* Capture the source of a virtual-operand copy about to be held. */
+static void svc_copy_snap_capture(struct nv_vgpu_device *vgpu, u64 dst,
+ u64 src, size_t total)
+{
+ struct svc_copy_snap *s = NULL;
+ u8 *buf;
+ size_t o;
+ u32 i;
+
+ if (!total || total > SVC_COPY_SNAP_MAX)
+ return;
+ if (svc_copy_snap_lookup(dst, src, total))
+ return; /* already captured */
+ for (i = 0; i < SVC_COPY_SNAP_N; i++) {
+ if (!svc_copy_snaps[i].buf) {
+ s = &svc_copy_snaps[i];
+ break;
+ }
+ if (time_after(jiffies, svc_copy_snaps[i].stamp +
+ msecs_to_jiffies(SVC_COPY_SNAP_TTL_MS))) {
+ kfree(svc_copy_snaps[i].buf);
+ svc_copy_snaps[i].buf = NULL;
+ s = &svc_copy_snaps[i];
+ break;
+ }
+ }
+ if (!s)
+ return; /* all slots live: keep the historical behaviour */
+ buf = kmalloc(total, GFP_KERNEL);
+ if (!buf)
+ return;
+ for (o = 0; o < total; ) {
+ size_t chunk = min_t(size_t, 0x1000 - ((src + o) & 0xfff),
+ total - o);
+ u64 pa = 0;
+ u32 ap = 0;
+
+ /* The source is a virtual host VA; resolve per page first. */
+ if (svc_op_translate(vgpu, src + o, &pa, &ap) ||
+ svc_read(vgpu, pa, buf + o, chunk)) {
+ kfree(buf);
+ return;
+ }
+ o += chunk;
+ }
+ s->dst = dst;
+ s->src = src;
+ s->total = total;
+ s->stamp = jiffies;
+ s->buf = buf;
+ pr_info_ratelimited("vgsp-sema: copy snapshot captured dst=0x%llx src=0x%llx total=%zu first4=%08x %08x %08x %08x\n",
+ dst, src, total,
+ total >= 4 ? *(const u32 *)buf : 0,
+ total >= 8 ? *(const u32 *)(buf + 4) : 0,
+ total >= 12 ? *(const u32 *)(buf + 8) : 0,
+ total >= 16 ? *(const u32 *)(buf + 12) : 0);
+}
+
+static int svc_fwd_operand_hpa(struct nv_vgpu_device *vgpu, u8 kind,
+ u64 pa, u32 off, u32 bytes, u64 *hpa,
+ u32 *out_off);
+
+/* r402 row-wise wrapper: see svc_fill. The source advances by spitch. */
+/* r402: read-only resolver probe (defined with the GR fill diagnostics). */
+static const char *svc_gr_src_probe(struct nv_vgpu_device *vgpu, u64 va,
+ u64 *pa_out, u32 *ap_out);
static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
- u32 dst_phys, u32 dst_aperture, u32 src_phys, u32 src_aperture)
+ u32 dst_phys, u32 dst_aperture, u32 src_phys, u32 src_aperture,
+ u64 *done_out, u32 rows, u32 dpitch, u32 spitch)
+{
+ if (rows > 1 && dpitch) {
+ u64 done = 0;
+ int rc = 0;
+ u32 r;
+
+ for (r = 0; r < rows; r++) {
+ u64 row_done = 0;
+
+ rc = svc_copy_1d(vgpu, dst + (u64)r * dpitch,
+ src + (u64)r * spitch, total,
+ dst_phys, dst_aperture, src_phys,
+ src_aperture, &row_done);
+ done += row_done;
+ if (rc)
+ break;
+ }
+ if (done_out)
+ *done_out = done;
+ return rc;
+ }
+ return svc_copy_1d(vgpu, dst, src, total, dst_phys, dst_aperture,
+ src_phys, src_aperture, done_out);
+}
+
+static int svc_copy_1d(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
+ u32 dst_phys, u32 dst_aperture, u32 src_phys, u32 src_aperture,
+ u64 *done_out)
{
u8 bounce[256];
size_t o;
@@ -4142,10 +6077,28 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
u64 dst_orig = dst, src_orig = src;
u32 dst_prov = 0, src_prov = 0;
bool bigcopy_this = false;
+ bool audit = total >= SVC_COPY_AUDIT_MIN;
+ u8 *snap = NULL;
static u64 bigcopy_log;
+ bool trace64 = (total == 65536u);
+ const char *dir = "?";
+ if (done_out)
+ *done_out = 0; /* 0 until the first byte lands */
if (!total || total > (256ULL << 20))
return -EINVAL;
+ /*
+ * fail4: name every copy the executor is asked to run (pre-
+ * resolution), so a copy that fails or is held before any witness
+ * still has an entry record.
+ */
+ if (gsp_sema_dbg && svc.copyent_log < 128) {
+ svc.copyent_log++;
+ pr_info("sema-copyent: h=0x%x dst=0x%llx src=0x%llx total=%zu dphys=%u dap=%u sphys=%u sap=%u\n",
+ svc.cur_ch, dst, src, total, dst_phys, dst_aperture,
+ src_phys, src_aperture);
+ }
+ snap = svc_copy_snap_lookup(dst_orig, src_orig, total);
if (!dst_phys) {
u64 pa;
@@ -4153,17 +6106,96 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
if (svc_vas_has(dst)) {
dst_shadow = true;
+ } else if (!svc_ledger_lookup(dst, &pa, &aper)) {
+ /*
+ * r402 diagnostic (bounded): name the case where the
+ * ledger's landing page for a copy DESTINATION
+ * disagrees with the live resolver (pin/walk). The
+ * D2H staging writes failed on the class this
+ * witness detects: the writer follows the ledger
+ * while the guest's reader holds a different page,
+ * so the client reads stale data (streams2 c1
+ * zeros / smiley 3-of-4 passes). Read-only probe
+ * (svc_gr_src_probe stores nothing).
+ */
+ if (gsp_sema_dbg && svc_dst_div_log < 64 &&
+ dst >= SVC_VAS_FLOOR) {
+ u64 wpa = 0;
+ u32 wap = 0;
+ const char *src =
+ svc_gr_src_probe(vgpu, dst, &wpa, &wap);
+
+ if (wpa &&
+ (((wpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ ((wap == SVC_APERTURE_FB) !=
+ (aper == SVC_APERTURE_FB)))) {
+ svc_dst_div_log++;
+ pr_err_ratelimited("vgsp-sema: DST-DIVERGE va=0x%llx ledger=0x%llx/%u probe=%s 0x%llx/%u (write follows ledger)\n",
+ dst, pa, aper, src, wpa, wap);
+ }
+ }
+ /*
+ * m180 unification: a previous write of this VA is
+ * the authority for where the content lives (and so
+ * for where a new write lands); it also spares the
+ * m169 stale-walk hold below, which exists precisely
+ * because no pin was known.
+ *
+ * r376 pin-agreement: a FRESH pin that disagrees is
+ * newer than the recorded landing - the guest freed
+ * or moved the buffer - so the pin wins and the
+ * entry is repaired (the same rule the fills and the
+ * launch paths apply; without it a stale entry
+ * writes a freed page - the guest-corruption class).
+ */
+ {
+ u64 ipa = 0;
+ int iap = SVC_APERTURE_SYS;
+
+ if (!svc_ipin_lookup(dst, &ipa, &iap) &&
+ (((ipa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ (u32)iap != (u32)aper)) {
+ svc_ledger_pin_repairs++;
+ if (svc_ledger_pin_repairs <= 64)
+ pr_info("sema-ledger: PIN-WINS copy-dst va=0x%llx ledger=0x%llx/%u pin=0x%llx/%d\n",
+ dst,
+ pa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ aper, ipa, iap);
+ svc_ledger_store(dst,
+ ipa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (u32)iap);
+ pa = ipa;
+ aper = (u32)iap;
+ }
+ }
+ dst = pa;
+ dst_aperture = (aper == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ dst_prov = 1;
+ dst_virt = true;
} else if (svc_op_translate(vgpu, dst, &pa, &aper)) {
- if (!svc_vas_can_shadow(dst)) {
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ if (!svc_ledger_lookup(dst, &lpa, &lap)) {
+ dst = lpa;
+ dst_aperture = (lap == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ dst_prov = 1;
+ dst_virt = true;
+ } else if (!svc_vas_can_shadow(dst)) {
if (gsp_sema_dbg && svc.op_tr_log < 64) {
svc.op_tr_log++;
pr_info("sema-tr-miss: DST va=0x%llx h=0x%x\n",
dst, svc.cur_ch);
}
return -ENOENT;
+ } else {
+ dst_shadow = true;
+ svc.vas_shadowed++;
}
- dst_shadow = true;
- svc.vas_shadowed++;
} else {
dst = pa;
dst_aperture = aper;
@@ -4171,24 +6203,174 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
dst_virt = true;
}
}
+ /*
+ * m169 (AGENT-F R1): a device-lane destination translated only by a
+ * page-directory walk (prov 3/4) or the RAM scan (5) can be a stale
+ * PTE from the previous session - the table CONTENT is not wiped by
+ * the session flush - and a wrong-but-valid vidmem write is silent.
+ * r172 streams2_diag: the stream-1 a H2D ran rc=0 through a
+ * walk-resolved destination while the launch (pin-resolved) read the
+ * true page still holding the previous client's h_a; the engine
+ * computed from stale inputs and the client verified 32768 bad.
+ * Since the resolver tries pins FIRST, prov >= 3 means no pin existed
+ * at resolve time: hold the copy (-ENOENT feeds the m163 bounded
+ * hold), which re-decodes after the client's pins land (seconds) and
+ * is loud on expiry - never silent corruption. Device destinations
+ * only; sysmem copies keep the historical sources.
+ */
+ if (dst_virt && dst_aperture == CE_APERTURE_VID &&
+ dst_prov >= 3 && dst_orig >= SVC_VAS_FLOOR &&
+ gsp_sema_copy_dst_pin_guard &&
+ svc_dst_guard_hold(dst_orig)) {
+ svc.copy_dst_hold++;
+ if (svc.copy_dst_hold <= 16)
+ pr_info("sema-svc: device-dst copy held: dst=0x%llx prov=%u (no pin yet; stale-table guard)\n",
+ dst_orig, dst_prov);
+ /*
+ * fail4: the staging source is valid NOW; snapshot it so
+ * the deferred execution copies the pipeline-order bytes.
+ */
+ if (!src_phys && src_orig >= SVC_VAS_FLOOR)
+ svc_copy_snap_capture(vgpu, dst_orig, src_orig,
+ total);
+ return -ENOENT;
+ }
+ /*
+ * r378: the same stale-table class for SYS destinations resolved by
+ * the walk/scan (prov >= 3): a FRESH injected pin that disagrees is
+ * the guest's newest statement, so writing through the resolved page
+ * lands on a page the guest has freed/re-homed (the guest-corruption
+ * class). Hold through the same bounded re-decode path instead of
+ * writing; a missing pin keeps the historical behaviour (the
+ * resolution is then the write authority).
+ */
+ if (dst_virt && dst_aperture == CE_APERTURE_SYS &&
+ dst_prov >= 3 && dst_orig >= SVC_VAS_FLOOR &&
+ gsp_sema_copy_dst_pin_guard) {
+ u64 ipa = 0;
+ int iap = SVC_APERTURE_SYS;
+
+ if (!svc_ipin_lookup(dst_orig, &ipa, &iap) &&
+ (((ipa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (dst & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ svc_ap_of(iap) != SVC_APERTURE_SYS) &&
+ svc_dst_guard_hold(dst_orig)) {
+ svc.copy_dst_hold++;
+ if (svc.copy_dst_hold <= 16)
+ pr_info("sema-svc: device-dst copy held: dst=0x%llx prov=%u pin=0x%llx (stale SYS target)\n",
+ dst_orig, dst_prov, ipa);
+ if (!src_phys && src_orig >= SVC_VAS_FLOOR)
+ svc_copy_snap_capture(vgpu, dst_orig,
+ src_orig, total);
+ return -ENOENT;
+ }
+ }
+ /*
+ * r231 READ-ORDER guard/witness: a device-src copy whose source VA
+ * has a PENDING device-dst write hold reads the PRE-write content -
+ * the writer's bytes land only when the guard resolves while the
+ * reader executes immediately (the ctaid_probe RAWA round trip is
+ * the reproducer). Witnessed always (bounded); deferred only with
+ * sema_read_order_guard, through the same bounded-hold re-decode.
+ */
+ if (src_orig >= SVC_VAS_FLOOR && svc_dst_guard_pending(src_orig)) {
+ if (svc_read_order_log < 16) {
+ svc_read_order_log++;
+ pr_info("sema-svc: READ-ORDER-WIT device-src copy va=0x%llx reads while a device-dst write to the same VA is still held%s\n",
+ src_orig, gsp_sema_read_order_guard ?
+ " (deferring)" : " (guard off)");
+ }
+ if (gsp_sema_read_order_guard)
+ return -ENOENT;
+ }
if (!src_phys) {
u64 pa;
u32 aper;
if (svc_vas_has(src)) {
src_shadow = true;
+ } else if (src_orig >= SVC_VAS_FLOOR &&
+ !svc_ledger_lookup(src, &pa, &aper)) {
+ /*
+ * r375 read-after-write authority: the landing table
+ * records where every write of this VA landed (writes
+ * store it, fresh pins repair it). A reader MUST read
+ * exactly that page or the engine's output is invisible
+ * (the sum=0 class). The walk/pin sources can answer
+ * differently - e.g. the same numeric pa in a different
+ * aperture - so the table wins for device VAs; only a
+ * VA with no recorded landing falls through to the
+ * resolver below.
+ *
+ * r376 pin-agreement: unless a FRESH pin disagrees -
+ * then the pin is newer (the guest re-pinned the VA)
+ * and wins, and the entry is repaired.
+ */
+ {
+ u64 ipa = 0;
+ int iap = SVC_APERTURE_SYS;
+
+ if (!svc_ipin_lookup(src, &ipa, &iap) &&
+ (((ipa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ (u32)iap != (u32)aper)) {
+ svc_ledger_pin_repairs++;
+ if (svc_ledger_pin_repairs <= 64)
+ pr_info("sema-ledger: PIN-WINS copy-src va=0x%llx ledger=0x%llx/%u pin=0x%llx/%d\n",
+ src,
+ pa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ aper, ipa, iap);
+ svc_ledger_store(src,
+ ipa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (u32)iap);
+ pa = ipa;
+ aper = (u32)iap;
+ }
+ }
+ src = pa;
+ src_aperture = (aper == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ src_prov = 1;
+ src_virt = true;
} else if (svc_op_translate(vgpu, src, &pa, &aper)) {
- if (gsp_sema_dbg && svc.op_tr_log < 64) {
- svc.op_tr_log++;
- pr_info("sema-tr-miss: SRC va=0x%llx h=0x%x\n",
- src, svc.cur_ch);
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ /*
+ * m180 unification: read the page the last host write
+ * of this VA landed on when the resolver can no longer
+ * answer (same rule as the per-page loop below), instead
+ * of failing the whole copy.
+ */
+ if (!svc_ledger_lookup(src, &lpa, &lap)) {
+ src = lpa;
+ src_aperture = (lap == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ src_prov = 1;
+ src_virt = true;
+ } else {
+ if (gsp_sema_dbg && svc.op_tr_log < 64) {
+ svc.op_tr_log++;
+ pr_info("sema-tr-miss: SRC va=0x%llx h=0x%x\n",
+ src, svc.cur_ch);
+ }
+ return -ENOENT;
}
- return -ENOENT;
} else {
src = pa;
src_aperture = aper;
src_prov = svc.op_tr_src;
src_virt = true;
+ /*
+ * r375b: do NOT record a resolver answer into the
+ * landing table here. The resolver can return a stale
+ * PTE page (the m169 class) and the table is the WRITE
+ * authority - a poisoned entry makes every later write
+ * of this VA land on the stale page (r375 boot: slab
+ * GPF in kmem_cache_alloc at guest t=77s, before any
+ * client). The table is fed by writes and fresh pins
+ * only.
+ */
}
}
@@ -4198,8 +6380,35 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
* 16 bytes on each side before the copy. The vadd data path is exactly
* these copies; this separates a wrong resolution from a wrong copy.
*/
- if (total >= (1u << 20) && bigcopy_log < 24) {
+ if ((total >= SVC_COPY_AUDIT_MIN ||
+ (dst_virt && dst_orig >= SVC_VAS_FLOOR)) &&
+ (bigcopy_log < gsp_sema_bigcopy_log_max || total == 65536u)) {
u8 sb[16] = {0}, db[16] = {0};
+ /*
+ * r235: 64 KB copies are exempt from the bigcopy budget - the
+ * probe's A upload/readbacks must ALWAYS appear (the budget
+ * belongs to the multi-MB traffic and had absorbed every 64 KB
+ * probe copy in the r235 capture).
+ */
+ bool trace64 = (total == 65536u);
+ const char *dir =
+ (dst_aperture == CE_APERTURE_VID &&
+ src_aperture == CE_APERTURE_SYS) ? "H2D" :
+ (src_aperture == CE_APERTURE_VID &&
+ dst_aperture == CE_APERTURE_SYS) ? "D2H" :
+ src_shadow ? "shadow->" : "phys";
+
+ /*
+ * r235: 64 KB copies are exempt from the bigcopy budget - the
+ * probe's A upload/readbacks must ALWAYS appear (the budget
+ * belongs to the multi-MB traffic and had absorbed every 64 KB
+ * probe copy in the r235 capture).
+ */
+ dir = (dst_aperture == CE_APERTURE_VID &&
+ src_aperture == CE_APERTURE_SYS) ? "H2D" :
+ (src_aperture == CE_APERTURE_VID &&
+ dst_aperture == CE_APERTURE_SYS) ? "D2H" :
+ src_shadow ? "shadow->" : "phys";
bigcopy_log++;
bigcopy_this = true;
@@ -4223,12 +6432,55 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
} else {
svc_read(vgpu, dst, db, sizeof(db));
}
- pr_info("sema-bigcopy: dst_va=0x%llx -> pa=0x%llx ap=%u prov=%u shadow=%u | src_va=0x%llx -> pa=0x%llx ap=%u prov=%u shadow=%u | bytes=%zu\n",
+ pr_info("sema-bigcopy: dst_va=0x%llx -> pa=0x%llx ap=%u prov=%u shadow=%u | src_va=0x%llx -> pa=0x%llx ap=%u prov=%u shadow=%u | bytes=%zu vbase=%px\n",
dst_orig, dst, dst_aperture, dst_prov, dst_shadow,
src_orig, src, src_aperture, src_prov, src_shadow,
- total);
+ total, (void *)vgpu->bars[1].vaddr);
+ /*
+ * r348 diagnostic: for a high-VA SYS source name the exact
+ * host PA this read will touch (gpa_to_hpa) - the direct
+ * comparison against the svc-fwd-hpa writeback line.
+ */
+ if (src_orig >= SVC_VAS_FLOOR &&
+ src_aperture == CE_APERTURE_SYS) {
+ u64 shpa = 0;
+ u32 soff = 0;
+
+ if (!svc_fwd_operand_hpa(vgpu, SVC_APERTURE_SYS,
+ src, 0, SEMA_PAGE_SIZE,
+ &shpa, &soff) && !soff)
+ pr_info("sema-bigcopy: src-hpa va=0x%llx gpa=0x%llx -> hpa=0x%llx\n",
+ src_orig, src, shpa);
+ else
+ pr_info("sema-bigcopy: src-hpa va=0x%llx gpa=0x%llx -> UNMAPPED\n",
+ src_orig, src);
+ }
pr_info("sema-bigcopy: src16=%16phN dst16_before=%16phN\n",
sb, db);
+ /*
+ * r234 A-TRACE: one labeled line per 64 KB copy so the probe A
+ * upload/readback is greppable end-to-end. pt=P1 = H2D source
+ * read (the uploaded bytes as ints), pt=P4 = D2H source read
+ * (the engine-authored page as ints). The expected content is
+ * ints 0,1,2,3,... for the probe A buffer.
+ */
+ if (trace64 && svc_atrace_log < 128u) {
+ u32 i0, i1, i2, i3, j0 = 0, j1 = 0, j2 = 0, j3 = 0;
+
+ svc_atrace_log++;
+ memcpy(&i0, sb, 4);
+ memcpy(&i1, sb + 4, 4);
+ memcpy(&i2, sb + 8, 4);
+ memcpy(&i3, sb + 12, 4);
+ memcpy(&j0, db, 4);
+ memcpy(&j1, db + 4, 4);
+ memcpy(&j2, db + 8, 4);
+ memcpy(&j3, db + 12, 4);
+ pr_info("vgsp: A-TRACE pt=%s dir=%s dst_va=0x%llx dst_pa=0x%llx dst_prov=%u | src_va=0x%llx src_pa=0x%llx src_prov=%u | src_int4=%d %d %d %d dst_int4_before=%d %d %d %d\n",
+ !strcmp(dir, "H2D") ? "P1" : "P4", dir,
+ dst_orig, dst, dst_prov, src_orig, src,
+ src_prov, i0, i1, i2, i3, j0, j1, j2, j3);
+ }
}
/*
@@ -4277,6 +6529,17 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
svc_fwd_cand.bytes = (u32)total;
/*
+ * m180 unification: record the destination landing
+ * even when the copy itself is deferred to the
+ * forwarded-request path (the software copy below
+ * never runs then, so the in-loop store would be
+ * skipped and the page would have no authority for
+ * the fill/D2H readers).
+ */
+ if (dst_orig >= SVC_VAS_FLOOR)
+ svc_ledger_store(dst_orig, dst_page, dst_kind);
+
+ /*
* P6b wave 2: with the hardware-primary path armed
* (parameter on and all three prerequisites on, so the
* request can queue as a guest copy and its releases
@@ -4410,64 +6673,267 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
chunk = min_t(size_t, chunk,
0x1000 - ((dst_orig + o) & 0xfff));
- if (src_shadow) {
+ if (snap) {
+ /*
+ * r314: consume the decode-time staging snapshot.
+ * The capture machinery existed since cb50282b but
+ * the execution never read it (lookup at svc_copy's
+ * top, free on success, NO consumer) - every copy
+ * read the live staging, which the driver had
+ * refilled once the push's fence fired (the
+ * deterministic 786335-bad vector_add output,
+ * identical across boots). Reading from the
+ * snapshot also needs no live source translation,
+ * so the copies execute on their first allowed
+ * pass instead of storming 60+ rc=-2 retries.
+ */
+ memcpy(bounce, snap + o, chunk);
+ } else if (src_shadow) {
if (svc_vas_read(src_orig + o, bounce, chunk))
- return -EIO;
+ SVC_COPY_TRUNC_RET(-EIO);
} else if (src_virt) {
u64 pa = 0;
u32 aper = 0;
+ u64 lpa = 0;
+ u32 lap = 0;
- if (svc_op_translate(vgpu, src_orig + o, &pa, &aper))
- return -ENOENT;
+ /*
+ * r375: the landing table is the read-after-write
+ * authority for device VAs (see the top-level src
+ * resolve). Per page, the table wins whenever it has
+ * an entry; the resolver below only answers a VA the
+ * table has never seen. r376: a fresh disagreeing pin
+ * is newer than the table and wins (repaired).
+ */
+ if (src_orig >= SVC_VAS_FLOOR &&
+ !svc_ledger_lookup(src_orig + o, &lpa, &lap)) {
+ u64 ipa = 0;
+ int iap = SVC_APERTURE_SYS;
+
+ if (!svc_ipin_lookup(src_orig + o, &ipa, &iap) &&
+ (((ipa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (lpa & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ (u32)iap != (u32)lap)) {
+ svc_ledger_pin_repairs++;
+ svc_ledger_store(src_orig + o,
+ ipa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (u32)iap);
+ lpa = ipa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ lap = (u32)iap;
+ }
+ pa = lpa;
+ aper = (lap == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ } else if (svc_op_translate(vgpu, src_orig + o, &pa, &aper)) {
+ /*
+ * m180 unification: a per-page resolution miss
+ * must not fail the whole copy when the unified
+ * table knows where this VA's content landed.
+ */
+ if (svc_ledger_lookup(src_orig + o, &lpa, &lap))
+ SVC_COPY_TRUNC_RET(-ENOENT);
+ pa = lpa;
+ aper = (lap == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ } else if (src_orig >= SVC_VAS_FLOOR &&
+ !svc_ledger_lookup(src_orig + o, &lpa, &lap) &&
+ ((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1)) ||
+ (lap == SVC_APERTURE_FB) !=
+ (aper == CE_APERTURE_VID))) {
+ /*
+ * m171 (AGENT-F R2): read-after-write - when
+ * a large H2D wrote this device VA page
+ * through a different translation, read the
+ * page the write landed on.
+ */
+ svc_ledger_diverges++;
+ if (svc_ledger_diverges <= 32)
+ pr_info("sema-ledger: DIVERGE read va=0x%llx wrote=0x%llx read=0x%llx\n",
+ src_orig + o, lpa, pa);
+ pa = lpa;
+ aper = (lap == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ }
if (aper == CE_APERTURE_VID) {
u8 *base = vgpu->bars[1].vaddr;
if (!base || pa + chunk > svc_fb_shadow_size(vgpu))
- return -EINVAL;
+ SVC_COPY_TRUNC_RET(-EINVAL);
memcpy(bounce, base + pa, chunk);
} else if (svc_read(vgpu, pa, bounce, chunk)) {
- return -EIO;
+ SVC_COPY_TRUNC_RET(-EIO);
}
} else if (src_aperture == CE_APERTURE_VID) {
u8 *base = vgpu->bars[1].vaddr;
size_t size = svc_fb_shadow_size(vgpu);
if (!base || src + o + chunk > size)
- return -EINVAL;
+ SVC_COPY_TRUNC_RET(-EINVAL);
memcpy(bounce, base + src + o, chunk);
} else {
if (svc_read(vgpu, src + o, bounce, chunk))
- return -EIO;
+ SVC_COPY_TRUNC_RET(-EIO);
+ }
+
+ /*
+ * r367 TABLE-COPY witness: a page-table page/entry copy is
+ * the guest's authoritative table content; dump the carried
+ * bytes so a table that later walks as ZERO can be traced to
+ * the copy that built it (or proven the source was already
+ * zero). Bounded; table-shaped sizes only.
+ */
+ if (!o && (total == 4096 || total == 16) &&
+ svc_tabcopy_log < 64) {
+ svc_tabcopy_log++;
+ pr_info("sema-tabcopy: dst_orig=0x%llx src_orig=0x%llx total=%zu dphys=%u dvirt=%d svirt=%d r_dst=0x%llx r_src=0x%llx first16=%16phN\n",
+ dst_orig, src_orig, total, dst_phys, dst_virt,
+ src_virt, dst, src, bounce);
}
if (dst_shadow) {
if (svc_vas_write(dst_orig + o, bounce, chunk))
- return -ENOMEM;
+ SVC_COPY_TRUNC_RET(-ENOMEM);
+ if (audit && (!o || o + chunk == total))
+ svc_copy_audit_chunk(vgpu, dst_orig, o, bounce,
+ chunk, true, 0, 0,
+ (o != 0));
} else if (dst_virt) {
u64 pa = 0;
u32 aper = 0;
+ u64 lpa = 0;
+ u32 lap = 0;
+ u32 wprov = 0;
- if (svc_op_translate(vgpu, dst_orig + o, &pa, &aper))
- return -ENOENT;
+ if (svc_op_translate(vgpu, dst_orig + o, &pa, &aper)) {
+ /*
+ * m180 unification: a per-page resolution miss
+ * lands on the known page instead of failing
+ * the whole copy (readers already use it).
+ */
+ if (svc_ledger_lookup(dst_orig + o, &lpa, &lap))
+ SVC_COPY_TRUNC_RET(-ENOENT);
+ pa = lpa;
+ aper = (lap == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ wprov = 0xF1;
+ } else if (dst_orig >= SVC_VAS_FLOOR &&
+ !svc_ledger_lookup(dst_orig + o, &lpa, &lap) &&
+ ((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1)) ||
+ (lap == SVC_APERTURE_FB) !=
+ (aper == CE_APERTURE_VID))) {
+ svc_ledger_diverges++;
+ if (svc.op_tr_src == 1) {
+ /*
+ * r336 PIN-WINS (the r335 vector_add
+ * guest-panic class): a pin-backed
+ * translation (guest pagemap truth,
+ * op_tr_src == 1) that disagrees
+ * with the ledger means the guest
+ * remapped or reused this VA. The
+ * ledger page is where an earlier
+ * write landed; the guest has since
+ * freed or moved it, so writing it
+ * can land on memory the guest
+ * kernel now owns (observed: RM
+ * object fields overwritten with
+ * client c-data, slab freelist
+ * corruption, guest panic). Trust
+ * the pin for the write and re-home
+ * the ledger (the store after the
+ * write records the fresh page);
+ * readers then converge on the same
+ * page too.
+ */
+ svc_ledger_pinwins++;
+ if (svc_ledger_diverges <= 512)
+ pr_info("sema-ledger: DIVERGE write va=0x%llx prev=0x%llx cur=0x%llx (pin wins)\n",
+ dst_orig + o, lpa, pa);
+ wprov = svc.op_tr_src;
+ } else {
+ /*
+ * m180 unification: keep ONE
+ * authoritative page per VA. A new
+ * write goes to the page the
+ * previous content landed on, and
+ * the fill/D2H readers (which
+ * prefer the table) stay consistent
+ * with it. Non-pin translations
+ * (2..5) can carry stale PTEs
+ * (m169), so the write history
+ * stays the authority there.
+ */
+ if (svc_ledger_diverges <= 512)
+ pr_info("sema-ledger: DIVERGE write va=0x%llx prev=0x%llx cur=0x%llx\n",
+ dst_orig + o, lpa, pa);
+ pa = lpa;
+ aper = (lap == SVC_APERTURE_FB) ?
+ CE_APERTURE_VID : CE_APERTURE_SYS;
+ wprov = 0xF0;
+ }
+ } else {
+ wprov = svc.op_tr_src;
+ }
+ /*
+ * D2H-MAP (diagnostic): per-page destination
+ * resolution of large host-dest copies - the direct
+ * witness for which guest pages a readback lands on.
+ * Bounded; one line per 4 KB page of copies >= 1 MB.
+ */
+ if (total >= (1u << 20) && !(o & (SEMA_PAGE_SIZE - 1)) &&
+ svc_d2hmap_log < 4096u) {
+ svc_d2hmap_log++;
+ pr_info("sema-d2hmap: va=0x%llx o=%zu pa=0x%llx ap=%u prov=%u\n",
+ dst_orig + o, o, pa, aper, wprov);
+ }
if (aper == CE_APERTURE_VID) {
u8 *base = vgpu->bars[1].vaddr;
if (!base || pa + chunk > svc_fb_shadow_size(vgpu))
- return -EINVAL;
+ SVC_COPY_TRUNC_RET(-EINVAL);
memcpy(base + pa, bounce, chunk);
} else if (svc_write(vgpu, pa, bounce, chunk)) {
- return -EIO;
+ SVC_COPY_TRUNC_RET(-EIO);
}
+ if (audit && (!o || o + chunk == total))
+ svc_copy_audit_chunk(vgpu, dst_orig, o, bounce,
+ chunk, false, pa, aper,
+ (o != 0));
+ /*
+ * m171/m180: a large device-dest write records its
+ * landing page - SYS and VID alike, because the
+ * sysmem-backed UVM buffers are the dominant
+ * device-visible case and were the half the table
+ * never saw. The later kernel fill / D2H read of the
+ * same VA then uses exactly this page even if its own
+ * resolution is stale.
+ */
+ if (dst_orig >= SVC_VAS_FLOOR && total >= (64u << 10))
+ svc_ledger_store(dst_orig + o, pa,
+ (aper == CE_APERTURE_VID) ?
+ SVC_APERTURE_FB :
+ SVC_APERTURE_SYS);
} else if (dst_aperture == CE_APERTURE_VID) {
u8 *base = vgpu->bars[1].vaddr;
size_t size = svc_fb_shadow_size(vgpu);
if (!base || dst + o + chunk > size)
- return -EINVAL;
+ SVC_COPY_TRUNC_RET(-EINVAL);
memcpy(base + dst + o, bounce, chunk);
+ if (audit && (!o || o + chunk == total))
+ svc_copy_audit_chunk(vgpu, dst_orig, o, bounce,
+ chunk, false, dst + o,
+ CE_APERTURE_VID,
+ (o != 0));
} else {
if (svc_write(vgpu, dst + o, bounce, chunk))
- return -EIO;
+ SVC_COPY_TRUNC_RET(-EIO);
+ if (audit && (!o || o + chunk == total))
+ svc_copy_audit_chunk(vgpu, dst_orig, o, bounce,
+ chunk, false, dst + o,
+ CE_APERTURE_SYS,
+ (o != 0));
}
(void)i;
o += chunk;
@@ -4493,6 +6959,23 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
}
pr_info("sema-bigcopy: dst16_after=%16phN (dst_va=0x%llx pa=0x%llx)\n",
da, dst_orig, dst);
+ /*
+ * r234 A-TRACE pt=P2/P4R: the destination content right after
+ * the copy - for H2D this is the landed upload (P2), for D2H
+ * the host-side readback result (P4R).
+ */
+ if (trace64 && svc_atrace_log < 128u) {
+ u32 i0, i1, i2, i3;
+
+ svc_atrace_log++;
+ memcpy(&i0, da, 4);
+ memcpy(&i1, da + 4, 4);
+ memcpy(&i2, da + 8, 4);
+ memcpy(&i3, da + 12, 4);
+ pr_info("vgsp: A-TRACE pt=%s dir=%s dst_va=0x%llx dst_pa=0x%llx dst_int4_after=%d %d %d %d\n",
+ !strcmp(dir, "H2D") ? "P2" : "P4R", dir,
+ dst_orig, dst, i0, i1, i2, i3);
+ }
}
if (gsp_sema_dbg && !src_shadow && !dst_shadow &&
@@ -4529,6 +7012,33 @@ static int svc_copy(struct nv_vgpu_device *vgpu, u64 dst, u64 src, size_t total,
pr_info("sema-copy-dst: after dst=0x%llx dst16=%*phN\n",
dst, n, da);
}
+ if (audit && svc_copy_audit_n < SVC_COPY_AUDIT_MAX) {
+ svc_copy_audit_n++;
+ pr_info("vgsp: COPYAUDIT path=ce-soft-copy va=0x%llx len=0x%llx done=0x%llx\n",
+ dst_orig, (u64)total, (u64)o);
+ }
+ /*
+ * m180 parity with svc_fill: a large virtual-destination copy is a
+ * write/read pair with the launch rec-build readers - record its
+ * landing pages so the GR fill follows the page the copy actually
+ * wrote. Without this, a rec table built before the pins landed
+ * kept the stale pre-pin pages while the copy landed on the pinned
+ * page (streams2 r254: the b1 rec range carried the a2 pages and
+ * the vadd read a2's pattern as its second input).
+ */
+ if (!dst_phys && dst_virt && dst_orig >= SVC_VAS_FLOOR &&
+ total >= (64u << 10)) {
+ u32 cap = (dst_aperture == CE_APERTURE_VID) ?
+ SVC_APERTURE_FB : SVC_APERTURE_SYS;
+ size_t po;
+
+ for (po = 0; po < total; po += SEMA_PAGE_SIZE)
+ svc_ledger_store(dst_orig + po, dst + po, cap);
+ }
+ if (snap)
+ svc_copy_snap_free(dst_orig, src_orig, total);
+ if (done_out)
+ *done_out = (u64)o; /* o == total on the success path */
return 0;
}
@@ -4559,6 +7069,23 @@ static int svc_grce_inline_write(struct nv_vgpu_device *vgpu, u64 dst_va,
svc.memsets_done++;
return 0;
}
+ /*
+ * m180 unification: a page with a known landing (previous H2D/fill or
+ * engine write-back) is authoritative - write the inline data there
+ * so the engine fill / D2H readers that follow the table see it.
+ */
+ {
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ if (!svc_ledger_lookup(dst_va, &lpa, &lap)) {
+ pa = lpa & ~0xfffULL;
+ ap = (lap == SVC_APERTURE_FB) ?
+ SVC_APERTURE_FB : SVC_APERTURE_SYS;
+ svc_pin_store(dst_va & ~0xfffULL, pa, ap);
+ goto write_landing;
+ }
+ }
if (svc_lookup_trusted(vgpu, dst_va & ~0xfffULL, &pa, &ap)) {
if (!svc_vas_write(dst_va, data, bytes)) {
svc.vas_shadowed++;
@@ -4578,6 +7105,7 @@ static int svc_grce_inline_write(struct nv_vgpu_device *vgpu, u64 dst_va,
return -ENOENT;
}
svc_pin_store(dst_va & ~0xfffULL, pa, ap);
+write_landing:
pa += dst_va & 0xfff;
if (gsp_sema_dbg &&
svc.grce_log < (gsp_sema_ops_log > 64 ? gsp_sema_ops_log : 64)) {
@@ -4600,17 +7128,115 @@ static int svc_grce_inline_write(struct nv_vgpu_device *vgpu, u64 dst_va,
for (o = 0; o < bytes; o += 4096) {
u32 chunk = bytes - o > 4096 ? 4096 : bytes - o;
- if (svc_write(vgpu, pa + o, (void *)(data + o), chunk))
+ if (svc_write(vgpu, pa + o, (void *)(data + o), chunk)) {
+ if (bytes >= SVC_COPY_AUDIT_MIN)
+ pr_err_ratelimited("vgsp: COPY-TRUNC va=0x%llx want=0x%llx done=0x%llx path=grce-inline rc=%d\n",
+ dst_va, (u64)bytes,
+ (u64)o, -EIO);
return -EIO;
+ }
}
}
+ /*
+ * COPYAUDIT (see the svc_copy comment): log the big inline write and
+ * read the first 16 destination bytes back through the aperture it
+ * was written to, comparing against the source bytes.
+ */
+ if (bytes >= SVC_COPY_AUDIT_MIN && svc_copy_audit_n < SVC_COPY_AUDIT_MAX) {
+ u8 rb[16] = {0};
+ bool ok = false;
+
+ if (ap == SVC_APERTURE_FB) {
+ u8 *base = vgpu->bars[1].vaddr;
+
+ if (base && pa + sizeof(rb) <= svc_fb_shadow_size(vgpu)) {
+ memcpy(rb, base + pa, sizeof(rb));
+ ok = true;
+ }
+ } else if (!svc_read(vgpu, pa, rb, sizeof(rb))) {
+ ok = true;
+ }
+ svc_copy_audit_n++;
+ pr_info("vgsp: COPYAUDIT path=grce-inline va=0x%llx len=0x%llx done=0x%llx\n",
+ dst_va, (u64)bytes, (u64)bytes);
+ if (ok && memcmp(rb, data, sizeof(rb)))
+ pr_info_ratelimited("vgsp: COPY-MISMATCH va=0x%llx off=0x0 path=grce-inline want=%*phN got=%*phN\n",
+ dst_va, 16, data, 16, rb);
+ }
+ /*
+ * m180: record the landing so the fill/D2H readers of this VA follow
+ * the bytes the engine's inline path wrote, not their own resolution.
+ */
+ if (dst_va >= SVC_VAS_FLOOR)
+ svc_ledger_store(dst_va, pa, (u32)ap);
svc.memsets_done++;
return 0;
}
+/* defined below (svc_handle_map_add); the CE decode path drains the queued
+ * scanner injections before resolving copy operands, exactly like the GR
+ * path's m143 drain. */
+static void svc_handle_map_add(void);
+
/* Execute a decoded operation list (fills and copies). `ops_done` (may be
* NULL) is the per-entry bitmap of ops that already ran for the entry's
* identity; `ops_base` is the sequence number of ops[0] within the entry. */
+
+/*
+ * r34x: evaluate a C56F SEM_EXECUTE acquire against the semaphore word.
+ * The class defines the opcodes (clc56f.h NVC56F_SEM_EXECUTE_OPERATION:
+ * 0 ACQUIRE, 1 RELEASE, 2 ACQ_STRICT_GEQ, 3 ACQ_CIRC_GEQ, 4 ACQ_AND,
+ * 5 ACQ_NOR, 6 REDUCTION) and the CUDA driver documents the wait
+ * conditions each op must implement (cuda_device.h CUIsemaphoreAquire-
+ * Flags_enum: GEQ default = circular >=, EQUAL = ==, AND = (v & p) != 0,
+ * NOR = ~(v | p) != 0, STRICT_GEQ = >= with no wrap; ampere_semaphore.c
+ * maps those flags onto the opcodes: EQUAL->0, STRICT_GEQ->2, GEQ->3,
+ * AND->4, NOR->5). Note op 0 is EQUALITY, not a >=: only op 2/3 are
+ * ordering compares. 32-bit payloads: every observed CUDA acquire is
+ * PAYLOAD_SIZE 32BIT (SEM_PAYLOAD_HI == 0); a 64-bit acquire is captured
+ * and judged on the low word only (its high word is not modelled).
+ */
+static bool svc_wait_satisfied(u32 cur, u8 op, u32 need)
+{
+ switch (op) {
+ case 0: /* ACQUIRE: wait for v == p */
+ return cur == need;
+ case 2: /* ACQ_STRICT_GEQ: v >= p, no wrap */
+ return cur >= need;
+ case 3: /* ACQ_CIRC_GEQ: v in [p, p + 2^31 - 1] (wrap allowed) */
+ return (s32)(cur - need) >= 0;
+ case 4: /* ACQ_AND: (v & p) != 0 */
+ return (cur & need) != 0;
+ case 5: /* ACQ_NOR: ~(v | p) != 0 */
+ return ~(cur | need) != 0;
+ default:
+ return true;
+ }
+}
+
+/* r34x: the per-channel first-hold clock for the bounded wait. */
+static struct svc_wait_hold *svc_wait_hold_get(u32 h, bool create)
+{
+ struct svc_wait_hold *free_slot = NULL;
+ int i;
+
+ for (i = 0; i < SVC_WAIT_HOLD_MAX; i++) {
+ struct svc_wait_hold *e = &svc_wait_holds[i];
+
+ if (e->active && e->h == h)
+ return e;
+ if (!e->active && !free_slot)
+ free_slot = e;
+ }
+ if (!create || !free_slot)
+ return NULL;
+ memset(free_slot, 0, sizeof(*free_slot));
+ free_slot->active = true;
+ free_slot->h = h;
+ free_slot->since = jiffies;
+ return free_slot;
+}
+
static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
u32 n_ops, u32 ch_h, u64 *ops_done, u32 ops_base)
{
@@ -4618,6 +7244,139 @@ static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
static u64 bigop_log;
svc.cur_ch = ch_h;
+ /*
+ * m163: apply every queued scanner injection before resolving this
+ * pass's copy operands. Without this, a CE copy decoded in the window
+ * between the client's push and its pins being processed resolves
+ * through the previous client's stale translation (or fails), while
+ * its release is still applied - the client's cudaMemcpy "completes"
+ * with no data and the next GR launch computes from the stale vidmem
+ * page (streams2 r163: a1 held the previous client's h_a; the vadd
+ * output was a_stale + b_fresh).
+ */
+ svc_handle_map_add();
+ /*
+ * r336/r34x: honor every host-semaphore acquire (C56F SEM_EXECUTE
+ * op 0/2/3/4/5) captured for THIS ring entry before any of its work
+ * runs, so a copy cannot execute ahead of the dependency its push
+ * waits on (the streams2 readback-before-scale class). Decision
+ * ladder per wait:
+ * 1. saved value satisfies the op's condition -> pass;
+ * 2. the entry's own release, when its method offset precedes the
+ * acquire's (release-then-acquire, channel.c crossEngineSync),
+ * counts as the candidate value - the release method has
+ * already executed by the time the acquire is evaluated;
+ * an acquire that precedes its own trailing tracking release
+ * does NOT count it (nothing has installed that value yet);
+ * 3. drain the pending-translation release queue (releases OUR
+ * machinery is merely late about) and re-read once;
+ * 4. still unsatisfied -> hold the entry (release withheld through
+ * the op-pending machinery) for at most gsp_sema_wait_hold_ms
+ * per episode, then fail open loudly ('give-up': the value
+ * cannot be installed by firmware alone - it sits behind an
+ * upstream opdef/operand hold - so the old pass-through
+ * behaviour is safer than parking the channel forever).
+ * Unreadable semaphore: fail-open (the pre-r336 behaviour).
+ * The pass/hold/give-up decisions are logged as 'sema-wait' rows
+ * (op, payload, observed value, decision).
+ */
+ if (svc_wait_n && gsp_sema_wait_gate) {
+ struct svc_wait_hold *gh;
+ bool hold = false;
+ u32 wi;
+
+ /*
+ * Kill switch: a wait that never satisfies (a counter our
+ * emulation does not advance the way the driver expects)
+ * would otherwise park entries until the sweep expires each
+ * one. After 2048 holds in a boot the gate fails open
+ * permanently (the pre-r336 behaviour) and says so once.
+ */
+ if (svc_wait_held > 2048) {
+ if (svc_wait_log < 4096) {
+ svc_wait_log++;
+ pr_info("sema-wait: gate disabled after %llu holds (fail-open)\n",
+ svc_wait_held);
+ }
+ svc_wait_pass++;
+ goto svc_wait_done;
+ }
+ gh = svc_wait_hold_get(ch_h, true);
+ for (wi = 0; wi < svc_wait_n; wi++) {
+ const struct svc_wait_op *w = &svc_waits[wi];
+ u32 cur = 0, waited_ms = 0;
+ bool ok = true, same_rel = false, pend = false;
+
+ if (gh)
+ waited_ms = jiffies_to_msecs(jiffies - gh->since);
+ if (svc_sem_read(vgpu, w->va, &cur))
+ continue; /* unreadable: fail-open */
+ ok = svc_wait_satisfied(cur, w->op, w->payload);
+ if (!ok && svc_wait_rel_have &&
+ svc_wait_rel_va == w->va &&
+ svc_wait_rel_off < w->off) {
+ same_rel = true;
+ ok = svc_wait_satisfied(svc_wait_rel_payload,
+ w->op, w->payload);
+ }
+ if (!ok) {
+ svc_apply_pending(vgpu);
+ if (!svc_sem_read(vgpu, w->va, &cur))
+ ok = svc_wait_satisfied(cur, w->op,
+ w->payload);
+ }
+ if (ok) {
+ if (svc_wait_pass_log < 256) {
+ svc_wait_pass_log++;
+ pr_info("sema-wait: pass ch=0x%x va=0x%llx op=%u pay=%u cur=%u same_rel=%u waited_ms=%u -> ops run this pass\n",
+ ch_h, w->va, w->op, w->payload,
+ cur, same_rel, waited_ms);
+ }
+ continue;
+ }
+ pend = svc_wait_pend_release(w->va);
+ if (waited_ms >= gsp_sema_wait_hold_ms) {
+ /*
+ * Bounded episode expired: fail open for the
+ * WHOLE entry (one row, then the ops run). A
+ * per-wait continue here would ping-pong
+ * with a second unsatisfied wait: the first
+ * gave up and reset the shared clock before
+ * the second was judged, and the pair would
+ * hold forever 100 ms at a time.
+ */
+ svc_wait_giveup++;
+ if (svc_wait_log < 2048) {
+ svc_wait_log++;
+ pr_info("sema-wait: give-up ch=0x%x va=0x%llx op=%u pay=%u cur=%u pend=%u waited_ms=%u -> fail-open (cannot be satisfied by firmware alone; ops run)\n",
+ ch_h, w->va, w->op, w->payload,
+ cur, pend, waited_ms);
+ }
+ if (gh)
+ gh->active = false;
+ hold = false;
+ svc_wait_pass++;
+ goto svc_wait_done;
+ }
+ hold = true;
+ if (svc_wait_log < 512) {
+ svc_wait_log++;
+ pr_info("sema-wait: hold ch=0x%x va=0x%llx op=%u pay=%u cur=%u same_rel=%u pend=%u waited_ms=%u -> entry held (acquire not satisfied)\n",
+ ch_h, w->va, w->op, w->payload, cur,
+ same_rel, pend, waited_ms);
+ }
+ break; /* one unsatisfied wait holds the entry */
+ }
+ if (hold) {
+ svc_wait_held++;
+ svc.op_tr_pending = 1;
+ return;
+ }
+ svc_wait_pass++;
+ if (gh)
+ gh->active = false;
+ }
+svc_wait_done:
for (i = 0; i < n_ops; i++) {
struct svc_memset_op *m = &ops[i];
u32 seq = ops_base + i;
@@ -4635,13 +7394,51 @@ static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
continue;
}
- if (m->type == 1)
- rc = svc_copy(vgpu, m->dst, m->src, m->bytes,
- m->phys, m->aperture,
- m->src_phys, m->src_aperture);
- else
- rc = svc_fill(vgpu, m->dst, m->value, m->elem,
- m->count, m->phys, m->aperture);
+ {
+ u64 ce_len = (m->type == 1) ? m->bytes :
+ (u64)m->elem * m->count;
+ u64 ce_done = 0;
+
+ if (m->type == 1)
+ rc = svc_copy(vgpu, m->dst, m->src, m->bytes,
+ m->phys, m->aperture,
+ m->src_phys, m->src_aperture,
+ &ce_done, m->rows, m->dpitch,
+ m->spitch);
+ else
+ rc = svc_fill(vgpu, m->dst, m->value, m->elem,
+ m->count, m->phys, m->aperture,
+ &ce_done, m->rows, m->dpitch);
+ /*
+ * CEAUDIT (2026-10-18 r220): per-op audit of the CE
+ * data path - the software executor of every guest
+ * cudaMemcpy - at/above SVC_COPY_AUDIT_MIN, including
+ * the rc != 0 attempts the copy-level COPYAUDIT cannot
+ * see (the pre-loop resolution misses return before
+ * any byte moves: done=0x0). src/dst are the guest
+ * VAs of the op; done is the byte count that landed.
+ * Bounded per boot.
+ */
+ if (ce_len >= SVC_COPY_AUDIT_MIN &&
+ svc_ceaudit_n < SVC_CEAUDIT_MAX) {
+ svc_ceaudit_n++;
+ /*
+ * A failing op is retried by the hold and would
+ * spam: throttle the rc != 0 repeats; successful
+ * big ops log unconditionally (budget-bounded).
+ */
+ if (rc)
+ pr_info_ratelimited("vgsp: CEAUDIT op=%s src=0x%llx dst=0x%llx len=0x%llx done=0x%llx rc=%d\n",
+ m->type == 1 ? "copy" : "fill",
+ m->type == 1 ? m->src : 0ull,
+ m->dst, ce_len, ce_done, rc);
+ else
+ pr_info("vgsp: CEAUDIT op=%s src=0x%llx dst=0x%llx len=0x%llx done=0x%llx rc=%d\n",
+ m->type == 1 ? "copy" : "fill",
+ m->type == 1 ? m->src : 0ull,
+ m->dst, ce_len, ce_done, rc);
+ }
+ }
if ((rc == 0 || rc == SVC_COPY_FORWARDED) &&
ops_done && seq < 64)
*ops_done |= 1ULL << seq;
@@ -4689,9 +7486,19 @@ static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
* (observed r72 c2) must not withhold a release that gates a GR
* launch with a 5 s poll deadline. On expiry the release is
* flushed and the op stays skipped (the pre-r62 fallback).
+ * m163 extends the hold to copy ops whose destination is a
+ * device buffer: a lost small H2D that silently "completes"
+ * corrupts the next launch's input (streams2 r163: the 128 KB
+ * a1 copy's release was applied with no copy op ever run; the
+ * vadd computed a_stale + b and the client read the wrong c).
+ * A small failing device copy now costs a bounded hold and the
+ * retried decode runs it once the pins land - never silent
+ * corruption. Small failing memsets keep the old trade.
*/
if (rc != 0 && rc != SVC_COPY_FORWARDED &&
- m->bytes >= (256u << 10))
+ (m->bytes >= (256u << 10) ||
+ (m->type == 1 && m->dst >= SVC_VAS_FLOOR) ||
+ (m->type == 0 && !m->phys && m->dst >= SVC_VAS_FLOOR)))
svc.op_tr_pending = 1;
if (gsp_sema_dbg && m->bytes == 256 && svc.sem256_log < 64) {
svc.sem256_log++;
@@ -4702,7 +7509,7 @@ static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
}
if (gsp_sema_dbg &&
((rc == 0 && svc.memsets_done <= gsp_sema_ops_log) ||
- (rc != 0 && svc.memsets_fail + svc.memsets_nomap <= 64)))
+ (rc != 0 && svc.memsets_fail + svc.memsets_nomap <= 2048)))
pr_info("sema-svc: %s dst=0x%llx value=0x%llx elem=%u count=%u bytes=%llu phys=%u ap=%u src=0x%llx srcphys=%u srcap=%u rc=%d h=0x%x\n",
m->type == 1 ? "memcopy" : "memset",
m->dst, m->value, m->elem, m->count,
@@ -4720,7 +7527,7 @@ static void svc_run_ops(struct nv_vgpu_device *vgpu, struct svc_memset_op *ops,
* read-back signature: always keep room for it past the
* general trace budget so the DtoH's src/dst/rc is on record.
*/
- if ((svc.op_trace_log < 160 &&
+ if ((svc.op_trace_log < (gsp_sema_dbg ? 32768 : 2048) &&
(time_before(jiffies, svc.op_trace_until) ||
m->dst >= SVC_VAS_FLOOR ||
(m->type == 1 && !m->src_phys && m->src >= SVC_VAS_FLOOR))) ||
@@ -4949,6 +7756,8 @@ static void svc_counter_release_apply(struct nv_vgpu_device *vgpu, u32 h,
bool skip;
int i;
u32 dbg = 0;
+ u64 rel_pa = 0;
+ u64 cnt_pa = 0;
if (!h || !payload) {
/*
@@ -4976,7 +7785,8 @@ static void svc_counter_release_apply(struct nv_vgpu_device *vgpu, u32 h,
spin_lock_irqsave(&svc.chan_lock, flags);
for (i = 0; i < SVC_MAX_CHANNELS; i++) {
- if (svc.chan[i].valid && svc.chan[i].h_object == h) {
+ if (svc.chan[i].valid && svc.chan[i].vgpu == vgpu &&
+ svc.chan[i].h_object == h) {
/*
* The release targets the slot the push itself named
* (release_pa learned from the release VA); the
@@ -4985,12 +7795,23 @@ static void svc_counter_release_apply(struct nv_vgpu_device *vgpu, u32 h,
* FORENSICS-grv7-counter F1). Fall back to counter_pa
* when the release slot could not be resolved.
*/
- pa = svc.chan[i].release_pa ?
- svc.chan[i].release_pa : svc.chan[i].counter_pa;
+ rel_pa = svc.chan[i].release_pa;
+ cnt_pa = svc.chan[i].counter_pa;
+ pa = rel_pa ? rel_pa : cnt_pa;
break;
}
}
spin_unlock_irqrestore(&svc.chan_lock, flags);
+ /*
+ * C1 (GAP-1 H3/H4): was the apply entered, and with which slot?
+ * Budget 512/session (reset in svc_session_flush); the per-branch
+ * prints below stay on their own counter_rel_dbg budget.
+ */
+ if (gsp_sema_dbg && svc.crel_entry_dbg < 512) {
+ svc.crel_entry_dbg++;
+ pr_info("sema-diag: crel-entry h=0x%x va=0x%llx pay=%u inc=%d rel_pa=0x%llx cnt_pa=0x%llx\n",
+ h, sem_va, payload, inc ? 1 : 0, rel_pa, cnt_pa);
+ }
if (!pa) {
/*
* Pool (UVM tracking) semaphores have no counter slot. The
@@ -5027,7 +7848,7 @@ static void svc_counter_release_apply(struct nv_vgpu_device *vgpu, u32 h,
wrc = svc_sem_write(vgpu, h, sem_va, payload);
if (!wrc) {
svc.releases_applied++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
if (gsp_sema_dbg && svc.counter_rel_dbg < 256) {
svc.counter_rel_dbg++;
pr_info("sema-chan: rel-apply h=0x%x va=0x%llx pay=%u cur=%u -> pool rescue\n",
@@ -5048,6 +7869,7 @@ static void svc_counter_release_apply(struct nv_vgpu_device *vgpu, u32 h,
spin_lock_irqsave(&svc.chan_lock, flags);
for (i = 0; i < SVC_MAX_CHANNELS; i++) {
if (svc.chan[i].valid &&
+ svc.chan[i].vgpu == vgpu &&
svc.chan[i].h_object == h) {
if (payload >
svc.chan[i].pending_rel_pay) {
@@ -5100,6 +7922,15 @@ static void svc_counter_release_apply(struct nv_vgpu_device *vgpu, u32 h,
return;
}
nv = inc ? cur + payload : payload;
+ /*
+ * r378: the slot PA was learned from the release VA (or registered
+ * from the client's pagemap) earlier; re-validate it against the
+ * current pin before the 4-byte release lands, so a slot the guest
+ * has since freed/re-homed is never written through the stale page.
+ */
+ if (sem_va && !gsp_sema_write_guard(sem_va, pa, SVC_APERTURE_SYS,
+ "counter-rel"))
+ return;
if (svc_write_ap(vgpu, pa, SVC_APERTURE_SYS, &nv, sizeof(nv))) {
if (gsp_sema_dbg && svc.counter_rel_dbg < 256) {
svc.counter_rel_dbg++;
@@ -5111,7 +7942,7 @@ static void svc_counter_release_apply(struct nv_vgpu_device *vgpu, u32 h,
svc.releases_applied++;
svc.counter_rel_applied++;
svc.gr_rel_applied++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
if (gsp_sema_dbg && svc.counter_rel_dbg < 256) {
svc.counter_rel_dbg++;
pr_info("sema-chan: rel-apply h=0x%x pa=0x%llx pay=%u cur=%u -> written\n",
@@ -5127,7 +7958,7 @@ static void svc_counter_release_apply(struct nv_vgpu_device *vgpu, u32 h,
* harmless for an absolute release (its guard drops it), but an increment
* would advance the slot twice.
*/
-static void svc_counter_pending_clear(u32 h)
+static void svc_counter_pending_clear(struct nv_vgpu_device *vgpu, u32 h)
{
unsigned long flags;
int i;
@@ -5136,7 +7967,8 @@ static void svc_counter_pending_clear(u32 h)
return;
spin_lock_irqsave(&svc.chan_lock, flags);
for (i = 0; i < SVC_MAX_CHANNELS; i++) {
- if (svc.chan[i].valid && svc.chan[i].h_object == h) {
+ if (svc.chan[i].valid && svc.chan[i].vgpu == vgpu &&
+ svc.chan[i].h_object == h) {
svc.chan[i].pending_rel_pay = 0;
svc.chan[i].pending_rel_inc = 0;
break;
@@ -5170,13 +8002,13 @@ static void svc_counter_flush_pending(struct nv_vgpu_device *vgpu,
if (payload > 0x100000) {
ch->pending_rel_pay = 0;
ch->pending_rel_inc = 0;
- svc_counter_pending_clear(ch->h_object);
+ svc_counter_pending_clear(ch->vgpu, ch->h_object);
return;
}
} else if (payload <= cur || payload - cur > 0x100000) {
ch->pending_rel_pay = 0;
ch->pending_rel_inc = 0;
- svc_counter_pending_clear(ch->h_object);
+ svc_counter_pending_clear(ch->vgpu, ch->h_object);
if (cur > ch->counter_last)
ch->counter_last = cur;
return;
@@ -5187,13 +8019,13 @@ static void svc_counter_flush_pending(struct nv_vgpu_device *vgpu,
return;
ch->pending_rel_pay = 0;
ch->pending_rel_inc = 0;
- svc_counter_pending_clear(ch->h_object);
+ svc_counter_pending_clear(ch->vgpu, ch->h_object);
if (nv > ch->counter_last)
ch->counter_last = nv;
svc.releases_applied++;
svc.counter_rel_applied++;
svc.gr_rel_applied++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
if (gsp_sema_dbg && svc.counter_rel_dbg < 256) {
svc.counter_rel_dbg++;
pr_info("sema-chan: rel-flush h=0x%x pa=0x%llx pay=%u cur=%u -> written\n",
@@ -5235,13 +8067,13 @@ static struct {
u64 pa_page;
} svc_cnt_der[SVC_CNT_DER_MAX];
static u32 svc_cnt_der_n;
+static u32 svc_cnt_der_log;
static int svc_counter_derive(struct nv_vgpu_device *vgpu, u64 va, u64 *pa_out)
{
u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
u64 off = va & (SEMA_PAGE_SIZE - 1);
u64 pa = 0;
- int ap = SVC_APERTURE_SYS;
u32 probe = 0;
u32 i;
bool fresh = true;
@@ -5254,13 +8086,57 @@ static int svc_counter_derive(struct nv_vgpu_device *vgpu, u64 va, u64 *pa_out)
}
}
if (fresh) {
- if (svc_walk_vaspaces(vgpu, page, &pa, &ap) &&
- svc_walk_discovered(vgpu, page, &pa, &ap) &&
- svc_targeted_resolve(vgpu, page, &pa))
- return -ENOENT;
- if (ap != SVC_APERTURE_SYS)
+ u64 p = 0;
+ int a = SVC_APERTURE_SYS;
+ bool ok = false;
+ const char *src = "none";
+
+ /*
+ * m177b: first READABLE source wins. The previous chain kept
+ * the first source that merely PRODUCED a translation and then
+ * failed -EIO on an unreadable page without trying the rest:
+ * the published directory can yield a stale/incorrect chain
+ * for a counter VA while the rm_mem-content scan would resolve
+ * it (r194: the counter page 0x20400f000 never resolved; its
+ * walk hit probed unreadable and targeted was never reached).
+ */
+ if (!svc_walk_vaspaces(vgpu, page, &p, &a) &&
+ a == SVC_APERTURE_SYS &&
+ !svc_read_ap(vgpu,
+ (p & ~(u64)(SEMA_PAGE_SIZE - 1)) | off,
+ SVC_APERTURE_SYS, &probe, sizeof(probe))) {
+ ok = true;
+ src = "vaspaces";
+ }
+ if (!ok && !svc_walk_discovered(vgpu, page, &p, &a) &&
+ a == SVC_APERTURE_SYS &&
+ !svc_read_ap(vgpu,
+ (p & ~(u64)(SEMA_PAGE_SIZE - 1)) | off,
+ SVC_APERTURE_SYS, &probe, sizeof(probe))) {
+ ok = true;
+ src = "discovered";
+ }
+ if (!ok && !svc_targeted_resolve(vgpu, page, &p) &&
+ !svc_read_ap(vgpu,
+ (p & ~(u64)(SEMA_PAGE_SIZE - 1)) | off,
+ SVC_APERTURE_SYS, &probe, sizeof(probe))) {
+ ok = true;
+ src = "targeted";
+ }
+ if (!ok) {
+ if (svc_cnt_der_log < 16) {
+ svc_cnt_der_log++;
+ pr_info("sema-chan: counter page 0x%llx unreadable via vaspaces/discovered/targeted; slot stays pending\n",
+ page);
+ }
return -ENOENT;
- pa = (pa & ~(u64)(SEMA_PAGE_SIZE - 1)) | off;
+ }
+ pa = (p & ~(u64)(SEMA_PAGE_SIZE - 1)) | off;
+ if (svc_cnt_der_log < 16) {
+ svc_cnt_der_log++;
+ pr_info("sema-chan: counter page 0x%llx resolved via %s (pa=0x%llx)\n",
+ page, src, pa);
+ }
}
/* Read the slot before trusting the mapping: an unreadable page is
* not registered, so the caller keeps the "slot pending" fallback. */
@@ -5278,14 +8154,22 @@ static int svc_counter_derive(struct nv_vgpu_device *vgpu, u64 va, u64 *pa_out)
/*
* Auto-register a GR channel's completion-counter slot from the observed
- * layout, without waiting for a readable push: the counter records form a
- * 16-byte-strided array in the client's counter page, allocated in channel
- * order from the low end (SA-COUNTER FORENSICS-grv7-counter: the ascending
- * slot fec0 + r*0x10 is the polled completion word; the push's REPORT_SEM
- * release writes a DIFFERENT (descending) family and is tracked separately
- * in release_pa). The slot PA comes from a host-verified pin or from the
- * in-kernel structural derivation (svc_counter_derive), so the write target
- * is a page the client actually polls.
+ * layout, without waiting for a readable push: the client zips its GR
+ * channels in ascending handle order onto DESCENDING 16-byte slots, so the
+ * rank-r channel's own completion word - the slot its pushes' REPORT_SEM
+ * release names, verified for all 20 ranks - is
+ * 0x20400fec0 + (SVC_GR_COUNTER_TOTAL-1-r)*0x10. The ascending formula
+ * (F1/eea5ea93) registered the completion word of the PAIRED rank (19-r)
+ * instead, so svc_counter_publish wrote each channel's ring PUT into
+ * another channel's waited word: the busy ctx-init channel rank 16
+ * (h=0x5c000059, PUT~94) overwrote rank 3's completion word 0x20400ffc0
+ * from 1 to 94 (witness `pub-adv h=0x5c000059 pa=...fc0 val=94 cur=1`),
+ * after which the rank-3 stream's own release (payload 1) was
+ * forward-only-skipped and its sync never completed (evtest3
+ * cudaStreamSynchronize(st[2]); victory_streams_events). The slot PA comes
+ * from a host-verified pin or from the in-kernel structural derivation
+ * (svc_counter_derive), so the write target is a page the client actually
+ * polls.
*/
static void svc_counter_autoreg(struct nv_vgpu_device *vgpu,
struct svc_channel *ch, int n_gr)
@@ -5298,7 +8182,8 @@ static void svc_counter_autoreg(struct nv_vgpu_device *vgpu,
return;
if (ch->gr_index >= n_gr)
return;
- va = 0x20400fec0ULL + (u64)ch->gr_index * 0x10ULL;
+ va = 0x20400fec0ULL +
+ (u64)(SVC_GR_COUNTER_TOTAL - 1 - ch->gr_index) * 0x10ULL;
if (ch->counter_pa) {
/*
* FORENSICS-v2 anomaly 6: a slot derived in an earlier pass
@@ -5326,6 +8211,7 @@ static void svc_counter_autoreg(struct nv_vgpu_device *vgpu,
ch->counter_pa = ipa & ~0x3ULL;
for (j = 0; j < SVC_MAX_CHANNELS; j++) {
if (svc.chan[j].valid &&
+ svc.chan[j].vgpu == vgpu &&
svc.chan[j].h_object == ch->h_object) {
svc.chan[j].counter_pa =
ch->counter_pa;
@@ -5374,6 +8260,7 @@ static void svc_counter_autoreg(struct nv_vgpu_device *vgpu,
spin_lock_irqsave(&svc.chan_lock, flags);
for (j = 0; j < SVC_MAX_CHANNELS; j++) {
if (svc.chan[j].valid &&
+ svc.chan[j].vgpu == vgpu &&
svc.chan[j].h_object != ch->h_object &&
svc.chan[j].counter_pa == pa) {
collision = true;
@@ -5399,6 +8286,7 @@ static void svc_counter_autoreg(struct nv_vgpu_device *vgpu,
ch->counter_pa = pa;
for (j = 0; j < SVC_MAX_CHANNELS; j++) {
if (svc.chan[j].valid &&
+ svc.chan[j].vgpu == vgpu &&
svc.chan[j].h_object == ch->h_object) {
svc.chan[j].counter_pa = pa;
break;
@@ -5424,12 +8312,14 @@ static void svc_counter_autoreg(struct nv_vgpu_device *vgpu,
/*
* Learn a GR channel's REPORT_SEM release slot from a release VA the push
* carried (0x20400fXX observed). This is the slot the release write
- * targets (release_pa); it is deliberately NOT the completion-counter slot
- * (counter_pa), which the publish advances and which the client polls
- * (SA-COUNTER FORENSICS-grv7-counter F1: green wrote both families of the
- * counter page). An injected pin is the authoritative source; on a miss the
- * slot is derived in-kernel (svc_counter_derive) so the release is authored
- * immediately rather than waiting for the external pin watcher.
+ * targets (release_pa) and - per the descending layout the autoreg above
+ * documents - the channel's own polled completion word, the same slot the
+ * autoreg registers as counter_pa; keeping it in its own field preserves
+ * the push-named truth even when the registration derive fails, and
+ * svc_counter_publish prefers it. An injected pin is the authoritative
+ * source; on a miss the slot is derived in-kernel (svc_counter_derive) so
+ * the release is authored immediately rather than waiting for the external
+ * pin watcher.
*/
static void svc_counter_learn(struct nv_vgpu_device *vgpu, u32 h, u64 sem_va)
{
@@ -5458,7 +8348,8 @@ static void svc_counter_learn(struct nv_vgpu_device *vgpu, u32 h, u64 sem_va)
spin_lock_irqsave(&svc.chan_lock, flags);
for (i = 0; i < SVC_MAX_CHANNELS; i++) {
- if (svc.chan[i].valid && svc.chan[i].h_object == h) {
+ if (svc.chan[i].valid && svc.chan[i].vgpu == vgpu &&
+ svc.chan[i].h_object == h) {
if (svc.chan[i].release_pa != (pa & ~0x3ULL)) {
svc.chan[i].release_pa = pa & ~0x3ULL;
svc.chan[i].relearn_n++;
@@ -5473,6 +8364,13 @@ static void svc_counter_learn(struct nv_vgpu_device *vgpu, u32 h, u64 sem_va)
pr_info("sema-chan: release learned h=0x%x pa=0x%llx (from release va=0x%llx)\n",
h, pa & ~0x3ULL, sem_va);
}
+ /*
+ * r378: keep the VA the slot was learned from so a
+ * late write to release_pa (the stale-recover zero)
+ * can re-validate the target through
+ * gsp_sema_write_guard before it lands.
+ */
+ svc.chan[i].release_va = sem_va;
break;
}
}
@@ -5505,7 +8403,7 @@ static bool svc_rel_apply_906f(struct nv_vgpu_device *vgpu,
cur = (op == HOST_SEM_OP_REDUCTION) ? cur + payload : payload;
if (!svc_sem_write(vgpu, ch->h_object, va, cur)) {
svc.releases_applied++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
return true;
}
}
@@ -5533,7 +8431,7 @@ static bool svc_rel_apply_reportsem(struct nv_vgpu_device *vgpu,
svc.pushes_pending++;
if (!svc_sem_write(vgpu, ch->h_object, va, payload)) {
svc.releases_applied++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
return true;
}
return false;
@@ -5544,6 +8442,22 @@ static bool svc_rel_apply_launch(struct nv_vgpu_device *vgpu,
u32 payload, u32 cur, bool pending,
bool inc, bool inc_apply)
{
+ /*
+ * P4 (GAP-1 H3): the exact-slot counter delivery runs before every
+ * path that can early-return, so a GR completion-counter release can
+ * never be skipped by the generic write's outcome. The generic
+ * write/learn tail below is skipped entirely for counter VAs: an
+ * increment release is not idempotent, so it must apply exactly once
+ * (the early return here is that exactly-once edge).
+ */
+ if (ch->engine_type == 0 && svc_counter_va(sem_va)) {
+ if (pending || inc_apply)
+ svc.pushes_pending++;
+ svc_counter_learn(vgpu, ch->h_object, sem_va);
+ svc_counter_release_apply(vgpu, ch->h_object, sem_va, payload,
+ inc && inc_apply);
+ return false;
+ }
if (pending || inc_apply) {
svc.pushes_pending++;
if (inc)
@@ -5555,12 +8469,14 @@ static bool svc_rel_apply_launch(struct nv_vgpu_device *vgpu,
* generic translation can be a stale content-scanned page;
* those releases go through the injected-pin-backed exact slot
* (svc_counter_learn / svc_counter_release_apply) below.
+ * Counter VAs are handled by the early block above; the check
+ * stays as belt-and-braces for the generic write.
*/
if (ch->engine_type == 0 && svc_counter_va(sem_va)) {
/* handled by the exact-slot path */
} else if (!svc_sem_write(vgpu, ch->h_object, sem_va, cur)) {
svc.releases_applied++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
return true;
}
}
@@ -5568,6 +8484,16 @@ static bool svc_rel_apply_launch(struct nv_vgpu_device *vgpu,
svc_counter_learn(vgpu, ch->h_object, sem_va);
svc_counter_release_apply(vgpu, ch->h_object, sem_va, payload,
inc && inc_apply);
+ /*
+ * Stall-class fix (R2): when the decode said this absolute release is
+ * genuinely pending and every write attempt above failed without
+ * queueing, the release must not vanish - retry it through the pending
+ * pass. Increments stay excluded (their one-shot claim makes
+ * re-queueing unsafe until a diag run proves otherwise).
+ */
+ if (pending && !inc && !(ch->engine_type == 0 &&
+ svc_counter_va(sem_va)))
+ svc_pending_add(sem_va, payload, 0);
return false;
}
@@ -5671,6 +8597,93 @@ static void svc_fwd_rel_direct_note(u32 h, u64 va, u32 payload)
* mapped to its register offset. Each completed QMD resets the accumulator,
* so a push may carry more than one launch.
*/
+/*
+ * m164: per-decode QMD witness for the CLASS-2 no-capture hold. Set to the
+ * max number of QMD words any accumulator saw during the current
+ * svc_decode_stream() call; reset at its top and read by the entry loop's
+ * no-capture check. Service-thread only.
+ */
+static u32 svc_gr_qmd_seen;
+static u64 svc_gr_nocap_log;
+
+/*
+ * r383: has a launch with this push VA ever been captured? Scans the live
+ * candidate ring, the recent-capture history and the r401 session-wide
+ * (push_va, hash) seen table. Used by the replayed-QMD dispatch below: a
+ * replay whose push was never captured is a launch that would otherwise be
+ * silently dropped (the streams2 second-vadd class); a replay of a
+ * captured/offered push is the exactly-once no-op. The hash is required
+ * for the seen table so a later, DIFFERENT push at the same VA (a loop
+ * iteration: same entry words, new content) is never misclassified as
+ * seen - the caller only consults this on a true replay anyway.
+ */
+static bool svc_gr_cand_push_seen(u64 push_va, u64 hash)
+{
+ u32 i;
+
+ if (!push_va)
+ return false;
+ for (i = 0; i < SVC_GR_CAND_RING; i++)
+ if (svc_gr_cand_ring[i].valid &&
+ svc_gr_cand_ring[i].push_va == push_va)
+ return true;
+ for (i = 0; i < SVC_GR_CAP_HIST; i++)
+ if (svc_gr_cap_hist[i] == push_va)
+ return true;
+ return svc_gr_seen_lookup(push_va, hash);
+}
+
+/*
+ * r391f: once-only identity for GRCE inline writes. A push's
+ * LOAD_INLINE_DATA runs author bytes into the vidmem shadow; a re-decode of
+ * the same push (replay/rescue/rehold) must not re-run them - the target
+ * page may have been superseded in between (victory_reduce: the client's
+ * cudaMemset inline write re-zeroed the accumulator after the launch's
+ * copy-out, the sum=0 class). Keyed by (push_va, push content hash, dst,
+ * bytes): the release payload inside every push makes the hash unique per
+ * push, so a new client's identical-looking write never collides. Session
+ * scoped (cleared by svc_session_flush). Service-thread only.
+ *
+ * Sizing (r398): the ring must outlive the gap between a push's first
+ * execution and its next re-decode. The observed failing case had 76
+ * inline writes between the two, which evicted a 64-slot table and let
+ * the memset re-run; 8192 slots (256 KB) gives a ~100x margin over the
+ * busiest observed burst.
+ */
+#define SVC_INLINE_ONCE_SLOTS 8192
+static struct {
+ u64 push_va;
+ u64 hash;
+ u64 dst;
+ u32 bytes;
+} svc_inline_once[SVC_INLINE_ONCE_SLOTS];
+static u32 svc_inline_once_next;
+
+static bool svc_inline_once_seen(u64 push_va, u64 hash, u64 dst, u32 bytes)
+{
+ u32 i;
+
+ if (!push_va)
+ return false;
+ for (i = 0; i < SVC_INLINE_ONCE_SLOTS; i++)
+ if (svc_inline_once[i].push_va == push_va &&
+ svc_inline_once[i].hash == hash &&
+ svc_inline_once[i].dst == dst &&
+ svc_inline_once[i].bytes == bytes)
+ return true;
+ return false;
+}
+
+static void svc_inline_once_store(u64 push_va, u64 hash, u64 dst, u32 bytes)
+{
+ u32 i = svc_inline_once_next++ % SVC_INLINE_ONCE_SLOTS;
+
+ svc_inline_once[i].push_va = push_va;
+ svc_inline_once[i].hash = hash;
+ svc_inline_once[i].dst = dst;
+ svc_inline_once[i].bytes = bytes;
+}
+
static void svc_qmd_accum(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
struct svc_push_state *st, u32 addr, u32 count,
const u8 *data, const u8 *push, u32 push_len,
@@ -5700,6 +8713,8 @@ static void svc_qmd_accum(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
}
}
}
+ if (st->qmd_n > svc_gr_qmd_seen)
+ svc_gr_qmd_seen = st->qmd_n;
if (st->qmd_have == 3)
st->qmd_dev_va = (((u64)st->qmd_hi << 32) | st->qmd_lo) << 8;
if (st->qmd_n == SVC_QMD_WORDS) {
@@ -5733,13 +8748,70 @@ static void svc_qmd_accum(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
memcpy(recs[n].data, rec->data, rec->len);
n++;
}
- if (gsp_phys_gr_forward_enabled() && trusted && !replay)
- svc_gr_cand_capture(ch, st, recs, n, push, push_len,
- st->push_va);
- else if (!replay || !gsp_phys_gr_forward_enabled())
+ /*
+ * C10 (GAP-2 A5): duplicate destinations in the newest-first
+ * inline capture decide which record wins the backend's
+ * overlay; bounded and unconditional (gsp_sema_dbg is defined
+ * later in the file and this site may not reference it).
+ */
+ if (svc_inline_recs_log < 64) {
+ u32 dup = 0, a2, b2;
+
+ for (a2 = 0; a2 < n; a2++)
+ for (b2 = a2 + 1; b2 < n; b2++)
+ if (recs[a2].dst_va ==
+ recs[b2].dst_va)
+ dup++;
+ svc_inline_recs_log++;
+ pr_info("sema-gr: inline recs n=%u dup_dst=%u\n",
+ n, dup);
+ }
+ if (gsp_phys_gr_forward_enabled()) {
+ u64 push_hash = (push && push_len) ?
+ svc_push_hash(push, push_len) : 0;
+
+ if (trusted && !replay) {
+ svc_gr_cand_capture(ch, st, recs, n, push,
+ push_len, st->push_va);
+ } else if (trusted && replay &&
+ !svc_gr_cand_push_seen(st->push_va,
+ push_hash)) {
+ /*
+ * r383 (streams2 second-vadd class): a
+ * complete QMD first seen on a replay pass
+ * fell through BOTH arms below - the
+ * forward path skips replays and the
+ * bridge arm needs !replay - so the
+ * launch was silently dropped (zero
+ * engine writes, client read stale/zero
+ * outputs). The push VA was never
+ * captured, so capture it now (trusted
+ * content only, as the first-sight path);
+ * a replay of an already captured/offered
+ * launch stays the exactly-once no-op.
+ */
+ pr_info("sema-gr: REPLAY-CAPTURE h=0x%x push=0x%llx (complete QMD first seen on a replay pass; no capture on record)\n",
+ ch->h_object, st->push_va);
+ svc_gr_cand_capture(ch, st, recs, n, push,
+ push_len, st->push_va);
+ } else if (replay && svc_gr_nocap_log < 64) {
+ svc_gr_nocap_log++;
+ pr_info("sema-gr: LAUNCH-SKIP h=0x%x push=0x%llx (replay; already captured=%d trusted=%d)\n",
+ ch->h_object, st->push_va,
+ svc_gr_cand_push_seen(st->push_va,
+ push_hash),
+ trusted);
+ } else if (!replay) {
+ gsp_cuda_bridge_launch(vgpu, ch->h_object,
+ st->qmd,
+ st->qmd_dev_va,
+ st->push_va, recs, n);
+ }
+ } else if (!replay) {
gsp_cuda_bridge_launch(vgpu, ch->h_object, st->qmd,
st->qmd_dev_va, st->push_va,
recs, n);
+ }
st->qmd_n = 0;
st->qmd_bits = 0;
st->qmd_have = 0;
@@ -5748,6 +8820,8 @@ static void svc_qmd_accum(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
}
}
+static u64 svc_pass_hash; /* r391f: this decode pass's push content hash */
+
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,
@@ -5755,6 +8829,35 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
u64 *ops_done)
{
struct svc_push_state st;
+
+ /* r391f: the inline-write once-only key needs the push's identity. */
+ svc_pass_hash = svc_push_hash(buf, len);
+
+ /*
+ * r316: pending releases record the channel they were decoded for;
+ * the verified pool resolver is channel-keyed (see svc_pending_add
+ * and the matmul r322 starved/hung note).
+ */
+ svc.cur_ch = ch->h_object;
+ /*
+ * r34x: the acquire-wait capture is PER RING ENTRY. It used to be
+ * reset only at SET_OBJECT, so a decode pass over a later entry (or
+ * a set of entries, since a held entry is re-decoded after the
+ * sweep walks ahead) leaked its acquire into an earlier entry's
+ * release-site gate: r340 held a D2H copy entry that carries no
+ * acquire at all on a later launch's 'wait for the next tracking
+ * value' (need=85 while the held entry releases 84) - a circular
+ * wait only our own withheld release could ever satisfy, so every
+ * hold expired via the 10 s fallback (wait=4542 etc.). Reset here,
+ * per entry; SET_OBJECT still resets at the push boundary, and the
+ * re-decode re-captures the same waits, so held entries re-evaluate
+ * identically.
+ */
+ svc_wait_n = 0;
+ svc_wait_rel_va = 0;
+ svc_wait_rel_payload = 0;
+ svc_wait_rel_off = 0;
+ svc_wait_rel_have = false;
/*
* Static for the same reason as svc_handle_chan_dump: a 32-entry ops
* array is a ~2 KB frame, and this is called from the single service
@@ -5766,6 +8869,9 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
u32 ops_seq = 0; /* ops appended this pass (sequence numbers) */
u32 off = 0;
+ /* m164: fresh CLASS-2 QMD witness for this decode pass. */
+ svc_gr_qmd_seen = 0;
+
/*
* A false VA->PA pair (the discovered map is content-scanned) can
* point a ring entry's push VA at an unrelated page - observed:
@@ -5805,14 +8911,34 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
svc_fwd_defer_armed = false;
svc.pages_decoded++;
/* Diagnostic: name every push decoded in the post-completion window. */
- if (svc.op_trace_log < 160 &&
+ if (svc.op_trace_log < (gsp_sema_dbg ? 32768 : 2048) &&
time_before(jiffies, svc.op_trace_until)) {
svc.op_trace_log++;
pr_info("sema-pushtrace: h=0x%x eng=%u va=0x%llx len=%u allow=%d trusted=%d\n",
ch->h_object, ch->engine_type, push_va, len,
allow_ops, trusted);
}
- if (gsp_sema_dbg && ch->engine_type && svc_dbg_streams < 96) {
+ /*
+ * fail4: de-windowed decode trace - every push the service looks at,
+ * bounded per session. The windowed/identified-channel diagnostics
+ * above can miss a whole client's push stream (unidentified channel,
+ * no bridge completion to arm the window); this record cannot.
+ */
+ /*
+ * fail4 r302: with sema_dbg on the boot-time driver init spends the
+ * 512-line budget long before the run under diagnosis, leaving the
+ * interesting push stream (a later client's copies) invisible. The
+ * debug flag raises the bound so one boot covers init + run.
+ */
+ if (svc.pushtrace_all < (gsp_sema_dbg ? 32768 : 512)) {
+ svc.pushtrace_all++;
+ pr_info("sema-pushall: h=0x%x eng=%u va=0x%llx pa=0x%llx len=%u allow=%d trusted=%d first8=%08x %08x\n",
+ ch->h_object, ch->engine_type, push_va, page_pa, len,
+ allow_ops, trusted,
+ len >= 4 ? *(const u32 *)(buf + 0) : 0,
+ len >= 8 ? *(const u32 *)(buf + 4) : 0);
+ }
+ if (gsp_sema_dbg && svc_dbg_streams < 96) {
svc_dbg_streams++;
pr_info("sema-dbg: stream h=0x%x eng=%u va=0x%llx len=%u allow=%d w=%08x %08x %08x %08x %08x %08x %08x %08x\n",
ch->h_object, ch->engine_type, push_va, len, allow_ops,
@@ -5860,28 +8986,187 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
addr = (hdr & 0x1fff) << 2;
/*
+ * fail4: name every big method-data run and every run whose
+ * address is the LOAD_INLINE_DATA method (any op/subch - the
+ * encoding of the 64 KB H2D upload is what we are pinning).
+ */
+ if (gsp_sema_dbg && len >= 8192 && count >= 1024 &&
+ svc.bigrun_log < 128) {
+ svc.bigrun_log++;
+ pr_info("sema-bigrun: h=0x%x push=0x%llx off=0x%x hdr=0x%08x op=%u cnt=%u subch=%u addr=0x%x have_out=%u out=0x%llx line=%u lcnt=%u w=%08x %08x\n",
+ ch->h_object, push_va, off, hdr, op, count, subch,
+ addr, st.have_out, st.offset_out, st.line_length,
+ st.gr_line_count,
+ off + 8 <= len ? *(const u32 *)(buf + off + 4) : 0,
+ off + 12 <= len ? *(const u32 *)(buf + off + 8) : 0);
+ }
+ if (gsp_sema_dbg && len >= 8192 &&
+ (addr == GRCE_M_LOAD_INLINE_DATA || addr == 0x1b8) &&
+ svc.lid_hdr_log < 64) {
+ svc.lid_hdr_log++;
+ pr_info("sema-lid-hdr: h=0x%x push=0x%llx off=0x%x hdr=0x%08x op=%u cnt=%u subch=%u addr=0x%x trusted=%d allow=%d\n",
+ ch->h_object, push_va, off, hdr, op, count, subch,
+ addr, trusted, allow_ops);
+ }
+
+ /*
* GRCE LOAD_INLINE_DATA: a data run (opcode 3) that carries
* the DMA source bytes in the push itself. Run the write to
* the destination the preceding methods selected.
*/
if (op == 3 && subch == 1 && addr == GRCE_M_LOAD_INLINE_DATA &&
count && off + 4 + count * 4 <= len) {
- if ((trusted || allow_ops) && !gsp_sema_skip_ops &&
- st.have_out && st.line_length) {
- u32 bytes = st.line_length *
- (st.gr_line_count ? st.gr_line_count : 1);
- u32 have = count * 4;
+ /*
+ * fail4 slot rule: in the UVM upload pushes each
+ * LOAD_INLINE_DATA run fills a whole 32 KB slot - its
+ * data runs from the header to the next run header
+ * (or to the next 0x8000 boundary when this is the
+ * last run), regardless of the count field. A compact
+ * push (no room for a following slot) keeps the
+ * count*4 reading (the small parameter banks). The
+ * old count*4 reading truncated the big runs to half
+ * their slot and left the tail to be misparsed as
+ * methods (the split-upload bug).
+ */
+ bool slot_run = (off + 0x8000 < len);
+ u32 slot_end = off + 0x8000;
+ u32 slot_bytes = 0;
+ /*
+ * fail4: the bridge's argument-block search needs the
+ * run's START as its destination; the cursor advance
+ * below must not leak into the rec (an end-of-run
+ * dst_va makes the QMD param patch miss and the
+ * engine never consumes the launch).
+ */
+ u64 run_dst = st.offset_out;
+
+ if (slot_run) {
+ u32 p;
+
+ for (p = off + 4 + count * 4;
+ p + 4 <= slot_end && p + 4 <= len; p += 4) {
+ u32 h2;
+
+ memcpy(&h2, buf + p, 4);
+ if (((h2 >> 29) & 0x7) == 3 &&
+ ((h2 >> 13) & 0x7) == 1 &&
+ ((h2 & 0x1fff) << 2) ==
+ GRCE_M_LOAD_INLINE_DATA &&
+ ((h2 >> 16) & 0xfff)) {
+ slot_end = p;
+ break;
+ }
+ }
+ slot_bytes = slot_end - (off + 4);
+ if (slot_bytes > 0x8000 - 4)
+ slot_bytes = 0x8000 - 4;
+ }
+ /*
+ * r391f once-only (the sum=0 class): the same
+ * inline write must not run twice for one push
+ * identity. The ops are gated by allow_ops
+ * (entry_replay clears it), but the inline-write
+ * gate `trusted || allow_ops` re-ran the write on
+ * every re-decode (a replay is trusted by
+ * construction): victory_reduce's
+ * cudaMemset(d_sum,0,8) is exactly such an inline
+ * write (observed: `inline write
+ * dst=0x7845ef000000 -> pa=0x7c00000 bytes=8
+ * data8=0` firing 86 us before the D2H readback,
+ * zeroing the accumulator the launch had just
+ * written - the client then read zeros). A coarse
+ * !replay gate is wrong (a push whose first pass
+ * was untrusted must still author its bytes on the
+ * later trusted pass); key the write by
+ * (push_va, push content hash, dst, bytes) - a new
+ * push's release payload makes the hash unique -
+ * and skip only a genuine duplicate. The table is
+ * session-scoped (a new client starts clean).
+ */
+ {
+ u32 bytes = slot_run ? slot_bytes :
+ st.line_length *
+ (st.gr_line_count ?
+ st.gr_line_count : 1);
+ u32 have = slot_run ? slot_bytes : count * 4;
if (bytes > have)
bytes = have;
- svc_grce_inline_write(vgpu, st.offset_out,
- buf + off + 4, bytes,
- ch->h_object);
- } else if (gsp_sema_dbg && svc.grce_log < 16) {
- svc.grce_log++;
- pr_info("sema-grce: inline data not run (trusted=%d allow=%d have_out=%d line=%u) h=0x%x\n",
- trusted, allow_ops, st.have_out,
- st.line_length, ch->h_object);
+ /*
+ * r391f once-only (the sum=0 class): the same
+ * inline write must not run twice for one push
+ * identity. The ops are gated by allow_ops
+ * (entry_replay clears it), but the
+ * inline-write gate `trusted || allow_ops`
+ * re-ran the write on every re-decode (a replay
+ * is trusted by construction): victory_reduce's
+ * cudaMemset(d_sum,0,8) is exactly such an
+ * inline write (observed: `inline write
+ * dst=0x7845ef000000 -> pa=0x7c00000 bytes=8
+ * data8=0` firing 86 us before the D2H
+ * readback, zeroing the accumulator the launch
+ * had just written - the client then read
+ * zeros). A coarse !replay gate is wrong (a push
+ * whose first pass was untrusted must still
+ * author its bytes on the later trusted pass);
+ * key the write by (push_va, push content hash,
+ * dst, bytes) - the release payload inside every
+ * push makes the hash unique per push - and skip
+ * only a genuine duplicate. Session-scoped: a new
+ * client starts clean.
+ */
+ if ((trusted || allow_ops) && !gsp_sema_skip_ops &&
+ st.have_out && st.line_length && bytes &&
+ !svc_inline_once_seen(push_va, svc_pass_hash,
+ st.offset_out,
+ bytes)) {
+ svc_inline_once_store(push_va,
+ svc_pass_hash,
+ st.offset_out,
+ bytes);
+ svc_grce_inline_write(vgpu, st.offset_out,
+ buf + off + 4,
+ bytes,
+ ch->h_object);
+ /*
+ * fail4: hardware semantics - a
+ * LOAD_INLINE_DATA run advances the
+ * destination cursor by the bytes it
+ * wrote. Without this, the consecutive
+ * runs of a split 64 KB upload all land
+ * at the first destination and the page
+ * ends up with only the last run's
+ * words (the RAWA junk).
+ */
+ st.offset_out += bytes;
+ /*
+ * fail4: name the header that follows
+ * each big LID run - the split 64 KB
+ * upload's other payload carriers sit
+ * exactly there.
+ */
+ if (gsp_sema_dbg && len >= 4096 &&
+ svc.next_hdr_log < 24) {
+ u32 noff = off + 4 + count * 4;
+ u32 nh = 0, nw = 0;
+
+ if (noff + 4 <= len)
+ memcpy(&nh, buf + noff, 4);
+ if (noff + 8 <= len)
+ memcpy(&nw, buf + noff + 4,
+ 4);
+ svc.next_hdr_log++;
+ pr_info("sema-next-hdr: h=0x%x push=0x%llx after=0x%x cnt=%u nxt_hdr=0x%08x nxt_w0=%08x cursor=0x%llx\n",
+ ch->h_object, push_va,
+ off, count, nh, nw,
+ st.offset_out);
+ }
+ } else if (gsp_sema_dbg && svc.grce_log < 64) {
+ svc.grce_log++;
+ pr_info("sema-grce: inline data not run (trusted=%d allow=%d have_out=%d line=%u) h=0x%x\n",
+ trusted, allow_ops, st.have_out,
+ st.line_length, ch->h_object);
+ }
}
/*
* Record the destination and the payload bytes for
@@ -5891,21 +9176,43 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
* kernel arguments in the QMD parameter bank, and the
* destination VA may not be readable later.
*/
- if (st.have_out && st.offset_out) {
+ if (st.have_out && run_dst) {
struct svc_grce_rec *rec =
&svc_grce_dst_ring[svc_grce_dst_ring_next %
SVC_GRCE_DST_MAX];
- rec->dst_va = st.offset_out;
- rec->len = min_t(u32, count * 4,
+ rec->dst_va = run_dst;
+ rec->len = min_t(u32,
+ slot_run ? slot_bytes : count * 4,
GSP_BRIDGE_INLINE_BYTES);
memcpy(rec->data, buf + off + 4, rec->len);
svc_grce_dst_ring_next++;
}
- off += 4 + count * 4;
+ /*
+ * fail4 slot rule: hop to the next slot's run header
+ * instead of walking the slot's raw tail as methods.
+ */
+ off += slot_run ? (slot_end - off) : 4 + count * 4;
continue;
}
+ /*
+ * fail4: name the moderate op=1 method runs on big pushes -
+ * the split 64 KB upload's payload carriers beyond the LID
+ * class land here (their addr selects the write port).
+ */
+ if (gsp_sema_dbg && len >= 4096 && op == 1 &&
+ count >= 16 && count <= 1023 && svc.mystery_log < 48) {
+ svc.mystery_log++;
+ pr_info("sema-mystery: h=0x%x push=0x%llx off=0x%x hdr=0x%08x cnt=%u subch=%u addr=0x%x have_out=%u out=0x%llx line=%u lcnt=%u w=%08x %08x %08x\n",
+ ch->h_object, push_va, off, hdr, count, subch,
+ addr, st.have_out, st.offset_out,
+ st.line_length, st.gr_line_count,
+ off + 8 <= len ? *(const u32 *)(buf + off + 4) : 0,
+ off + 12 <= len ? *(const u32 *)(buf + off + 8) : 0,
+ off + 16 <= len ? *(const u32 *)(buf + off + 12) : 0);
+ }
+
if (op == 4 || op == 7) {
/* Header-only methods (immediate data in the header
* for op 4; end-of-push segment for op 7). */
@@ -5970,6 +9277,13 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
* held INC-shaped words queued a bogus LAUNCH_DMA).
* No release is decoded from these encodings.
*/
+ if (gsp_sema_dbg && len >= 8192 && count >= 1024 &&
+ svc.skiprun_log < 64) {
+ svc.skiprun_log++;
+ pr_info("sema-skiprun: h=0x%x push=0x%llx off=0x%x hdr=0x%08x op=%u cnt=%u subch=%u addr=0x%x trusted=%d allow=%d\n",
+ ch->h_object, push_va, off, hdr, op,
+ count, subch, addr, trusted, allow_ops);
+ }
off += 4 + count * 4;
continue;
}
@@ -6036,6 +9350,12 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
/* SET_OBJECT on the channel subchannel: a push starts */
n_ops = 0;
memset(&st, 0, sizeof(st));
+ /* r336/r34x: new push, new acquire set + release mirror */
+ svc_wait_n = 0;
+ svc_wait_rel_va = 0;
+ svc_wait_rel_payload = 0;
+ svc_wait_rel_off = 0;
+ svc_wait_rel_have = false;
off += 4 + count * 4;
continue;
}
@@ -6054,6 +9374,10 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
svc.pushes_seen++;
st.rel_va = va;
st.rel_payload = payload;
+ svc_wait_rel_va = va;
+ svc_wait_rel_payload = payload;
+ svc_wait_rel_off = off;
+ svc_wait_rel_have = true;
st.have_rel = 1;
svc_rel_va_seen = va;
svc_rel_count++;
@@ -6106,7 +9430,8 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
or.va = va;
or.payload = payload;
or.entry_idx = entry_idx;
- if (svc_fwd_defer_arm()) {
+ if (svc_fwd_defer_arm() &&
+ !svc.op_tr_pending) {
svc_opdef_drop(vgpu, ch,
entry_idx);
if (svc_fwd_rel_n < SVC_FWD_REL_MAX) {
@@ -6151,6 +9476,42 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
va, payload, op);
}
}
+ } else if (op != HOST_SEM_OP_RELEASE &&
+ va >= SEMA_VA_LO && va < SEMA_VA_HI) {
+ /*
+ * r336/r34x: C56F SEM_EXECUTE acquire ops
+ * (0 = ACQUIRE/equal, 2 = strict GEQ,
+ * 3 = circular GEQ, 4 = AND, 5 = NOR).
+ * Core CUDA starts its compute-channel
+ * pushes with one of these to wait for a
+ * progress counter (observed:
+ * [LO=0x0400ffd0][HI=2][pay=4][0][0x1003] on
+ * the D2H readback push - ACQ_CIRC_GEQ on
+ * the channel counter 0x20400ffd0).
+ * svc_run_ops honors every captured acquire
+ * of this entry before running its work, so
+ * a copy cannot execute ahead of the
+ * dependency it waits on (the streams2
+ * readback-before-scale class). Capturing
+ * into the per-entry list (instead of a
+ * single last-wait global) is what stops a
+ * later entry's acquire from gating an
+ * earlier entry (the r340 over-hold).
+ * PAYLOAD_SIZE 64BIT (execute bit 24) is
+ * captured on the low word only; every
+ * observed CUDA acquire is 32-bit.
+ */
+ if (svc_wait_n < SVC_WAIT_MAX) {
+ struct svc_wait_op *w =
+ &svc_waits[svc_wait_n++];
+
+ w->va = va;
+ w->payload = payload;
+ w->op = (u8)op;
+ w->off = off;
+ } else {
+ svc_wait_ovf++;
+ }
}
off += 4 + count * 4;
continue;
@@ -6200,6 +9561,10 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
svc.pushes_seen++;
st.rel_va = va;
st.rel_payload = st.rs_payload;
+ svc_wait_rel_va = va;
+ svc_wait_rel_payload = st.rs_payload;
+ svc_wait_rel_off = off;
+ svc_wait_rel_have = true;
st.have_rel = 1;
svc_rel_va_seen = va;
svc_rel_count++;
@@ -6335,7 +9700,7 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
* operand state, so one run covers both the
* C5B5 and the GRCE copy shapes.
*/
- if (ch->h_object && gsp_sema_dbg &&
+ if (gsp_sema_dbg &&
svc.xfer_raw_log < 48) {
u32 dl = len < 192 ? len : 192;
@@ -6377,11 +9742,61 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
st.have_sem = 1;
break;
case CE_M_SET_REMAP_CONST_A:
+ /*
+ * r343 FILL instrument: a fill-shaped LAUNCH_DMA
+ * (REMAP_ENABLE + destination PITCH, OFFSET_OUT and
+ * no OFFSET_IN) needs this constant to build its op.
+ * The 8MB-memset class (memcpy_roundtrip all-bad,
+ * victory_memset2d) shows fill-shaped launches with
+ * have_remap=0 that decode nothing and are consumed
+ * silently. Log every decode (bounded) so one run
+ * captures the real run shape even when the count
+ * guard below cannot use it.
+ */
+ if (svc_remapconst_log < 256) {
+ svc_remapconst_log++;
+ pr_info("sema-remapconst: h=0x%x push=0x%llx off=0x%x count=%u v0=0x%x v1=0x%x v2=0x%x trusted=%d allow=%u\n",
+ ch->h_object, push_va, off, count,
+ v0, count >= 2 ? v1 : 0u,
+ count >= 3 ? v2 : 0u, trusted, allow_ops);
+ }
if (count >= 3) {
st.remap_a = v0;
st.remap_b = v1;
st.remap_comp = v2;
st.have_remap = 1;
+ st.have_remap_a = 1;
+ st.have_remap_b = 1;
+ st.have_remap_c = 1;
+ } else if (count >= 1) {
+ /*
+ * r365: the driver also pushes the fill constants
+ * as SEPARATE count=1 methods (the CE
+ * page-table fills carry only SET_REMAP_CONST_A
+ * with A=0). Capture the individual parts but do
+ * NOT set have_remap: r365b proved that letting
+ * these pass the fill guard executes below-floor
+ * window fills (0x2000c0xxx, 0x204020000) whose
+ * svc_fill resolution landed in GUEST RAM and
+ * zeroed slab pages (guest kernel GPF in
+ * kmem_cache_alloc -> panic at ~75 s). The
+ * window fills only run behind gsp_sema_winfill
+ * with the correct resolution proven first.
+ */
+ st.remap_a = v0;
+ st.have_remap_a = 1;
+ }
+ break;
+ case CE_M_SET_REMAP_CONST_B:
+ if (count >= 1) {
+ st.remap_b = v0;
+ st.have_remap_b = 1;
+ }
+ break;
+ case CE_M_SET_REMAP_COMPONENTS:
+ if (count >= 1) {
+ st.remap_comp = v0;
+ st.have_remap_c = 1;
}
break;
case CE_M_SET_SRC_PHYS_MODE:
@@ -6395,11 +9810,24 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
case CE_M_LINE_LENGTH_IN:
st.line_length = v0;
break;
+ /* r402: 2D geometry (cudaMemset2D / cudaMemcpy2D). */
+ case CE_M_LINE_COUNT:
+ st.line_count = v0;
+ break;
+ case CE_M_PITCH_IN:
+ st.pitch_in = v0 & 0xffffff;
+ st.have_pitch_in = 1;
+ break;
+ case CE_M_PITCH_OUT:
+ st.pitch_out = v0 & 0xffffff;
+ st.have_pitch_out = 1;
+ break;
case CE_M_LAUNCH_DMA: {
u32 dma = v0;
u32 transfer = DMA_DATA_TRANSFER_TYPE(dma);
+ u32 n_ops_before = n_ops;
- if (gsp_sema_dbg && ch->h_object &&
+ if (gsp_sema_dbg &&
svc.lda_log < (gsp_sema_ops_log > 48 ? gsp_sema_ops_log : 48)) {
svc.lda_log++;
pr_info("sema-lda: h=0x%x eng=%u push=0x%llx inj=%u allow=%u dma=0x%08x xfer=%u remap=%u dpitch=%u dstphys=%u srcphys=%u sem=%u out=0x%llx in=0x%llx line=%u have_out=%u have_in=%u have_remap=%u have_sem=%u ops=%u\n",
@@ -6416,7 +9844,7 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
}
/* Diagnostic: full operand state in the post-completion
* window (or for high device VAs), bounded. */
- if (svc.op_trace_log < 160 && ch->h_object &&
+ if (svc.op_trace_log < (gsp_sema_dbg ? 32768 : 2048) && ch->h_object &&
(time_before(jiffies, svc.op_trace_until) ||
st.offset_out >= SVC_VAS_FLOOR ||
st.offset_in >= SVC_VAS_FLOOR)) {
@@ -6440,7 +9868,7 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
* bounded number of such launches so the operand
* encoding (or its absence) is on record.
*/
- if (transfer != 0 && ch->h_object && gsp_sema_dbg &&
+ if (transfer != 0 && gsp_sema_dbg &&
svc.xfer_raw_log < 48 &&
(!st.have_out || !st.line_length)) {
u32 dl = len < 192 ? len : 192;
@@ -6455,25 +9883,155 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
if (allow_ops && transfer != 0 && st.line_length &&
st.have_out) {
- if (DMA_REMAP_ENABLE(dma) && st.have_remap &&
+ /*
+ * r361: a fill-shaped transfer whose
+ * destination is PHYSICAL guest RAM (the
+ * driver zeroes freshly-allocated PTE /
+ * page-directory pages with exactly this
+ * push shape) needs no translation: the
+ * destination already IS the GPA. The old
+ * `offset_out >= SVC_VAS_FLOOR` guard
+ * compared the GPA against the VA floor
+ * and blocked every one of these, so the
+ * PD0 pages the GMMU walk later reads
+ * stayed stale and the first CUDA client
+ * of a fresh boot dropped operand pages
+ * (GRFILL-DROP). SEMA_PA_MAX bounds the
+ * range so a ramfill can only touch guest
+ * RAM; virtual engine-window fills
+ * (0x2040xxxxx) stay excluded.
+ */
+ bool ramfill = DMA_DST_TYPE_PHYSICAL(dma) &&
+ st.have_dst_mode &&
+ st.dst_phys_mode == CE_APERTURE_SYS &&
+ st.offset_out + (u64)st.line_length <=
+ SEMA_PA_MAX;
+ /* r365: vidmem-window fill (see gsp_sema_winfill). */
+ bool winfill = gsp_sema_winfill &&
+ st.offset_out >= SVC_VIDMEM_VA_LO &&
+ st.offset_out < SVC_VIDMEM_VA_BASE +
+ svc_fb_shadow_size(vgpu);
+
+ if (DMA_REMAP_ENABLE(dma) &&
+ (st.have_remap ||
+ (st.offset_out >= SVC_VAS_FLOOR) ||
+ ramfill || winfill) &&
DMA_DST_MEMORY_LAYOUT_PITCH(dma) &&
n_ops < SVC_MAX_MEMSETS) {
- u32 csz = (st.remap_comp >> 16) & 0x3;
- u32 ndst = (st.remap_comp >> 24) & 0x3;
+ u32 csz = st.have_remap ?
+ ((st.remap_comp >> 16) & 0x3) : 0;
+ u32 ndst = st.have_remap ?
+ ((st.remap_comp >> 24) & 0x3) : 0;
+ u32 dst_x = st.have_remap ?
+ (st.remap_comp & 0x7) : 4;
struct svc_memset_op *m = &ops[n_ops];
m->type = 0;
m->dst = st.offset_out;
- m->value = ndst >= 1 ?
- (((u64)st.remap_b << 32) |
- st.remap_a) : st.remap_b;
- m->elem = (csz + 1) * (ndst + 1);
- m->count = st.line_length;
+ /*
+ * r367: the driver's own memset push
+ * (channel_utils.c) proves the
+ * encoding: LINE_LENGTH_IN is the
+ * fill SIZE IN BYTES, and
+ * SET_REMAP_COMPONENTS selects
+ * COMPONENT_SIZE ([17:16], 0..3 ->
+ * 1/2/3/4 bytes) and
+ * NUM_DST_COMPONENTS ([25:24], n+1
+ * components). elem =
+ * (csz+1)*(ndst+1) is the pattern
+ * width; count must therefore be
+ * line_length/elem - the old
+ * count=line_length wrote
+ * elem x too much (r365: a 383620-
+ * byte memset wrote 1534480 bytes,
+ * clobbering the bytes after it).
+ * The value comes from the mapped
+ * constants: X<-CONST_A (4) and
+ * Y<-CONST_B (5); with two
+ * components the pattern is
+ * B:A 64-bit.
+ */
+ if (st.have_remap)
+ m->value = ndst >= 1 ?
+ (((u64)st.remap_b << 32) |
+ st.remap_a) :
+ (dst_x == 5 ? st.remap_b :
+ st.remap_a);
+ else
+ /*
+ * r357: a fill-shaped transfer
+ * (REMAP_ENABLE + DST PITCH,
+ * no source) to a CLIENT DEVICE
+ * VA whose value constant did
+ * not decode is a ZERO fill:
+ * the driver's page-table
+ * clears and cudaMemset(0)
+ * issue exactly this shape with
+ * no SET_REMAP_CONST method.
+ * Building no op left the
+ * guest's GPU page tables
+ * stale (the GMMU walk then
+ * missed freshly-mapped
+ * operand pages and the first
+ * CUDA client of every fresh
+ * boot ran with dropped
+ * operands). The >= floor
+ * guard keeps engine-window
+ * fills (0x2040xxxxx init
+ * clears) OUT of the executor:
+ * executing those clobbered
+ * emulated engine state and
+ * hung client init (r356).
+ */
+ m->value = 0;
+ m->elem = st.have_remap ?
+ (csz + 1) * (ndst + 1) : 1;
+ if (m->elem > 1 &&
+ st.line_length % m->elem) {
+ /* not pattern-divisible: exact
+ * byte count wins */
+ m->elem = 1;
+ m->count = st.line_length;
+ } else {
+ m->count = st.line_length /
+ m->elem;
+ }
+ /*
+ * r402: a 2D fill (cudaMemset2D):
+ * LINE_COUNT rows of
+ * LINE_LENGTH_IN bytes at
+ * PITCH_OUT stride. Only a pitch
+ * WIDER than the row selects row
+ * mode; a flat fill carries
+ * pitch == row length (or no
+ * pitch method) and keeps the
+ * historical single-run path.
+ */
+ if (st.have_pitch_out &&
+ st.line_count > 1 &&
+ st.pitch_out > st.line_length) {
+ m->rows = st.line_count;
+ m->dpitch = st.pitch_out;
+ }
+ if (m->rows > 1 && svc_pitch_log < 32) {
+ svc_pitch_log++;
+ pr_info("sema-2d: fill rows=%u dpitch=%u line=%u dst=0x%llx h=0x%x push=0x%llx\n",
+ m->rows, m->dpitch,
+ st.line_length, m->dst,
+ ch->h_object, push_va);
+ }
m->phys = DMA_DST_TYPE_PHYSICAL(dma);
m->aperture = st.have_dst_mode ?
st.dst_phys_mode : CE_APERTURE_VID;
n_ops++;
ops_seq++;
+ if (ramfill && svc.fill_ram_log < 24) {
+ svc.fill_ram_log++;
+ pr_info("vgsp-sema: RAMFILL-EXEC push=0x%llx dst=0x%llx line=%u ap=%u value=0x%llx (physical guest-RAM fill built)\n",
+ push_va, st.offset_out,
+ st.line_length,
+ m->aperture, m->value);
+ }
} else if (!DMA_REMAP_ENABLE(dma) &&
st.have_in &&
n_ops < SVC_MAX_MEMSETS) {
@@ -6490,6 +10048,26 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
DMA_SRC_TYPE_PHYSICAL(dma);
m->src_aperture = st.have_src_mode ?
st.src_phys_mode : CE_APERTURE_VID;
+ /*
+ * r402: a 2D copy (cudaMemcpy2D):
+ * LINE_COUNT rows of
+ * LINE_LENGTH_IN bytes with
+ * PITCH_IN/PITCH_OUT strides.
+ * Row mode only when a pitch is
+ * wider than the row (a flat 1D
+ * copy keeps pitch == length).
+ */
+ if (st.line_count > 1 &&
+ st.have_pitch_out &&
+ st.pitch_out > st.line_length) {
+ m->rows = st.line_count;
+ m->dpitch = st.pitch_out;
+ m->spitch = st.have_pitch_in ?
+ (st.pitch_in ?
+ st.pitch_in :
+ st.pitch_out) :
+ st.pitch_out;
+ }
n_ops++;
ops_seq++;
} else if (gsp_sema_dbg && svc.dma_skip_log < 32) {
@@ -6518,6 +10096,290 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
}
/*
+ * r365 FILL-RAW: the fill class that still decodes no
+ * op (window VA, no remap const decoded) - dump the
+ * raw push and decode its method stream so the true
+ * value/component encoding is on record. Bounded.
+ */
+ if (allow_ops && transfer != 0 &&
+ DMA_REMAP_ENABLE(dma) &&
+ DMA_DST_MEMORY_LAYOUT_PITCH(dma) &&
+ st.have_out && !st.have_in && !st.have_remap &&
+ svc_fill_raw_log < 48) {
+ u32 dl = len < 160 ? len : 160;
+ u32 mo = 0, mn = 0;
+
+ svc_fill_raw_log++;
+ pr_info("sema-fill-raw: h=0x%x push=0x%llx out=0x%llx line=%u dma=0x%08x dmode=%u have_dstmode=%u trusted=%d allow=%d\n",
+ ch->h_object, push_va, st.offset_out,
+ st.line_length, dma, st.dst_phys_mode,
+ st.have_dst_mode, trusted, allow_ops);
+ pr_info("sema-fill-raw: raw len=%u %*phN\n",
+ len, dl, buf);
+ /* decode the first method pairs */
+ while (mo + 8 <= len && mn < 14) {
+ u32 h2, c2, a2;
+
+ memcpy(&h2, buf + mo, 4);
+ c2 = (h2 >> 16) & 0xfff;
+ a2 = (h2 & 0x1fff) << 2;
+ if ((h2 >> 29) != 1 || !c2 ||
+ mo + 4 + c2 * 4 > len)
+ break;
+ if (c2 >= 2)
+ pr_info("sema-fill-raw: m[%u] addr=0x%x cnt=%u v0=0x%08x v1=0x%08x\n",
+ mn, a2, c2,
+ *(const u32 *)(buf + mo + 4),
+ *(const u32 *)(buf + mo + 8));
+ else
+ pr_info("sema-fill-raw: m[%u] addr=0x%x cnt=%u v0=0x%08x\n",
+ mn, a2, c2,
+ *(const u32 *)(buf + mo + 4));
+ mo += 4 + c2 * 4;
+ mn++;
+ }
+ }
+
+ /*
+ * r343 FILL diagnostic (LOG-ONLY, no hold): a
+ * fill-shaped transfer (REMAP_ENABLE + destination
+ * PITCH, OFFSET_OUT and no OFFSET_IN) whose value
+ * constant did not decode currently decodes no op
+ * and is consumed silently - the guest's cudaMemset
+ * then reports success with zero bytes written
+ * (r341 memcpy_roundtrip: 8MB memset readback
+ * all-bad; victory_memset2d). An op_tr_pending hold
+ * here WEDGED client init (r343: first cudaMalloc
+ * retried forever - driver init issues fill-shaped
+ * CE pushes, the hold re-armed every decode pass
+ * and never expired). So: log loudly only; the
+ * sema-remapconst rows above capture the real
+ * decode shape to build the proper fill decoder.
+ */
+ if (allow_ops && transfer != 0 &&
+ st.line_length >= 1024 && st.have_out &&
+ !st.have_in && DMA_REMAP_ENABLE(dma) &&
+ DMA_DST_MEMORY_LAYOUT_PITCH(dma) &&
+ !st.have_remap && n_ops == n_ops_before &&
+ svc_fill_noremap < 8) {
+ svc_fill_noremap++;
+ pr_err("vgsp: FILL-NOREMAP h=0x%x push=0x%llx dma=0x%08x line=%u out=0x%llx phys=%u dstmode=%u: fill-shaped launch, value source not decoded (log-only diagnostic)\n",
+ ch->h_object, push_va, dma,
+ st.line_length, st.offset_out,
+ DMA_DST_TYPE_PHYSICAL(dma),
+ st.have_dst_mode ? st.dst_phys_mode : 99);
+ }
+
+ /*
+ * fail4: name a transfer-shaped launch that decoded NO
+ * op because the pass was denied (allow_ops=0) - the
+ * op-chain above (and its dma-skip dump) is skipped
+ * wholesale in that case, so this class was invisible.
+ */
+ if (gsp_sema_dbg && transfer != 0 && !allow_ops &&
+ svc.dmatrace_log < (gsp_sema_dbg ? 4096 : 64)) {
+ svc.dmatrace_log++;
+ pr_info("sema-dmatrace: denied h=0x%x push=0x%llx dma=0x%08x line=%u in=0x%llx out=0x%llx have_in=%u have_out=%u have_remap=%u trusted=%d replay=%d eng=%u pend_would=%d\n",
+ ch->h_object, push_va, dma,
+ st.line_length, st.offset_in,
+ st.offset_out, st.have_in, st.have_out,
+ st.have_remap, trusted, replay,
+ ch->engine_type,
+ (!allow_ops && trusted &&
+ !ch->engine_type && transfer != 0 &&
+ st.line_length && st.have_out &&
+ st.have_in &&
+ !DMA_REMAP_ENABLE(dma) &&
+ st.offset_out >= SVC_VAS_FLOOR &&
+ entry_idx != SVC_ENTRY_NONE &&
+ entry_idx < SVC_GPFIFO_MAX_BITS &&
+ ch->slot_pend_tries[entry_idx] < 4) ? 1 : 0);
+ }
+ /*
+ * fail4 r302: a denied device-destination copy that
+ * WOULD have built a type=1 op (the exact branch shape
+ * above: no remap, both offsets, device dst) must not
+ * be consumed silently. This is the sw-copy client
+ * mode: the GR-classified GRCE channel's ops run only
+ * once the pin/trust stream (injected) reaches the
+ * entry, and the first pass over a chunk push lands
+ * before that - the copy then never ran and the guest
+ * computed on stale VRAM (vector_add r302: chunk
+ * copies cc..d1 skipped; only the tail entries were
+ * re-decoded by a rewind and ran). Mark the pass
+ * op-pending so the pump holds the entry through the
+ * m163/opdef machinery and the retry runs the copy as
+ * soon as the trust (or the operand translation)
+ * arrives; the bounded SVC_OPDEF_HOLD_MS expiry keeps
+ * the pre-existing skip as the last resort.
+ */
+ if (!allow_ops && trusted &&
+ !ch->engine_type && transfer != 0 &&
+ st.line_length && st.have_out && st.have_in &&
+ !DMA_REMAP_ENABLE(dma) &&
+ st.offset_out >= SVC_VAS_FLOOR) {
+ /*
+ * r314 (CONT.57 lead 2): capture the staging
+ * source on the DENIED pass too. This is the
+ * pipeline-order moment: the push was just
+ * decoded, the driver has not seen this
+ * push's fence (it applies later in this same
+ * pass at the earliest), so the staging still
+ * holds this chunk's bytes. The old capture
+ * gate lived in the op-build block, which
+ * only runs on ALLOWED passes - by then the
+ * fence had long fired and the staging was
+ * refilled (r302-r313: captures 0-3/8, the
+ * late executions read refilled staging, the
+ * deterministic 786335-bad output). Same
+ * translation-probe guard as the build-time
+ * gate: only an SYS-resolving source is
+ * host staging.
+ */
+ if (st.line_length <= SVC_COPY_SNAP_MAX) {
+ u64 spa = 0;
+ u32 sap = 0;
+
+ if (!svc_op_translate(vgpu,
+ st.offset_in,
+ &spa, &sap) &&
+ sap == CE_APERTURE_SYS)
+ svc_copy_snap_capture(vgpu,
+ st.offset_out,
+ st.offset_in,
+ st.line_length);
+ }
+ /*
+ * r308: the `!replay` gate above kept this
+ * dead on the sw-copy channel: after the first
+ * consumed pass every later pass classifies as
+ * a replay, so the denied chunk copies were
+ * never retried (vector_add: 6 of 8 chunks
+ * skipped). A replay pass that marks the slot
+ * pending un-completes it (slot_ops_complete
+ * is written from svc.op_tr_pending below), so
+ * the next pass is treated fresh and the copy
+ * runs once trust/translation arrives. The
+ * per-slot counter caps the re-holds so a slot
+ * whose trust never arrives cannot wedge the
+ * channel in 10 s hold cycles.
+ */
+ if (entry_idx != SVC_ENTRY_NONE &&
+ entry_idx < SVC_GPFIFO_MAX_BITS &&
+ ch->slot_pend_tries[entry_idx] < 4) {
+ ch->slot_pend_tries[entry_idx]++;
+ svc.op_tr_pending = 1;
+ if (gsp_sema_dbg &&
+ svc.pend_dbg_log < 512) {
+ svc.pend_dbg_log++;
+ pr_info("sema-pend: h=0x%x push=0x%llx idx=%u out=0x%llx in=0x%llx line=%u replay=%d trusted=%d tries=%u -> op_tr_pending\n",
+ ch->h_object, push_va,
+ entry_idx, st.offset_out,
+ st.offset_in,
+ st.line_length,
+ replay, trusted,
+ ch->slot_pend_tries[entry_idx]);
+ }
+ }
+ }
+ /*
+ * fail4: name every op appended to this pass (copy or
+ * fill) with its resolved operand fields, so the
+ * executor's population is on record even when the
+ * 64 KB copy witness below never fires for it.
+ */
+ if (n_ops != n_ops_before) {
+ struct svc_memset_op *lm = &ops[n_ops - 1];
+
+ /*
+ * fail4 CONT.53: H2D-shaped copies (virtual
+ * device destination, virtual host source)
+ * read the driver's ping-pong staging. The
+ * driver paces restaging on the copy's
+ * completion fence; our execution may run
+ * later than the push's processing position,
+ * so capture the source NOW (bounded pool)
+ * and let svc_copy execute from the capture -
+ * the hardware's in-order staging read.
+ */
+ /*
+ * H2D shape ONLY: device destination (VID
+ * aperture), host source (SYS). A D2H readback
+ * (host dst) captured here would freeze
+ * pre-kernel zeros (observed in the r300
+ * captures) and break readbacks.
+ */
+ if (lm->type == 1 && !lm->phys &&
+ lm->dst >= SVC_VAS_FLOOR &&
+ lm->aperture == CE_APERTURE_VID &&
+ !lm->src_phys &&
+ lm->src >= SVC_VAS_FLOOR &&
+ lm->bytes <= SVC_COPY_SNAP_MAX) {
+ /*
+ * r311: the src_aperture equality
+ * gate never matched the sw-copy
+ * client (the driver sends no
+ * SET_SRC_PHYS_MODE, so src_ap
+ * defaults to VID=0 while the
+ * source is host staging; r302-r310
+ * logged zero captures and the
+ * late executions read refilled
+ * staging). The translation is the
+ * truth: capture only when the
+ * first source page resolves
+ * through the SYS aperture.
+ */
+ u64 spa = 0;
+ u32 sap = 0;
+
+ if (!svc_op_translate(vgpu, lm->src,
+ &spa, &sap) &&
+ sap == CE_APERTURE_SYS)
+ svc_copy_snap_capture(vgpu,
+ lm->dst,
+ lm->src,
+ lm->bytes);
+ if (gsp_sema_dbg &&
+ svc.snap_gate_log < 256) {
+ svc.snap_gate_log++;
+ pr_info("sema-snapgate: dst=0x%llx src=0x%llx trc=%d sap=%u\n",
+ lm->dst, lm->src,
+ svc_op_translate(vgpu, lm->src, &spa, &sap),
+ sap);
+ }
+ }
+
+ /*
+ * fail4 CE linear auto-advance, BOTH sides: the
+ * driver re-sets only ONE offset per op and the
+ * copy engine advances the other by the transfer
+ * size. H2D ops show an explicit advancing DST
+ * with a FIXED SRC base (every 1 MB chunk read
+ * the source base again: vector_add's device a
+ * held h_a[0..1MB) x4 -> 75% mismatch; the
+ * readbacks are the mirror image). Advance both
+ * registers; an explicit re-set simply overrides.
+ */
+ {
+ u64 adv = (lm->type == 1) ? lm->bytes :
+ (u64)lm->elem * lm->count;
+
+ st.offset_out += adv;
+ if (lm->type == 1)
+ st.offset_in += adv;
+ }
+ if (svc.opadd_log < 2048) {
+ svc.opadd_log++;
+ pr_info("sema-opadd: h=0x%x type=%u dst=0x%llx src=0x%llx bytes=%llu elem=%u count=%u ap=%u src_ap=%u phys=%u src_phys=%u\n",
+ ch->h_object, lm->type,
+ lm->dst, lm->src, lm->bytes,
+ lm->elem, lm->count,
+ lm->aperture, lm->src_aperture,
+ lm->phys, lm->src_phys);
+ }
+ }
+
+ /*
* Both release encodings write the payload word first
* (NvGpuSemaphore.data[0] for the four-word format, the
* single word for the one-word format), and the client's
@@ -6543,6 +10405,10 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
st.sem_va_lo;
st.rel_va = sem_va;
st.rel_payload = st.sem_payload;
+ svc_wait_rel_va = sem_va;
+ svc_wait_rel_payload = st.sem_payload;
+ svc_wait_rel_off = off;
+ svc_wait_rel_have = true;
st.have_rel = 1;
if (st.have_sem && st.sem_payload &&
@@ -6640,6 +10506,22 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
ch->entry_want = st.sem_payload;
have_cur = true;
pending = 1;
+ /*
+ * fail4: apply this seed ABSOLUTE.
+ * The generic applier still sees the
+ * increment flag (inc), so leaving
+ * the residue in cur makes it write
+ * cur+payload (observed 134+1=135)
+ * - a completed value far past the
+ * guest's queued count, which
+ * trips the guest UVM assert
+ * (uvm_channel.c:204 completed <=
+ * queued) and kills the device.
+ * cur=0 makes inc-apply land the
+ * payload itself, exactly the
+ * re-seed the client polls.
+ */
+ cur = 0;
svc.pool_residue++;
if (svc.pool_residue_log < 32) {
svc.pool_residue_log++;
@@ -6852,7 +10734,44 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
*/
svc.fwd_rel_overflow++;
svc_fwd_soft_copy_inline(vgpu);
- if (svc_rel_apply_launch(vgpu, ch,
+ /*
+ * fail4 CONT.53: never clear a
+ * push's fence before its ops
+ * read their source. The pass's
+ * ops run at the pass end; the
+ * driver's ping-pong staging
+ * restages as soon as the fence
+ * clears, and a later op read
+ * then returns chunk+N bytes
+ * (vector_add +2 MB). Queue the
+ * release through the pending
+ * pass instead.
+ */
+ if (n_ops > 0 ||
+ svc.op_tr_pending) {
+ /*
+ * r316 (CONT.60):
+ * op_tr_pending covers
+ * the denied pass - the
+ * pass built no op
+ * (allow=0) but a copy is
+ * still owed; applying
+ * the fence here (the old
+ * n_ops==0 path) let the
+ * driver refill the
+ * ping-pong staging before
+ * the copy ever ran - the
+ * cold-run capture gap
+ * (vector_add run 1
+ * bad=157696/193536 vs
+ * bad=0 warm).
+ */
+ svc_pending_add(sem_va,
+ st.sem_payload,
+ inc ? 1u : 0u);
+ svc_rel_va_seen = sem_va;
+ svc_rel_count++;
+ } else if (svc_rel_apply_launch(vgpu, ch,
sem_va,
st.sem_payload,
cur, pending,
@@ -6886,7 +10805,20 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
svc_fwd_rel_direct_note(ch->h_object,
sem_va,
st.sem_payload);
- if (svc_rel_apply_launch(vgpu, ch, sem_va,
+ /*
+ * fail4 CONT.53: same ops-order
+ * guard as above - a pass with
+ * pending copy ops must run
+ * them before its fence clears.
+ */
+ if (n_ops > 0) {
+ svc_pending_add(sem_va,
+ st.sem_payload,
+ inc ? 1u : 0u);
+ svc_rel_va_seen = sem_va;
+ svc_rel_count++;
+ } else if (svc_rel_apply_launch(vgpu, ch,
+ sem_va,
st.sem_payload,
cur, pending, inc,
inc_apply)) {
@@ -6906,7 +10838,8 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
off += 4 + count * 4;
}
- if (gsp_sema_dbg && ch->h_object && svc.entry_log < 512) {
+ if (gsp_sema_dbg && ch->h_object &&
+ svc.entry_log < 16384) {
svc.entry_log++;
{
u8 tail[24];
@@ -6920,6 +10853,36 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
tlen ? tail : NULL);
}
}
+
+ /*
+ * m151 Class-2 diagnostic: a CE push with real content that decoded no
+ * release is the one that pays the 8 s grace. Log its decode state
+ * (bounded, content-gated so all-zero reads do not wash it out).
+ */
+ if (gsp_sema_dbg && ch->engine_type && ch->h_object && !st.have_rel) {
+ u8 head[64];
+ u32 hlen = len < sizeof(head) ? len : (u32)sizeof(head);
+ bool nz = false;
+ u32 i;
+
+ for (i = 0; i + 4 <= hlen; i += 4) {
+ u32 w;
+
+ memcpy(&w, buf + i, 4);
+ if (w) {
+ nz = true;
+ break;
+ }
+ }
+ if (nz && svc.entry_norel_log < 2048) {
+ svc.entry_norel_log++;
+ memcpy(head, buf, hlen);
+ pr_info("sema-norel: h=0x%x eng=%u va=0x%llx len=%u ops=%u have_sem=%u rel=0x%llx pay=%u head=%*phN\n",
+ ch->h_object, ch->engine_type, push_va, len,
+ ops_runs, st.have_sem, st.rel_va,
+ st.rel_payload, hlen, head);
+ }
+ }
}
/* ------------------------------------------------------------------ */
@@ -6942,6 +10905,12 @@ struct svc_pending_release {
u64 va;
u32 payload;
u32 op; /* 0 = RELEASE, 1 = REDUCTION (add payload) */
+ u32 h; /* r316: channel the release was decoded for; the
+ * verified pool resolver is channel-keyed and h=0
+ * never matches its per-channel entries (matmul
+ * r322: pool-rel cand resolved at decode (rrc=0,
+ * h=channel) while svc_apply_pending with h=0
+ * starved forever and hung the client). */
/*
* Resolution attempts that found no translation for this VA.
* Telemetry for ISSUE-0001: a pending release that keeps failing to
@@ -6958,25 +10927,67 @@ static struct svc_pending_release svc_pending[SVC_PENDING_MAX];
static u32 svc_pending_n;
static u32 svc_pending_next;
+static u64 svc_pending_rescued; /* stall-class: entries the old >= dedup would have dropped */
+static u32 svc_pending_rescued_log;
+/*
+ * STALLFREE: the pending ring silently overwrote its oldest entry when full
+ * (an evicted release is a client wait that never clears). The counters are
+ * hoisted next to the ledger counters so the periodic diag (above) can print
+ * them; the ring is a bounded queue, so under churn this is the back-pressure
+ * signal, not an error path.
+ */
+
static void svc_pending_add(u64 va, u32 payload, u32 op)
{
u32 i;
if (!payload)
return;
- for (i = 0; i < svc_pending_n; i++)
- if (svc_pending[i].va == va &&
- svc_pending[i].payload >= payload)
+ for (i = 0; i < svc_pending_n; i++) {
+ if (svc_pending[i].va != va)
+ continue;
+ /*
+ * Stall-class fix (R1): only an EXACT duplicate may be
+ * dropped. The old rule dropped every release behind an
+ * already-queued higher payload; in a burst the queue could
+ * end up holding only the last value (observed: payloads 7..34
+ * decoded, queue held {7,34}, the guest word stopped short of
+ * the waiter's value and the client spun forever with
+ * starved=0). Distinct releases must always queue - the apply
+ * pass already stale-drops values the word has passed.
+ */
+ if (svc_pending[i].payload == payload &&
+ svc_pending[i].op == op)
return;
+ if (svc_pending[i].payload >= payload) {
+ svc_pending_rescued++;
+ if (svc_pending_rescued_log < 32) {
+ svc_pending_rescued_log++;
+ pr_info("sema-svc: pending rescue va=0x%llx pay=%u op=%u (queued; ahead entry pay=%u op=%u)\n",
+ va, payload, op,
+ svc_pending[i].payload,
+ svc_pending[i].op);
+ }
+ }
+ }
if (svc_pending_n < SVC_PENDING_MAX) {
i = svc_pending_n++;
} else {
i = svc_pending_next++ % SVC_PENDING_MAX;
+ svc_pending_ovf++;
+ if (svc_pending_ovf_log < 8) {
+ svc_pending_ovf_log++;
+ pr_warn("sema-svc: PENDING-OVF ring full (%u); overwriting oldest entry va=0x%llx pay=%u op=%u (n=%llu)\n",
+ SVC_PENDING_MAX, svc_pending[i].va,
+ svc_pending[i].payload, svc_pending[i].op,
+ svc_pending_ovf);
+ }
}
svc_pending[i].va = va;
svc_pending[i].payload = payload;
svc_pending[i].op = op;
svc_pending[i].tries = 0;
+ svc_pending[i].h = svc.cur_ch;
}
/*
@@ -6988,11 +10999,43 @@ static void svc_pending_add(u64 va, u32 payload, u32 op)
* is reproducibly green. Held releases belong to waiters that no longer
* exist; drop them instead of resolving them against the next session.
*/
+static void svc_gr_diag_reset(void); /* m150: defined with the fill diag */
+
static void svc_session_flush(void)
{
unsigned long flags;
u32 i;
+ /*
+ * m167: never wipe a LIVE session. The flush is requested when a
+ * channel close leaves the table empty, but that transition fires
+ * during ordinary UVM channel cycling and the deferred execution can
+ * land after the same client (or the next client, when its chan_add
+ * raced the requested flush) has registered channels again - r169:
+ * the flush executed 2.5 s after smiley's launch with its channels
+ * live (registered 7 s earlier), wiped the queued completion releases,
+ * and the client spun forever in uvm_channel_is_value_completed
+ * (release never applied; pool word frozen). A live channel's session
+ * owns the tables - skipping is always safe because a genuine session
+ * end re-arms the flush when its own channels close.
+ */
+ {
+ bool live = false;
+
+ spin_lock_irqsave(&svc.chan_lock, flags);
+ for (i = 0; i < SVC_MAX_CHANNELS; i++)
+ if (svc.chan[i].valid) {
+ live = true;
+ break;
+ }
+ spin_unlock_irqrestore(&svc.chan_lock, flags);
+ if (live) {
+ pr_info("sema-svc: session flush skipped: live channel(s) present (deferred flush raced a new session registration)\n");
+ return;
+ }
+ }
+
+ svc_gr_diag_reset(); /* m150: per-session diagnostic budgets */
svc.map_n = 0;
svc_pb_n = 0;
svc_pd_found = 0;
@@ -7000,7 +11043,26 @@ static void svc_session_flush(void)
svc_pd_best = 0;
svc_pd_cands = 0;
svc_pd_ev_hash = 0;
- svc.direct_base = 0;
+ /*
+ * m177c: the UVM ring-space directory is per-client guest memory
+ * (and its samples come from this session's rings): reset with the
+ * session like the primary slot.
+ */
+ svc_pd_r_found = 0;
+ svc_pd_r_attempts = 0;
+ svc_pd_r_base = 0;
+ svc_pd_r_ev_hash = 0;
+ svc_pd_r_n = 0;
+ /*
+ * m177: svc.direct_base is NOT cleared here. It is the guest kernel's
+ * direct-map base - a per-BOOT constant, identical for every client
+ * session of the same guest boot - and it is the gate and input of
+ * the pin-free resolution paths (svc_targeted_resolve). Clearing it
+ * per session forced every client to wait for a rebuild scan while
+ * its first cudaMalloc polled, and made the module depend on the
+ * host pin tools entirely. It is wiped on a guest reboot by
+ * svc_guest_reset_wipe() (gsp_sema_service_guest_reset()).
+ */
svc_pending_n = 0;
svc_pending_next = 0;
/*
@@ -7011,6 +11073,18 @@ static void svc_session_flush(void)
svc_opdef_reset_all();
svc_pool_pa_n = 0;
svc_pool_pa_next = 0;
+ /*
+ * m168 addendum (r171): the UVM semaphore pool is per-CLIENT, not
+ * per-boot. The boot cache's conflict line proved it: the same VA
+ * resolved to a different PA for the next client
+ * (cached=0x1247b5000 new=0x1321fa000). A cache surviving the flush
+ * therefore served the previous client's dead page: the release was
+ * applied (read-back passes on a dead-but-readable page), dropped
+ * from pending, and the live client spun forever. Session-scope the
+ * cache; the scanner's per-client resolution is the source of truth.
+ */
+ svc_pool_boot_n = 0;
+ svc_pool_boot_next = 0;
svc_retry_next = 0;
svc_fwd_rel_n = 0;
svc_grce_dst_ring_next = 0;
@@ -7065,9 +11139,32 @@ static void svc_session_flush(void)
* because the counters were already exhausted by the first client).
*/
svc.counter_rel_dbg = 0;
+ svc.crel_entry_dbg = 0;
+ svc.walk_dbg = 0;
+ svc.gr_rel_auth_log = 0;
svc.counter_noslot_dbg = 0;
svc.sem256_log = 0;
svc.xfer_raw_log = 0;
+ svc.pushtrace_all = 0;
+ svc.opadd_log = 0;
+ svc.dmatrace_log = 0;
+ svc.copyent_log = 0;
+ svc_wait_log = 0;
+ svc_wait_pass_log = 0;
+ svc_remapconst_log = 0;
+ svc_pitch_log = 0;
+ svc_fill_raw_log = 0;
+ svc_lowfill_log = 0;
+ svc_tabcopy_log = 0;
+ svc_fill_noremap = 0;
+ svc_wait_n = 0;
+ svc_wait_rel_have = false;
+ memset(svc_wait_holds, 0, sizeof(svc_wait_holds));
+ svc.bigrun_log = 0;
+ svc.lid_hdr_log = 0;
+ svc.skiprun_log = 0;
+ svc.mystery_log = 0;
+ svc.next_hdr_log = 0;
/*
* The push decode budget (`sema-push` lines) is per-session too: the
* first client of a boot otherwise spends it and the next session's
@@ -7078,6 +11175,14 @@ static void svc_session_flush(void)
svc.entry_log = 0;
svc_pushalt_log = 0;
/*
+ * m169: the op-trace budget is session state too. Left un-reset, the
+ * first client of a boot spends it and the failing client's copy ops
+ * are invisible in exactly the runs that need them (r172: streams2's
+ * H2D evidence was blacked out because the shared budget was spent at
+ * 29704, before the client ran).
+ */
+ svc.op_trace_log = 0;
+ /*
* Per-entry/stream decode diagnostics are per-session: the first
* client otherwise spends them and the second client's CE->GR
* transition is invisible (r24-r30: c2's follow-up pushes never
@@ -7096,6 +11201,8 @@ static void svc_session_flush(void)
memset(svc_retry_q, 0, sizeof(svc_retry_q));
memset(svc_pool_pa, 0, sizeof(svc_pool_pa));
memset(svc_fwd_rel, 0, sizeof(svc_fwd_rel));
+ memset(svc_inline_once, 0, sizeof(svc_inline_once));
+ svc_inline_once_next = 0;
memset(svc_grce_dst_ring, 0, sizeof(svc_grce_dst_ring));
for (i = 0; i < SVC_VAS_SLOTS; i++) {
if (svc_vas[i].data)
@@ -7105,11 +11212,117 @@ static void svc_session_flush(void)
}
svc_vas_n = 0;
memset(svc.lat, 0, sizeof(svc.lat));
+ /* m171 (AGENT-F R2): the write/read ledger is per-session. */
+ svc_ledger_clear();
spin_lock_irqsave(&svc.vas_lock, flags);
for (i = 0; i < SVC_MAX_VASPACES; i++)
svc.vas[i].valid = false;
spin_unlock_irqrestore(&svc.vas_lock, flags);
- pr_info("sema-svc: session flush (all channels closed): discovery state reset for the next client\n");
+ /*
+ * m176: carryover state that previously survived the flush and could
+ * poison the next client:
+ * - user-verified scan failures (tr_failed) held the pool VA page for
+ * 60 s even after the new client's page was reachable by targeted
+ * scan - clear them so the next session starts unblocked;
+ * - a forwarding hold whose channel is gone is resolved against a
+ * recycled handle with stale entry indices - drop it (no channels
+ * are live by definition of this flush);
+ * - the captured GR candidate belongs to the dead session's QMD/push;
+ * offering it to a recycled channel would replay a dead launch.
+ */
+ memset(tr_failed, 0, sizeof(tr_failed));
+ tr_fail_next = 0;
+ svc_fwd_hold_clear();
+ memset(&svc_gr_cand, 0, sizeof(svc_gr_cand));
+ /* m176: re-arm the opdef diagnostic budget (it was never per-session,
+ * so a late client's strand could be silent). */
+ svc_opdef_log = 0;
+ /* One-cycle diag budgets (C2/C3/C7/C8/C9/C10) are per-session too. */
+ svc_relx_log = 0;
+ svc_opdef_clr_log = 0;
+ svc_busy_defer_log = 0;
+ svc_hold_fb_log = 0;
+ svc_cand_ovw_log = 0;
+ svc_inline_recs_log = 0;
+ /*
+ * r401: the exactly-once capture memory is session-scoped. A new
+ * client's pushes restart their release counters, so their content
+ * (and hence hash) can collide with the previous client's - keeping
+ * the table would wrongly classify a fresh launch of the new
+ * session as already captured (LAUNCH-SKIP, silent drop).
+ */
+ memset(svc_gr_seen, 0, sizeof(svc_gr_seen));
+ svc_gr_seen_stale = 0;
+ /* m176: new session generation - channels and scrubbers re-stamp on
+ * registration and never inherit per-session learned state. */
+ svc_sess_gen++;
+ pr_info("sema-svc: session flush (all channels closed): discovery state reset for the next client (gen %u)\n",
+ svc_sess_gen);
+}
+
+/*
+ * m177: boot-scoped wipe for a GUEST boot (RM reload). Unlike the
+ * per-client session flush this clears the state that is valid for one
+ * guest boot only: the direct-map base and every content-scanned table.
+ * Runs on the svc thread (svc_guest_reset_wipe is invoked from the thread
+ * loop below), the only writer of this state.
+ */
+static void svc_guest_reset_wipe(void)
+{
+ svc.direct_base = 0;
+ svc.map_n = 0;
+ svc_pd_found = 0;
+ svc_pd_attempts = 0;
+ svc_pd_best = 0;
+ svc_pd_cands = 0;
+ svc_pd_ev_hash = 0;
+ svc_pd_r_found = 0;
+ svc_pd_r_attempts = 0;
+ svc_pd_r_base = 0;
+ svc_pd_r_ev_hash = 0;
+ svc_pd_r_n = 0;
+ svc_pb_n = 0;
+ svc_pool_boot_n = 0;
+ svc_pool_boot_next = 0;
+ svc_pool_pa_n = 0;
+ svc_pool_pa_next = 0;
+ memset(svc_pool_pa, 0, sizeof(svc_pool_pa));
+ memset(tr_failed, 0, sizeof(tr_failed));
+ tr_fail_next = 0;
+ memset(svc.lat, 0, sizeof(svc.lat));
+ pr_info("sema-svc: guest reset: boot-scoped discovery state wiped (direct_base, map, caches)\n");
+}
+
+/*
+ * m177: called by gsp_rm_emu_reset() when the guest's GSP-RM is (re)loaded
+ * - a new guest boot, where every VA->PA relation can have changed. The
+ * request is latched and executed on the svc thread, which owns the
+ * discovery state (a cross-thread wipe here would race svc_rebuild).
+ */
+void gsp_sema_service_guest_reset(void)
+{
+ WRITE_ONCE(svc_guest_reset_req, 1);
+ if (svc.task)
+ wake_up_process(svc.task);
+}
+
+/*
+ * r317: queue a pass's recorded forward releases through the pending
+ * machinery instead of applying them at a hold break / fallback while an
+ * op of that pass is still owed. Applying the fence early let the driver
+ * refill the ping-pong staging before the copy read it (the cold-run
+ * capture gap: vector_add run 1 of every cold boot, last ~2 chunks). The
+ * pending store applies each release once the word resolves (the copy has
+ * run by then, since the op execution is what clears the pending state on
+ * the retry).
+ */
+static void svc_fwd_rel_queue_pending(struct svc_fwd_rel *rel, u8 n)
+{
+ u8 i;
+
+ for (i = 0; i < n; i++)
+ svc_pending_add(rel[i].va, rel[i].payload,
+ rel[i].inc_claimed ? 1u : 0u);
}
/*
@@ -7143,8 +11356,11 @@ static void svc_apply_pending(struct nv_vgpu_device *vgpu)
* resolver: the learned map can answer with a stale
* page whose value makes this release look already
* delivered and drops it (see svc_sem_write).
+ * r316: resolve with the channel the release was
+ * decoded for (h=0 never matches the per-channel
+ * pool entries - matmul r322 starved/hung).
*/
- if (svc_pool_pa_resolve(vgpu, 0, p->va, &ppa, &pap,
+ if (svc_pool_pa_resolve(vgpu, p->h, p->va, &ppa, &pap,
NULL)) {
/* not translatable yet: keep (ISSUE-0001) */
p->tries++;
@@ -7172,7 +11388,7 @@ static void svc_apply_pending(struct nv_vgpu_device *vgpu)
* a pool VA the base may not have landed: applying while
* the word is still 0 is absorbed, so hold and retry.
*/
- if (svc_pool_sem_va(p->va) && svc_pool_hold_active()) {
+ if (svc_pool_sem_va(p->va) && svc_pool_hold_active_va(p->va)) {
p->tries++;
svc_pending_note_starved(p);
i++;
@@ -7198,7 +11414,7 @@ static void svc_apply_pending(struct nv_vgpu_device *vgpu)
if (!svc_sem_write(vgpu, 0, p->va,
p->op ? cur + p->payload : p->payload)) {
svc.releases_applied++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
if (gsp_sema_dbg)
pr_info("sema-svc: deferred release va=0x%llx payload=%u (was cur=%u)\n",
p->va, p->payload, cur);
@@ -7330,6 +11546,25 @@ static u32 __maybe_unused __svc_apply_page_data_disabled(struct nv_vgpu_device *
}
/*
+ * F1-b: while the forwarded GR launch this release belongs to
+ * is still in flight, the sweep must not author its QMD
+ * completion word - the backend poll would break on a
+ * host-authored ack (r198: qmd_rel satisfied ~6 ms after the
+ * doorbell, zero engine writes). Defer: nothing is written or
+ * consumed here; the release stays in the guest page and later
+ * sweep passes re-see it, while the hold/fallback path owns a
+ * failed hardware copy.
+ */
+ if (gsp_sema_gr_swrel_gate && svc_gr_rel_inflight(va, NULL)) {
+ if (svc.gr_rel_auth_log < 256) {
+ svc.gr_rel_auth_log++;
+ pr_info("sema-diag: GR-REL-AUTHOR src=sweep va=0x%llx pay=%u h=0x%x DEFER\n",
+ va, payload, 0u);
+ }
+ continue;
+ }
+
+ /*
* A release at page offset 0x10 with payload 1 in front of a
* channel's first push identifies the page as that channel's
* push-buffer base (see svc_pb_add).
@@ -7338,9 +11573,21 @@ static u32 __maybe_unused __svc_apply_page_data_disabled(struct nv_vgpu_device *
(lo & 0xfff) < 0x20 && svc_pb_first_push(buf))
svc_pb_add(pa_page, lo & 0xfff);
+ /*
+ * F1-a: tag software authorship of the forwarded launch's
+ * release word (gate off, or the request already resolved and
+ * the forward progress rule still allows the write).
+ */
+ if (svc_gr_rel_match(va, NULL) &&
+ svc.gr_rel_auth_log < 256) {
+ svc.gr_rel_auth_log++;
+ pr_info("sema-diag: GR-REL-AUTHOR src=sweep va=0x%llx pay=%u h=0x%x\n",
+ va, payload, 0u);
+ }
+
if (svc_sem_write(vgpu, 0, va, payload) == 0) {
svc.releases_applied++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
applied++;
if (payload != cur + 1) {
svc.releases_jumped++;
@@ -7559,7 +11806,8 @@ static void svc_pb_discover(struct nv_vgpu_device *vgpu)
u32 slot, k;
bool have = false;
- if (!svc.chan[i].valid || !svc.chan[i].engine_type)
+ if (!svc.chan[i].valid || svc.chan[i].vgpu != vgpu ||
+ !svc.chan[i].engine_type)
continue;
ce = true;
if (svc.chan[i].ce_index >= 32)
@@ -7923,6 +12171,8 @@ static bool svc_push_prefix_ok(const u8 *buf, size_t len)
/* Fallback-line budget for the whole boot. */
static u32 svc_pushpg_fb_log;
+/* Bounded split-read rejection lines (cross-page CE push, r129 class). */
+static u32 svc_pushpg_split_log;
/*
* Read a push the way the engine does: every 4 KB page of the range is
@@ -7942,11 +12192,16 @@ static u32 svc_pushpg_fb_log;
* method stream is rejected and the remaining candidates are tried
* (observed: a wrong injected pin for the push page made every push
* decode no release, starving the client's tracker);
- * 2. validated self-pin (svc_pin_lookup_valid): read-back-validated pages
+ * 2. the displaced injected pair (svc_ipin_alt_lookup): what the page
+ * resolved to before a later injection overwrote the primary slot -
+ * the fall-back when a foreign/stale injection shadowed a good page
+ * (observed r129: a continuation page of a cross-page CE push under
+ * an all-FF pin);
+ * 3. validated self-pin (svc_pin_lookup_valid): read-back-validated pages
* (content rescue, first-push pairing), window guesses excluded;
- * 3. the channel's linear window mapping (svc_pb_win_lookup): last resort
+ * 4. the channel's linear window mapping (svc_pb_win_lookup): last resort
* for a page no validated source knows;
- * 4. outside any window: the remaining trusted sources
+ * 5. outside any window: the remaining trusted sources
* (svc_lookup_trusted), exactly the old behaviour for GR/other pushes.
*
* For a contiguous allocation every path resolves to the same PA the old
@@ -7974,8 +12229,9 @@ static int svc_read_va_push_ex(struct nv_vgpu_device *vgpu,
have_win = !svc_pb_win_lookup(ch, page_va, &win_pa);
- for (src = 0; src < 4 && !done; src++) {
+ for (src = 0; src < 5 && !done; src++) {
bool via_ipin = false;
+ bool untrusted = false;
pa = 0;
ap = SVC_APERTURE_SYS;
@@ -7985,22 +12241,39 @@ static int svc_read_va_push_ex(struct nv_vgpu_device *vgpu,
svc_ipin_lookup(va, &pa, &ap))
continue;
via_ipin = true;
+ untrusted = true;
if (have_win)
svc_pushpg_note(ch, page_va, pa,
win_pa);
break;
case 1:
+ /*
+ * Displaced injected pin (svc_ipin_alt):
+ * the mapping this page had before a later
+ * injection overwrote it. The push decode
+ * is the consumer here, so a pin that no
+ * longer reads like the page's validated
+ * content (all-FF continuation chunks,
+ * see below) can fall back to the pair the
+ * current injection displaced.
+ */
+ if (svc_ipin_alt_lookup(va, &pa, &ap))
+ continue;
+ untrusted = true;
+ break;
+ case 2:
if (svc_pin_lookup_valid(va, &pa, &ap))
continue;
if (have_win)
svc_pushpg_note(ch, page_va, pa,
win_pa);
break;
- case 2:
+ case 3:
if (!have_win)
continue;
pa = win_pa + (va & (SEMA_PAGE_SIZE - 1));
ap = SVC_APERTURE_SYS;
+ untrusted = true;
break;
default:
/* learned cache / discovered map / page
@@ -8026,18 +12299,45 @@ static int svc_read_va_push_ex(struct nv_vgpu_device *vgpu,
!svc_push_prefix_ok(p, chunk))
continue; /* content says: wrong pin */
/*
+ * A continuation chunk (not the push start) read from
+ * an unvalidated source can be a stale/foreign pin:
+ * observed r129, a 216-byte CE push whose last 24
+ * bytes (the SET_SEMAPHORE_A + LAUNCH_DMA) fell in a
+ * page whose injected pin resolved to an all-0xFF
+ * page while the first page was correct. A full
+ * chunk of 0xFF is never a method stream - fresh
+ * emulation pages read 0xFF - so reject it and try
+ * the remaining sources (the displaced alt pair
+ * first). The bounded line names the rejected
+ * (va, pa, src) for the post-mortem.
+ */
+ if (untrusted && !first && chunk >= 64 &&
+ !memchr_inv(p, 0xff, chunk)) {
+ if (svc_pushpg_split_log < 8) {
+ svc_pushpg_split_log++;
+ pr_info("sema-pushpg: split reject va=0x%llx pa=0x%llx src=%s (all-FF) at page 0x%llx\n",
+ va, pa,
+ src == 0 ? "ipin" :
+ (src == 1 ? "ipin-alt" :
+ "win"),
+ page_va);
+ }
+ last_rc = -EIO;
+ continue;
+ }
+ /*
* ISSUE-0002 (r51): a window guess that reads ALL
* ZEROS cannot be right - the paired base page is
* real, so a zero first chunk means the linear window
* does not describe this page (r49: the second
* session's CE push pages read zeros and, because the
* read "succeeded", shadowed the per-page resolvers).
- * Fall through to src=3 instead. Non-zero window
+ * Fall through to src=4 instead. Non-zero window
* content stays trusted exactly as before (r50:
* requiring method-stream plausibility rejected valid
* non-method push prefixes and corrupted c1).
*/
- if (src == 2 && first && !memchr_inv(p, 0, chunk))
+ if (src == 3 && first && !memchr_inv(p, 0, chunk))
continue;
done = true;
}
@@ -8385,7 +12685,7 @@ static void svc_notify_complete(struct nv_vgpu_device *vgpu,
/* Keep the completion visible to clients that start waiting
* after the event fired. */
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
}
}
@@ -8682,6 +12982,9 @@ static bool svc_gr_content_pin(struct nv_vgpu_device *vgpu, u32 rank,
svc_pin_store(push_va, push_pa, SVC_APERTURE_SYS);
svc_lat_store(push_va, push_pa, SVC_APERTURE_SYS);
svc_mark_hot(push_pa);
+ /* m177c: a content-verified UVM-space pair - feed the ring-space
+ * directory discovery (counter pages resolve through it). */
+ svc_pd_ring_note(push_va, push_pa);
if (ring_ap == SVC_APERTURE_FB)
pr_info("sema-chan: content-discovered GR ring rank=%u va=0x%llx -> fb 0x%llx (entry0 va=0x%llx len=%u; push pa=0x%llx; userd rc=%d put=%u get=%u)\n",
rank, ring_va, ring_pa, push_va, len, push_pa, prc,
@@ -8788,7 +13091,8 @@ static void svc_gr_content_discover(struct nv_vgpu_device *vgpu)
int pap = 0;
u32 r = c->gr_index;
- if (!c->valid || c->engine_type || r >= SVC_GR_CONTENT_RANKS)
+ if (!c->valid || c->vgpu != vgpu || c->engine_type ||
+ r >= SVC_GR_CONTENT_RANKS)
continue;
if (test_bit(r, &svc_gr_ring_done))
continue;
@@ -9017,6 +13321,194 @@ bool gsp_sema_counter_derive = true;
unsigned gsp_sema_gr_grace_ms = 8000;
/*
+ * GRFIX FIX1b (r213): absolute per-LAUNCH bound (ms) for the data-stalled
+ * hold of svc_gr_data_fill. The identity is the launch push VA, not the
+ * GPFIFO slot: pin-batch rewinds/replays re-decode the same launch at
+ * different slot indices (r213: entry 107 -> 107 -> 99 -> ... on one
+ * channel), so an idx-keyed deadline re-armed on every pass and never
+ * fired - 410+ holds over 216 s, gr_hold_exp=0, client killed at its 240 s
+ * timeout. The deadline is fixed at the first hold of a launch; when it
+ * passes, svc_gr_request proceeds with the launch exactly as the
+ * pre-8b058884 path did (forward best-effort - never block the guest).
+ */
+unsigned gsp_phys_gr_hold_max_ms = 3000;
+
+/*
+ * GRFIX FIX1e (r215): master gate for the GR fill hold on dropped operand
+ * pages. Default 0 = LOG-ONLY: svc_gr_data_fill never holds on dropped
+ * pages - the drops are logged (GRFILL-DROP) and the launch takes the
+ * historical forward path immediately (exactly the pre-8b058884 behaviour);
+ * the GRFILL-HOLD/GRFILL-EXPIRED machinery and the per-launch absolute
+ * bound are skipped entirely (the pump's own m150/m156 hold and its
+ * absolute bound are NOT - they run regardless of this gate; the record
+ * fields are now in the snapshot write-back list, see the GRFIX NOTE).
+ * When set to 1 the page-count hold + absolute bound apply on the fill
+ * path too.
+ */
+unsigned gsp_phys_gr_fill_hold; /* default 0: log-only, never hold */
+
+/*
+ * r346: trace GMMU walks for unresolved high-VA operands (the arg-miss
+ * diagnostic re-walks each published vaspace for a failing base). When
+ * nonzero, those walks print the per-level entries (L0..L2, PD0 halves)
+ * so a failing VA shows exactly where the walk dies.
+ */
+unsigned gsp_sema_walk_trace;
+
+/*
+ * F1-b: GR release-word authorship gate (default 1). While a forwarded GR
+ * launch is in flight, the RAM-content sweep must not author the very QMD
+ * completion release the backend poll is waiting for: a sweep write could
+ * satisfy the poll before the engine retires the QMD (false completion,
+ * silent corruption). With the gate on, such a write is deferred - the
+ * release stays in RAM and later sweep passes re-see it; when the hardware
+ * copy resolves, the hold/fallback path applies it. 0 restores the
+ * unconditional sweep apply for an A/B.
+ */
+bool gsp_sema_gr_swrel_gate = true;
+
+/*
+ * m164 CLASS-2: hold window (ms) for a readable GR launch push whose decode
+ * saw QMD words but built no launch candidate for its channel (a replay-
+ * suppressed capture, a partial/split QMD, or a push page the pin stream has
+ * not reached yet). The entry is left unconsumed for this window so the next
+ * pass re-decodes it with a forced fresh capture (gr_rehold_idx); on expiry
+ * the historical consume applies. Without this, such entries were consumed
+ * silently and the client's launch never ran (streams2 second vadd: 56
+ * consumes / 2 captures on one channel; the client read zeros).
+ */
+unsigned gsp_sema_gr_nocap_hold_ms = 6000;
+
+/*
+ * m165 (D-F2a): when set (default), a pool release is only deferred by the
+ * post-registration hold while NO host-verified injected pin exists for its
+ * VA; a fresh pin is this session's ground truth and lets the apply land at
+ * pin time (~0.5-2 s) instead of registration+5 s. Set 0 to restore the
+ * legacy blanket 5 s hold.
+ */
+bool gsp_sema_pool_hold_fresh = 1;
+
+/*
+ * m169 (AGENT-F R1): hold device-destination copies whose only
+ * translation is a page-directory walk or RAM scan (no pin). Default on;
+ * 0 restores the historical immediate-write behavior.
+ */
+bool gsp_sema_copy_dst_pin_guard = 1;
+/*
+ * r336/r34x wait gate: honor the C56F host-semaphore acquire ops captured
+ * for a ring entry before running its work. Still DEFAULT OFF (opt-in
+ * experiment; the module param sema_wait_gate=1 enables it), but the
+ * semantics are now the class contract's: op 0 = equal, 2 = strict GEQ,
+ * 3 = circular GEQ, 4 = AND, 5 = NOR; the wait set is captured PER ENTRY
+ * (the old single-last-wait global leaked a later entry's acquire into an
+ * earlier entry - the r340 over-hold); the entry's own release counts only
+ * when its method order precedes the acquire; pending releases our
+ * machinery is merely late about are drained first; and every hold is
+ * bounded at SVC_WAIT_HOLD_MS (100 ms) then fails open loudly ('give-up')
+ * so a value that can only be installed behind an upstream opdef hold
+ * cannot park a channel. Diagnostics: 'sema-wait: pass/hold/give-up' rows
+ * carry op, payload, observed value and decision.
+ */
+bool gsp_sema_wait_gate;
+
+/*
+ * m169b: the guard above must never deadlock the boot's first client. The
+ * first device-destination H2D of a client runs before any launch, and the
+ * pins for device VAs are (re)discovered by the launch/fill paths - so at
+ * that instant NO pin can exist. r173: mail's H2D was held forever
+ * (opdef_held ~6000 passes, six expiry cycles, the entry re-held on every
+ * re-decode because the retry keeps resolving prov>=3). Bound the wait per
+ * destination page: after this many ms the guard releases and the copy runs
+ * with the walk translation (the historical behavior; the walk of a
+ * just-built client table is fresh), logged loudly.
+ */
+unsigned gsp_sema_copy_dst_guard_ms = 3000;
+
+/*
+ * r231 READ-ORDER guard (default 0: witness only). A device-src copy whose
+ * source VA has a PENDING device-dst write hold (the m169 guard window is
+ * still open) reads the PRE-write content: the writer's bytes land only
+ * when the guard resolves, while the reader can execute immediately. The
+ * ctaid_probe RAWA round trip is the reproducer - upload a fresh 64 KB
+ * buffer, read it straight back with no kernel - and the readback came
+ * back as foreign deterministic content. With this on, such a read is
+ * deferred through the existing bounded-hold machinery (-ENOENT) so it
+ * re-decodes after the write resolves; with it off the condition is
+ * logged (READ-ORDER-WIT) so the next capture proves the coincidence.
+ * Opt-in until an A/B proves no green regression.
+ */
+bool gsp_sema_read_order_guard;
+
+/*
+ * COPYWIT fail4: per-boot budget for the sema-bigcopy resolution witnesses
+ * (svc_copy's large-copy proof: both operands' VA -> pa/aperture/provenance
+ * plus the first 16 bytes each side). The shipped default kept the boot's
+ * head only; a failing-client run happens minutes later, so the budget is a
+ * module parameter now - raise it for a discrimination cycle and correlate
+ * each D2H's resolved source PA with the booter's OUT-PAGE-WIT copy-out PA
+ * for the same device VA. 0 disables the lines.
+ */
+unsigned gsp_sema_bigcopy_log_max = 24;
+#define SVC_DST_GUARD_SLOTS 16
+struct svc_dst_guard {
+ u64 va_page;
+ unsigned long first;
+};
+static struct svc_dst_guard svc_dst_guard[SVC_DST_GUARD_SLOTS];
+static u32 svc_dst_guard_next;
+
+/* true = the guard may hold this destination; false = release it. */
+static bool svc_dst_guard_hold(u64 va)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ unsigned long now = jiffies;
+ u32 i, slot = SVC_DST_GUARD_SLOTS;
+
+ for (i = 0; i < SVC_DST_GUARD_SLOTS; i++) {
+ if (svc_dst_guard[i].va_page == page) {
+ if (time_before(now, svc_dst_guard[i].first +
+ msecs_to_jiffies(
+ gsp_sema_copy_dst_guard_ms)))
+ return true;
+ if (svc_dst_guard[i].first) {
+ svc_dst_guard[i].first = 0;
+ svc.copy_dst_relaxed++;
+ pr_info("sema-svc: device-dst guard released without a pin va=0x%llx (using the walk translation)\n",
+ page);
+ }
+ return false;
+ }
+ if (!svc_dst_guard[i].va_page)
+ slot = i;
+ }
+ if (slot == SVC_DST_GUARD_SLOTS)
+ slot = svc_dst_guard_next++ % SVC_DST_GUARD_SLOTS;
+ svc_dst_guard[slot].va_page = page;
+ svc_dst_guard[slot].first = now;
+ return true;
+}
+
+/*
+ * r231 READ-ORDER guard: true while a held device-dst write for this VA
+ * page is still inside its bounded window (the writer's bytes have not
+ * landed yet). See the gsp_sema_read_order_guard declaration.
+ */
+static bool svc_dst_guard_pending(u64 va)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ unsigned long now = jiffies;
+ u32 i;
+
+ for (i = 0; i < SVC_DST_GUARD_SLOTS; i++)
+ if (svc_dst_guard[i].va_page == page &&
+ svc_dst_guard[i].first &&
+ time_before(now, svc_dst_guard[i].first +
+ msecs_to_jiffies(gsp_sema_copy_dst_guard_ms)))
+ return true;
+ return false;
+}
+
+/*
* CE release fallback: when a push carrying a tracking-semaphore release
* cannot be read (and the content rescue failed), advance the channel's
* tracking semaphore to the expected payload directly. The push's data
@@ -9047,6 +13539,24 @@ bool gsp_sema_rel_recover;
bool gsp_sema_dbg;
/*
+ * r365: execute fill-shaped CE transfers whose destination is a VIDMEM
+ * WINDOW VA (>= SVC_VIDMEM_VA_LO, below the 1 TiB VA floor). The guest's
+ * page-table writes arrive exactly this way (dma=0x586, REMAP+PITCH, a
+ * count=1 SET_REMAP_CONST_A, line=4, out=0x2000c0xxx) and previously
+ * decoded no op, so the shadow never saw them and the first client of a
+ * fresh boot dropped operand pages (GRFILL-DROP -> launch held/fail-open
+ * with 8 dropped pages -> wrong results or a client wedge).
+ *
+ * The op is built with the VA unchanged (phys=0): svc_fill's virtual path
+ * resolves it through the guest's own published page tables
+ * (svc_op_translate) and only falls back to the shadow for floor-range
+ * VAs - never through the window convention (r362 proved a forced
+ * `addr - SVC_VIDMEM_VA_BASE` writes the wrong page and corrupts results:
+ * reduce sum=0 + mail hang). Default off for A/B.
+ */
+bool gsp_sema_winfill;
+
+/*
* Skip executing a push's collected work (CE memsets/copies into the vidmem
* shadow) while still applying its semaphore release. The client only ever
* observes the release; the shadow writes exist so the emulation can walk
@@ -9129,7 +13639,8 @@ static void svc_gr_ring_autopin(struct nv_vgpu_device *vgpu,
*/
spin_lock_irqsave(&svc.chan_lock, flags);
for (k = 0; k < SVC_MAX_CHANNELS; k++)
- if (svc.chan[k].valid && svc.chan[k].engine_type &&
+ if (svc.chan[k].valid && svc.chan[k].vgpu == vgpu &&
+ svc.chan[k].engine_type &&
svc.chan[k].ce_index + 1 > max_ce)
max_ce = svc.chan[k].ce_index + 1;
spin_unlock_irqrestore(&svc.chan_lock, flags);
@@ -9471,6 +13982,9 @@ static void svc_pb_pair(struct nv_vgpu_device *vgpu, struct svc_channel *ch)
}
svc_pb[hit_i].va = va;
set_bit(ch->ce_index, &svc_pb_done);
+ /* m177c: the base page was content-read (svc_chan_entry0) - a
+ * verified UVM-space sample for the ring-space directory. */
+ svc_pd_ring_note(va, hit_pa);
/*
* The pinned pairs are verified samples of UVM's own page directory;
@@ -9686,6 +14200,14 @@ static int svc_rescue_try(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
entry_len)) {
svc_pin_store(cand_va_page, cand_pa, SVC_APERTURE_SYS);
svc_lat_store(cand_va_page, cand_pa, SVC_APERTURE_SYS);
+ /*
+ * m150: a content-validated page is stronger evidence than an
+ * unverified host injection. Replace the injected pair so a
+ * wrong injection (e.g. an off-by-one watcher pin) cannot
+ * shadow the validated page on the next read and re-run this
+ * rescue per pass (the second-session rescue crawl).
+ */
+ svc_ipin_store(cand_va_page, cand_pa, SVC_APERTURE_SYS);
svc_mark_hot(cand_pa);
pr_info("sema-svc: content rescue ch=0x%x rank=%u slot=%u entry_va=0x%llx len=%u want=%u -> pa=0x%llx va_page=0x%llx (cand %u/%u)\n",
ch->h_object, ch->ce_index, 4 * ch->ce_index, entry_va,
@@ -9808,6 +14330,12 @@ static int svc_rescue_run_try(struct nv_vgpu_device *vgpu,
for (i = 0; i < npages; i++) {
svc_pin_store(pin_va[i], pin_pa[i], SVC_APERTURE_SYS);
svc_lat_store(pin_va[i], pin_pa[i], SVC_APERTURE_SYS);
+ /*
+ * m150: see svc_rescue_try - validated pages replace
+ * a wrong injection so the rescue is paid once, not
+ * on every rewind pass.
+ */
+ svc_ipin_store(pin_va[i], pin_pa[i], SVC_APERTURE_SYS);
svc_mark_hot(pin_pa[i]);
}
svc.resc_run++;
@@ -9823,6 +14351,19 @@ static int svc_rescue_run_try(struct nv_vgpu_device *vgpu,
}
/*
+ * STALLFREE: wall-clock cap for one content-rescue scan range. The scan is
+ * bounded by the guest-RAM range, not by time: the full fallback walks up to
+ * 6 GB in 4 KB vfio reads, and under a pin storm / leftover crashed client's
+ * churn that walk can run for tens of seconds on the service thread,
+ * delaying every channel (GR launches included). Green scans answer from the
+ * hot set or the near window in milliseconds, so a 3 s cap never fires on a
+ * healthy boot; when it does fire the entry falls through its existing grace
+ * path (hold, then consume with the software release) instead of pinning the
+ * whole pump.
+ */
+#define SVC_RESCUE_SCAN_MAX_MS 3000u
+
+/*
* Scan a guest-RAM range descending for a page that validates as this entry's
* push (method stream + expected release payload), pinning it and returning 0
* on the first hit. `hi` and `lo` are inclusive chunk start addresses.
@@ -9835,10 +14376,20 @@ static int svc_rescue_scan_range(struct nv_vgpu_device *vgpu,
{
u64 gpa = hi;
unsigned long t0 = jiffies;
+ unsigned long deadline =
+ t0 + msecs_to_jiffies(SVC_RESCUE_SCAN_MAX_MS);
u32 total = 0;
u64 bytes = 0;
for (;;) {
+ if (time_after_eq(jiffies, deadline)) {
+ svc_rescue_scan_capped++;
+ if (svc_rescue_scan_capped <= 8)
+ pr_warn("sema-svc: RESCUE-SCAN-CAP h=0x%x va=0x%llx [0x%llx..0x%llx] %llu MB in %u ms (capped; entry keeps its grace path)\n",
+ ch->h_object, entry_va, hi, lo,
+ bytes >> 20, SVC_RESCUE_SCAN_MAX_MS);
+ break;
+ }
if (!svc_read(vgpu, gpa, buf, SVC_SCAN_CHUNK)) {
u32 ncand, n;
@@ -10188,19 +14739,76 @@ static void svc_counter_publish(struct nv_vgpu_device *vgpu,
{
u32 val;
u32 cur = 0;
+ /*
+ * STALL FIX (rank-3 completion-word pollution): the publish must
+ * target the channel's OWN completion word - the descending slot its
+ * pushes release, which the autoreg above registers and which the
+ * decoded releases write (release_pa). Writing the ring PUT into the
+ * ascending rank slot (F1/eea5ea93) made every channel's publish land
+ * in the paired rank (19-r)'s waited word: the busy ctx-init channel
+ * rank 16 (h=0x5c000059, PUT~94) overwrote rank 3's word 0x20400ffc0
+ * from 1 to 94 (`pub-adv h=0x5c000059 pa=...fc0 val=94 cur=1`), the
+ * rank-3 stream's own release (payload 1) was then forward-only-
+ * skipped, and its exact-value sync wait never completed (evtest3
+ * cudaStreamSynchronize(st[2]); victory_streams_events). release_pa
+ * is the push-named truth and wins when known; counter_pa stays the
+ * fallback for a channel whose pushes are unreadable.
+ */
+ u64 pa = ch->release_pa ? ch->release_pa : ch->counter_pa;
- if (!ch->counter_pa)
+ if (!pa)
+ return;
+ /*
+ * COMPLETION GATE (r205 mail/matmul regression): GP_PUT is the
+ * client's SUBMISSION position; the pump's last_get is its next
+ * unconsumed entry. While any submitted entry is outstanding
+ * (last_get != put) the pushes up to put have NOT all completed, so
+ * the put must never be published into the completion word: doing so
+ * short-circuits every client wait (r205 mail: rank-0's word jumped
+ * to the submitted 108 at last_get=104 - `pub-adv h=0x5c000019
+ * pa=...ff0 val=108 cur=104` - ~1 s before the engine ran
+ * `output retirement wait: 1/968 ... settled`, so the host's
+ * sub-put release payloads were then forward-only-skipped and the
+ * client read a stale buffer and failed; matmul's stage ordering
+ * broke the same way). Only the release applies deliver completions;
+ * this publish is the catch-up for entries the pump consumed WITHOUT
+ * a decoded release, so it may run solely when nothing is
+ * outstanding - the same equality the entry loop consumes to
+ * (wrap-safe for the 1024-entry rings).
+ */
+ if (ch->last_get != put)
return;
svc.counter_pub_calls++;
if (gsp_sema_dbg && !ch->engine_type &&
svc.counter_pub_dbg < 96) {
svc.counter_pub_dbg++;
pr_info("sema-chan: pub-info h=0x%x rank=%u pa=0x%llx put=%u last=%u\n",
- ch->h_object, ch->gr_index, ch->counter_pa, put,
+ ch->h_object, ch->gr_index, pa, put,
ch->counter_last);
}
svc_counter_flush_pending(vgpu, ch);
+ /*
+ * mm2 (r207/r208 second-client hang): suppress the GR publish until
+ * the channel is proven to belong to the current session. During a
+ * fresh session's first pass the pump consumes the PREVIOUS
+ * session's residual ring; the completion gate above passes exactly
+ * then (the stale consumption satisfies last_get == put) and the
+ * residual put would land in the client's own completion word
+ * (release_pa). The fresh client's UVM then sees a far-forward
+ * payload followed by its real release sequence - a downward
+ * observation larger than UVM_GPU_SEMAPHORE_MAX_JUMP that corrupts
+ * the tracking counter and wedges the session's first waits (r207
+ * victory_matmul: 1 GR push per channel, zero forwards, 242 s).
+ * gr_pub_armed is set at the fresh-put-drop rewind (session switch
+ * proven) or when a GR channel is seen with put <= 1 (pristine
+ * ring, nothing stale to mispublish); a live mid-stream discovery
+ * (put high, never dropping) loses only this catch-up publish,
+ * which the release applies already cover.
+ */
+ if (!ch->engine_type && !ch->gr_pub_armed)
+ return;
+
val = put;
if (val == ch->counter_last)
return;
@@ -10209,22 +14817,40 @@ static void svc_counter_publish(struct nv_vgpu_device *vgpu,
* advanced the slot past the put position the client last submitted;
* the client polls this u32, so never write it backwards.
*/
- if (svc_read_ap(vgpu, ch->counter_pa, SVC_APERTURE_SYS, &cur,
- sizeof(cur)))
+ if (svc_read_ap(vgpu, pa, SVC_APERTURE_SYS, &cur, sizeof(cur)))
return;
if (val <= cur) {
if (cur > ch->counter_last)
ch->counter_last = cur;
return;
}
- if (svc_write_ap(vgpu, ch->counter_pa, SVC_APERTURE_SYS,
- &val, sizeof(val)))
- return;
+ /*
+ * C5 (GAP-1 H4 secondary): the put-based publish is about to advance
+ * the same word the decoded releases target; a pre-advance here can
+ * make a later absolute release a silent skip. Witness every advance.
+ */
+ if (gsp_sema_dbg)
+ pr_info("sema-diag: pub-adv h=0x%x pa=0x%llx val=%u cur=%u last=%u\n",
+ ch->h_object, pa, val, cur,
+ ch->counter_last);
+ {
+ int wrc = svc_write_ap(vgpu, pa, SVC_APERTURE_SYS,
+ &val, sizeof(val));
+ if (wrc) {
+ if (gsp_sema_dbg && svc.pend_dbg_log < 1024) {
+ svc.pend_dbg_log++;
+ pr_info("sema-diag: pub-adv-wrc h=0x%x pa=0x%llx val=%u wrc=%d (write did not land)\n",
+ ch->h_object, pa, val, wrc);
+ }
+ return;
+ }
+ }
ch->counter_last = val;
+ ch->counter_gen = svc_sess_gen;
if (gsp_sema_dbg && svc.counter_pub_dbg < 48) {
svc.counter_pub_dbg++;
pr_info("sema-chan: counter publish h=0x%x pa=0x%llx val=%u (was %u)\n",
- ch->h_object, ch->counter_pa, val, cur);
+ ch->h_object, pa, val, cur);
}
}
@@ -10318,7 +14944,7 @@ static bool svc_fake_release(struct nv_vgpu_device *vgpu, struct svc_channel *ch
svc.releases_applied++;
if (!gsp_sema_fake_rel)
svc.releases_recovered++;
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
}
}
}
@@ -10372,7 +14998,33 @@ static bool svc_entry_want_reached(struct nv_vgpu_device *vgpu,
/* Mark a consumed GPFIFO slot for the current put epoch. */
static void svc_replay_note(struct svc_channel *ch, u32 idx, u32 put)
{
+ static u32 consume_log;
+
+ /*
+ * fail4 r303: name every consumed slot (all channels). Paired with
+ * the sema-entry decode rows this shows exactly which entries were
+ * consumed without ops and when (the vector_add chunk-drop chase:
+ * a consumed slot whose decode row carried a device-dst copy but
+ * whose op never ran is the silent skip under diagnosis).
+ */
+ if (gsp_sema_dbg && consume_log < 16384) {
+ consume_log++;
+ pr_info("sema-consume: h=0x%x idx=%u put=%u\n",
+ ch->h_object, idx, put);
+ }
(void)put;
+ /*
+ * STALL FIX (entry=82/pay=84 class, r186): the entry leaving the
+ * decode path must never strand a release set that svc_opdef_release()
+ * withheld for it while an operand was pending. svc_opdef_flush() is
+ * only reachable from the entry decode path; once the entry is
+ * consumed (e.g. via the entry_want shortcut) the withheld set sat
+ * until a reset/drop cleared it WITHOUT applying, and the guest's
+ * tracker word stopped short of the waiter's value (evtest3
+ * cudaStreamSynchronize(st[2]) hung with starved=0). Deliver the set
+ * here, forward-only, before the slot is marked done.
+ */
+ svc_opdef_finish_ch(ch, idx);
if (idx < SVC_GPFIFO_MAX_BITS)
set_bit(idx, ch->slot_done_epoch);
}
@@ -10499,7 +15151,29 @@ struct svc_fwd_req {
bool gr;
bool gr_bridge_done;
bool gr_page_overflow;
+ /*
+ * STALLFREE: bounded-completion state for the forwarded launch. The
+ * request's backend outcome is the only host edge that can complete a
+ * launch whose release records engage no hold (svc_fwd_rel_n == 0);
+ * gr_swrel_done is the exactly-once latch for the software authorship
+ * of the QMD completion word, and gr_fill_jiffies stamps the request
+ * so the GR-STALL line reports the real waited time.
+ */
+ bool gr_swrel_done;
+ /*
+ * STALLFREE: set by the work item when release0 could not target the
+ * guest page (unresolved release PA / HPA), so a successful launch's
+ * engine completion lands only on the module semaphore and the guest
+ * QMD word stays behind. The pump's bounded outcome check then
+ * authors it in software (the same write the hold's success edge
+ * performs), so the client always completes.
+ */
+ bool gr_rel_unobserved;
+ unsigned long gr_fill_jiffies;
u32 gr_h_object;
+ u32 gr_dropped; /* GRFIX: pages the fill's last pass could not
+ * resolve (0 = launch fully resolved); read
+ * by the absolute-bound expiry log */
u64 gr_push_va;
u32 gr_push_len;
u32 gr_push_n;
@@ -10547,6 +15221,8 @@ static struct workqueue_struct *svc_fwd_wq;
static unsigned long svc_fwd_state;
static u64 svc_fwd_requests;
static u64 svc_fwd_dropped;
+static u32 svc_grvaudit_launches; /* GRVAUDIT: per-launch audit counter */
+static u32 svc_readdiv_log; /* r233 READ-DIVERGE-WIT budget */
/*
* Pause gate shared by the pump's request path and the backend release path:
* quiesce takes this lock, sets paused and waits for the in-flight work item;
@@ -10558,6 +15234,34 @@ static DEFINE_MUTEX(svc_fwd_lock);
static bool svc_fwd_paused;
/*
+ * F1 helpers: does a software release write target the release word of the
+ * forwarded GR launch the request slot holds? The slot is written only by the
+ * service thread (this code also runs there) and is never reused while the
+ * hold is active, so no lock is needed - same convention as the existing
+ * svc_fwd_hold_gr_release()/svc_fwd_hold_expired() reads. svc_gr_rel_match()
+ * covers the just-resolved hold (done may already be true); the inflight
+ * variant additionally requires the SVC_FWD_INFLIGHT bit and an unresolved
+ * work item, i.e. the hardware copy the poll is waiting on is still running.
+ */
+static bool svc_gr_rel_match(u64 va, u32 *payload)
+{
+ if (!va || !svc_fwd_req.gr || svc_fwd_req.gr_rel_va != va)
+ return false;
+ if (payload)
+ *payload = svc_fwd_req.gr_rel_payload;
+ return true;
+}
+
+static bool svc_gr_rel_inflight(u64 va, u32 *payload)
+{
+ if (!svc_gr_rel_match(va, payload))
+ return false;
+ if (!test_bit(SVC_FWD_INFLIGHT, &svc_fwd_state))
+ return false;
+ return !smp_load_acquire(&svc_fwd_req.done);
+}
+
+/*
* Resolve one captured operand to an HPA. SYS (guest RAM) operands resolve
* the page-aligned GPA through the guest-RAM backing file; VID operands
* resolve the shadow byte offset through the vmalloc backing of BAR1 (the
@@ -10587,11 +15291,29 @@ static int svc_fwd_operand_hpa(struct nv_vgpu_device *vgpu, u8 kind,
return -EFAULT;
*hpa = (u64)page_to_phys(pg);
*out_off = page_off;
+ /*
+ * r372 two-path identity check: the copy-out writes through
+ * __va(hpa) while every reader reads base+pa - if those are
+ * not the same memory the engine's output silently vanishes
+ * (the sum=0 class). Prove it empirically, bounded.
+ */
+ if (svc_fbhpa_log < 16) {
+ u64 at_base = 0, at_va = 0;
+
+ svc_fbhpa_log++;
+ memcpy(&at_base, base + page, sizeof(at_base));
+ memcpy(&at_va, (u8 *)__va(*hpa), sizeof(at_va));
+ pr_info("sema-fbhpa: pa=0x%llx base=%px pg=%px hpa=0x%llx shadow=0x%llx at_base=%016llx at___va=%016llx %s\n",
+ page, base, pg, *hpa,
+ (unsigned long long)svc_fb_shadow_size(vgpu),
+ at_base, at_va,
+ at_base == at_va ? "SAME" : "DIFFER");
+ }
return 0;
}
if (kind != SVC_APERTURE_SYS)
return -EINVAL;
- ret = gsp_phys_gpa_to_hpa(page, hpa);
+ ret = gsp_phys_gpa_to_hpa_vgpu(vgpu, page, hpa);
if (ret)
return ret;
*out_off = page_off;
@@ -10760,9 +15482,10 @@ struct svc_fwd_hold {
static struct svc_fwd_hold svc_fwd_hold;
-static void svc_fwd_hold_abort(u32 h_object)
+static void svc_fwd_hold_abort(struct nv_vgpu_device *vgpu, u32 h_object)
{
if (READ_ONCE(svc_fwd_hold.active) &&
+ svc_fwd_hold.vgpu == vgpu &&
svc_fwd_hold.ch_handle == h_object)
WRITE_ONCE(svc_fwd_hold.aborted, true);
}
@@ -10806,8 +15529,19 @@ static void svc_fwd_rel_apply_launch_deferred(struct nv_vgpu_device *vgpu,
bool pending = r->op != 0;
bool inc = DMA_SEM_REDUCTION_EN(r->dma) &&
DMA_SEM_REDUCTION(r->dma) == SEM_REDUCTION_INC;
+ int rd = svc_sem_read(vgpu, r->va, &cur);
- if (svc_sem_read(vgpu, r->va, &cur)) {
+ /*
+ * C2 (GAP-1 H2/H3): prove the opdef apply handed the record to the
+ * helper and what the software read of the release word returned.
+ */
+ if (gsp_sema_dbg && svc_relx_log < 128) {
+ svc_relx_log++;
+ pr_info("sema-diag: relx kind=%u op=%u inc_claim=%u va=0x%llx pay=%u rd=%d\n",
+ (unsigned)r->kind, (unsigned)r->op,
+ (unsigned)r->inc_claimed, r->va, r->payload, rd);
+ }
+ if (rd) {
svc_pending_add(r->va, r->payload, inc ? 1 : 0);
svc_rel_apply_launch(vgpu, ch, r->va, r->payload, 0, pending,
inc, r->inc_claimed);
@@ -10944,8 +15678,31 @@ static struct svc_opdef_ent *svc_opdef_alloc(struct nv_vgpu_device *vgpu,
return NULL;
}
-static void svc_opdef_clear(struct svc_opdef_ent *e)
+/* C3 caller tags for the opdef-clear witness (H2: which path cleared). */
+#define SVC_OPDEF_CLR_APPLY 0u
+#define SVC_OPDEF_CLR_FINISH 1u
+#define SVC_OPDEF_CLR_FLUSH 2u
+#define SVC_OPDEF_CLR_DROP 3u
+#define SVC_OPDEF_CLR_STALE 4u
+#define SVC_OPDEF_CLR_COUNTED 5u
+#define SVC_OPDEF_CLR_SWEEP 6u
+#define SVC_OPDEF_CLR_STOP 7u
+
+static void svc_opdef_clear(struct svc_opdef_ent *e, u32 caller)
{
+ /*
+ * C3 (GAP-1 H2): every clear is witnessed with its caller and the
+ * slot contents, so a "silent" clear (n=0, or a kind the apply
+ * switch ignores) is distinguishable from a delivery.
+ */
+ if (gsp_sema_dbg && e->active && svc_opdef_clr_log < 128) {
+ svc_opdef_clr_log++;
+ pr_info("sema-diag: opdef-clear caller=%u h=0x%x entry=%u n=%u kind0=%u va0=0x%llx pay0=%u\n",
+ caller, e->h_object, e->entry_idx, (unsigned)e->n,
+ e->n ? (unsigned)e->rel[0].kind : 0u,
+ e->n ? e->rel[0].va : 0ull,
+ e->n ? e->rel[0].payload : 0u);
+ }
memset(e, 0, sizeof(*e));
}
@@ -10977,13 +15734,78 @@ static void svc_opdef_apply(struct nv_vgpu_device *vgpu,
if (e->n) {
svc_fwd_rel_apply_all(vgpu, ch, e->rel, e->n);
svc.opdef_applied++;
- if (gsp_sema_dbg && svc_opdef_log < 32) {
+ if (gsp_sema_dbg && svc_opdef_log < (gsp_sema_dbg ? 512 : 32)) {
svc_opdef_log++;
pr_info("sema-opdef: apply h=0x%x entry=%u n=%u (ops complete)\n",
ch->h_object, entry_idx, e->n);
}
}
- svc_opdef_clear(e);
+ svc_opdef_clear(e, SVC_OPDEF_CLR_APPLY);
+}
+
+/*
+ * r34x: is a release to this VA currently withheld by our own machinery?
+ * True when the pending-translation ring holds one (translation late) or
+ * an opdef slot has one recorded (operand hold). The acquire gate calls
+ * this to classify an unsatisfied wait in its diagnostics: pend=1 names
+ * the 'value sits behind our own withhold' case - the bounded 100 ms
+ * give-up is exactly for it (waiting longer cannot install the value
+ * until the upstream entry's ops complete). Never forces an opdef release
+ * here: those releases are withheld because their ops have NOT completed,
+ * and delivering early re-opens the work-before-fence hole they close.
+ */
+static bool svc_wait_pend_release(u64 va)
+{
+ u32 i;
+
+ for (i = 0; i < svc_pending_n; i++)
+ if (svc_pending[i].va == va)
+ return true;
+ for (i = 0; i < SVC_OPDEF_SLOTS; i++) {
+ struct svc_opdef_ent *e = &svc_opdef[i];
+ u8 k;
+
+ if (!e->active)
+ continue;
+ for (k = 0; k < e->n; k++)
+ if (e->rel[k].va == va)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * STALL FIX (r186 entry=82/pay=84): called from svc_replay_note() on every
+ * entry-consume path. If a release set was withheld for this entry while an
+ * operand was pending and the entry is now leaving the decode path (so the
+ * 10 s flush can never run for it), deliver the set forward-only and clear
+ * the slot. A drop without delivery is the stall class: the guest word
+ * stops short of the value its tracker waits for.
+ */
+static void svc_opdef_finish_ch(struct svc_channel *ch, u32 idx)
+{
+ u32 i;
+
+ if (!ch || idx == SVC_ENTRY_NONE)
+ return;
+ for (i = 0; i < SVC_OPDEF_SLOTS; i++) {
+ struct svc_opdef_ent *e = &svc_opdef[i];
+
+ if (!e->active || e->h_object != ch->h_object ||
+ e->vgpu != ch->vgpu || e->entry_idx != idx)
+ continue;
+ if (e->n) {
+ svc_fwd_rel_apply_all(e->vgpu, ch, e->rel, e->n);
+ svc.opdef_delivered_late++;
+ if (svc_opdef_log < 64) {
+ svc_opdef_log++;
+ pr_info("sema-opdef: LATE-FLUSH h=0x%x entry=%u n=%u (entry consumed before deadline; releases delivered)\n",
+ ch->h_object, idx, e->n);
+ }
+ }
+ svc_opdef_clear(e, SVC_OPDEF_CLR_FINISH);
+ return;
+ }
}
/*
@@ -11020,7 +15842,7 @@ static bool svc_opdef_release(struct nv_vgpu_device *vgpu,
if (!svc_opdef_contains(e, r)) {
if (r->payload && e->n < SVC_FWD_REL_MAX) {
e->rel[e->n++] = *r;
- if (gsp_sema_dbg && svc_opdef_log < 32) {
+ if (gsp_sema_dbg && svc_opdef_log < (gsp_sema_dbg ? 512 : 32)) {
svc_opdef_log++;
pr_info("sema-opdef: hold h=0x%x entry=%u kind=%u va=0x%llx pay=%u (operand pending)\n",
ch->h_object, entry_idx,
@@ -11062,7 +15884,7 @@ static bool svc_opdef_release(struct nv_vgpu_device *vgpu,
* flushes the slot and consumes the entry.
*/
static bool svc_opdef_hold(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
- u32 entry_idx)
+ u32 entry_idx, u64 launch_va)
{
struct svc_opdef_ent *e = svc_opdef_find(vgpu, ch->h_object, entry_idx);
@@ -11071,11 +15893,46 @@ static bool svc_opdef_hold(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
if (!e)
return false;
}
+ /*
+ * r361: absolute per-launch bound keyed to the LAUNCH PUSH VA
+ * (the GRFIX lesson, r213). The slot's `since` restarts whenever
+ * a pin-batch rewind replays the entry at a different GPFIFO
+ * index, so a slot-keyed-only bound can be re-armed forever -
+ * observed as opdef=3301/32/0 (held climbing, expired 0) with the
+ * guest wedged. The push VA never changes across replays, so the
+ * first hold of a launch arms an ABSOLUTE deadline nothing moves.
+ */
+ if (!launch_va)
+ launch_va = 1; /* anonymous launch: still absolutely bound */
+ /*
+ * r368: an ACTIVE bound is never re-armed. The pump walks up to
+ * SVC_OPDEF_SLOTS held entries, each with its OWN push VA; the r361
+ * arm-on-VA-change rule restarted the deadline on every entry
+ * change, so with several held launches the deadline moved forever
+ * (streams2 wedge: opdef=955/13/0, held climbing, expired 0). The
+ * first hold arms the absolute deadline; entries held while it is
+ * active ride it and flush together when it fires. A fresh bound is
+ * armed once the previous one has passed.
+ */
+ if (!ch->opdef_hold_dead_va ||
+ time_after_eq(jiffies, ch->opdef_hold_deadline)) {
+ ch->opdef_hold_dead_va = launch_va;
+ ch->opdef_hold_deadline = jiffies +
+ msecs_to_jiffies(SVC_OPDEF_HOLD_MS);
+ ch->opdef_hold_exp_logged = false;
+ }
if (time_before(jiffies,
- e->since + msecs_to_jiffies(SVC_OPDEF_HOLD_MS))) {
+ e->since + msecs_to_jiffies(SVC_OPDEF_HOLD_MS)) &&
+ time_before(jiffies, ch->opdef_hold_deadline)) {
svc.opdef_held++;
return true;
}
+ if (!ch->opdef_hold_exp_logged) {
+ ch->opdef_hold_exp_logged = true;
+ pr_err_ratelimited("vgsp-sema: OPDEF-EXPIRED h=0x%x idx=%u va=0x%llx (absolute per-launch bound %u ms; rewind replays no longer re-arm)\n",
+ ch->h_object, entry_idx, launch_va,
+ SVC_OPDEF_HOLD_MS);
+ }
return false;
}
@@ -11093,7 +15950,7 @@ static void svc_opdef_flush(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
ch->h_object, entry_idx, e->n);
}
svc.opdef_expired++;
- svc_opdef_clear(e);
+ svc_opdef_clear(e, SVC_OPDEF_CLR_FLUSH);
}
/*
@@ -11107,17 +15964,67 @@ static void svc_opdef_drop(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
{
struct svc_opdef_ent *e = svc_opdef_find(vgpu, ch->h_object, entry_idx);
- if (e)
- svc_opdef_clear(e);
+ if (!e)
+ return;
+ /*
+ * STALL FIX (r186/r187 entry=82, va=0x20400fef0 pay=84): when the
+ * forwarding machinery takes ownership of an entry, the withheld set
+ * may NOT ride the forwarded candidate's rel[] (svc_opdef_release()
+ * returned before the release was appended), so a blind clear strands
+ * the guest's tracker word with NO LATE-FLUSH/LOSS/EXPIRED ever
+ * logged (crel freezes right after the launches; evtest3
+ * cudaStreamSynchronize(st[2]) hangs). Counter releases are
+ * monotonic completion signals: apply them forward-only here - a
+ * record at/ahead of the word is a no-op, so the fwd edge cannot
+ * double-apply and an additive reduction is skipped.
+ */
+ {
+ u8 i;
+ u32 delivered = 0;
+
+ for (i = 0; i < e->n; i++) {
+ if (!svc_counter_va(e->rel[i].va))
+ continue;
+ if (e->rel[i].kind == SVC_FWD_REL_906F &&
+ e->rel[i].op == HOST_SEM_OP_REDUCTION)
+ continue; /* additive: not idempotent */
+ svc_fwd_rel_apply_all(vgpu, ch, &e->rel[i], 1);
+ svc.opdef_delivered_late++;
+ delivered++;
+ pr_info_ratelimited("sema-opdef: DROP-APPLY h=0x%x entry=%u va=0x%llx pay=%u (counter release delivered at fwd-ownership handoff)\n",
+ ch->h_object, entry_idx,
+ e->rel[i].va, e->rel[i].payload);
+ }
+ if (delivered < e->n) {
+ svc.opdef_drop_lost += (u32)(e->n - delivered);
+ pr_info_ratelimited("sema-opdef: LOSS h=0x%x entry=%u %u of %u withheld release(s) dropped at fwd-ownership handoff\n",
+ ch->h_object, entry_idx,
+ (u32)(e->n - delivered), e->n);
+ }
+ }
+ svc_opdef_clear(e, SVC_OPDEF_CLR_DROP);
}
/*
* The ring advanced past a slot's entry (a slot for another entry of the
* same channel is being processed): its set belongs to a consumed entry.
* Deliver late (forward-only, so it cannot double-apply) and drop it.
+ *
+ * r406: an entry is only "passed" if the ring actually CONSUMED it
+ * (slot_done_epoch). Without that proof the pin-batch rewind breaks the
+ * invariant: it re-opens lower indices (e.g. 83) while a held entry
+ * (e.g. 91) sits AHEAD in the same epoch, and the old unconditional drop
+ * treated the held entry as consumed and delivered its withheld releases
+ * early. Observed (r406 inplace_scale): the H2D copy's operand was
+ * unresolved (rc=-2), the entry held with its release withheld; a rewind
+ * pass at idx=83 fired drop-stale caller=4 for idx=91 and the release
+ * went out at 104955.751 - while the copy only executed at 104957.418.
+ * The client raced past its blocking memcpy, launched, the launch read
+ * the stale buffer (previous run's data), and the copy landed after the
+ * launch's copy-out: wrong results with 32/1032 "changed".
*/
static void svc_opdef_drop_stale(struct nv_vgpu_device *vgpu,
- struct svc_channel *ch, u32 keep_idx)
+ struct svc_channel *ch, u32 keep_idx, u32 put)
{
int i;
@@ -11127,36 +16034,115 @@ static void svc_opdef_drop_stale(struct nv_vgpu_device *vgpu,
if (!e->active || e->vgpu != vgpu ||
e->h_object != ch->h_object || e->entry_idx == keep_idx)
continue;
+ if (!svc_replay_seen(ch, e->entry_idx, put))
+ continue; /* never consumed: not stale (rewind) */
if (e->n)
svc_fwd_rel_apply_all(vgpu, ch, e->rel, e->n);
- svc_opdef_clear(e);
+ svc_opdef_clear(e, SVC_OPDEF_CLR_STALE);
+ }
+}
+
+/* A slot of THIS pump's guest whose channel is gone from the pass snapshot:
+ * drop it. Slots of other guests are never touched (their channels are not in
+ * this snapshot by construction). */
+static void svc_opdef_drop_counted(struct svc_opdef_ent *e)
+{
+ if (e->n) {
+ svc.opdef_drop_lost += e->n;
+ pr_info_ratelimited("sema-opdef: LOSS entry h=0x%x idx=%u dropped with %u undelivered release(s)\n",
+ e->h_object, e->entry_idx, e->n);
}
+ svc_opdef_clear(e, SVC_OPDEF_CLR_COUNTED);
}
-/* A slot whose channel is gone from this pass's snapshot: drop it. */
-static void svc_opdef_drop_gone(const struct svc_channel *local, int n)
+static void svc_opdef_drop_gone(struct nv_vgpu_device *vgpu,
+ const struct svc_channel *local, int n)
{
int i, k;
for (i = 0; i < SVC_OPDEF_SLOTS; i++) {
struct svc_opdef_ent *e = &svc_opdef[i];
- if (!e->active)
+ if (!e->active || e->vgpu != vgpu)
continue;
for (k = 0; k < n; k++)
- if (local[k].h_object == e->h_object)
+ if (local[k].vgpu == e->vgpu &&
+ local[k].h_object == e->h_object)
break;
if (k == n)
- svc_opdef_clear(e);
+ svc_opdef_drop_counted(e);
}
}
static void svc_opdef_reset_all(void)
{
+ int i;
+
+ /* STALL FIX: a non-applying clear is a silent release loss - count it. */
+ for (i = 0; i < SVC_OPDEF_SLOTS; i++)
+ if (svc_opdef[i].active && svc_opdef[i].n)
+ svc_opdef_drop_counted(&svc_opdef[i]);
memset(svc_opdef, 0, sizeof(svc_opdef));
}
/*
+ * STALL FIX (r189 entry=82/83 va=0x20400fef0 pay=84/85): the 10 s deadline
+ * flush (svc_opdef_flush) is only reachable while the pump keeps RE-DECODING
+ * the entry. When a withheld entry stops being revisited (ring advanced, the
+ * channel went quiet, the entry was parked while the pump moved on), the
+ * deadline never runs and the withheld counter release strands forever -
+ * the guest's tracker word stops short (evtest3 cudaStreamSynchronize(st[2])
+ * hangs; opdef=531/8/0 static, no EXPIRED/LOSS lines). Sweep expired slots
+ * once per service iteration: deliver forward-only to the still-live owner
+ * channel, count a LOSS when the owner is gone.
+ */
+static void svc_opdef_expire_sweep(void)
+{
+ /*
+ * struct svc_channel is >25 KB (per-slot op bitmaps); a stack copy
+ * overflows the 16 KB kernel stack. Only the single service thread
+ * calls this, so a static scratch is safe (same pattern as
+ * svc_handle_chan_dump / svc_pump_channels).
+ */
+ static struct svc_channel ch_scratch;
+ int i;
+
+ for (i = 0; i < SVC_OPDEF_SLOTS; i++) {
+ struct svc_opdef_ent *e = &svc_opdef[i];
+ struct svc_channel *local = NULL;
+ unsigned long flags;
+ int c;
+
+ if (!e->active || !e->n)
+ continue;
+ if (time_before(jiffies, e->since +
+ msecs_to_jiffies(SVC_OPDEF_HOLD_MS)))
+ continue;
+ spin_lock_irqsave(&svc.chan_lock, flags);
+ for (c = 0; c < SVC_MAX_CHANNELS; c++)
+ if (svc.chan[c].valid &&
+ svc.chan[c].vgpu == e->vgpu &&
+ svc.chan[c].h_object == e->h_object) {
+ ch_scratch = svc.chan[c];
+ local = &ch_scratch;
+ break;
+ }
+ spin_unlock_irqrestore(&svc.chan_lock, flags);
+ if (local) {
+ svc_fwd_rel_apply_all(e->vgpu, local, e->rel, e->n);
+ svc.opdef_expired++;
+ pr_info_ratelimited("sema-opdef: SWEEP-EXPIRED h=0x%x entry=%u n=%u applied forward-only (entry parked past the hold window)\n",
+ e->h_object, e->entry_idx, e->n);
+ } else {
+ svc.opdef_drop_lost += e->n;
+ pr_info_ratelimited("sema-opdef: SWEEP-LOSS h=0x%x entry=%u n=%u owner channel gone (releases dropped)\n",
+ e->h_object, e->entry_idx, e->n);
+ }
+ svc_opdef_clear(e, SVC_OPDEF_CLR_SWEEP);
+ }
+}
+
+/*
* Proof line for a held record set resolved without a successful forwarded
* copy (failure/timeout/abort): the releases below are applied directly, so
* none is lost to a hardware copy that never ran. Called with svc_fwd_hold
@@ -11193,6 +16179,16 @@ static void svc_gr_sw_release(struct nv_vgpu_device *vgpu, u32 h, u64 va,
return;
if (!svc_sem_read(vgpu, va, &cur) && cur >= payload)
return;
+ /*
+ * F1-a: this is the hold/fallback authorship of the forwarded launch's
+ * release word (src="hold"), budgeted with the sweep tag so one
+ * 256-line session window covers both authors.
+ */
+ if (svc_gr_rel_match(va, NULL) && svc.gr_rel_auth_log < 256) {
+ svc.gr_rel_auth_log++;
+ pr_info("sema-diag: GR-REL-AUTHOR src=hold va=0x%llx pay=%u h=0x%x\n",
+ va, payload, h);
+ }
if (!svc_sem_write(vgpu, h, va, payload)) {
svc.fwd_gr_sw_rel++;
pr_info("vgsp-work: GR launch release software-applied h=0x%x va=0x%llx pay=0x%x (no successful hardware copy; forward-only)\n",
@@ -11215,6 +16211,73 @@ static void svc_fwd_hold_gr_release(struct nv_vgpu_device *vgpu,
svc_fwd_req.gr_rel_payload);
}
+/*
+ * STALLFREE: bounded completion for a failed forwarded GR launch that no hold
+ * completes. A GR push whose decode recorded no push-level release records
+ * (svc_fwd_rel_n == 0) engages no release hold, so when the backend fails
+ * (ladder timeout, fault abort, refusal) nothing authors the QMD completion
+ * word the guest client polls after the kick: the client's launch wait then
+ * has no host edge and lasts until the guest's own timeout (the r219/r222
+ * stall class, qmd_rel frozen at the previous launch's value).
+ *
+ * This runs on the service thread at the top of every pump pass, before any
+ * request fill of that pass (svc_fwd_request requires SVC_FWD_INFLIGHT free,
+ * which the work item only releases after publishing done, and the fill's
+ * first write is done=false), so a pass that sees done also sees this
+ * request's fields, and every finished request is observed before its slot
+ * can be reused. Authors the release once (forward-only, full resolver - the
+ * same write the hold's failure edge performs), latches gr_swrel_done, and
+ * logs the distinguishable GR-STALL line.
+ *
+ * While a hold is active the hold owns the completion (its failure edge
+ * applies the recorded releases AND the QMD word, bounded by the existing
+ * hold timeout), so this check stands down for it.
+ */
+static void svc_fwd_outcome_check(struct nv_vgpu_device *vgpu)
+{
+ bool done = smp_load_acquire(&svc_fwd_req.done);
+ unsigned long waited;
+
+ if (!svc_fwd_req.gr || svc_fwd_req.vgpu != vgpu ||
+ svc_fwd_req.gr_swrel_done || svc_fwd_hold.active || !done)
+ return;
+ /*
+ * Complete when the backend failed (the never-completing r219/r222
+ * class) OR when a successful launch's release0 could not target the
+ * guest page, so the engine's completion landed only on the module
+ * semaphore and the client's QMD word is still behind (the same edge
+ * the hold's success path covers for held requests).
+ */
+ if (svc_fwd_req.hw_rc == 0 && !svc_fwd_req.gr_rel_unobserved)
+ return;
+ /*
+ * The outcome is final; latch it first so no later pass, hold edge or
+ * consume tail accounts it again (the release write itself is
+ * forward-only, so even a duplicate could not corrupt the word).
+ */
+ svc_fwd_req.gr_swrel_done = true;
+ waited = jiffies_to_msecs(jiffies - svc_fwd_req.gr_fill_jiffies);
+ if (!svc_fwd_req.gr_rel_va || !svc_fwd_req.gr_rel_payload) {
+ pr_warn("vgsp-gr: GR-STALL-backend va=0x%llx waited_ms=%lu action=proceed (launch carries no release record; no word to author)\n",
+ svc_fwd_req.gr_push_va, waited);
+ return;
+ }
+ svc_gr_sw_release(vgpu, svc_fwd_req.gr_h_object,
+ svc_fwd_req.gr_rel_va,
+ svc_fwd_req.gr_rel_payload);
+ svc.fwd_gr_stall_swrel++;
+ if (svc_fwd_req.hw_rc != 0)
+ pr_warn("vgsp-gr: GR-STALL-backend va=0x%llx waited_ms=%lu action=proceed (failed forwarded launch, rc=%d; QMD release authored in software)\n",
+ svc_fwd_req.gr_push_va, waited, svc_fwd_req.hw_rc);
+ else
+ pr_warn("vgsp-gr: GR-STALL-backend va=0x%llx waited_ms=%lu action=proceed (successful launch whose release0 could not target the guest page; QMD release authored in software)\n",
+ svc_fwd_req.gr_push_va, waited);
+ pr_info("vgsp-gr: GR-COMPLETE-SW h=0x%x push=0x%llx rel=0x%llx pay=0x%x waited_ms=%lu n=%llu (backend outcome without a hold)\n",
+ svc_fwd_req.gr_h_object, svc_fwd_req.gr_push_va,
+ svc_fwd_req.gr_rel_va, svc_fwd_req.gr_rel_payload, waited,
+ svc.fwd_gr_stall_swrel);
+}
+
static bool svc_fwd_hold_expired(void)
{
if (svc_fwd_req.gr && !smp_load_acquire(&svc_fwd_req.done))
@@ -11432,6 +16495,15 @@ static void svc_fwd_hold_expire_check(struct nv_vgpu_device *vgpu,
svc_fwd_hold.relay_armed)
svc_fwd_fault_deliver(hv, ch);
/*
+ * STALLFREE: the unpumpable-channel expiry edge (the entry loop never
+ * ran for this hold). The releases and the QMD completion word are
+ * software-applied below; exactly one line per hold.
+ */
+ if (svc_fwd_req.gr)
+ pr_warn("vgsp-gr: GR-STALL-fwd-hold va=0x%llx waited_ms=%u action=proceed (hold expired on an unpumpable channel; releases + QMD word software-applied)\n",
+ svc_fwd_req.gr_push_va,
+ jiffies_to_msecs(jiffies - svc_fwd_hold.since));
+ /*
* P6b wave 2: a skipped candidate whose hardware copy did not complete
* successfully is authored by one software copy here, BEFORE the
* recorded releases below. A successful hardware copy needs none.
@@ -11542,9 +16614,36 @@ static bool svc_fwd_hold_poll(struct nv_vgpu_device *vgpu,
* done && hw_rc == -ETIMEDOUT; abort/cancel paths never write
* guest memory).
*/
+ /*
+ * C7 (GAP-2 A1): the false-completion edge - a hold whose
+ * hardware copy failed/timed out still software-applies the
+ * release set here.
+ */
+ if (gsp_sema_dbg && svc_hold_fb_log < 64) {
+ svc_hold_fb_log++;
+ pr_info("sema-diag: hold-fallback h=0x%x gr=%d done=%d hw_rc=%d nrel=%u rel0=0x%llx/%u -> sw apply\n",
+ ch->h_object, ch->engine_type == 0 ? 1 : 0,
+ done ? 1 : 0, svc_fwd_req.hw_rc,
+ (unsigned)svc_fwd_hold.nrel,
+ svc_fwd_hold.nrel ?
+ svc_fwd_hold.rel[0].va : 0ull,
+ svc_fwd_hold.nrel ?
+ svc_fwd_hold.rel[0].payload : 0u);
+ }
if (done && svc_fwd_req.hw_rc == -ETIMEDOUT &&
svc_fwd_hold.relay_armed)
svc_fwd_fault_deliver(hv, ch);
+ /*
+ * STALLFREE: the bounded failure edge of a held forwarded GR
+ * launch - the releases AND the QMD completion word are
+ * software-applied below, so the client always completes.
+ * Exactly one line per hold (the hold is cleared at the end).
+ */
+ if (svc_fwd_req.gr)
+ pr_warn("vgsp-gr: GR-STALL-fwd-fail va=0x%llx waited_ms=%u action=proceed (hold resolved on backend failure/expiry; releases + QMD word software-applied)\n",
+ svc_fwd_req.gr_push_va,
+ jiffies_to_msecs(jiffies -
+ svc_fwd_hold.since));
svc_fwd_hold_soft_copy(hv);
if (svc_fwd_hold.relay_armed)
svc.fwd_relay_fallback++;
@@ -11570,6 +16669,14 @@ static bool svc_fwd_hold_poll(struct nv_vgpu_device *vgpu,
ch->last_get = (idx + 1) % ch->gpfifo_entries;
ch->rescue_fail = 0;
ch->defer_since = 0;
+ /*
+ * GRFIX FIX1c (r214): do NOT reset the per-launch bound record
+ * here. This consume can belong to a different entry than the held
+ * launch, and resetting on every consume re-armed the bound before
+ * it could pass (r214: 420 re-holds, GRFILL-EXPIRED=0 over 220 s).
+ * The record is keyed by the launch push VA; it resets only on
+ * channel (re)registration or the session diag reset.
+ */
ch->submissions++;
svc.gpfifo_entries_done++;
if (ch->engine_type && ch->expected_sem_payload)
@@ -11586,6 +16693,31 @@ static bool svc_fwd_hold_poll(struct nv_vgpu_device *vgpu,
}
/*
+ * Is this mdev still on the driver list with an open VFIO device? Decides
+ * whether a hold that names it can still be resolved through it: a removed
+ * device must never be dereferenced (its struct can be freed before the next
+ * pump pass would see the hold). Pointer comparisons only - safe to call
+ * with a possibly-stale pointer.
+ */
+static bool svc_vgpu_open(const struct nv_vgpu_device *vgpu)
+{
+ struct nv_vgpu_device *d;
+ bool open = false;
+
+ if (!vgpu)
+ return false;
+ mutex_lock(&nv_vgpu_driver.lock);
+ list_for_each_entry(d, &nv_vgpu_driver.devices, list) {
+ if (d == vgpu && atomic_read(&d->vfio_dev_open_count) > 0) {
+ open = true;
+ break;
+ }
+ }
+ mutex_unlock(&nv_vgpu_driver.lock);
+ return open;
+}
+
+/*
* Channel vanished while a hold was active. Any resolved hold (completed,
* aborted, failed or expired) applies the recorded releases through a shim
* channel - holds exist only for CE channels and the vanished channel object
@@ -11605,6 +16737,18 @@ static void svc_fwd_hold_gone_finish(struct nv_vgpu_device *vgpu)
bool done = smp_load_acquire(&svc_fwd_req.done);
bool aborted = READ_ONCE(svc_fwd_hold.aborted);
+ if (svc_fwd_hold.vgpu && !svc_vgpu_open(svc_fwd_hold.vgpu)) {
+ /*
+ * The owning mdev was removed mid-hold (the mdev cleanup path
+ * already released its session state): its guest can never
+ * observe the releases and dereferencing the device would be a
+ * use-after-free. Drop the hold; the request slot frees for the
+ * next guest.
+ */
+ svc.fwd_hold_canceled++;
+ svc_fwd_hold_clear();
+ return;
+ }
if (!done && !aborted && !svc_fwd_hold_expired())
return;
/*
@@ -11657,6 +16801,15 @@ static void svc_fwd_hold_stop_flush(struct nv_vgpu_device *vgpu)
if (!svc_fwd_hold.active)
return;
+ /*
+ * A hold whose owning mdev is already gone must never be dereferenced:
+ * drop it (the guest cannot observe the releases anyway).
+ */
+ if (svc_fwd_hold.vgpu && !svc_vgpu_open(svc_fwd_hold.vgpu)) {
+ svc.fwd_hold_canceled++;
+ svc_fwd_hold_clear();
+ return;
+ }
done = smp_load_acquire(&svc_fwd_req.done);
/*
* P6b wave 2: the backend is going away; a skipped candidate whose
@@ -11713,6 +16866,10 @@ static void svc_fwd_gr_work(void)
struct gsp_phys_backend_copy_result cp = { 0 };
u64 rel_hpa = 0;
u32 off = 0, i;
+ /* GRVAUDIT: aggregated from the data[]-build loop below (first
+ * problem page in ga_va/ga_used/ga_led; 0 while clean). */
+ u32 ga_unlanded = 0, ga_ptebad = 0;
+ u64 ga_va = 0, ga_used = 0, ga_led = 0;
int hw_rc;
memset(&gs, 0, sizeof(gs));
@@ -11745,6 +16902,7 @@ static void svc_fwd_gr_work(void)
(SEMA_PAGE_SIZE - 1));
u8 rel_kind = (svc_fwd_req.gr_rel_ap == SVC_APERTURE_FB) ?
SVC_APERTURE_FB : SVC_APERTURE_SYS;
+ bool rel_unobserved = false;
if (svc_fwd_req.gr_rel_pa &&
(svc_fwd_operand_hpa(svc_fwd_req.vgpu, rel_kind,
@@ -11757,11 +16915,24 @@ static void svc_fwd_gr_work(void)
* means the engine would author nothing this launch; point
* release0 at the module semaphore instead so the host still
* observes completion.
+ *
+ * STALLFREE: when the guest page could not be targeted at all
+ * (unresolved release PA or HPA), the engine's completion will
+ * land only on the module semaphore and the guest QMD word
+ * stays behind - a launch whose client would otherwise wait
+ * forever even on a SUCCESSFUL submit. Record it so the pump's
+ * bounded outcome check authors the word in software (the
+ * same write the hold's success edge performs). A resolved
+ * page whose word is already ahead needs nothing.
*/
- if (rel_hpa &&
- READ_ONCE(*(u32 *)((u8 *)__va(rel_hpa) + roff)) >=
+ if (rel_hpa) {
+ if (READ_ONCE(*(u32 *)((u8 *)__va(rel_hpa) + roff)) >=
svc_fwd_req.gr_rel_payload)
- rel_hpa = 0;
+ rel_hpa = 0;
+ } else {
+ rel_unobserved = true;
+ }
+ svc_fwd_req.gr_rel_unobserved = rel_unobserved;
}
gs.qmd = svc_fwd_req.gr_qmd;
@@ -11797,6 +16968,96 @@ static void svc_fwd_gr_work(void)
data[gs.n_data].va = dg->va;
data[gs.n_data].hpa = dhpa;
data[gs.n_data].reloc_va = dg->reloc_va;
+ /*
+ * r348 diagnostic: the last 32 high-VA data pages (the output
+ * operands live there) with the exact (gpa, ap) -> hpa the
+ * writeback will use - comparable against the D2H read's
+ * resolution line (sema-bigcopy) for the same VA.
+ */
+ if (dg->va >= SVC_VAS_FLOOR &&
+ i + 32 >= svc_fwd_req.gr_n_data)
+ pr_info("svc-fwd-hpa: va=0x%llx gpa=0x%llx ap=%u hpa=0x%llx bi=%u\n",
+ dg->va, dg->pa, dg->ap, dhpa, i);
+ /*
+ * GRVAUDIT (diagnostic only): audit the m180 landing
+ * authority against the PA this submit will put on the wire
+ * for every tracked data page - the data[]/n_data set the
+ * backend's NOWRITE analysis later enumerates. The lookup
+ * runs BEFORE the store underneath re-records this launch's
+ * own landing, so it reads the pre-existing authority:
+ * unlanded = no landing recorded for this VA at submit;
+ * pte_bad = a landing exists on a different page/aperture
+ * than the PA the submit path computed/used. Pages below
+ * SVC_VAS_FLOOR are outside the ledger's domain (all store
+ * sites gate on the floor) and are not classified.
+ */
+ if (dg->va >= SVC_VAS_FLOOR) {
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ if (svc_ledger_lookup(dg->va, &lpa, &lap)) {
+ ga_unlanded++;
+ if (!ga_va) {
+ ga_va = dg->va;
+ ga_used = dg->pa;
+ ga_led = 0;
+ }
+ } else if ((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (dg->pa & ~(u64)(SEMA_PAGE_SIZE - 1)) ||
+ (u32)lap != (u32)dkind) {
+ ga_ptebad++;
+ if (!ga_va) {
+ ga_va = dg->va;
+ ga_used = dg->pa;
+ ga_led = lpa;
+ }
+ }
+ }
+ /*
+ * r233 READ-DIVERGE-WIT: the engine's read of this VA uses
+ * exactly what the fill resolved (dg->pa: the page whose
+ * content was copied into the bounce). The copy paths (H2D
+ * writes, D2H reads) resolve independently through
+ * svc_op_translate/ledger. For small launches (the probe
+ * class) compare the three for the first recs and flag any
+ * page-level split - the direct witness for a read/write
+ * permutation. Bounded: first 8 recs, 4 lines per launch,
+ * boot budget 128.
+ */
+ if (svc_fwd_req.gr_n_data <= 1024u && i < 8u &&
+ svc_readdiv_log < 128u) {
+ u64 opa = 0, lpa2 = 0;
+ u32 oap = 0, lap2 = 0;
+ bool op_miss, have_led;
+
+ op_miss = svc_op_translate(svc_fwd_req.vgpu, dg->va,
+ &opa, &oap) != 0;
+ have_led = !svc_ledger_lookup(dg->va, &lpa2, &lap2);
+ if (op_miss || (!op_miss &&
+ (opa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (dg->pa & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ (have_led &&
+ (lpa2 & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (dg->pa & ~(u64)(SEMA_PAGE_SIZE - 1)))) {
+ svc_readdiv_log++;
+ pr_info("vgsp-gr: READ-DIVERGE-WIT va=0x%llx fill_pa=0x%llx op_pa=0x%llx op_src=%u op_rc=%d ledger_pa=0x%llx%s\n",
+ dg->va, dg->pa, opa,
+ op_miss ? 0xffffffffu : svc.op_tr_src,
+ op_miss ? -ENOENT : 0,
+ have_led ? lpa2 : 0,
+ op_miss ? " (op-miss)" : "");
+ }
+ }
+ /*
+ * C3 (data class): after the run the backend copies this
+ * page's results back to dg->pa (gsp_phys_booter.c copy-out).
+ * Record that page as the truth for this VA so the guest's
+ * D2H read of an engine-authored output follows the page the
+ * results actually landed on, whatever a later resolver says
+ * (atomics: engine ran, readback read a different page).
+ */
+ if (dg->va >= SVC_VAS_FLOOR)
+ svc_ledger_store(dg->va, dg->pa, dkind);
gs.n_data++;
}
gs.data = data;
@@ -11816,6 +17077,12 @@ static void svc_fwd_gr_work(void)
gs.rel_hpa = rel_hpa;
gs.rel_off = (u32)(svc_fwd_req.gr_rel_va & (SEMA_PAGE_SIZE - 1));
gs.rel_payload = svc_fwd_req.gr_rel_payload;
+ /*
+ * r378: carry the release VA so the backend can re-validate the
+ * release-word write targets (zero-on-retry, restore-on-accept)
+ * through gsp_sema_gr_write_guard before they land.
+ */
+ gs.rel_va = svc_fwd_req.gr_rel_va;
gs.sem_hpa = 0;
gs.sem_off = 0;
gs.sem_payload = 0;
@@ -11827,6 +17094,17 @@ static void svc_fwd_gr_work(void)
gs.raw_qmd_end = svc_fwd_req.gr_push_qmd_end;
gs.h_object = svc_fwd_req.gr_h_object;
+ /*
+ * GRVAUDIT: one line per launch (n_data matches the backend
+ * NOWRITE line's req->n_data); nonzero counts always log, an
+ * all-clean audit logs once per 50 launches.
+ */
+ svc_grvaudit_launches++;
+ if (ga_unlanded || ga_ptebad || !(svc_grvaudit_launches % 50u))
+ pr_info("vgsp-gr: GRVAUDIT n_data=%u unlanded=%u pte_bad=%u sample_va=0x%llx pa_used=0x%llx ledger_pa=0x%llx\n",
+ gs.n_data, ga_unlanded, ga_ptebad,
+ ga_va, ga_used, ga_led);
+
hw_rc = gsp_phys_backend_guest_gr_submit(&gs, &cp);
if (!hw_rc) {
svc.fwd_gr_ok++;
@@ -12098,12 +17376,16 @@ static void svc_fwd_relay_select(struct svc_fwd_req *req)
* svc_gr_req_fill from its QMD pointer association and the captured inline
* bytes. Overflow is a failure (fail-closed), not a silent drop.
*/
+static const char *svc_gr_src_probe(struct nv_vgpu_device *vgpu, u64 va,
+ u64 *pa_out, u32 *ap_out);
static void svc_gr_page_note(struct nv_vgpu_device *vgpu, u64 va, u32 qmd_word)
{
u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
u64 pa = 0;
u32 ap = 0;
u32 i;
+ /* r372 diagnostic: which branch resolved this launch page. */
+ const char *src = "?";
if (!page)
return;
@@ -12122,10 +17404,132 @@ static void svc_gr_page_note(struct nv_vgpu_device *vgpu, u64 va, u32 qmd_word)
i = svc_fwd_req.gr_n_pages++;
svc_fwd_req.gr_pages[i].va = page;
svc_fwd_req.gr_pages[i].qmd_word = qmd_word;
+ /*
+ * m181 (relocation/ledger unification): the unified landing table is
+ * the authority for a page a host write already landed on. The CE
+ * inline writes that author these launch pages (param, program,
+ * constant banks, inline destinations) go through
+ * svc_grce_inline_write(), which records its landing. The backend's
+ * QMD relocation reads the page content through pg->pa (win0 shadow
+ * copy) and the m129 mirror patches that same page, so resolving
+ * independently here could hand the engine a stale page whose
+ * pointer values are not this launch's VAs: the relocation then
+ * matches nothing, the engine executes against unrelocated guest VAs
+ * it cannot walk, and the grid aborts silently (zero stores, ack
+ * still delivered) - the cross-session zero-write class - or runs on
+ * the previous client's parameters (the multi-page wrongness).
+ * Resolve once, from the table, and record the independent answer as
+ * stale when it differs.
+ */
+ {
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ /*
+ * r372 pin-wins: a live injected pin is the guest's newest
+ * statement about this VA. If it disagrees with the landing
+ * table, the pin wins and the table is repaired (see the
+ * PIN-REPAIR note in svc_handle_map_add): the old landing was
+ * a freed page, and resolving the launch there wrote engine
+ * output onto memory the guest kernel had reused.
+ */
+ {
+ u64 ipa = 0;
+ int iap = SVC_APERTURE_SYS;
+
+ if (!svc_ipin_lookup(page, &ipa, &iap)) {
+ u64 lpa2 = 0;
+ u32 lap2 = 0;
+
+ if (svc_ledger_lookup(page, &lpa2, &lap2) ||
+ ((lpa2 & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (ipa & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ (u32)lap2 != (u32)iap) {
+ svc_ledger_pin_repairs++;
+ if (svc_ledger_pin_repairs <= 32)
+ pr_info("sema-ledger: PIN-WINS note va=0x%llx ledger=0x%llx/%u pin=0x%llx/%d\n",
+ page, lpa2, lap2, ipa, iap);
+ svc_ledger_store(page,
+ ipa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (u32)iap);
+ }
+ svc_fwd_req.gr_pages[i].pa =
+ ipa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ svc_fwd_req.gr_pages[i].ap = (u8)iap;
+ svc_pin_store(page,
+ ipa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ iap);
+ src = "ipin";
+ goto note_done;
+ }
+ }
+ if (!svc_ledger_lookup(page, &lpa, &lap)) {
+ u64 rpa = 0;
+ u32 rap = 0;
+ const char *rsrc = svc_gr_src_probe(vgpu, page, &rpa,
+ &rap);
+
+ if (rpa &&
+ ((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (rpa & ~(u64)(SEMA_PAGE_SIZE - 1)) ||
+ (u32)lap != (u32)rap)) {
+ svc_ledger_reloc_diverges++;
+ if (svc_ledger_reloc_diverges <= 16)
+ pr_info("sema-ledger: RELOC-DIVERGE va=0x%llx ledger_pa=0x%llx %s_pa=0x%llx (ledger wins)\n",
+ page,
+ lpa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ rsrc, rpa &
+ ~(u64)(SEMA_PAGE_SIZE - 1));
+ }
+ svc_fwd_req.gr_pages[i].pa =
+ lpa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ svc_fwd_req.gr_pages[i].ap = (u8)lap;
+ /*
+ * Keep the pin table consistent with the authority so
+ * the win0 copy path and later resolves agree (and no
+ * stale self-pin is planted for this VA).
+ */
+ svc_pin_store(page, lpa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (int)lap);
+ pa = lpa;
+ ap = lap;
+ src = "ledger";
+ goto note_done;
+ }
+ }
if (svc_resolve_va(vgpu, page, &pa, &ap))
return;
svc_fwd_req.gr_pages[i].pa = pa & ~(u64)(SEMA_PAGE_SIZE - 1);
svc_fwd_req.gr_pages[i].ap = (u8)svc_ap_of(ap);
+ /*
+ * r370: store the landing table for EVERY launch page, not just the
+ * data-fill spans. An indirect/bank operand (the kernel's output
+ * buffer, qmd_word NONE) was resolved HERE, the engine wrote it via
+ * this hpa, the copy-out wrote it back to this hpa - and the client's
+ * D2H readback then resolved the same VA through the ledger/pin to a
+ * DIFFERENT page (an older landing), read stale zeros and the kernel
+ * looked wrong (victory_reduce sum=0 with the correct sum proven in
+ * the bounce page by OUT-PAGE-WIT). Recording this resolution makes
+ * the readback read exactly the page the engine wrote.
+ */
+ svc_ledger_store(page, pa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (u32)svc_ap_of(ap));
+ svc_pin_store(page, pa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (int)svc_ap_of(ap));
+ src = "walk";
+note_done:
+ /*
+ * r372 diagnostic: the exact (pa, ap) and branch the launch page
+ * resolved through - the value the engine's hpa and the copy-out use.
+ * A D2H readback that lands elsewhere (the sum=0 class) names its
+ * disagreement against these lines.
+ */
+ if (svc_gr_pagenote_log < 512) {
+ svc_gr_pagenote_log++;
+ pr_info("sema-gr: page-note va=0x%llx pa=0x%llx ap=%u src=%s qmd=%u\n",
+ page, svc_fwd_req.gr_pages[i].pa,
+ svc_fwd_req.gr_pages[i].ap, src, qmd_word);
+ }
}
/* Does any captured inline write land in this page? */
@@ -12159,26 +17563,377 @@ static bool svc_gr_page_inline_covers(u64 page)
* Resolution only happens on the service thread; the work item turns the
* guest PAs into HPAs and the backend plants the identity PTEs.
*/
-#define SVC_GR_DATA_RELOC_BASE 0x110000ULL
+/*
+ * B-2 (large-operand data path): the SVC assigns each resolved operand page
+ * a reloc VA in the grown boot-planted region (the layout constants live in
+ * gsp_phys_booter.h so the two sides cannot drift; gsp_phys_gr_data_region
+ * selects grown vs legacy, default grown). The legacy 0x110000 window below
+ * the mail page (205 data pages max, 68 per pointer) remains selectable with
+ * gsp_phys_gr_data_region=0 for A/B trialing.
+ */
+#define SVC_GR_DATA_RELOC_BASE GSP_PHYS_RMCALL_GR_DATA_BOUNCE_VA0
+#define SVC_GR_DATA_RELOC_BASE_OLD 0x110000ULL
/*
* Per candidate pointer, map at most this many pages from the pointer's
- * page onward (the engine-visible main-PT window has ~220 free 4 KB slots;
- * the demo buffers are small on the native path).
- * m120: lowered 64 -> 32 so every pointer page fits under the 100-page
- * bounce cap (mail 2x32=64, vadd 3x32=96); at 64/ptr the second/third
- * candidate run was truncated by the cap and its pointer word never
- * rewritten (m119: first LDG base c[0x0][0x168] possibly untouched).
- * m140: 32 -> 33. A cudaMalloc'd 128 KB buffer whose pointer is 0x400
- * into its page spans 33 pages (32 full + the 1 KB tail), and the kernel
- * accesses the tail: with 32 pages the tail read the NEXT candidate's
- * relocated page (wrong values; vadd tails). 3x33=99 <= the 100-page cap.
- */
-#define SVC_GR_DATA_PAGES_PER_PTR 33u
-/* m114: total pages the pre-commit bounce window can carry (slot 100 is
- * the mail message). */
-#define SVC_GR_DATA_BOUNCE_MAX 100u
+ * page onward. With the grown region this is a runaway guard, not a budget:
+ * a cold page still stops the run (stop-on-miss below), so a real operand's
+ * span is bounded by its extent. History: 64 -> 32 (m120) -> 33 (m140) under
+ * the old 100-page cap -> 68 (m150, 256 KB operands under the 205-page
+ * window); the old value is kept for the legacy path.
+ */
+#define SVC_GR_DATA_PAGES_PER_PTR 4096u
+#define SVC_GR_DATA_PAGES_PER_PTR_OLD 68u
+/*
+ * Total operand pages the engine-visible data region can carry; derived from
+ * the booter's shared constant (no drift). A cap hit is FAIL-CLOSED: the
+ * fill marks the launch stalled for the hold/retry path and returns instead
+ * of silently truncating later pointers - a truncated operand must never
+ * reach the engine. The legacy value is the pre-B-2 window below the mail
+ * page (GSP_PHYS_RMCALL_GR_MAIL_SLOT = 206).
+ */
+/*
+ * fail4: each launch may relocate up to half of the grown region - the
+ * other half is the alternate base of a concurrently in-flight launch
+ * (see the per-capture base flip in svc_gr_data_fill).
+ */
+#define SVC_GR_DATA_BOUNCE_MAX (GSP_PHYS_RMCALL_GR_DATA_BOUNCE_PAGES / 2u)
+#define SVC_GR_DATA_BOUNCE_MAX_OLD 205u
static u32 svc_gr_data_log; /* m142: bounded per-page attribution log */
+static u32 svc_gr_src_log; /* m150: bounded per-candidate source log */
+static bool svc_gr_data_stalled; /* m150: high data args unresolved */
+/*
+ * m156 (R1): the GR request was refused because a forward was already
+ * in flight (or a release hold was active) - the launch COULD run later, so
+ * the entry must be deferred, not consumed with a fabricated software
+ * release. r157 streams2: kernels 2 and 3 were refused-while-busy (kernel 1
+ * in flight) and soft-released 36 us later; the client read all zeros.
+ */
+static bool svc_gr_busy_defer;
+static u64 svc_gr_hold_log; /* m150: bounded GR-hold proof lines */
+static u64 svc_gr_drop_log_va; /* GRFIX: launch whose dropped-page detail
+ * (GRFILL-DROP) was already logged - the
+ * detail prints once per launch episode,
+ * not once per ~100 ms re-decode */
+
+/* m150: re-arm both fill diagnostics for each client session. */
+static void svc_gr_diag_reset(void)
+{
+ svc_gr_data_log = 0;
+ svc_gr_src_log = 0;
+ svc_gr_drop_log_va = 0;
+}
+
+/*
+ * m150 (second-client zeros diagnosis): identify, read-only, which trusted
+ * source answers a candidate VA at fill time. Mirrors svc_resolve_va's source
+ * order without storing anything, so the fill's choice can be attributed in
+ * the dmesg record (the second client's data VAs resolve through no source -
+ * this names the one that serves the green client and the one missing).
+ * Codes: 0 none, 1 ipin, 2 pin, 3 lat, 4 map, 5 published vaspace,
+ * 6 discovered directory, 7 vidmem window convention.
+ */
+static const char *svc_gr_src_probe(struct nv_vgpu_device *vgpu, u64 va,
+ u64 *pa_out, u32 *ap_out)
+{
+ u64 pa = 0;
+ int ap = SVC_APERTURE_SYS;
+ u64 wpa;
+ int wap;
+
+ if (!svc_ipin_lookup(va, &pa, &ap)) {
+ *pa_out = pa;
+ *ap_out = svc_ap_of(ap);
+ return "ipin";
+ }
+ if (!svc_pin_lookup(va, &pa, &ap)) {
+ *pa_out = pa;
+ *ap_out = svc_ap_of(ap);
+ return "pin";
+ }
+ if (!svc_lat_lookup(va, &pa, &ap)) {
+ *pa_out = pa;
+ *ap_out = svc_ap_of(ap);
+ return "lat";
+ }
+ if (!svc_map_lookup(va, &pa)) {
+ *pa_out = pa;
+ *ap_out = SVC_APERTURE_SYS;
+ return "map";
+ }
+ wpa = 0;
+ wap = SVC_APERTURE_SYS;
+ if (!svc_walk_vaspaces(vgpu, va & ~0xfffULL, &wpa, &wap)) {
+ *pa_out = wpa + (va & 0xfff);
+ *ap_out = svc_ap_of(wap);
+ return "vas";
+ }
+ wpa = 0;
+ wap = SVC_APERTURE_SYS;
+ if (!svc_walk_discovered(vgpu, va & ~0xfffULL, &wpa, &wap)) {
+ *pa_out = wpa + (va & 0xfff);
+ *ap_out = svc_ap_of(wap);
+ return "disc";
+ }
+ if (va >= SVC_VIDMEM_VA_BASE &&
+ va - SVC_VIDMEM_VA_BASE + 4 <= svc_fb_shadow_size(vgpu)) {
+ *pa_out = va - SVC_VIDMEM_VA_BASE;
+ *ap_out = SVC_APERTURE_FB;
+ return "win";
+ }
+ *pa_out = 0;
+ *ap_out = 0;
+ return "none";
+}
+
+/*
+ * relfix PART 3: append one attempt to the resolver's diagnostic chain
+ * ("name:rc,"). Bounded; the tail is simply dropped once full.
+ */
+static void svc_gr_reloc_stage_note(struct gsp_sema_gr_reloc *out,
+ const char *name, int rc)
+{
+ size_t n = strlen(out->stage);
+
+ if (n + 16 >= sizeof(out->stage))
+ return;
+ scnprintf(out->stage + n, sizeof(out->stage) - n, "%s:%d,", name, rc);
+}
+
+/*
+ * relfix (agent-A FIX A): rewrite-time GR relocation resolver (see
+ * gsp_sema_service.h). Mirrors the per-page source order of the fill
+ * (svc_gr_data_fill: svc_ipin_lookup -> svc_resolve_va's trusted chain ->
+ * ledger) and of the read-only attribution probe svc_gr_src_probe above,
+ * then applies the m180 ledger in the fill's two roles: fallback when
+ * nothing above answered (the C1 landing path), and the m171 landing
+ * preference for VAs at/above the ledger floor when the ledger disagrees
+ * with the resolver (the skip-ahead class where a walk result is stale but
+ * the landing is authoritative).
+ *
+ * Deliberately excluded versus svc_resolve_va: pin_store/lat_store writes,
+ * svc_pd_discover and the sliced targeted RAM scan - those mutate
+ * service-thread state or can block for seconds, and this runs on the GR
+ * work item (process context; the page-table walks take svc.vas_lock only
+ * for their publication snapshot and may sleep in vfio_dma_rw, exactly the
+ * discipline the walk comment above svc_walk_vaspaces mandates).
+ *
+ * Every attempt is recorded in out->stage ("name:rc," chain; the resolving
+ * source ends in ":0,") for the booter's RELOC-GAP-STAGE diagnostic.
+ *
+ * Returns 0 with out filled on a successful translation, -ENOENT on a miss
+ * (caller must leave the word untouched), or a conversion errno.
+ */
+int gsp_sema_gr_reloc_resolve(u64 va, struct gsp_sema_gr_reloc *out)
+{
+ struct nv_vgpu_device *vgpu = svc_fwd_req.vgpu;
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u64 pa = 0, lpa = 0, hpa = 0;
+ int ap = SVC_APERTURE_SYS;
+ u32 lap = 0, off = 0;
+ bool resolved = false;
+ int rc, ret;
+
+ if (!vgpu || !out)
+ return -EINVAL;
+
+ out->stage[0] = '\0';
+ out->stage_rc = 0;
+
+ rc = svc_ipin_lookup(page, &pa, &ap);
+ svc_gr_reloc_stage_note(out, "ipin", rc);
+ if (!rc)
+ resolved = true;
+ if (!resolved) {
+ rc = svc_pin_lookup(page, &pa, &ap);
+ svc_gr_reloc_stage_note(out, "pin", rc);
+ if (!rc)
+ resolved = true;
+ }
+ if (!resolved) {
+ rc = svc_lat_lookup(page, &pa, &ap);
+ svc_gr_reloc_stage_note(out, "lat", rc);
+ if (!rc)
+ resolved = true;
+ }
+ if (!resolved) {
+ rc = svc_map_lookup(page, &pa);
+ svc_gr_reloc_stage_note(out, "map", rc);
+ if (!rc)
+ resolved = true;
+ }
+ if (!resolved) {
+ rc = svc_walk_vaspaces(vgpu, page, &pa, &ap);
+ svc_gr_reloc_stage_note(out, "vas", rc);
+ if (!rc)
+ resolved = true;
+ }
+ if (!resolved) {
+ rc = svc_walk_discovered(vgpu, page, &pa, &ap);
+ svc_gr_reloc_stage_note(out, "disc", rc);
+ if (!rc)
+ resolved = true;
+ }
+
+ if (!resolved) {
+ /* C1 fallback: a landing is a page the client already backed
+ * with content even when every resolver missed (r174). */
+ rc = svc_ledger_lookup(page, &lpa, &lap);
+ svc_gr_reloc_stage_note(out, "ledger", rc);
+ if (rc) {
+ out->stage_rc = -ENOENT;
+ return -ENOENT;
+ }
+ pa = lpa;
+ ap = (int)lap;
+ } else if (page >= SVC_VAS_FLOOR) {
+ /* m171: the landing table wins over a disagreeing resolver. */
+ if (!svc_ledger_lookup(page, &lpa, &lap) &&
+ ((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1)) ||
+ (u32)lap != svc_ap_of(ap))) {
+ pa = lpa;
+ ap = (int)lap;
+ }
+ }
+
+ ret = svc_fwd_operand_hpa(vgpu,
+ svc_ap_of(ap) == SVC_APERTURE_FB ?
+ SVC_APERTURE_FB : SVC_APERTURE_SYS,
+ pa, 0, SEMA_PAGE_SIZE, &hpa, &off);
+ if (ret || off) {
+ svc_gr_reloc_stage_note(out, "hpa", ret ? ret : -EFAULT);
+ out->stage_rc = ret ? ret : -EFAULT;
+ return ret ? ret : -EFAULT;
+ }
+
+ out->pa = pa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ out->ap = svc_ap_of(ap);
+ out->hpa = hpa;
+ out->stage_rc = 0;
+ return 0;
+}
+
+/*
+ * r378 write guard, GR backend half (see gsp_sema_service.h). The GR submit
+ * resolves every operand page once and the copy-out/release writes run
+ * seconds later (the bounded poll, the retire wait, the outcome retries); a
+ * guest buffer freed and re-homed inside that window would receive a full
+ * page of another buffer's content through the stale HPA - the guest
+ * boot-corruption class (slab freelist clobbered by a foreign page write).
+ *
+ * Authority order mirrors the fill's own choice (r372/r376 pin-wins): a
+ * FRESH injected pin is the guest's newest statement, so the guard compares
+ * the write target against the pin's page first; only a pin-less VA falls
+ * back to the trusted resolver chain. The landing table is deliberately NOT
+ * the authority here - our own launches feed it, so it would confirm the
+ * target by construction. A miss on every source keeps the historical
+ * behaviour (the recorded landing is the write authority), so the guard can
+ * only remove provably stale writes. Bounded diagnostic budget.
+ */
+static u32 svc_wguard_log; /* r378 WGUARD-SKIP budget (256/boot) */
+bool gsp_sema_gr_write_guard(u64 va, u64 hpa, const char *path)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u64 ipa = 0, cur_hpa = 0;
+ int iap = SVC_APERTURE_SYS;
+ u32 off = 0;
+ int ret;
+
+ if (!va || !hpa)
+ return true;
+ if (!svc_ipin_lookup(page, &ipa, &iap)) {
+ u32 kind = svc_ap_of(iap) == SVC_APERTURE_FB ?
+ SVC_APERTURE_FB : SVC_APERTURE_SYS;
+
+ ret = svc_fwd_operand_hpa(svc_fwd_req.vgpu, kind,
+ ipa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ 0, SEMA_PAGE_SIZE, &cur_hpa, &off);
+ if (ret || off)
+ return true; /* pin page unresolvable: keep */
+ } else {
+ struct gsp_sema_gr_reloc r;
+
+ if (gsp_sema_gr_reloc_resolve(va, &r))
+ return true; /* no current answer: keep */
+ cur_hpa = r.hpa;
+ }
+ if ((cur_hpa & ~(u64)(SEMA_PAGE_SIZE - 1)) ==
+ (hpa & ~(u64)(SEMA_PAGE_SIZE - 1)))
+ return true;
+ if (svc_wguard_log < 256) {
+ svc_wguard_log++;
+ pr_warn("vgsp-sema: WGUARD-SKIP path=%s va=0x%llx used=0x%llx now=0x%llx (stale write target)\n",
+ path, va, hpa, cur_hpa);
+ }
+ return false;
+}
+
+/*
+ * r378 write guard, service half (see gsp_sema_service.h). A fresh injected
+ * pin is the guest's newest statement about a VA; a pin that disagrees with
+ * the recorded write target means the page was freed/re-homed (the r372/r376
+ * pin-wins rule applied at the write instead of only at the resolution). A
+ * missing pin keeps the historical behaviour - the recorded landing is then
+ * the write authority - so no green path loses its write.
+ */
+bool gsp_sema_write_guard(u64 va, u64 pa, u32 ap, const char *path)
+{
+ u64 ipa = 0;
+ int iap = SVC_APERTURE_SYS;
+
+ if (!va || !pa || va < SVC_VAS_FLOOR)
+ return true;
+ if (svc_ipin_lookup(va, &ipa, &iap))
+ return true; /* no fresh pin: nothing newer to obey */
+ if ((ipa & ~(u64)(SEMA_PAGE_SIZE - 1)) ==
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1)) &&
+ svc_ap_of(iap) == svc_ap_of((int)ap))
+ return true;
+ if (svc_wguard_log < 256) {
+ svc_wguard_log++;
+ pr_warn("vgsp-sema: WGUARD-SKIP path=%s va=0x%llx used=0x%llx/%u pin=0x%llx/%d (stale write target)\n",
+ path, va, pa, ap, ipa, iap);
+ }
+ return false;
+}
+
+/*
+ * r391 diagnostic (sum=0 class): the landing-table resolution of a GR data VA
+ * plus the first 8 bytes read through it - the exact page the D2H reader's
+ * ledger-first branch will use. Printed by the GR copy-out next to the write
+ * target, so a launch/readback disagreement is visible in one line.
+ */
+bool gsp_sema_gr_va_snap(u64 va, u64 *pa, u32 *ap, u64 *val8)
+{
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ if (svc_ledger_lookup(va, &lpa, &lap))
+ return false;
+ *pa = lpa & ~(u64)(SEMA_PAGE_SIZE - 1);
+ *ap = lap;
+ *val8 = 0;
+ if (!svc_fwd_req.vgpu)
+ return true;
+ if (lap == SVC_APERTURE_FB) {
+ u8 *base = svc_fwd_req.vgpu->bars[1].vaddr;
+ size_t size = svc_fb_shadow_size(svc_fwd_req.vgpu);
+
+ if (base && *pa + 8 <= size)
+ memcpy(val8, base + *pa, 8);
+ } else {
+ svc_read(svc_fwd_req.vgpu, *pa, val8, 8);
+ }
+ return true;
+}
+
+/* r391 diagnostic: the shadow base this service resolves through (compare
+ * against the CE copy path's own vgpu->bars[1].vaddr in the same run). */
+u8 *gsp_sema_gr_shadow_base(void)
+{
+ return svc_fwd_req.vgpu ? svc_fwd_req.vgpu->bars[1].vaddr : NULL;
+}
/*
* m144 (second-client launch-state divergence): collect device-VA-shaped
@@ -12212,7 +17967,7 @@ static void svc_gr_scan_cand(u64 *wins, u32 *n_cand, const u8 *data, u32 bytes)
for (w = 0; w < *n_cand; w++)
if (wins[w] == wv)
break;
- if (w == *n_cand && *n_cand < 16u)
+ if (w == *n_cand && *n_cand < SVC_GR_CAND_MAX)
wins[(*n_cand)++] = wv;
}
}
@@ -12230,15 +17985,164 @@ static void svc_gr_scan_inline(u64 *wins, u32 *n_cand, u64 page_va)
}
}
-static void svc_gr_data_fill(struct nv_vgpu_device *vgpu)
+/*
+ * STALLFREE: identity of the launch currently held/retried on this channel -
+ * the capture's push VA when it belongs to this channel (the busy-refusal
+ * path never reaches the fill, so the request slot can still hold a previous
+ * launch's VA), else the request slot's when it names this channel. The
+ * absolute hold bound is keyed to this identity so a pin-batch rewind that
+ * replays the entry at a different slot index can never re-arm it.
+ */
+static u64 svc_gr_launch_va(const struct svc_channel *ch)
{
- u64 wins[16];
- u32 n_win = 0, n_cand;
+ if (svc_gr_cand.valid && svc_gr_cand.h_object == ch->h_object)
+ return svc_gr_cand.push_va;
+ if (svc_fwd_req.gr && svc_fwd_req.gr_h_object == ch->h_object)
+ return svc_fwd_req.gr_push_va;
+ return 0;
+}
+
+/*
+ * GRFIX FIX1b (r213): absolute per-launch hold bound. The m150/m156 data
+ * hold had no working bound: the 8b058884 deadline was keyed by the GPFIFO
+ * slot index, and a pin-batch rewind/replay re-decodes the same launch push
+ * at different slots (r213: entry 107 -> 107 -> 99 on one channel, other
+ * passes at yet other indices), so "first arm" re-armed on every pass; the
+ * launch held for 216+ s (410+ GRFILL-HOLD lines) until the client's 240 s
+ * timeout killed it (gr_hold_exp=0). The identity is now the launch push VA:
+ * the first hold of a VA arms an ABSOLUTE deadline that nothing moves while
+ * the launch is held. Returns true when the bound has passed (caller must
+ * let the launch proceed); false leaves the record armed.
+ */
+static bool svc_gr_hold_bound_expired(struct svc_channel *ch, u64 launch_va)
+{
+ u32 cap_ms = gsp_phys_gr_hold_max_ms;
+
+ /*
+ * STALLFREE (r213-r215 follow-up): the absolute per-launch bound is
+ * now effective on the default path. Before the write-back fix the
+ * bound's record fields were excluded from the pump snapshot and
+ * zeroed every pass, so this returned false forever and the legacy
+ * m150/m156 hold window (gr_hold_until) was the only expiry - and
+ * that window is keyed to the GPFIFO slot index, which a pin-batch
+ * rewind changes on every replay (the r213 identity bug). Under pin
+ * churn the same launch was therefore re-held indefinitely: r220
+ * held one launch from 155221 to 155417 (196 s, 1631 re-fills of
+ * entry 106) until the guest's 240 s kill. The bound is keyed to the
+ * LAUNCH PUSH VA and nothing re-arms it, so a replayed slot no
+ * longer extends the hold; at the cap the pump falls through to the
+ * historical consume, where STALLFREE authors the QMD completion
+ * release and logs GR-STALL-hold action=proceed. Default cap 3000 ms
+ * (gsp_phys_gr_hold_max_ms) sits above the observed 0.5-2 s pin
+ * latency, so the legitimate pin-late retry still wins.
+ */
+ if (!launch_va)
+ launch_va = 1; /* anonymous launch: still absolutely bound */
+ if (!cap_ms)
+ cap_ms = 3000;
+ /*
+ * r368: mirror of the opdef fix - an ACTIVE bound is never re-armed
+ * by a different launch VA. The pump can hold several launches whose
+ * push VAs differ; arming on every VA change moved the deadline
+ * forever (the same identity bug class as the opdef=955/13/0
+ * streams2 wedge). The first hold arms; others ride it; a fresh
+ * bound arms once the previous one has passed.
+ */
+ if (!ch->gr_hold_dead_va ||
+ time_after_eq(jiffies, ch->gr_hold_deadline)) {
+ ch->gr_hold_dead_va = launch_va;
+ ch->gr_hold_dead_since = jiffies;
+ ch->gr_hold_deadline = jiffies + msecs_to_jiffies(cap_ms);
+ ch->gr_hold_exp_logged = false;
+ }
+ return time_after_eq(jiffies, ch->gr_hold_deadline);
+}
+
+static void svc_gr_data_fill(struct nv_vgpu_device *vgpu,
+ struct svc_channel *ch)
+{
+ u64 wins[SVC_GR_CAND_MAX];
+ bool is_arg[SVC_GR_CAND_MAX]; /* m150: candidate came from the arg scan */
+ u32 n_cand;
u32 off = 0, i, w;
u8 kind;
+ /*
+ * B-2: active data-region geometry. gsp_phys_gr_data_region=1 (the
+ * default) selects the grown 16384-page region planted by the booter
+ * at GSP_PHYS_RMCALL_GR_DATA_BOUNCE_VA0; 0 keeps the legacy 205-page
+ * window below the mail page for A/B trialing.
+ */
+ bool region_on = gsp_phys_gr_data_region_value() != 0;
+ /*
+ * fail4: two concurrent forwarded launches (streams2's vadd pair)
+ * both relocated their operands from the same base, so the second
+ * launch's copy-in overwrote the first's bounce windows and each
+ * engine read the other's inputs (c1 came out as a2+b2). Alternate
+ * the per-launch base across the grown region's two halves (32 MB
+ * each, above any single launch's span), keyed to the capture push
+ * so a retried launch keeps its half, so concurrent launches
+ * cannot collide.
+ */
+ static u64 svc_gr_reloc_half_key;
+ static u8 svc_gr_reloc_half;
+ u64 reloc_off = 0;
+
+ if (region_on) {
+ if (svc_gr_cand.push_va != svc_gr_reloc_half_key) {
+ svc_gr_reloc_half_key = svc_gr_cand.push_va;
+ svc_gr_reloc_half ^= 1;
+ }
+ if (svc_gr_reloc_half)
+ reloc_off = (u64)(GSP_PHYS_RMCALL_GR_DATA_BOUNCE_PAGES /
+ 2u) * SEMA_PAGE_SIZE;
+ }
+ u64 reloc_base = region_on ?
+ SVC_GR_DATA_RELOC_BASE + reloc_off :
+ SVC_GR_DATA_RELOC_BASE_OLD;
+ u32 per_ptr = region_on ? SVC_GR_DATA_PAGES_PER_PTR :
+ SVC_GR_DATA_PAGES_PER_PTR_OLD;
+ u32 bounce_max = region_on ? SVC_GR_DATA_BOUNCE_MAX :
+ SVC_GR_DATA_BOUNCE_MAX_OLD;
+ /*
+ * m150: high (user/device-space) argument candidates and how many of
+ * them produced at least one resolved page. None resolving is the
+ * second-client zeros signature: the engine would run the kernel
+ * against unresolved pointers. Such a launch is held for a bounded
+ * retry instead of forwarded (svc_gr_data_stalled).
+ */
+ u32 n_arg_hi = 0, n_arg_hi_res = 0;
+ /*
+ * GRFIX FIX1 (full-page fail-closed): every page of every tracked
+ * span whose resolution chain (ipin > resolve > ledger) misses is
+ * counted here, first page or mid-span alike. Any drop holds the
+ * launch through the same bounded m150/m156 retry path as the
+ * first-page guard below, so a QMD is never forwarded with a data
+ * pointer left as an unresolvable guest VA (the zero-store class)
+ * and the bounce window is never served stale for this launch.
+ * FIX1b: the hold carries an absolute per-launch bound
+ * (gsp_phys_gr_hold_max_ms) - when it passes the launch proceeds.
+ */
+ u32 dropped_pages = 0;
+ bool bound_hit = false; /* GRFIX FIX1c: the absolute bound fired this
+ * pass - the launch proceeds, no hold */
+ /*
+ * GRFIX FIX1c (r213): detail for the first (up to 4) dropped pages,
+ * so the cycle records WHICH VAs and which candidate class dropped.
+ * There is no read/write direction anywhere in the candidate or
+ * data[] structures (both are va/pa/reloc_va only), so kind is the
+ * truthful candidate origin: 'a' = argument page (c[0x0] param /
+ * inline), 'b' = const-bank page; idx is the candidate index (the
+ * same index the per-candidate probe lines use).
+ */
+ u64 drop_va[4];
+ char drop_kind[4];
+ u32 drop_idx[4];
svc_fwd_req.gr_n_data = 0;
+ svc_fwd_req.gr_dropped = 0;
svc_fwd_req.gr_n_data_wins = 0;
+ svc_gr_data_stalled = false;
+ memset(is_arg, 0, sizeof(is_arg));
/*
* m100: scan every const-bank page the launch carries (QMD words
* 32..47 are the bank LO/HI pairs; the demo's LDG.E base comes from
@@ -12291,6 +18195,57 @@ static void svc_gr_data_fill(struct nv_vgpu_device *vgpu)
* pointers. */
svc_gr_scan_inline(wins, &n_cand, pp->va);
}
+ /* m150: everything collected so far came from the c[0x0] argument
+ * page (or its inline bytes) - these are the pointers the kernel
+ * actually dereferences. */
+ for (w = 0; w < n_cand; w++)
+ is_arg[w] = true;
+ /*
+ * relfix PART 3 (fill candidate superset): the rewrite scans the WHOLE
+ * authored param page (0x0..0xfff) and re-resolves every
+ * device-VA-shaped word it finds; any word this fill does not collect
+ * resurfaces as a RELOC-GAP (r217: the two words the late path fixed
+ * were param/program descriptor fields below 0x150 / past 0x800 that
+ * the argument scan never looked at). Collect the page remainder here,
+ * AFTER the argument marking, so these page words keep is_arg=false -
+ * the per-argument hold and diagnostic semantics stay exactly as
+ * before. Budget priority is also unchanged: the argument candidates
+ * above are resolved first. Launch-internal pages remain filtered at
+ * resolution time (R3 hygiene below).
+ */
+ for (i = 0; i < svc_fwd_req.gr_n_pages; i++) {
+ const struct svc_gr_page *pp = &svc_fwd_req.gr_pages[i];
+ u64 hpa = 0;
+ const u8 *page;
+ u32 k;
+
+ if (pp->qmd_word != 32u || !pp->pa)
+ continue;
+ kind = (pp->ap == SVC_APERTURE_FB) ? SVC_APERTURE_FB :
+ SVC_APERTURE_SYS;
+ if (svc_fwd_operand_hpa(vgpu, kind, pp->pa, 0,
+ SEMA_PAGE_SIZE, &hpa, &off) || off)
+ continue;
+ page = (const u8 *)__va(hpa);
+ if (!page)
+ continue;
+ for (k = 0; k < SEMA_PAGE_SIZE / 8; k++) {
+ u64 v, wv;
+
+ if (k >= 0x150u / 8u && k < 256u)
+ continue; /* arg region: scanned above */
+ v = get_unaligned_le64(page + k * 8);
+ if (v < 0x10000000ULL || v >= (1ULL << 47) ||
+ (v & 3))
+ continue;
+ wv = v & ~(u64)(SEMA_PAGE_SIZE - 1);
+ for (w = 0; w < n_cand; w++)
+ if (wins[w] == wv)
+ break;
+ if (w == n_cand && n_cand < ARRAY_SIZE(wins))
+ wins[n_cand++] = wv;
+ }
+ }
for (i = 0; i < svc_fwd_req.gr_n_pages; i++) {
const struct svc_gr_page *pp = &svc_fwd_req.gr_pages[i];
u64 hpa = 0;
@@ -12308,7 +18263,12 @@ static void svc_gr_data_fill(struct nv_vgpu_device *vgpu)
page = (const u8 *)__va(hpa);
if (!page)
continue;
- for (k = 0; k < 256; k++) {
+ /*
+ * relfix PART 3: whole page, matching the rewrite's scan
+ * range (it walks 0x0..0xfff qword-pairs); the old 0..0x7ff
+ * bound left the second half as permanent RELOC-GAPs.
+ */
+ for (k = 0; k < SEMA_PAGE_SIZE / 8; k++) {
u64 v = get_unaligned_le64(page + k * 8);
u64 wv;
@@ -12326,15 +18286,84 @@ static void svc_gr_data_fill(struct nv_vgpu_device *vgpu)
* inline-captured bytes as well (see svc_gr_scan_inline). */
svc_gr_scan_inline(wins, &n_cand, pp->va);
}
- /* m110: one contiguous run of pages per candidate pointer, relocated
- * into the engine-walkable main-PT window (0x110000 onward, fresh
- * PTE lines; the commit-time snapshot only preserves that window). */
+ /* m110/B-2: one contiguous run of pages per candidate pointer,
+ * relocated into the active engine-visible data region (grown region
+ * 0x0a000000+ with gsp_phys_gr_data_region=1 - the default - or the
+ * legacy 0x110000 window; both are boot-planted, never-rewritten PTE
+ * ranges the commit-time snapshot preserves). */
for (w = 0; w < n_cand; w++) {
u64 base = wins[w];
u32 p;
u32 before = svc_fwd_req.gr_n_data;
+ bool first_unresolved = false; /* m170: span's first page failed */
+ u64 pba = 0;
+ u32 pba_ap = 0;
+ u32 span;
+ const char *pba_src = "?";
- for (p = 0; p < SVC_GR_DATA_PAGES_PER_PTR; p++) {
+ /*
+ * Window budget discipline (large buffers): only real device
+ * operands (high, cudaMalloc-shaped VAs) get the full per-pointer
+ * span. Low-VA candidates (the launch's own QMD/release/reloc
+ * pointers) need a single page at most, and the launch's QMD VA
+ * is not a kernel operand at all - relocating a full span for it
+ * starved the operands that follow and blew the old window cap
+ * (r141: a 256 KB operand resolved 0 pages because the QMD and
+ * param candidates consumed the window).
+ */
+ if ((base & ~(u64)(SEMA_PAGE_SIZE - 1)) ==
+ (svc_fwd_req.gr_qmd_dev_va & ~(u64)(SEMA_PAGE_SIZE - 1)))
+ continue;
+ /*
+ * R3 hygiene (large-buffer budget discipline): the launch's own
+ * param/bank pages (the qmd_word 32..47 gr_pages entries) and
+ * the param VA are driver bookkeeping, not kernel operands - the
+ * observed fills resolved a full 68-page span for the param page
+ * in every launch and starved the real operands under the old
+ * window. Skip them as data candidates; they are
+ * shadowed/rewritten through gr_pages/Tier 2, not this path.
+ * P6: the QMD program pair (word 48/49) is launch-internal in
+ * exactly the same way and is skipped with them.
+ */
+ if (svc_fwd_req.gr_param_va &&
+ (base & ~(u64)(SEMA_PAGE_SIZE - 1)) ==
+ (svc_fwd_req.gr_param_va & ~(u64)(SEMA_PAGE_SIZE - 1)))
+ continue;
+ {
+ bool launch_internal = false;
+
+ for (i = 0; i < svc_fwd_req.gr_n_pages; i++) {
+ const struct svc_gr_page *pp =
+ &svc_fwd_req.gr_pages[i];
+
+ if (pp->qmd_word >= 32u &&
+ pp->qmd_word <= 49u &&
+ (base & ~(u64)(SEMA_PAGE_SIZE - 1)) ==
+ (pp->va & ~(u64)(SEMA_PAGE_SIZE - 1))) {
+ launch_internal = true;
+ break;
+ }
+ }
+ if (launch_internal)
+ continue;
+ }
+ span = (base >= 0x40000000000ULL) ? per_ptr : 1u;
+
+ if (is_arg[w] && base >= 0x40000000000ULL)
+ n_arg_hi++;
+
+ /* m150: name the source that answers this candidate at fill
+ * time (read-only probe before the real resolution). */
+ if (svc_gr_src_log < 24) {
+ svc_gr_src_log++;
+ pba_src = svc_gr_src_probe(vgpu, base, &pba, &pba_ap);
+ pr_info("vgsp-sema: svc-gr cand[%u]%s base=0x%llx probe=%s pa=0x%llx ap=%u pd_found=%u ipin_n=%u\n",
+ w, is_arg[w] ? " arg" : "", base,
+ pba_src, pba, pba_ap,
+ svc_pd_found, svc.ipin_n);
+ }
+
+ for (p = 0; p < span; p++) {
u64 va = wins[w] + (u64)p * SEMA_PAGE_SIZE;
u64 pa = 0;
u32 ap = 0, x;
@@ -12364,41 +18393,355 @@ static void svc_gr_data_fill(struct nv_vgpu_device *vgpu)
pa = ipa;
ap = (u32)iap;
} else if (svc_resolve_va(vgpu, va, &pa, &ap)) {
- break;
+ /*
+ * C1 (stall/data class): the page may
+ * still be readable - the ledger holds
+ * the landing page of the H2D that
+ * wrote it even when every resolver
+ * misses (r174 cand[] probe=none, 0
+ * pages, bad=65536). Use it before
+ * declaring the page dead; only a page
+ * with no ledger entry can truncate
+ * the span.
+ */
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ if (!svc_ledger_lookup(va, &lpa, &lap)) {
+ pa = lpa;
+ ap = lap;
+ } else {
+ /*
+ * m170 (R4a): the very first
+ * page of this span failed to
+ * resolve - the fail-closed
+ * trigger below distinguishes
+ * it from the benign "0 new
+ * pages" of a span whose
+ * pages were already mapped.
+ *
+ * GRFIX FIX1: every failed
+ * page counts (first or mid-
+ * span) for the launch-level
+ * hold; the span still stops
+ * here exactly as before.
+ * FIX1c: capture the first 4
+ * (VA + candidate origin +
+ * candidate index) for the
+ * GRFILL-DROP detail.
+ */
+ if (p == 0 &&
+ svc_fwd_req.gr_n_data == before)
+ first_unresolved = true;
+ /*
+ * COPYAUDIT (2026-10-18): a
+ * span cut after at least one
+ * authored page is the
+ * completeness hazard - the
+ * rewritten base pointer is
+ * still handed to the engine,
+ * whose base+offset reads then
+ * run past the authored pages
+ * into later operands' bounce
+ * pages. Make the truncation
+ * loud (want is a lower bound:
+ * the guest asked for >= p+1
+ * pages; done is the authored
+ * page count before the cut).
+ */
+ if (p > 0)
+ pr_info_ratelimited("vgsp: COPY-TRUNC va=0x%llx want>=0x%llx done=0x%llx path=gr-fill-span\n",
+ wins[w],
+ ((u64)p + 1) << 12,
+ (u64)p << 12);
+ /*
+ * r377 pin-demand: name every
+ * unresolved operand page as a
+ * stalled VA. The userspace pin
+ * stream watches dmesg for this
+ * exact line and pushes the VA
+ * to the in-guest scanner's
+ * demand file, so the page gets
+ * pinned and the bounded launch
+ * hold (gsp_phys_gr_fill_hold)
+ * resumes on real data instead
+ * of waiting out its bound (the
+ * r345 wedge: held forever
+ * because nobody demanded the
+ * page).
+ */
+ svc_log_stalled_va(va);
+ if (dropped_pages < 4) {
+ drop_va[dropped_pages] = va;
+ drop_kind[dropped_pages] =
+ is_arg[w] ? 'a' : 'b';
+ drop_idx[dropped_pages] = w;
+ }
+ dropped_pages++;
+ break;
+ }
}
}
- if (svc_fwd_req.gr_n_data >= SVC_GR_DATA_BOUNCE_MAX) {
- pr_info("vgsp-sema: svc-gr data fill: %u candidate ptrs, %u pages resolved (reloc 0x%llx+) CAP-HIT\n",
+ /*
+ * m171 (AGENT-F R2): prefer the write/read ledger's
+ * landing page for this operand VA when it disagrees
+ * with the resolution - the kernel then reads exactly
+ * the page a large H2D wrote (r174: write landed on
+ * pa X while the fill read a stale page Y). Compare
+ * aperture-aware (a SYS page and an FB page with the
+ * same numeric address are not the same page).
+ */
+ if (va >= SVC_VAS_FLOOR) {
+ u64 lpa = 0;
+ u32 lap = 0;
+ u64 ipa = 0;
+ int iap = SVC_APERTURE_SYS;
+
+ /*
+ * r372 pin-wins: when a live pin exists it is the
+ * newest truth; the landing table may name a page
+ * the guest has since freed (VA reuse across
+ * clients). Keep the pin and repair the table.
+ * Only a pin-less VA still follows the table.
+ */
+ if (!svc_ipin_lookup(va, &ipa, &iap)) {
+ pa = ipa;
+ ap = (u32)iap;
+ if (!svc_ledger_lookup(va, &lpa, &lap) &&
+ (((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (ipa & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ (u32)lap != (u32)iap)) {
+ svc_ledger_pin_repairs++;
+ if (svc_ledger_pin_repairs <= 32)
+ pr_info("sema-ledger: PIN-WINS fill va=0x%llx ledger=0x%llx/%u pin=0x%llx/%d\n",
+ va,
+ lpa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ lap, ipa, iap);
+ svc_ledger_store(va,
+ ipa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ (u32)iap);
+ }
+ } else if (!svc_ledger_lookup(va, &lpa, &lap) &&
+ ((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ (pa & ~(u64)(SEMA_PAGE_SIZE - 1)) ||
+ (u32)lap != (u32)ap)) {
+ svc_ledger_diverges++;
+ if (svc_ledger_diverges <= 32)
+ pr_info("sema-ledger: DIVERGE fill va=0x%llx wrote=0x%llx read=0x%llx\n",
+ va, lpa, pa);
+ pa = lpa;
+ ap = lap;
+ }
+ }
+ if (svc_fwd_req.gr_n_data >= bounce_max) {
+ /*
+ * B-2 fail-closed cap: the active region is
+ * full and later pointers would be silently
+ * dropped (the old CAP-HIT return forwarded
+ * a truncated operand set). Mark the launch
+ * stalled so svc_gr_request holds/retries it
+ * via the existing bounded hold instead of
+ * forwarding incomplete data.
+ */
+ svc_gr_data_stalled = true;
+ pr_err("vgsp-sema: svc-gr data fill: %u candidate ptrs, %u pages resolved (reloc 0x%llx+) CAP-HIT: region full, launch held fail-closed (no silent truncation)\n",
n_cand, svc_fwd_req.gr_n_data,
- (u64)SVC_GR_DATA_RELOC_BASE);
+ reloc_base);
return;
}
rec = &svc_fwd_req.gr_data[svc_fwd_req.gr_n_data];
rec->va = va;
rec->pa = pa & ~(u64)(SEMA_PAGE_SIZE - 1);
- rec->reloc_va = SVC_GR_DATA_RELOC_BASE +
+ rec->reloc_va = reloc_base +
((u64)svc_fwd_req.gr_n_data << 12);
rec->ap = (u8)svc_ap_of(ap);
svc_fwd_req.gr_n_data++;
/*
+ * r349: record the launch's own resolution as the
+ * ledger's landing authority for this VA. The read
+ * path's m171 read-after-write rule follows the
+ * ledger when it disagrees with the live translation,
+ * and the engine writeback below writes exactly this
+ * (pa, ap). Without the store the ledger kept a stale
+ * page for engine-written outputs (victory_reduce:
+ * the launch wrote FB 0x7c00000 while the ledger
+ * said SYS 0x7c00000; the D2H read followed the
+ * ledger and returned zeros while the correct sum
+ * sat on the written page).
+ */
+ if (va >= SVC_VAS_FLOOR)
+ svc_ledger_store(va, rec->pa, rec->ap);
+ /*
* m142: bounded per-page attribution log. A wrong data
* page is invisible in the aggregate counters; these
* lines let a post-mortem compare the chosen PA against
* the guest's own mapping.
*/
- if (svc_gr_data_log < 16) {
+ if (svc_gr_data_log < 192) {
svc_gr_data_log++;
pr_info("vgsp-sema: svc-gr data page va=0x%llx -> pa=0x%llx ap=%u\n",
va, rec->pa, rec->ap);
}
}
(void)base;
+ if (is_arg[w] && base >= 0x40000000000ULL &&
+ svc_fwd_req.gr_n_data > before)
+ n_arg_hi_res++;
pr_info("vgsp-sema: svc-gr candidate[%u] base=0x%llx -> %u new pages (total %u)\n",
w, base, svc_fwd_req.gr_n_data - before,
svc_fwd_req.gr_n_data);
+ if (is_arg[w] && svc_fwd_req.gr_n_data == before &&
+ first_unresolved) {
+ /*
+ * m170 (AGENT-F R4a): a high argument pointer whose
+ * FIRST page resolved nothing at fill time is the
+ * partial-operand signature - the launch would forward
+ * with stale/zero data for that operand (r174: matmul
+ * cand[2]/[3] probe=none, 0 pages, bad=65536; the
+ * other operands resolved via pins). Fail closed: mark
+ * the launch stalled so the m150 hold keeps the entry
+ * and retries the fill while the pins land (bounded,
+ * then forwards anyway - never a hang, never silent
+ * truncation).
+ */
+ svc_gr_data_stalled = true;
+ pr_err_ratelimited("vgsp-sema: svc-gr data fill: arg base=0x%llx resolved 0 pages; launch held fail-closed\n",
+ base);
+ }
+ /*
+ * m150: an argument pointer that resolves zero pages is the
+ * second-client zeros signature; record the walk result per
+ * published vaspace (pdb/entries/ap + rc) so the post-mortem
+ * shows whether the client's user VA space directory is
+ * published at fill time and whether the walk reaches it.
+ */
+ if (is_arg[w] && svc_fwd_req.gr_n_data == before &&
+ svc_gr_src_log < 64) {
+ int vi;
+ char vb[240];
+ int o = 0;
+
+ svc_gr_src_log++;
+ for (vi = 0; vi < SVC_MAX_VASPACES; vi++) {
+ u64 wa = 0;
+ int wap = SVC_APERTURE_SYS;
+ int wrc;
+
+ if (!svc.vas[vi].valid)
+ continue;
+ wrc = svc_gmmu_walk(vgpu, svc.vas[vi].pdb,
+ svc.vas[vi].entries, base,
+ svc.vas[vi].ap, &wa, &wap,
+ gsp_sema_walk_trace != 0);
+ o += snprintf(vb + o, sizeof(vb) - o,
+ " vas[%d] pdb=0x%llx ent=%u ap=%d rc=%d pa=0x%llx",
+ vi, svc.vas[vi].pdb,
+ svc.vas[vi].entries,
+ svc.vas[vi].ap, wrc, wa);
+ }
+ pr_info("vgsp-sema: svc-gr arg-miss base=0x%llx probe=%s n_cand=%u pd_found=%u ipin_n=%u disc=0x%llx/%d%s\n",
+ base, pba_src, n_cand, svc_pd_found,
+ svc.ipin_n, svc_pd_base, svc_pd_ap, vb);
+ }
}
- pr_info("vgsp-sema: svc-gr data fill: %u candidate ptrs, %u pages resolved (reloc 0x%llx+)\n",
- n_cand, svc_fwd_req.gr_n_data, (u64)SVC_GR_DATA_RELOC_BASE);
+ /*
+ * m170 revert: the per-argument fail-closed signal is kept as
+ * DIAGNOSTIC only (the held fail-closed line above identifies the
+ * exact unresolved operand); the historical aggregate rule held only
+ * a launch with NO resolvable high argument, because a fresh client's
+ * launch stream carries BENIGN 0-page candidates (junk pointer words
+ * that never translate) and persisting the per-arg flag held mail's
+ * first launch forever (r176).
+ *
+ * GRFIX FIX1: the stall is now page-count based - ANY page in a
+ * tracked span that failed resolution (or any tracked data VA whose
+ * resolution returned nothing) holds the launch, so a QMD can never
+ * be forwarded with a data pointer left as an unresolvable guest VA
+ * and no bounce page is ever served stale for this launch. The
+ * response is the unchanged bounded m150/m156 hold (leave the GPFIFO
+ * entry unconsumed, retry the fill while the pins land); the pump
+ * side now carries a hard wall-clock bound (gr_hold_deadline) so a
+ * word that never resolves can no longer re-arm the window forever:
+ * at the bound the entry is consumed with the historical software
+ * release - bounded, never a hang, never a silent stale-operand
+ * submission.
+ */
+ svc_gr_data_stalled = (dropped_pages > 0) ||
+ (n_arg_hi > 0 && n_arg_hi_res == 0);
+ svc_fwd_req.gr_dropped = dropped_pages;
+ /*
+ * GRFIX FIX1e (r215): gsp_phys_gr_fill_hold is the master gate.
+ * Default 0 = LOG-ONLY: the fill NEVER holds on dropped pages - the
+ * stalled verdict keeps only the historical pre-8b058884 aggregate
+ * rule (all high argument pointers unresolved) and the HOLD/EXPIRED
+ * machinery below is skipped entirely; the launch takes the forward
+ * path immediately. When 1 the page-count hold applies, bounded by
+ * the per-launch absolute deadline below.
+ *
+ * GRFIX FIX1c/FIX1d (live only with fill_hold=1): the decision point
+ * is self-enforcing - the absolute per-launch bound is consulted at
+ * the only place the stalled verdict is produced, before the
+ * GRFILL-HOLD line can be emitted. When the bound passes the launch
+ * PROCEEDS via the historical forward path. The benign class
+ * (probe=none / walk rc=-2) is indistinguishable at fill time from a
+ * page whose pin has not landed yet, so a "never hold" predicate
+ * would also disable the pin-late retry; the bound is the only
+ * guard. NOTE (r213-r215): the bound never fired in the live cycles
+ * because these per-channel record fields are not in the
+ * svc_pump_channels snapshot write-back list (see the GRFIX NOTE
+ * there) and were zeroed on every pass - which is why the gate now
+ * defaults to 0.
+ */
+ if (!gsp_phys_gr_fill_hold) {
+ svc_gr_data_stalled = (n_arg_hi > 0 && n_arg_hi_res == 0);
+ } else if (svc_gr_data_stalled && ch &&
+ svc_gr_hold_bound_expired(ch, svc_fwd_req.gr_push_va)) {
+ svc.fwd_gr_hold_exp++;
+ bound_hit = true;
+ if (!ch->gr_hold_exp_logged) {
+ ch->gr_hold_exp_logged = true;
+ pr_info("vgsp-gr: GRFILL-EXPIRED launch=0x%llx dropped=%u waited_ms=%u\n",
+ svc_fwd_req.gr_push_va, dropped_pages,
+ jiffies_to_msecs(jiffies -
+ ch->gr_hold_dead_since));
+ }
+ svc_gr_data_stalled = false; /* proceed: forward */
+ }
+ if (dropped_pages > 0) {
+ if (gsp_phys_gr_fill_hold && !bound_hit)
+ pr_info_ratelimited("vgsp-gr: GRFILL-HOLD launch=0x%llx dropped=%u kept=%u\n",
+ svc_fwd_req.gr_push_va, dropped_pages,
+ svc_fwd_req.gr_n_data);
+ /*
+ * GRFIX FIX1c: name the dropped pages once per launch
+ * episode (the fill re-runs every pass for a held launch;
+ * the marker makes the detail a first-pass-only record).
+ * kind 'a' = argument-page candidate, 'b' = bank candidate;
+ * idx = candidate index (same indexing as the probe lines).
+ */
+ if (svc_gr_drop_log_va != svc_fwd_req.gr_push_va) {
+ u32 dl;
+
+ svc_gr_drop_log_va = svc_fwd_req.gr_push_va;
+ for (dl = 0; dl < 4 && dl < dropped_pages; dl++)
+ pr_info("vgsp-gr: GRFILL-DROP va=0x%llx kind=%c idx=%u\n",
+ drop_va[dl], drop_kind[dl],
+ drop_idx[dl]);
+ }
+ }
+ if (svc_gr_data_stalled)
+ pr_info("vgsp-sema: svc-gr data fill: %u candidate ptrs, %u pages resolved; %u high argument ptr(s), %u resolved; %u dropped page(s) -> launch held for a bounded retry\n",
+ n_cand, svc_fwd_req.gr_n_data, n_arg_hi, n_arg_hi_res,
+ dropped_pages);
+ else if (bound_hit)
+ pr_info_ratelimited("vgsp-sema: svc-gr data fill: %u candidate ptrs, %u pages resolved; %u dropped page(s); hold bound expired -> launch proceeds\n",
+ n_cand, svc_fwd_req.gr_n_data, dropped_pages);
+ else if (dropped_pages > 0 && !gsp_phys_gr_fill_hold)
+ pr_info_ratelimited("vgsp-sema: svc-gr data fill: %u candidate ptrs, %u pages resolved; %u dropped page(s); fill hold disabled (log-only) -> launch proceeds\n",
+ n_cand, svc_fwd_req.gr_n_data, dropped_pages);
+ else
+ pr_info("vgsp-sema: svc-gr data fill: %u candidate ptrs, %u pages resolved (reloc 0x%llx+)\n",
+ n_cand, svc_fwd_req.gr_n_data, reloc_base);
}
static int svc_gr_req_fill(struct nv_vgpu_device *vgpu,
@@ -12412,6 +18755,11 @@ static int svc_gr_req_fill(struct nv_vgpu_device *vgpu,
svc_fwd_req.guest = false;
svc_fwd_req.gr = true;
svc_fwd_req.gr_bridge_done = false;
+ /* STALLFREE: fresh request - re-arm the software-completion latch and
+ * stamp the fill time for the GR-STALL waited_ms accounting. */
+ svc_fwd_req.gr_swrel_done = false;
+ svc_fwd_req.gr_rel_unobserved = false;
+ svc_fwd_req.gr_fill_jiffies = jiffies;
svc_fwd_req.defer = true;
svc_fwd_req.relay = false;
svc_fwd_req.relay_index = 0;
@@ -12514,7 +18862,14 @@ static int svc_gr_req_fill(struct nv_vgpu_device *vgpu,
if (!svc_gr_page_inline_covers(pg->va))
return -ENOENT;
}
- svc_gr_data_fill(vgpu);
+ /*
+ * GRFIX FIX1c: the fill hosts the self-enforcing decision point and
+ * therefore needs the channel's per-launch bound record. The const
+ * view exists for the read-only gates; the channel is the pump's
+ * mutable live bookkeeping object and this runs on the same
+ * service thread.
+ */
+ svc_gr_data_fill(vgpu, (struct svc_channel *)ch);
return 0;
}
@@ -12551,20 +18906,41 @@ static void svc_gr_req_bridge_fallback(struct nv_vgpu_device *vgpu)
* forwarding can never overlap; the caller engages the release hold on a
* true return. Service-thread only.
*/
+static void svc_handle_map_add(void);
+
static bool svc_gr_request(struct nv_vgpu_device *vgpu,
const struct svc_channel *ch)
{
int rc;
+ /* m156: never let a previous call's busy verdict leak into an early
+ * (candidate-mismatch) return; only the busy paths below set it. */
+ svc_gr_busy_defer = false;
/*
* The candidate is per-channel: a capture left unconsumed by an
* earlier pass (e.g. the entry was held before its request) must never
* be queued against a different channel's handle by that channel's
- * own end-of-pass request call.
+ * own end-of-pass request call. fail4: with several reserved ring
+ * slots, select THIS channel's oldest unconsumed candidate first.
*/
+ if (gsp_phys_gr_forward_enabled()) {
+ u32 ri;
+
+ for (ri = 0; ri < SVC_GR_CAND_RING; ri++) {
+ u32 rp = (svc_gr_cand_pos + ri) % SVC_GR_CAND_RING;
+
+ if (svc_gr_cand_ring[rp].valid &&
+ !svc_gr_cand_ring[rp].offered &&
+ svc_gr_cand_ring[rp].h_object == ch->h_object) {
+ svc_gr_cand_pos = rp;
+ break;
+ }
+ }
+ }
if (!gsp_phys_gr_forward_enabled() || !svc_gr_cand.valid ||
svc_gr_cand.h_object != ch->h_object)
return false;
+ svc_gr_data_stalled = false; /* m150: only the fill sets this */
svc.fwd_gr_req++;
if (!gsp_phys_backend_gr_ready() || !gsp_phys_guest_mem_enabled()) {
svc.fwd_gr_refused++;
@@ -12579,6 +18955,17 @@ static bool svc_gr_request(struct nv_vgpu_device *vgpu,
if (svc_fwd_hold.active) {
svc.fwd_gr_refused++;
svc.fwd_gr_fallback++;
+ /*
+ * m159: with the bridge disabled the capture is not consumed:
+ * the candidate survives for the caller's bounded
+ * wait+retry (the launch can still run once the active
+ * forward resolves). With the bridge enabled the fallback
+ * owns the work and consumes the candidate as before.
+ */
+ if (!gsp_cuda_bridge_enabled()) {
+ svc_gr_busy_defer = true;
+ return false;
+ }
svc_gr_bridge_fallback(vgpu);
return false;
}
@@ -12588,9 +18975,24 @@ static bool svc_gr_request(struct nv_vgpu_device *vgpu,
mutex_unlock(&svc_fwd_lock);
svc.fwd_gr_refused++;
svc.fwd_gr_fallback++;
+ if (!gsp_cuda_bridge_enabled()) {
+ svc_gr_busy_defer = true;
+ return false;
+ }
svc_gr_bridge_fallback(vgpu);
return false;
}
+ /*
+ * m143: drain the host injection queue before resolving the launch's
+ * pages. The watcher's pins for the client's a/b/c buffers can land
+ * between the service loop's map_add drain and this capture; the fill
+ * then misses them, nothing is relocated, and the win0 param page the
+ * SM reads keeps the driver's original guest VAs (the second client's
+ * all-zero results, rk1: candidate[0..2] -> 0 new pages). Draining
+ * here closes that window; svc_handle_map_add() runs on this same
+ * service thread, so there is no race with the loop's own drain.
+ */
+ svc_handle_map_add();
rc = svc_gr_req_fill(vgpu, ch);
if (rc) {
clear_bit_unlock(SVC_FWD_INFLIGHT, &svc_fwd_state);
@@ -12604,6 +19006,68 @@ static bool svc_gr_request(struct nv_vgpu_device *vgpu,
return false;
}
/*
+ * m150: the fill found high argument pointers that no source can
+ * translate (the second-client zeros class). Do NOT forward a launch
+ * whose kernel would dereference unresolved pointers: leave the
+ * candidate unconsumed and return false so the pump holds the entry
+ * and retries it on later passes (svc_gr_data_stalled), bounded by
+ * the channel's gr_hold window at the call site. Once the window
+ * expires the entry is consumed exactly as before.
+ */
+ if (svc_gr_data_stalled && gsp_phys_gr_fill_hold) {
+ u64 lva = svc_fwd_req.gr_push_va;
+ /*
+ * The channel is the pump's mutable live bookkeeping object
+ * (the const view exists for the read-only gates); these
+ * record fields are the same ones the pump's hold writes,
+ * on this same service thread.
+ */
+ struct svc_channel *mch = (struct svc_channel *)ch;
+
+ /*
+ * GRFIX FIX1b (r213): the ABSOLUTE per-launch bound. The
+ * first hold of this push VA arms a fixed deadline
+ * (svc_gr_hold_bound_expired) that nothing re-arms; once it
+ * passes, the launch PROCEEDS exactly as the pre-8b058884
+ * path - the candidate is consumed below and forwarded
+ * best-effort - so the guest is never blocked by a page that
+ * never resolves. r213: a slot-keyed deadline was re-armed
+ * on every re-decode (the same launch is replayed at
+ * different GPFIFO slots after rewinds) and held one launch
+ * for 216+ s until the client's 240 s timeout killed it.
+ * GRFIX FIX1e: gated by gsp_phys_gr_fill_hold (default 0) -
+ * with the gate off the fill never stalls on drops, so this
+ * block only ever sees the historical aggregate stall.
+ */
+ if (svc_gr_hold_bound_expired(mch, lva)) {
+ svc.fwd_gr_hold_exp++;
+ if (!mch->gr_hold_exp_logged) {
+ mch->gr_hold_exp_logged = true;
+ pr_info("vgsp-gr: GRFILL-EXPIRED launch=0x%llx dropped=%u waited_ms=%u\n",
+ lva, svc_fwd_req.gr_dropped,
+ jiffies_to_msecs(jiffies -
+ mch->gr_hold_dead_since));
+ }
+ svc_gr_data_stalled = false; /* proceed: forward */
+ if (svc_gr_retry_log < 64) {
+ svc_gr_retry_log++;
+ pr_info("vgsp-gr: GRFILL-PROCEED launch=0x%llx (bound expired; forwarding best-effort)\n",
+ lva);
+ }
+ }
+ }
+ if (svc_gr_data_stalled) {
+ clear_bit_unlock(SVC_FWD_INFLIGHT, &svc_fwd_state);
+ mutex_unlock(&svc_fwd_lock);
+ svc.fwd_gr_refused++;
+ svc.fwd_gr_defer++;
+ if ((svc.fwd_gr_defer & SVC_FWD_LOG_MASK) == 1)
+ pr_info("vgsp-work: GR launch held (data args unresolved) h=0x%x push=0x%llx defer=%llu\n",
+ ch->h_object, svc_gr_cand.push_va,
+ svc.fwd_gr_defer);
+ return false; /* candidate kept: the pump retries the entry */
+ }
+ /*
* The candidate is consumed: the request copy owns it from here, so a
* later capture/replay cannot re-offer it and the bridge is only
* reached through the request's own latch.
@@ -12977,7 +19441,7 @@ static bool svc_push_read_displaced(struct nv_vgpu_device *vgpu, u32 h,
static void svc_entry_outcome(const char *what, const struct svc_channel *ch,
u64 idx, u64 va, u32 len)
{
- if (!gsp_sema_dbg || !ch->engine_type || svc.gpfifo_entry_log >= 512)
+ if (!gsp_sema_dbg || svc.gpfifo_entry_log >= 16384)
return;
svc.gpfifo_entry_log++;
pr_info("sema-entry: %s h=0x%x idx=%llu va=0x%llx len=%u want=%u exp=%u unread=%d\n",
@@ -13045,6 +19509,172 @@ static void svc_userd_sweep(struct nv_vgpu_device *vgpu, struct svc_channel *ch,
ch->h_object, hi, lo);
}
+/*
+ * STALLFREE r225: stale-epoch / stale-replay wedge watchdog.
+ *
+ * r225 evidence (witness-r225b.log): after a client was killed mid-run, a
+ * fresh client's GR channel (0x5c000059) was registered with a RESIDUAL
+ * USERD put=101 left by the dead session. Its fresh session wrote no put
+ * (it was stuck at ctx-init, polling MC_SERVICE_INTERRUPTS once a second),
+ * so the m181 fresh-put-drop recovery - which requires put < put_prev -
+ * never fired, and the host-side completion-counter publish stayed
+ * withheld (gr_pub_armed false). Meanwhile the pin-batch rewind
+ * (svc_handle_map_add, SVC_PIN_REWIND=8) kept yanking last_get from 101
+ * back to 93 on every injection batch, so the entry loop re-walked slots
+ * 93..100 forever: 8 stale entries per batch, all decoding to nothing
+ * (rel=0, no capture) and consuming with zero delivery. The whole stall
+ * window shows zero captures, zero GR requests and therefore zero
+ * GR-STALL lines - the launch never reached the bounded submit path
+ * because the session epoch itself was wedged.
+ *
+ * Two signatures are detected, both keyed to NO FORWARD PROGRESS for
+ * SVC_GR_STALE_WEDGE_MS (the (last_get, put) pair unchanged across the
+ * window):
+ *
+ * 1. stale-epoch: a GR channel that still carries a residual put
+ * (put > 1 at this pass, publish withheld) and whose ring the host
+ * believes is fully consumed (last_get == put) - the fresh session
+ * never proved its switch with a put drop. The host applies the
+ * m181 recovery on a TIMEOUT instead: arm the counter publish and
+ * void the withheld publish state (no last_get rewind - a timeout is
+ * not proof the ring is the dead session's; the proven m181 rewind
+ * stays reserved for its put-drop signature). This is the recovery
+ * the killed-client rerun needs.
+ *
+ * 2. stale-replay: last_get pinned behind put while put == get (the
+ * producer has nothing pending) AND the loop consumed entries during
+ * the window (submissions moved) - i.e. the rewind is re-opening an
+ * already-consumed window faster than the loop closes it. The window
+ * is dropped (last_get = put) and the pin-batch rewind is put on a
+ * cooldown (wd_rewind_off_until) so the futile retry cannot pin the
+ * pump; the rewind budget returns later and a translation that lands
+ * can still retry (bounded duty cycle).
+ *
+ * Nothing here can fire on a green channel: a fresh channel has put <= 1
+ * (signature 1) or consumes its first entries within milliseconds
+ * (submissions > 0); signature 2 additionally requires BOTH a stalled
+ * (last_get, put) pair for 3 s with no pending producer work AND consumed
+ * entries during that window, which only the rewind re-opening of a
+ * consumed window produces.
+ */
+#define SVC_GR_STALE_WEDGE_MS 3000u
+static void svc_gr_stale_wedge_check(struct nv_vgpu_device *vgpu,
+ struct svc_channel *ch, u32 put, u32 get)
+{
+ bool residual, replay;
+ const char *reason = "";
+ unsigned long waited;
+
+ if (ch->engine_type || !ch->gpfifo_entries)
+ return;
+ /* New work (put advanced) clears the replay cooldown immediately. */
+ if (ch->wd_rewind_off_until && ch->wd_put != put)
+ ch->wd_rewind_off_until = 0;
+ if (ch->wd_since &&
+ (ch->wd_put != put || ch->wd_last_get != ch->last_get))
+ ch->wd_since = 0; /* progress (or a new position): re-arm */
+ residual = (!ch->gr_pub_armed && put > 1 && ch->last_get == put);
+ replay = (ch->last_get != put && put == get &&
+ ch->wd_since && ch->submissions != ch->wd_sub);
+ if (!residual && !replay) {
+ ch->wd_since = 0;
+ ch->wd_put = put;
+ ch->wd_last_get = ch->last_get;
+ return;
+ }
+ if (!ch->wd_since) {
+ ch->wd_since = jiffies;
+ ch->wd_put = put;
+ ch->wd_last_get = ch->last_get;
+ ch->wd_sub = ch->submissions;
+ return;
+ }
+ if (time_before(jiffies, ch->wd_since +
+ msecs_to_jiffies(SVC_GR_STALE_WEDGE_MS)))
+ return;
+ waited = jiffies_to_msecs(jiffies - ch->wd_since);
+ if (residual) {
+ /*
+ * m181 recovery on timeout: the fresh session never dropped
+ * the residual put, so prove the epoch switch here. Arm the
+ * publish (the withheld completion counter was the plausible
+ * ctx-init wait - r225's client polled service-interrupts 1/s
+ * with no host edge) and void any publish state the stale
+ * pass advanced, exactly like m181; the guest word itself is
+ * only zeroed when the host authored it. No last_get rewind:
+ * a timeout is not proof that the ring is the dead session's
+ * (re-walking it could re-capture a stale launch), so the
+ * proven m181 rewind stays reserved for its put-drop
+ * signature; the client's own first put write will trigger it.
+ */
+ ch->gr_pub_armed = true;
+ if (ch->counter_last) {
+ u64 upa = ch->release_pa ? ch->release_pa :
+ ch->counter_pa;
+ u32 zero = 0;
+
+ /*
+ * r311: zero only a word authored by a DEAD
+ * generation. Zeroing a word this live session
+ * authored makes the client's UVM observe a
+ * downward jump (its tracker then never completes)
+ * while the publish re-advances it - the r311
+ * vector_add wedge (pub-adv/zero cycles, client
+ * polling cur=0 forever).
+ */
+ if (ch->counter_gen == svc_sess_gen) {
+ if (gsp_sema_dbg && svc.pend_dbg_log < 1024) {
+ svc.pend_dbg_log++;
+ pr_info("sema-diag: stale-recover-zero-skip h=0x%x upa=0x%llx last=%u (authored by live gen)\n",
+ ch->h_object, upa,
+ ch->counter_last);
+ }
+ ch->counter_last = 0;
+ } else if (upa) {
+ int zrc;
+
+ /*
+ * r378: the zero is authored from a PA
+ * learned earlier (release_va -> release_pa);
+ * re-validate it before the 4-byte write
+ * lands so a freed/re-homed slot is never
+ * zeroed through the stale page.
+ */
+ if (!gsp_sema_write_guard(ch->release_va,
+ upa,
+ SVC_APERTURE_SYS,
+ "stale-recover-zero"))
+ zrc = -ESTALE;
+ else
+ zrc = svc_write_ap(vgpu, upa,
+ SVC_APERTURE_SYS,
+ &zero, sizeof(zero));
+ if (gsp_sema_dbg && svc.pend_dbg_log < 1024) {
+ svc.pend_dbg_log++;
+ pr_info("sema-diag: stale-recover-zero h=0x%x upa=0x%llx last=%u zrc=%d\n",
+ ch->h_object, upa,
+ ch->counter_last, zrc);
+ }
+ }
+ ch->counter_last = 0;
+ }
+ reason = "stale-epoch";
+ } else {
+ /* Drop the rewind-reopened consumed window; cool the rewind. */
+ ch->last_get = put;
+ ch->wd_rewind_off_until = jiffies +
+ msecs_to_jiffies(8u * SVC_GR_STALE_WEDGE_MS);
+ reason = "stale-replay";
+ }
+ ch->wd_since = 0;
+ ch->wd_put = put;
+ ch->wd_last_get = ch->last_get;
+ 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=%u action=recover reason=%s\n",
+ ch->h_object, ch->gpfifo_va, (u32)ch->last_get,
+ put, get, ch->submissions, waited, reason);
+}
+
static void svc_pump_channels(struct nv_vgpu_device *vgpu)
{
/* Only ever called from the service thread. */
@@ -13061,7 +19691,7 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
spin_lock_irqsave(&svc.chan_lock, flags);
for (i = 0; i < SVC_MAX_CHANNELS; i++) {
- if (svc.chan[i].valid)
+ if (svc.chan[i].valid && svc.chan[i].vgpu == vgpu)
local[n++] = svc.chan[i];
}
spin_unlock_irqrestore(&svc.chan_lock, flags);
@@ -13069,6 +19699,14 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
n_gr = SVC_GR_COUNTER_TOTAL;
/*
+ * STALLFREE: complete a failed forwarded GR launch no hold owns (see
+ * svc_fwd_outcome_check). Runs before this pass's channel loop, so a
+ * finished request is always observed before any fill can reuse its
+ * slot.
+ */
+ svc_fwd_outcome_check(vgpu);
+
+ /*
* H1 last resort: one self-gated content-discovery pass for the GR
* ctx-init rings before this pass's channel loop. The pins it
* installs (ring VA + push VA pages, both validated from content and
@@ -13097,7 +19735,7 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
* pass's snapshot belongs to a vanished channel; its waiter is gone,
* so drop the set instead of resolving it later.
*/
- svc_opdef_drop_gone(local, n);
+ svc_opdef_drop_gone(vgpu, local, n);
for (i = 0; i < n; i++) {
struct svc_channel *ch = &local[i];
@@ -13106,6 +19744,7 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
int rrc;
u32 put = 0, get = 0, guard = 0;
u32 get_before;
+ u32 put_prev_wit;
u64 applied_before;
bool held = false;
@@ -13152,7 +19791,8 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
* svc_counter_autoreg). Skip while a relay hold owns
* the channel, exactly like the end-of-pass call.
*/
- if (ch->counter_pa && ch->userd_pa &&
+ if ((ch->release_pa || ch->counter_pa) &&
+ ch->userd_pa &&
!(svc_fwd_hold.active &&
svc_fwd_hold.vgpu == vgpu &&
svc_fwd_hold.ch_handle == ch->h_object) &&
@@ -13270,6 +19910,20 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
ch->logged = true;
}
+ /*
+ * mm2: a GR channel whose USERD already reads PUT <= 1 cannot
+ * hold a stale consumed prefix, so its publishes (the catch-up
+ * for releases missing from consumed entries) are safe to
+ * enable immediately - this is the pristine first-boot ring.
+ * Channels discovered with a residual put stay unarmed until
+ * the fresh-put-drop rewind proves the session switch, so the
+ * residual put can never reach the fresh client's completion
+ * word (see gr_pub_armed and svc_counter_publish).
+ */
+ if (!ch->engine_type && !ch->gr_pub_armed && put <= 1 &&
+ !ch->userd_wait_fresh)
+ ch->gr_pub_armed = true;
+
/* Initialize expected semaphore payload from the pool (if mapped). */
svc_init_expected_payload(vgpu, ch);
@@ -13323,12 +19977,103 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
ch->h_object, ch->ce_index, put,
(u32)ch->last_get);
ch->last_get = put;
+ } else if (!ch->engine_type && ch->gpfifo_entries &&
+ ch->last_get > put &&
+ ch->submissions < ch->gpfifo_entries / 2) {
+ /*
+ * m181 (r203 GR ctx-init stall): a GR channel's
+ * ring/USERD live in fixed VRAM slots
+ * (fb 0x140000 + rank*0x3000) that keep the
+ * PREVIOUS session's final state across the
+ * session flush (m176 reset learned counter
+ * state but not the ring-consumption
+ * bookkeeping). A new session's first pump
+ * pass therefore sees the residual put (e.g.
+ * 107 = the last session's final GP_PUT) and
+ * consumes the stale ring up to it, advancing
+ * last_get before the client has written its
+ * own entries. The client's fresh put then
+ * drops below put_prev, and without this
+ * rewind last_get stays ahead of the producer:
+ * the live ctx-init entries are never decoded,
+ * their REPORT_SEM releases are never applied
+ * (the completion word freezes at the first
+ * stale-decoded payload), and the client's
+ * ctx-init wait polls forever - r203 rank 0
+ * hung exactly so in multikernel / streams2 /
+ * victory_mail / smiley whenever the prior
+ * session had reached a high put (the sessions
+ * after a hung one, whose final put is 1,
+ * proceeded - a perfect alternation). Rewind
+ * to slot 0 like the r48 resync: a fresh
+ * session restarts its push sequence at slot
+ * 0, and re-decoding slot 0 is idempotent
+ * (the stale pass could only have authored the
+ * same payload, and the forward-only apply
+ * drops a duplicate). A genuine wrap cannot be
+ * this fresh (submissions guard), mirroring
+ * the r73 CE rewind.
+ */
+ pr_info_ratelimited("sema-chan: fresh put drop (GR) h=0x%x rank=%u put=%u last=%u -> rewind to 0\n",
+ ch->h_object, ch->gr_index, put,
+ (u32)ch->last_get);
+ ch->last_get = 0;
+ /*
+ * mm2: the session switch is now proven - arm
+ * the publish for the live entries that follow,
+ * and void any publish state the stale pass
+ * advanced. Everything consumed to reach
+ * last_get was the dead session's ring; a
+ * publish issued on it wrote the residual put
+ * into the very word the fresh client waits on
+ * (its tracking semaphore; pub target is
+ * release_pa since 8c634144) and the
+ * forward-only apply then skips the real
+ * release payloads that follow. Re-arm the
+ * word to 0 and reset the catch-up bookkeeping
+ * so the re-decoded live releases re-advance
+ * it from the client's own sequence (1, 2,
+ * ...). Idempotent when no stale publish
+ * happened (counter_last == 0).
+ */
+ ch->gr_pub_armed = true;
+ if (ch->counter_last) {
+ u64 upa = ch->release_pa ?
+ ch->release_pa : ch->counter_pa;
+ u32 zero = 0;
+
+ if (upa)
+ (void)svc_write_ap(vgpu, upa,
+ SVC_APERTURE_SYS,
+ &zero, sizeof(zero));
+ ch->counter_last = 0;
+ }
}
bitmap_zero(ch->slot_done_epoch, SVC_GPFIFO_MAX_BITS);
+ memset(ch->slot_pend_tries, 0,
+ sizeof(ch->slot_pend_tries));
+ /* GRFIX FIX1b: a recycled slot in the next put epoch
+ * gets a fresh absolute-bound record; without this a
+ * wrapped slot would inherit a stale expired launch
+ * record. */
+ ch->gr_hold_dead_va = 0;
+ ch->gr_hold_exp_logged = false;
+ /* r361: same for the opdef absolute bound. */
+ ch->opdef_hold_dead_va = 0;
+ ch->opdef_hold_exp_logged = false;
}
+ put_prev_wit = ch->put_prev;
ch->put_prev = put;
/*
+ * STALLFREE r225: stale-epoch / stale-replay wedge watchdog.
+ * Runs before the entry loop so a wedged session epoch is
+ * recovered (and logged) instead of spinning: see
+ * svc_gr_stale_wedge_check.
+ */
+ svc_gr_stale_wedge_check(vgpu, ch, put, get);
+
+ /*
* 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
@@ -13363,6 +20108,20 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
get_before = ch->last_get;
applied_before = svc.releases_applied;
+ /*
+ * C4 (GAP-1 H1): the GR walk witness - does put advance, and
+ * does last_get reach it? Budget 512/session; fires on a put
+ * change or while last_get is in the stalled 80..100 band.
+ */
+ if (gsp_sema_dbg && svc.walk_dbg < 512 &&
+ (put != put_prev_wit ||
+ ((u32)ch->last_get >= 80 && (u32)ch->last_get <= 100))) {
+ svc.walk_dbg++;
+ pr_info("sema-diag: walk h=0x%x put=%u get=%u last=%u prev=%u\n",
+ ch->h_object, put, get, (u32)ch->last_get,
+ put_prev_wit);
+ }
+
while (ch->last_get != put && guard++ < ch->gpfifo_entries) {
u8 entry[SVC_GPFIFO_ENTRY_SIZE];
u64 va, idx = ch->last_get;
@@ -13395,7 +20154,7 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
* (forward-only, so it cannot double-apply) and drop
* it before this entry is examined.
*/
- svc_opdef_drop_stale(vgpu, ch, (u32)idx);
+ svc_opdef_drop_stale(vgpu, ch, (u32)idx, put);
/*
* A pin-batch rewind replays recently consumed
@@ -13476,6 +20235,20 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
* leaves the per-op done bitmap to decide:
* work that already ran must not run again,
* the pending op must get its retry.
+ *
+ * r354: the content witness decides (the hash
+ * override sits below, after the push bytes
+ * are read): entry words alone cannot tell a
+ * true rewind replay (identical bytes - must
+ * not run twice) from a REWRITTEN push that
+ * reuses the same push-buffer VA and length
+ * with new methods (the driver's repeated
+ * page-table/fill pushes at the same ring
+ * slots - suppressing those left the guest's
+ * GPU page tables stale, the GMMU walk
+ * missing operand pages, and the first CUDA
+ * client of every fresh boot failing with
+ * dropped operands).
*/
entry_replay = same &&
ch->slot_ops_complete[idx];
@@ -13718,6 +20491,86 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
{
u32 rel_n_before = svc_rel_count;
bool rel_seen;
+ /*
+ * m156: this entry is inside a live
+ * GR hold window and its decode is a
+ * replay that would suppress the
+ * launch capture. Force exactly one
+ * fresh capture pass (ops stay
+ * suppressed: they already ran; only
+ * the candidate build is re-done) so
+ * the deferred launch is forwarded
+ * for real once the engine's
+ * in-flight work completes.
+ */
+ bool rehold =
+ !ch->engine_type &&
+ ch->gr_rehold_idx ==
+ (u32)idx;
+ /*
+ * m160: the capture gate used to
+ * require a host-injected pin. A
+ * fresh ring entry whose push read
+ * succeeded and whose bytes parse
+ * as a plausible method stream is
+ * the client's actual work - the
+ * same evidence the untrusted ops
+ * gate already accepts - and a GR
+ * capture built from it is exactly
+ * what the m156 rehold forces for
+ * deferred launches. Without this,
+ * a channel whose pages the pin
+ * stream had not reached yet (the
+ * streams2 second-channel case:
+ * entry consumed, no capture, the
+ * client read zeros) never gets
+ * its launch forwarded at all.
+ * Replays stay suppressed: a
+ * consumed entry must not run its
+ * work twice.
+ */
+ bool cap_trust = injected || rehold;
+
+ /*
+ * r354: the content witness for
+ * the once-only op gate. Entry
+ * words alone cannot tell a true
+ * rewind replay (identical bytes)
+ * from a rewritten push that
+ * reuses the same push VA and
+ * length with new methods (the
+ * driver's repeated fill /
+ * page-table pushes at the same
+ * ring slots). Suppressing the
+ * rewritten push left the GPU
+ * page tables stale and the first
+ * client's operands unresolved.
+ * slot_ops_hash has been stored
+ * since r62 for this comparison.
+ */
+ if (entry_replay &&
+ ch->slot_ops_hash[idx] !=
+ svc_push_hash(pb, len)) {
+ entry_replay = false;
+ if (svc_gr_retry_log < 64) {
+ svc_gr_retry_log++;
+ pr_info("vgsp-sema: rewritten push runs (idx=%llu va=0x%llx len=%u)\n",
+ (unsigned long long)idx, va, len);
+ }
+ }
+ if (rehold && !injected &&
+ svc_gr_retry_log < 64) {
+ svc_gr_retry_log++;
+ pr_info("vgsp-gr: GR rehold capture idx=%llu va=0x%llx\n",
+ (unsigned long long)idx, va);
+ }
+ if (!cap_trust && !entry_replay &&
+ !ch->engine_type &&
+ gsp_phys_gr_forward_enabled() &&
+ !unreadable &&
+ svc_method_stream_plausible(pb,
+ len))
+ cap_trust = true;
svc_entry_outcome("decode", ch,
idx, va, len);
@@ -13725,15 +20578,18 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
svc_decode_stream(vgpu, pb,
len, 0,
(allow_ops || injected) &&
- !entry_replay,
+ !entry_replay &&
+ !rehold,
ch,
idx,
va,
- injected,
- entry_replay,
- entry_replay ?
+ cap_trust,
+ entry_replay && !rehold,
+ (entry_replay && !rehold) ?
NULL :
&entry_done);
+ if (rehold)
+ ch->gr_rehold_idx = ~0u;
/*
* Record what ran for this entry
* identity before any hold path can
@@ -13781,8 +20637,14 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
*/
if (svc.op_tr_pending &&
svc_opdef_hold(vgpu, ch,
- (u32)idx)) {
+ (u32)idx,
+ va)) {
svc.gpfifo_nomap++;
+ if (gsp_sema_dbg && svc.pend_dbg_log < 1024) {
+ svc.pend_dbg_log++;
+ pr_info("sema-pend: hold h=0x%x idx=%u va=0x%llx\n",
+ ch->h_object, (u32)idx, va);
+ }
/*
* P6b wave 1.5:
* releases recorded
@@ -13842,6 +20704,241 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
*/
fwd_guest_queued =
svc_fwd_request(vgpu, ch);
+ if (fwd_guest_queued &&
+ !ch->engine_type)
+ ch->gr_hold_idx = ~0u;
+ /*
+ * m159: a busy refusal (forward
+ * in flight / release hold
+ * active) means this launch CAN
+ * still run - only later. The
+ * candidate survives the refusal
+ * (the busy paths do not consume
+ * it), so wait bounded for the
+ * slot to free - resolving the
+ * active hold through its own
+ * channel, since this pump thread
+ * is the hold resolver - then
+ * retry the request inline. This
+ * serializes multi-launch clients
+ * (streams2: kernels 2/3 queued
+ * while kernel 1 is in flight)
+ * without losing their releases.
+ * On timeout the historical
+ * consume + soft-release path
+ * stands (bounded, never a hang).
+ */
+ if (!fwd_guest_queued &&
+ !ch->engine_type &&
+ svc_gr_busy_defer) {
+ unsigned long wend = jiffies +
+ msecs_to_jiffies(4000);
+
+ while (time_before(jiffies,
+ wend)) {
+ if (svc_fwd_hold.active &&
+ svc_fwd_hold.vgpu ==
+ vgpu) {
+ int hk;
+
+ for (hk = 0; hk < n; hk++) {
+ if (local[hk].h_object !=
+ svc_fwd_hold.ch_handle)
+ continue;
+ svc_fwd_hold_poll(vgpu,
+ &local[hk],
+ 0);
+ break;
+ }
+ }
+ if (!svc_fwd_hold.active &&
+ !test_bit(SVC_FWD_INFLIGHT,
+ &svc_fwd_state) &&
+ smp_load_acquire(
+ &svc_fwd_req.done))
+ break;
+ msleep(5);
+ }
+ if (!svc_fwd_hold.active &&
+ !test_bit(SVC_FWD_INFLIGHT,
+ &svc_fwd_state))
+ fwd_guest_queued =
+ svc_fwd_request(vgpu,
+ ch);
+ if (fwd_guest_queued) {
+ svc.fwd_gr_defer++;
+ } else if (!svc_gr_hold_bound_expired(ch,
+ svc_gr_launch_va(ch)) &&
+ (ch->gr_hold_idx != (u32)idx ||
+ time_before(jiffies,
+ ch->gr_hold_until))) {
+ u32 hold_ms = gsp_sema_gr_grace_ms ?
+ gsp_sema_gr_grace_ms : 8000;
+
+ /*
+ * P2 (GAP-2 A3): the wait
+ * expired without queueing.
+ * Do NOT fall through to the
+ * software-release consume -
+ * the launch was never
+ * submitted, so authoring
+ * its release would be a
+ * false completion. Hold
+ * exactly like the
+ * data-stalled path
+ * (m150/m156); the one-shot
+ * window lets the historical
+ * consume resume after it
+ * expires.
+ */
+ ch->gr_rehold_idx = (u32)idx;
+ ch->gr_hold_idx = (u32)idx;
+ ch->gr_hold_until = jiffies +
+ msecs_to_jiffies(hold_ms);
+ svc.fwd_gr_defer++;
+ if (svc_busy_defer_log < 64) {
+ svc_busy_defer_log++;
+ pr_info("vgsp-work: GR busy-defer timeout h=0x%x idx=%llu -> hold %u ms\n",
+ ch->h_object,
+ (unsigned long long)idx,
+ hold_ms);
+ }
+ kfree(pb);
+ break;
+ } else if (svc_busy_defer_log < 64) {
+ /*
+ * C8 (GAP-2 A3): the bounded
+ * hold window is over; the
+ * historical consume with the
+ * software release resumes.
+ */
+ svc_busy_defer_log++;
+ pr_info("vgsp-work: GR busy-defer timeout h=0x%x idx=%llu -> consume w/ sw release\n",
+ ch->h_object,
+ (unsigned long long)idx);
+ }
+ }
+ /*
+ * m150: the GR fill held this
+ * launch because it carries
+ * data pages that do not
+ * resolve yet. Leave the entry
+ * unconsumed (do not write
+ * GP_GET) so the pump
+ * re-captures and retries it.
+ * GRFIX FIX1b (r213): the hold
+ * is bounded ABSOLUTELY per
+ * launch push VA
+ * (svc_gr_hold_bound_expired
+ * fixes the deadline on the
+ * first hold and nothing
+ * re-arms it; svc_gr_request
+ * enforces it and forwards the
+ * launch once it passes). The
+ * extra check here is the
+ * defence for a stale stalled
+ * verdict when the request
+ * path did not run this pass -
+ * past the bound the entry
+ * falls through to the
+ * historical consume.
+ */
+ if (!fwd_guest_queued &&
+ !ch->engine_type &&
+ svc_gr_data_stalled &&
+ !svc_gr_hold_bound_expired(ch,
+ svc_gr_launch_va(ch)) &&
+ (ch->gr_hold_idx != (u32)idx ||
+ time_before(jiffies,
+ ch->gr_hold_until))) {
+ u32 hold_ms = gsp_sema_gr_grace_ms ?
+ gsp_sema_gr_grace_ms : 8000;
+
+ /*
+ * m156: arm the forced
+ * re-capture so the next
+ * decode of this entry
+ * rebuilds the GR candidate
+ * (a replay decode would
+ * suppress it) and the
+ * deferred launch is
+ * forwarded for real once
+ * the in-flight work has
+ * completed.
+ */
+ ch->gr_rehold_idx =
+ (u32)idx;
+ ch->gr_hold_idx = (u32)idx;
+ ch->gr_hold_until = jiffies +
+ msecs_to_jiffies(hold_ms);
+ svc.fwd_gr_defer++;
+ if (svc_gr_hold_log < 16) {
+ svc_gr_hold_log++;
+ pr_info("sema-svc: GR hold h=0x%x entry=%u (%s; retry window %u ms)\n",
+ ch->h_object,
+ (u32)idx,
+ svc_gr_data_stalled ?
+ "data args unresolved" :
+ "forward busy (m156)",
+ hold_ms);
+ }
+ kfree(pb);
+ break;
+ }
+ /*
+ * STALLFREE: the absolute per-launch
+ * hold bound just fired - the entry
+ * falls through to the historical
+ * consume, where the tail authors the
+ * QMD completion release and logs
+ * GR-STALL-hold. This line pins the
+ * bound expiry itself (the r220 hold
+ * storm's fix: one line per launch,
+ * nothing re-arms it across rewind
+ * replays).
+ */
+ if (!fwd_guest_queued &&
+ !ch->engine_type &&
+ svc_gr_data_stalled &&
+ svc_gr_hold_bound_expired(ch,
+ svc_gr_launch_va(ch)) &&
+ !ch->gr_hold_exp_logged) {
+ ch->gr_hold_exp_logged = true;
+ svc.fwd_gr_hold_exp++;
+ /*
+ * r351: the bound firing used to
+ * fall through to the historical
+ * consume (entry consumed, QMD
+ * release authored) WITHOUT ever
+ * forwarding the launch - the
+ * client's kernel never ran and
+ * the client hung (victory_reduce
+ * with gsp_phys_gr_fill_hold=1:
+ * bound fired every 3 s, zero GR
+ * LAUNCH lines, 110 s+ hang).
+ * Instead, force ONE more fresh
+ * capture (rehold) with the stall
+ * verdict cleared: the request's
+ * own bound is expired too, so it
+ * proceeds and forwards the launch
+ * best-effort with whatever
+ * resolved. If that retry cannot
+ * run, the next pass (flag set)
+ * falls through to the historical
+ * consume as before.
+ */
+ pr_warn("vgsp-gr: GR-STALL-hold-bound va=0x%llx waited_ms=%u action=retry-forward (bound expired; one best-effort forward pass)\n",
+ svc_gr_launch_va(ch),
+ jiffies_to_msecs(jiffies -
+ ch->gr_hold_dead_since));
+ svc_gr_data_stalled = false;
+ ch->gr_rehold_idx = (u32)idx;
+ ch->gr_hold_idx = (u32)idx;
+ ch->gr_hold_until = jiffies +
+ msecs_to_jiffies(200);
+ kfree(pb);
+ break;
+ }
/*
* P6b wave 1.5: when
* this pass recorded
@@ -13918,14 +21015,98 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
* bridge job owns the release.
*/
if (!ch->engine_type &&
- !gsp_cuda_bridge_enabled())
+ !gsp_cuda_bridge_enabled()) {
svc_gr_sw_release(vgpu,
ch->h_object,
svc_gr_cand.rel_va,
svc_gr_cand.rel_payload);
+ /* STALLFREE: latch so the
+ * consume tail cannot author
+ * the same release twice. */
+ svc_gr_cand.rel_done = true;
+ }
}
}
/*
+ * m164 CLASS-2 no-capture hold: a
+ * readable GR launch push whose
+ * decode saw QMD words but built
+ * no launch candidate for this
+ * channel (replay-suppressed
+ * capture, partial/split QMD, or
+ * a page the pin stream has not
+ * reached) must not be consumed
+ * at the tail - consuming drops
+ * the client's launch and only
+ * the RAM sweep's release reaches
+ * it (r161/r167 streams2 second
+ * vadd: 56 consumes / 2 captures
+ * on channel 0x5c000023). Hold
+ * the entry and force a fresh
+ * capture on the next pass
+ * (gr_rehold_idx makes the replay
+ * decode rebuild the candidate);
+ * one bounded window per
+ * (slot, push VA), then fall
+ * through to the historical
+ * consume so the channel can
+ * never livelock.
+ */
+ if (!ch->engine_type && va && len &&
+ gsp_phys_gr_forward_enabled() &&
+ svc_gr_qmd_seen > 0 &&
+ !entry_replay &&
+ !(svc_gr_cand.valid &&
+ svc_gr_cand.h_object ==
+ ch->h_object &&
+ svc_gr_cand.push_va == va)) {
+ if (ch->gr_nocap_idx !=
+ (u32)idx ||
+ ch->gr_nocap_va != va) {
+ ch->gr_nocap_idx =
+ (u32)idx;
+ ch->gr_nocap_va = va;
+ ch->gr_nocap_replay =
+ entry_replay ?
+ 1 : 0;
+ ch->gr_nocap_since =
+ jiffies;
+ }
+ if (gsp_sema_gr_nocap_hold_ms &&
+ time_before(jiffies,
+ ch->gr_nocap_since +
+ msecs_to_jiffies(
+ gsp_sema_gr_nocap_hold_ms))) {
+ /* Force a fresh capture on
+ * the retry: a replay decode
+ * would suppress it. */
+ ch->gr_rehold_idx =
+ (u32)idx;
+ svc.gpfifo_held++;
+ svc.gr_nocap_held++;
+ if (svc_gr_nocap_log < 16) {
+ svc_gr_nocap_log++;
+ pr_info("sema-chan: GR no-capture hold h=0x%x entry=%u va=0x%llx len=%u replay=%d qmd_seen=%u (forcing capture; window %u ms)\n",
+ ch->h_object,
+ (u32)idx, va,
+ len,
+ ch->gr_nocap_replay,
+ svc_gr_qmd_seen,
+ gsp_sema_gr_nocap_hold_ms);
+ }
+ kfree(pb);
+ break;
+ }
+ /* Window expired: consume
+ * exactly as the pre-change
+ * tail does, with a proof
+ * line. */
+ svc.gr_nocap_consumed++;
+ pr_info_ratelimited("sema-chan: GR no-capture hold expired h=0x%x entry=%u va=0x%llx len=%u (consuming without a launch)\n",
+ ch->h_object,
+ (u32)idx, va, len);
+ }
+ /*
* A CE push that should carry this
* channel's next tracking release but
* decoded none read from a
@@ -14461,10 +21642,20 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
ch->defer_since = 0;
ch->unread_va = 0;
ch->unread_until = 0;
- if (gsp_sema_fake_rel ||
- gsp_sema_rel_recover)
- svc_replay_note(ch, idx,
- put);
+ /*
+ * m177: deliver any
+ * withheld set even
+ * on the GR stale-skip
+ * path. GR channels
+ * have no rescue and
+ * their consume-time
+ * delivery gate is
+ * below; without this
+ * the slot strands
+ * until the sweep.
+ */
+ svc_replay_note(ch, idx,
+ put);
ch->last_get = (idx + 1) %
ch->gpfifo_entries;
svc.gpfifo_skipped++;
@@ -14500,13 +21691,61 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
}
}
- if (ch->engine_type && svc_rel_count != rel_n_iter)
+ /*
+ * m177: GR channels (engine_type == 0) have no rescue and
+ * no CE-shaped delivery branches above; deliver any
+ * withheld set for this entry whenever the entry is
+ * consumed so a held release can never strand until the
+ * 10 s sweep (evtest3's st[2] sync hang).
+ */
+ if (svc_rel_count != rel_n_iter || !ch->engine_type)
svc_replay_note(ch, idx, put);
+ /*
+ * STALLFREE: bounded completion for a GR capture that was
+ * consumed without ever being forwarded - a data-stall,
+ * busy-defer or no-capture hold window expired and the
+ * entry fell through to the historical consume. No
+ * hardware copy is coming and no release hold owns the
+ * launch, so without this the QMD completion word the
+ * client polls is never authored by the host and the
+ * launch wait runs to the guest's own timeout. Author
+ * it now, forward-only, latched once per capture.
+ * svc_gr_cand.push_va is the entry's push VA, so a
+ * candidate captured for a later entry of the same
+ * channel can never be completed by this entry's
+ * consume.
+ */
+ if (!ch->engine_type && svc_gr_cand.valid &&
+ svc_gr_cand.h_object == ch->h_object &&
+ !svc_gr_cand.offered && !svc_gr_cand.rel_done &&
+ svc_gr_cand.rel_va &&
+ svc_gr_cand.push_va == va) {
+ svc_gr_cand.rel_done = true;
+ svc_gr_sw_release(vgpu, ch->h_object,
+ svc_gr_cand.rel_va,
+ svc_gr_cand.rel_payload);
+ svc.fwd_gr_stall_swrel++;
+ pr_warn("vgsp-gr: GR-STALL-hold va=0x%llx waited_ms=%u action=proceed (unforwarded GR capture consumed; QMD release authored in software)\n",
+ va,
+ jiffies_to_msecs(jiffies -
+ svc_gr_cand.captured_jiffies));
+ }
ch->last_get = (idx + 1) % ch->gpfifo_entries;
ch->rescue_fail = 0;
ch->defer_since = 0;
+ /* m164: the entry was consumed (or fell through the
+ * no-capture window); clear the hold identity so a
+ * future push on this slot opens a fresh window. */
+ ch->gr_nocap_idx = ~0u;
+ ch->gr_nocap_since = 0;
+ ch->gr_nocap_va = 0;
ch->unread_va = 0;
ch->unread_until = 0;
+ /* GRFIX FIX1c (r214): do NOT reset the per-launch
+ * bound record here - a consume of an unrelated entry
+ * would re-arm the held launch's bound every pass
+ * (see svc_fwd_hold_poll). Reset happens on channel
+ * (re)registration and the session diag reset. */
/* Publish progress the way the GPU would. */
g = ch->last_get;
@@ -14571,7 +21810,7 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
spin_lock_irqsave(&svc.chan_lock, flags);
for (k = 0; k < SVC_MAX_CHANNELS; k++) {
- if (svc.chan[k].valid &&
+ if (svc.chan[k].valid && svc.chan[k].vgpu == vgpu &&
svc.chan[k].h_object == ch->h_object) {
svc.chan[k].userd_pa = ch->userd_pa;
svc.chan[k].userd_as = ch->userd_as;
@@ -14614,6 +21853,84 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
svc.chan[k].unread_idx = ch->unread_idx;
svc.chan[k].unread_until = ch->unread_until;
svc.chan[k].unread_ipin_pa = ch->unread_ipin_pa;
+ /*
+ * m150: the GR data-arg hold window is snapshot
+ * state too; without the write-back every pass
+ * would re-arm it and a permanently unresolved
+ * launch would never be consumed.
+ */
+ svc.chan[k].gr_hold_until = ch->gr_hold_until;
+ svc.chan[k].gr_hold_idx = ch->gr_hold_idx;
+ /*
+ * GRFIX NOTE (r213-r215): ANY new per-channel
+ * record field MUST be added to this snapshot
+ * write-back list or it is zeroed on every pass
+ * (the pump works on the local[] snapshot copied
+ * at the top of svc_pump_channels).
+ *
+ * STALLFREE (r213-r215 follow-up): the GRFIX
+ * absolute-bound fields were the proven record
+ * destroyer - they were intentionally left out,
+ * which is why gsp_phys_gr_fill_hold defaulted
+ * to 0 (the bound could never fire). They are now
+ * written back, so the per-launch bound survives
+ * the snapshot and is effective on the default
+ * path: a data-stalled launch is held for at most
+ * gsp_phys_gr_hold_max_ms (default 3000) and then
+ * is consumed with a software completion, instead
+ * of being re-held forever by a rewind replay at
+ * a different slot index (the r220 196 s hold).
+ */
+ svc.chan[k].gr_hold_dead_va = ch->gr_hold_dead_va;
+ svc.chan[k].gr_hold_dead_since =
+ ch->gr_hold_dead_since;
+ svc.chan[k].gr_hold_deadline =
+ ch->gr_hold_deadline;
+ svc.chan[k].gr_hold_exp_logged =
+ ch->gr_hold_exp_logged;
+ /* r361: the opdef absolute-bound record is
+ * snapshot state too (the GRFIX lesson) -
+ * without the write-back the deadline would
+ * be zeroed every pass and the bound could
+ * never fire. */
+ svc.chan[k].opdef_hold_dead_va =
+ ch->opdef_hold_dead_va;
+ svc.chan[k].opdef_hold_deadline =
+ ch->opdef_hold_deadline;
+ svc.chan[k].opdef_hold_exp_logged =
+ ch->opdef_hold_exp_logged;
+ /* m164: the CLASS-2 no-capture window is snapshot
+ * state too - without the write-back every pass
+ * would re-arm it and rehold the entry forever
+ * (the m150 lesson). */
+ svc.chan[k].gr_nocap_idx = ch->gr_nocap_idx;
+ svc.chan[k].gr_nocap_replay =
+ ch->gr_nocap_replay;
+ svc.chan[k].gr_nocap_va = ch->gr_nocap_va;
+ svc.chan[k].gr_nocap_since = ch->gr_nocap_since;
+ /*
+ * STALLFREE r225: the stale-wedge watchdog
+ * state is snapshot state too - without the
+ * write-back the window would restart every
+ * pass and never expire.
+ */
+ svc.chan[k].wd_last_get = ch->wd_last_get;
+ svc.chan[k].wd_put = ch->wd_put;
+ svc.chan[k].wd_sub = ch->wd_sub;
+ svc.chan[k].wd_since = ch->wd_since;
+ /*
+ * r312: gr_pub_armed / counter_gen live only on
+ * the snapshot copy if not persisted. Losing the
+ * armed bit re-arms the residual watchdog every
+ * pass; its recovery then keeps zeroing the
+ * completion word and re-publishing - the r311/
+ * r312 vector_add wedge loop (66 stale-wedge
+ * rows with the client polling forever).
+ */
+ svc.chan[k].gr_pub_armed = ch->gr_pub_armed;
+ svc.chan[k].counter_gen = ch->counter_gen;
+ svc.chan[k].wd_rewind_off_until =
+ ch->wd_rewind_off_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;
@@ -14639,6 +21956,9 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
memcpy(svc.chan[k].slot_ops_ident,
ch->slot_ops_ident,
sizeof(ch->slot_ops_ident));
+ memcpy(svc.chan[k].slot_pend_tries,
+ ch->slot_pend_tries,
+ sizeof(ch->slot_pend_tries));
memcpy(svc.chan[k].slot_ops_done,
ch->slot_ops_done,
sizeof(ch->slot_ops_done));
@@ -14877,7 +22197,7 @@ static void svc_handle_chan_dump(struct nv_vgpu_device *vgpu)
spin_lock_irqsave(&svc.chan_lock, flags);
for (i = 0; i < SVC_MAX_CHANNELS; i++)
- if (svc.chan[i].valid)
+ if (svc.chan[i].valid && svc.chan[i].vgpu == vgpu)
local[n++] = svc.chan[i];
spin_unlock_irqrestore(&svc.chan_lock, flags);
@@ -14955,6 +22275,41 @@ static u32 svc_map_add_head; /* producer */
static u32 svc_map_add_tail; /* consumer (service thread) */
static DEFINE_SPINLOCK(svc_map_add_lock);
+/*
+ * m154: does this injected-pin VA fall inside any live channel's GPFIFO ring
+ * range? The ring VA translation must come from the channel's own discovery
+ * path (channel object / push-buffer scan); a scanner run that happens to
+ * span the ring VA is enumerating a different mapping and its translation is
+ * always wrong for the ring (r153: an all-zero read consumed a ring entry
+ * and wedged the client for the boot). Called on the service thread; takes
+ * chan_lock like the rewind below.
+ */
+static bool svc_va_hits_chan_ring(u64 va)
+{
+ unsigned long flags;
+ u32 i;
+ bool hit = false;
+
+ spin_lock_irqsave(&svc.chan_lock, flags);
+ for (i = 0; i < SVC_MAX_CHANNELS; i++) {
+ struct svc_channel *ch = &svc.chan[i];
+ u64 lo, hi;
+
+ if (!ch->valid || !ch->gpfifo_va || !ch->gpfifo_entries)
+ continue;
+ lo = ch->gpfifo_va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ hi = (ch->gpfifo_va +
+ (u64)ch->gpfifo_entries * 8 + SEMA_PAGE_SIZE - 1) &
+ ~(u64)(SEMA_PAGE_SIZE - 1);
+ if (va >= lo && va < hi) {
+ hit = true;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&svc.chan_lock, flags);
+ return hit;
+}
+
static void svc_handle_map_add(void)
{
unsigned long flags;
@@ -14979,8 +22334,79 @@ static void svc_handle_map_add(void)
ap = req.ap ? req.ap : SVC_APERTURE_SYS;
for (off = 0; off < (req.len ? req.len : 1);
off += SEMA_PAGE_SIZE) {
- svc_ipin_store(req.va + off, req.pa + off, ap);
- svc_lat_store(req.va + off, req.pa + off, ap);
+ u64 va = req.va + off;
+
+ /*
+ * m154: never let an injected pin shadow a live
+ * channel's GPFIFO ring. r153: the scanner's merged
+ * CE-window run (va=0x121000000 len=0x40000, the
+ * length varies per session) covered a CE channel's
+ * ring VA 0x121010000; the injected translation took
+ * precedence in svc_ipin_lookup/read, the ring then
+ * read the guest page at 0x102e02000 - an all-zero
+ * entry was silently consumed, the pool release was
+ * never authored, and the client spun in userspace
+ * for the rest of the boot (pushes frozen). The ring
+ * pages already have their own discovery path (the
+ * channel object / push-buffer scan); a colliding
+ * injection is always the wrong translation, so drop
+ * it and leave the ring to that path.
+ */
+ if (svc_va_hits_chan_ring(va)) {
+ svc_ring_pin_skips++;
+ if (svc_ring_pin_skips <= 8)
+ pr_info("sema-svc: injected pin va=0x%llx DROPPED (live channel ring range; ring reads stay on the channel's own translation)\n",
+ va);
+ continue;
+ }
+ svc_ipin_store(va, req.pa + off, ap);
+ svc_lat_store(va, req.pa + off, ap);
+ /*
+ * r372 (stale-ledger corruption class): a fresh pin is the
+ * guest's newest statement of where this VA's content
+ * lives. The landing table (ledger) otherwise only moves
+ * on host writes, so a VA re-pinned to a NEW page (the
+ * guest freed the old buffer and reallocated it - CUDA
+ * reuses VA ranges across processes) kept resolving to
+ * the OLD, freed page: every later write through that
+ * ledger entry landed on memory the guest kernel had
+ * since reused (proven class: 8 zero bytes into an
+ * nv_free_system_pages page array -> guest oops at
+ * client teardown). Repair the entry only when it
+ * disagrees with the pin (a no-op otherwise, so the
+ * table is not flooded with pin pages).
+ */
+ {
+ u64 lpa = 0;
+ u32 lap = 0;
+
+ if (!svc_ledger_lookup(va, &lpa, &lap) &&
+ (((lpa & ~(u64)(SEMA_PAGE_SIZE - 1)) !=
+ ((req.pa + off) & ~(u64)(SEMA_PAGE_SIZE - 1))) ||
+ (u32)lap != (u32)ap)) {
+ svc_ledger_pin_repairs++;
+ if (svc_ledger_pin_repairs <= 32)
+ pr_info("sema-ledger: PIN-REPAIR va=0x%llx old=0x%llx ap=%u -> new=0x%llx ap=%u\n",
+ va,
+ lpa & ~(u64)(SEMA_PAGE_SIZE - 1),
+ lap,
+ req.pa + off, ap);
+ svc_ledger_store(va, req.pa + off, ap);
+ }
+ }
+ }
+ /*
+ * m149: bounded per-session evidence that the CE push-buffer
+ * pins arrived - the source the second session's ring entries
+ * resolve through.
+ */
+ if (ap == SVC_APERTURE_SYS && req.va >= 0x120000000ULL &&
+ req.va < 0x121000000ULL && svc_cepin_log < 4) {
+ svc_cepin_log++;
+ pr_info("sema-svc: CE push-buffer pin va=0x%llx pa=0x%llx len=0x%llx (%u/4)\n",
+ (unsigned long long)req.va,
+ (unsigned long long)req.pa,
+ (unsigned long long)req.len, svc_cepin_log);
}
rewound = true;
if (gsp_sema_dbg)
@@ -15010,6 +22436,20 @@ static void svc_handle_map_add(void)
if (!ch->valid || !ch->gpfifo_entries)
continue;
/*
+ * STALLFREE r225: a channel under the stale-replay
+ * watchdog's rewind cooldown is NOT rewound. Without
+ * this the rewind re-opened the same consumed window
+ * every batch while the producer had nothing pending,
+ * pinning last_get and re-walking stale entries
+ * forever (r225: slots 93..100 of 0x5c000059 for
+ * 42+ s). The cooldown expires on its own (bounded
+ * duty cycle) and is cleared by new work, so a
+ * translation landing later can still retry.
+ */
+ if (ch->wd_rewind_off_until &&
+ time_before(jiffies, ch->wd_rewind_off_until))
+ continue;
+ /*
* Clamp the rewind at 0: wrapping below zero (get=0 ->
* entries - N) makes the pump read ring slots past the
* producer that hold stale entries, so replay only
@@ -15189,9 +22629,145 @@ int gsp_sema_service_counter_add(u32 h_object, u64 pa)
return ret;
}
+/*
+ * Register the guest PMA scrubber's channel-buffer words. Called from the
+ * sema_scrubbuf_add debug parameter with the PAs the guest scanner resolved
+ * (vmallocinfo vmap area + kcore page-table walk). fin_pa is the finish
+ * payload the guest's scrub wait polls; hsem_pa is the channel's host
+ * semaphore (channelWaitForFreeEntry polls it). Plain state update.
+ *
+ * Dual-guest: the words are written through a specific guest's address space
+ * (see svc_scrub_pass), so the registration binds to the FIRST open mdev -
+ * the same single-guest convention the scanner tooling follows (it tracks
+ * one client). Refused with no open mdev rather than parked globally.
+ */
+int gsp_sema_service_scrubbuf_add(u64 fin_pa, u64 hsem_pa)
+{
+ struct nv_vgpu_device *vgpu = NULL;
+ struct nv_vgpu_device *d;
+ struct svc_scrub *s;
+
+ if (!svc.task)
+ return -ENODEV;
+ /* Both words are u32: require 4-byte alignment and a RAM-shaped PA. */
+ if (!fin_pa || (fin_pa & 0x3) || fin_pa >= SEMA_PA_MAX)
+ return -EINVAL;
+ if (!hsem_pa || (hsem_pa & 0x3) || hsem_pa >= SEMA_PA_MAX)
+ return -EINVAL;
+
+ mutex_lock(&nv_vgpu_driver.lock);
+ list_for_each_entry(d, &nv_vgpu_driver.devices, list) {
+ if (atomic_read(&d->vfio_dev_open_count) > 0) {
+ vgpu = d;
+ break;
+ }
+ }
+ mutex_unlock(&nv_vgpu_driver.lock);
+ if (!vgpu) {
+ pr_warn_ratelimited("sema-svc: scrubber buffer registration ignored: no open mdev to bind it to\n");
+ return -ENODEV;
+ }
+ s = svc_scrub_get(vgpu, true);
+ if (!s)
+ return -ENOSPC;
+
+ s->fin_pa = fin_pa;
+ s->hsem_pa = hsem_pa;
+ s->pa_ok = true;
+ s->pa_gen++;
+ pr_info("sema-svc: scrubber buffer registered fin_pa=0x%llx hsem_pa=0x%llx (gen %u)\n",
+ fin_pa, hsem_pa, s->pa_gen);
+ return 0;
+}
+
+/*
+ * Author the CeUtils scrubber channel's progress once per service pass.
+ *
+ * The physical CE would advance, per completed scrub block, the channel's
+ * finish payload (channel buffer + 0x6c004) and write the channel's host
+ * semaphore as it consumes GPFIFO entries. Nothing in this emulation does,
+ * so the guest PMA's scrub wait (scrubCheckAndWaitForSize) freezes from the
+ * third CUDA session on. This pass closes that loop from the channel's own
+ * USERD GPPut (which the guest driver writes before every submit):
+ *
+ * submitted = number of scrub blocks submitted (GPPut is end-exclusive
+ * and counts submissions until the ring wraps; track the wrap ourselves),
+ * then author USERD GP_GET = put (consumed), host sema = put and finish
+ * payload = submitted. Forward-only, idempotent, retried every pass so a
+ * late scanner registration (or a post-flush reset) heals by itself.
+ *
+ * The scrub work itself is not executed (no shadow memsets): the guest
+ * only needs the progress to proceed. Executing the memsets requires
+ * decoding the channel's pushes, which the scanner can later provide pins
+ * for; correctness impact is limited to stale data in pages the guest
+ * believes scrubbed.
+ */
+static void svc_scrub_pass(struct nv_vgpu_device *vgpu)
+{
+ struct svc_scrub *s = svc_scrub_get(vgpu, false);
+ u32 put = 0;
+ u64 delta;
+
+ if (!s || !s->userd_ok || !s->pa_ok || !s->entries)
+ return;
+ if (svc_read_ap(vgpu, s->userd_pa + SVC_USERD_GP_PUT, s->userd_as,
+ &put, sizeof(put))) {
+ /* Retry through the alternate aperture (as registration did). */
+ if (svc_read_ap(vgpu, s->userd_pa + SVC_USERD_GP_PUT,
+ s->userd_as == SVC_APERTURE_FB ?
+ SVC_APERTURE_SYS : SVC_APERTURE_FB,
+ &put, sizeof(put)))
+ return;
+ }
+ if (put >= s->entries)
+ return; /* junk translation; retry next pass */
+
+ if (!s->seeded) {
+ s->seeded = true;
+ s->put_last = put;
+ /* GPPut = (lastSubmittedEntry + 1) % entries: the submission
+ * count until the ring wraps. Seeding from it adopts any
+ * pre-host scrub work (the boot session's scrubbers). */
+ s->submitted = put;
+ } else if (put != s->put_last) {
+ delta = (put + s->entries - s->put_last) % s->entries;
+ if (!delta)
+ delta = s->entries; /* exact wrap */
+ s->submitted += delta;
+ s->put_last = put;
+ } else if (s->submitted == s->authored) {
+ return; /* nothing new; no guest writes */
+ }
+
+ if (s->log_budget < 64) {
+ s->log_budget++;
+ pr_info("sema-svc: scrub author put=%u submitted=%llu fin_pa=0x%llx\n",
+ put, s->submitted, s->fin_pa);
+ }
+
+ /*
+ * Author (forward-only). The USERD GP_GET and host-sema writes mark
+ * the submissions consumed; the finish write releases the scrub
+ * wait. Written only when GP_PUT advanced (or on the seeding pass),
+ * so the per-pass cost is zero while the scrubber idles.
+ */
+ svc_write_ap(vgpu, s->userd_pa + SVC_USERD_GP_GET, s->userd_as,
+ &put, sizeof(put));
+ svc_write_ap(vgpu, s->hsem_pa, SVC_APERTURE_SYS, &put, sizeof(put));
+ {
+ u32 fin = (u32)s->submitted;
+
+ svc_write_ap(vgpu, s->fin_pa, SVC_APERTURE_SYS, &fin,
+ sizeof(fin));
+ }
+ s->authored = s->submitted;
+}
+
static int svc_thread(void *arg)
{
+ struct nv_vgpu_device *vgpus[SVC_MAX_GUESTS];
struct nv_vgpu_device *vgpu;
+ int n_vgpus, g;
pr_info("sema-svc: thread started\n");
@@ -15211,129 +22787,192 @@ static int svc_thread(void *arg)
complete_all(&svc_sess_flush_done);
}
- vgpu = NULL;
+ /*
+ * m177: a guest RM (re)load means a new guest boot: wipe the
+ * boot-scoped discovery state here, on the thread that owns
+ * it. Raised by gsp_sema_service_guest_reset().
+ */
+ if (READ_ONCE(svc_guest_reset_req)) {
+ svc_guest_reset_req = 0;
+ svc_guest_reset_wipe();
+ }
+
+ /*
+ * STALL FIX (r189): expire withheld release sets whose entry
+ * stopped being re-decoded (the deadline flush is only
+ * reachable from the entry path). Forward-only delivery to a
+ * live owner; counted LOSS otherwise. Cheap: 8 slots.
+ */
+ svc_opdef_expire_sweep();
+
+ /*
+ * Service EVERY open mdev each pass, in list order. With one
+ * mdev this is exactly the historical single-vgpu pass; with
+ * two, both guests get a pump turn per iteration (hardware
+ * forwarding stays serialized by the single-inflight slot; see
+ * the forwarding section). The list walk is the historical
+ * bounded scan under nv_vgpu_driver.lock.
+ */
+ n_vgpus = 0;
mutex_lock(&nv_vgpu_driver.lock);
{
struct nv_vgpu_device *d;
list_for_each_entry(d, &nv_vgpu_driver.devices, list) {
- if (atomic_read(&d->vfio_dev_open_count) > 0) {
- vgpu = d;
- break;
- }
+ if (atomic_read(&d->vfio_dev_open_count) > 0 &&
+ n_vgpus < SVC_MAX_GUESTS)
+ vgpus[n_vgpus++] = d;
}
}
mutex_unlock(&nv_vgpu_driver.lock);
- if (!vgpu) {
+ if (!n_vgpus) {
msleep(500);
continue;
}
- svc_handle_probe(vgpu);
- svc_handle_walk_probe(vgpu);
+ /* Host-injection queue drain: once per pass, not per guest. */
svc_handle_map_add();
- svc_handle_chan_dump(vgpu);
- if (svc_sess_flush || !svc.direct_base ||
- time_after(jiffies,
- (unsigned long)(svc.last_build + 10 * HZ)))
- svc_rebuild(vgpu);
+ for (g = 0; g < n_vgpus; g++) {
+ struct svc_guest_sess *sess;
+ u64 done_before;
- /*
- * Channel-driven execution: notice submissions as they are made
- * and run the pushed work immediately, so the guest's UVM
- * progress waits complete in milliseconds rather than in the
- * time it takes the fallback scan to find the push. A doorbell
- * token means a channel was just scheduled; run everything now.
- */
- {
- u64 done_before = svc.gpfifo_entries_done;
+ vgpu = vgpus[g];
+ svc_handle_probe(vgpu);
+ svc_handle_walk_probe(vgpu);
+ svc_handle_chan_dump(vgpu);
+ svc_scrub_pass(vgpu);
+
+ /*
+ * Per-guest rebuild freshness: the discovery map is
+ * still shared between guests (staged G4), but each
+ * guest's RAM must be scanned on its own clock, or the
+ * first guest's 10 s tick would starve the second
+ * guest of rebuilds. The freshness stamp is the one
+ * svc_rebuild() publishes (svc.last_build).
+ */
+ sess = svc_guest_get(vgpu, true);
+ if (svc_sess_flush || !svc.direct_base ||
+ !sess ||
+ time_after(jiffies,
+ (unsigned long)(sess->last_build +
+ 10 * HZ))) {
+ svc_rebuild(vgpu);
+ if (sess)
+ sess->last_build = svc.last_build;
+ }
+ /*
+ * Channel-driven execution: notice submissions as they
+ * are made and run the pushed work immediately, so the
+ * guest's UVM progress waits complete in milliseconds
+ * rather than in the time it takes the fallback scan
+ * to find the push. A doorbell token means a channel
+ * was just scheduled; run everything now.
+ */
+ done_before = svc.gpfifo_entries_done;
svc.doorbell_pending = false;
svc_pump_channels(vgpu);
- pumped = (svc.gpfifo_entries_done != done_before);
- }
+ if (svc.gpfifo_entries_done != done_before)
+ pumped = true;
- /*
- * Host CUDA bridge: marshal one queued launch into a daemon
- * job and resolve completions. Inert until a launch is seen.
- */
- gsp_cuda_bridge_service(vgpu);
+ /*
+ * Host CUDA bridge: marshal one queued launch into a
+ * daemon job and resolve completions. Inert until a
+ * launch is seen.
+ */
+ gsp_cuda_bridge_service(vgpu);
- /*
- * Releases whose target VA was not translatable when the scan
- * saw them: retry every pass, so a pin or a learned pair applies
- * them immediately instead of on the next full sweep.
- */
- svc_apply_pending(vgpu);
+ /*
+ * Releases whose target VA was not translatable when
+ * the scan saw them: retry every pass, so a pin or a
+ * learned pair applies them immediately instead of on
+ * the next full sweep.
+ */
+ svc_apply_pending(vgpu);
- /*
- * A fresh VA->PA injection changes which releases are
- * applicable: run the fast sliced sweep while the forced window
- * is open (the pump's stall path also uses it).
- */
- if (time_before(jiffies, (unsigned long)svc.fast_force_until))
- svc_apply_sweep_slice(vgpu);
+ /*
+ * A fresh VA->PA injection changes which releases are
+ * applicable: run the fast sliced sweep while the
+ * forced window is open (the pump's stall path also
+ * uses it).
+ */
+ if (time_before(jiffies,
+ (unsigned long)svc.fast_force_until))
+ svc_apply_sweep_slice(vgpu);
+ }
/*
* While the pump is making progress the client is streaming
* pushes and waiting on their releases: loop tightly and skip
- * the discovery/sweep passes, which otherwise dominate the pass
- * period (the pump cadence is what the client's context init
- * deadline sees). Scans resume the first pass without progress.
+ * the discovery/sweep passes, which otherwise dominate the
+ * pass period (the pump cadence is what the client's context
+ * init deadline sees). Scans resume the first pass without
+ * progress. (With two guests, any guest's progress keeps the
+ * loop tight; the next iteration pumps both.)
*/
if (pumped) {
msleep(1);
continue;
}
- /*
- * Push-buffer base discovery: a chunked walk of guest RAM
- * finds the pages whose first push names a channel, so a
- * channel that appears with work already waiting is pinned and
- * pumped without waiting for the rolling sweep.
- */
- svc_pb_discover(vgpu);
+ for (g = 0; g < n_vgpus; g++) {
+ vgpu = vgpus[g];
- /*
- * Completion hint: re-post the events for a few seconds after a
- * completion, so a client that starts waiting after the
- * edge-triggered wake-up still observes it.
- */
- if (time_before(jiffies, (unsigned long)svc.post_hint_until) &&
- time_after(jiffies, (unsigned long)svc.last_hint + HZ / 2)) {
- int rc;
+ /*
+ * Push-buffer base discovery: a chunked walk of guest
+ * RAM finds the pages whose first push names a
+ * channel, so a channel that appears with work
+ * already waiting is pinned and pumped without
+ * waiting for the rolling sweep.
+ */
+ svc_pb_discover(vgpu);
- svc.last_hint = jiffies;
- rc = gsp_rm_post_all_events(vgpu);
- if (gsp_sema_dbg)
- pr_info_ratelimited("sema-svc: completion hint rc=%d\n",
- rc);
- }
+ /*
+ * Completion hint: re-post the events for a few
+ * seconds after a completion, so a client that starts
+ * waiting after the edge-triggered wake-up still
+ * observes it.
+ */
+ if (time_before(jiffies,
+ (unsigned long)svc.post_hint_until) &&
+ time_after(jiffies,
+ (unsigned long)svc.last_hint + HZ / 2)) {
+ int rc;
+
+ svc.last_hint = jiffies;
+ rc = gsp_rm_post_all_events(vgpu);
+ if (gsp_sema_dbg)
+ pr_info_ratelimited("sema-svc: completion hint rc=%d\n",
+ rc);
+ }
- /*
- * Fast path for the pushes themselves: every mapped page is a
- * candidate pushbuffer page, and there are far fewer of those
- * than there are pages of guest RAM.
- */
- if (svc.direct_base && svc.map_n)
- svc_apply_mapped(vgpu);
+ /*
+ * Fast path for the pushes themselves: every mapped
+ * page is a candidate pushbuffer page, and there are
+ * far fewer of those than there are pages of guest
+ * RAM.
+ */
+ if (svc.direct_base && svc.map_n)
+ svc_apply_mapped(vgpu);
- /*
- * Rolling RAM scan: the pushbuffers of some clients are not
- * covered by the VA map, so sweep guest RAM in bounded windows
- * in parallel with the targeted pass. Work per pass is fixed,
- * which keeps the emulation responsive while a full sweep
- * completes every few seconds.
- */
- svc_apply_pushes(vgpu);
+ /*
+ * Rolling RAM scan: the pushbuffers of some clients
+ * are not covered by the VA map, so sweep guest RAM in
+ * bounded windows in parallel with the targeted pass.
+ * Work per pass is fixed, which keeps the emulation
+ * responsive while a full sweep completes every few
+ * seconds.
+ */
+ svc_apply_pushes(vgpu);
- /*
- * Deferred readability probes for faked CE pushes (observation
- * only; see svc_retry_poll).
- */
- svc_retry_poll(vgpu);
+ /*
+ * Deferred readability probes for faked CE pushes
+ * (observation only; see svc_retry_poll).
+ */
+ svc_retry_poll(vgpu);
+ }
msleep(10);
}
@@ -15456,6 +23095,97 @@ void gsp_sema_service_stop(void)
svc_vas_free_all();
}
+/*
+ * Release every piece of service state owned by one mdev (dual-guest goal
+ * 1.0). Called from the mdev cleanup path after that mdev's emulation thread
+ * stopped and before its struct can be freed:
+ * - quiesce forwarding so no in-flight work item can still be reading the
+ * request's vgpu, and abort a release hold that names it;
+ * - drop any hold that survives (a request that never queued leaves no work
+ * item to clear it) BEFORE the device can go away - resolution would
+ * dereference it;
+ * - drop the guest's channels; request the global session flush only when no
+ * guest has live channels left (the discovery tables are still shared;
+ * staged G4);
+ * - drop the guest's held-operand release slots (its waiters are gone);
+ * - free the per-guest session and scrubber records so the vgpu pointer can
+ * never match a newly allocated device.
+ */
+void gsp_sema_service_vgpu_cleanup(struct nv_vgpu_device *vgpu)
+{
+ unsigned long flags;
+ int i;
+
+ if (!vgpu)
+ return;
+
+ /*
+ * The forward work item runs on its own kthread and may be using the
+ * request's vgpu; quiesce waits for it (bounded by the submit's
+ * 200 ms poll cap) and aborts an active hold. Resume afterwards so the
+ * other guests keep forwarding.
+ */
+ gsp_sema_service_work_quiesce();
+ if (READ_ONCE(svc_fwd_hold.active) && svc_fwd_hold.vgpu == vgpu)
+ WRITE_ONCE(svc_fwd_hold.aborted, true);
+ gsp_sema_service_work_resume();
+
+ if (READ_ONCE(svc_fwd_hold.active) && svc_fwd_hold.vgpu == vgpu) {
+ /*
+ * Pure state clear (never a resolution through the vanishing
+ * device): the releases belong to a guest that is gone.
+ */
+ svc.fwd_hold_canceled++;
+ svc_fwd_hold_clear();
+ }
+
+ spin_lock_irqsave(&svc.chan_lock, flags);
+ for (i = 0; i < SVC_MAX_CHANNELS; i++) {
+ struct svc_channel *ch = &svc.chan[i];
+
+ if (!ch->valid || ch->vgpu != vgpu)
+ continue;
+ pr_info("sema-svc: channel 0x%x dropped (mdev cleanup; submissions=%llu)\n",
+ ch->h_object, ch->submissions);
+ memset(ch, 0, sizeof(*ch));
+ }
+ if (svc_sess_active) {
+ struct svc_guest_sess *sess = svc_guest_get(vgpu, false);
+ bool any = false;
+
+ for (i = 0; i < SVC_MAX_CHANNELS; i++)
+ if (svc.chan[i].valid) {
+ any = true;
+ break;
+ }
+ if (sess)
+ sess->active = false;
+ if (!any) {
+ svc_sess_active = 0;
+ svc_sess_flush = 1;
+ }
+ }
+ spin_unlock_irqrestore(&svc.chan_lock, flags);
+
+ /* Held-operand release slots of this guest cannot be delivered. */
+ for (i = 0; i < SVC_OPDEF_SLOTS; i++)
+ if (svc_opdef[i].active && svc_opdef[i].vgpu == vgpu)
+ svc_opdef_clear(&svc_opdef[i], SVC_OPDEF_CLR_STOP);
+
+ /* Free the per-guest session and scrubber records. */
+ spin_lock_irqsave(&svc_guest_lock, flags);
+ for (i = 0; i < SVC_MAX_GUESTS; i++)
+ if (svc_guests[i].vgpu == vgpu)
+ memset(&svc_guests[i], 0, sizeof(svc_guests[i]));
+ spin_unlock_irqrestore(&svc_guest_lock, flags);
+ for (i = 0; i < SVC_MAX_GUESTS; i++)
+ if (svc_scrubs[i].vgpu == vgpu)
+ memset(&svc_scrubs[i], 0, sizeof(svc_scrubs[i]));
+
+ if (svc_sess_flush && svc.task)
+ wake_up_process(svc.task);
+}
+
/* ------------------------------------------------------------------ */
/* host CUDA bridge exports (gsp_cuda_bridge.c) */
/* ------------------------------------------------------------------ */
@@ -15651,6 +23381,6 @@ int gsp_sema_service_sem_write(struct nv_vgpu_device *vgpu, u32 h, u64 va,
* after the write still receives the completion event.
*/
if (!rc)
- svc.post_hint_until = jiffies + 10 * HZ;
+ svc.post_hint_until = jiffies + msecs_to_jiffies(SVC_POST_HINT_MS);
return rc;
}