summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_sema_service.c121
1 files changed, 103 insertions, 18 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 1add702..a62e3e4 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -212,6 +212,18 @@ struct svc_channel {
* the end with no operand left pending, so a
* replay suppresses every op. When clear, the
* done bitmap decides per op (retry path). */
+ u64 slot_ops_hash[SVC_GPFIFO_MAX_BITS];
+ /* Content witness for slot_ops_ident: the
+ * FNV-1a hash of the push bytes the identity
+ * was recorded from. Entry words alone are
+ * not enough - a client that rewrites its
+ * push buffer in place reuses the same (VA,
+ * length) words, and suppressing that new
+ * entry's ops loses its work (observed: a
+ * suppressed rewritten push left a staged
+ * chunk overwritten and the vadd input
+ * shifted by one chunk). Suppression requires
+ * the content to match too. */
u32 put_prev; /* put value at the previous pass (wrap detect) */
u64 last_rel_va; /* last tracking-sem VA seen in a readable push */
u32 logged_expected; /* last pool value logged (rate limit) */
@@ -461,6 +473,7 @@ struct sema_svc {
u32 op_trace_log;
u64 ring_ops; /* op runs from ring-decoded pushes */
u64 ops_once_skipped; /* ops skipped by the done bitmap on a retried entry */
+ u64 ops_reident; /* suppressions cancelled by a content mismatch */
u64 lda_log; /* LAUNCH_DMA inventory budget */
u64 rescue_rej_log; /* rescue rejection budget */
u64 sem256_log; /* 256-byte op trace budget */
@@ -2723,10 +2736,10 @@ 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\n",
+ pr_info("sema-svc: drain held=%llu starved=%llu forwarded=%llu faked=%llu rec=%llu ops_once=%llu reident=%llu\n",
svc.gpfifo_held, svc.gpfifo_starved, svc.releases_forwarded,
svc.releases_faked, svc.releases_recovered,
- svc.ops_once_skipped);
+ svc.ops_once_skipped, svc.ops_reident);
pr_info("sema-svc: counter pub_calls=%llu gr_rel=%llu reresolve=%llu pushpg_fb=%llu\n",
svc.counter_pub_calls, svc.gr_rel_applied,
svc.sem_write_reresolve, svc.pushpg_fallback);
@@ -6652,6 +6665,10 @@ u64 gsp_sema_copy_dst2;
* copies the client will read back) must run before the release the client
* synchronizes on; the translation usually lands within a service pass, so
* keep the entry pending inside the window instead of dropping the work.
+ * The window stays generous on purpose: a faked entry's work is never run
+ * (running it late, after the client refilled the staging buffer, would
+ * write the next chunk's data over a finished one), so the hold is the only
+ * chance the work has.
*/
unsigned gsp_sema_ce_grace_ms = 8000;
@@ -7610,6 +7627,25 @@ static bool svc_replay_seen(const struct svc_channel *ch, u32 idx, u32 put)
test_bit(idx, ch->slot_done_epoch);
}
+/*
+ * FNV-1a over the raw push bytes: the content witness that lets the
+ * once-only op gate tell a true rewind replay (identical bytes) from a
+ * rewritten push that happens to carry the same entry words (same push-buffer
+ * VA and length, new methods). Entry words alone cannot separate the two, and
+ * suppressing a rewritten push's ops loses its work.
+ */
+static u64 svc_push_hash(const u8 *p, u32 len)
+{
+ u64 h = 0xcbf29ce484222325ULL;
+ u32 i;
+
+ for (i = 0; i < len; i++) {
+ h ^= p[i];
+ h *= 0x100000001b3ULL;
+ }
+ return h;
+}
+
/* ------------------------------------------------------------------ */
/* P5/P6b wave-1 shadow work forwarding */
/* (gsp_phys_work_forward=1, + gsp_phys_guest_mem=1 for guest data) */
@@ -9158,6 +9194,29 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
}
/*
+ * A pin-batch rewind replays recently consumed
+ * entries. Re-processing one (rescue, grace) costs
+ * seconds and delivers nothing, and a slot consumed
+ * without running its work (the grace fake) must
+ * never execute that work later: by the time the
+ * rewind re-reads it the client has been told the
+ * release completed and may have refilled the
+ * staging buffer, so a late copy writes the next
+ * chunk's data over a finished one (observed:
+ * deterministic vadd input shift). The epoch mark is
+ * the authority - it is set on every consume path
+ * and cleared only when put wraps - so a consumed
+ * slot is skipped regardless of the channel's
+ * deferral state. defer_since must not re-open it.
+ */
+ if (ch->engine_type && svc_replay_seen(ch, idx, put)) {
+ ch->last_get = (idx + 1) %
+ ch->gpfifo_entries;
+ svc.gpfifo_skipped++;
+ continue;
+ }
+
+ /*
* Remember the payload this entry should deliver the
* first time it is examined. If the pool advances past
* it later, the release was already delivered (the
@@ -9166,23 +9225,8 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
* consumed instead of waiting out the grace window -
* otherwise every push costs one full grace period.
*/
- if (!ch->defer_since) {
+ if (!ch->defer_since)
ch->entry_want = ch->expected_sem_payload;
- /*
- * A pin-batch rewind replays recently consumed
- * entries. Re-processing one (rescue, grace)
- * costs seconds and delivers nothing: consume
- * it at once when the producing put has not
- * advanced since it was consumed.
- */
- if (ch->engine_type &&
- svc_replay_seen(ch, idx, put)) {
- ch->last_get = (idx + 1) %
- ch->gpfifo_entries;
- svc.gpfifo_skipped++;
- continue;
- }
- }
u32 rel_n_iter = svc_rel_count;
@@ -9392,6 +9436,28 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
if (!unreadable) {
ch->push_hold_until = 0;
/*
+ * The entry words repeated, but the
+ * push bytes may not have: a client
+ * that rewrites its push buffer in
+ * place produces a new push with the
+ * same (VA, length) words. Suppress
+ * ops only when the content matches
+ * the recorded pass; a changed page
+ * is a new entry and its ops must
+ * run (observed: a suppressed reread
+ * of a rewritten push left a staged
+ * chunk overwritten, shifting the
+ * vadd input by one chunk).
+ */
+ if (entry_ident &&
+ (entry_replay || entry_done) &&
+ svc_push_hash(pb, len) !=
+ ch->slot_ops_hash[idx]) {
+ entry_replay = false;
+ entry_done = 0;
+ svc.ops_reident++;
+ }
+ /*
* Fills/copies execute only for CE
* channels: the push was written for the
* copy engine, so its method space is
@@ -9438,6 +9504,9 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
ch->slot_ops_complete[idx] =
svc.op_tr_pending ?
0 : 1;
+ ch->slot_ops_hash[idx] =
+ svc_push_hash(pb,
+ len);
}
/*
* An operand of this push
@@ -9972,6 +10041,19 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
}
ch->rescue_fail = 0;
ch->defer_since = 0;
+ /*
+ * Mark the slot consumed in this
+ * put epoch before advancing: the
+ * work was not run (the release was
+ * faked), so a later pin-batch
+ * rewind must skip the slot instead
+ * of executing the work against a
+ * staging buffer the client has
+ * since refilled. Missing this mark
+ * was the deterministic vadd input
+ * shift.
+ */
+ svc_replay_note(ch, idx, put);
ch->last_get = (idx + 1) %
ch->gpfifo_entries;
svc.gpfifo_skipped++;
@@ -10179,6 +10261,9 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
memcpy(svc.chan[k].slot_ops_complete,
ch->slot_ops_complete,
sizeof(ch->slot_ops_complete));
+ memcpy(svc.chan[k].slot_ops_hash,
+ ch->slot_ops_hash,
+ sizeof(ch->slot_ops_hash));
/*
* Persist the counter slot autoreg resolved on the
* snapshot: without this the next pass re-derives