summaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/gsp_sema_service.c239
1 files changed, 228 insertions, 11 deletions
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index cd7ff73..ff95598 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -920,6 +920,11 @@ struct sema_svc {
u64 sem_write_reresolve; /* writes re-resolved after read-back */
u64 pool_alt_applied; /* pool releases fanned to a
* displaced pin (ISSUE-0002) */
+ u64 pool_dual_applied; /* r539: pool releases fanned
+ * to the secondary candidate page
+ * (pin/walk disagreement; the
+ * r538 init-stall class) */
+ u64 pool_dual_log; /* r539 fan-out log budget */
u64 pool_residue; /* fresh-channel first releases whose
* pool word read far ahead of the
* payload (previous session's
@@ -3560,6 +3565,26 @@ struct svc_pool_boot_pa {
u64 va_page;
u64 pa_page;
int ap;
+ /*
+ * r539: secondary candidate. The pool VA can be answered by two
+ * read-back-proven sources that disagree (the injected pin - the
+ * guest kernel's own tracker->page->rm_mem chain - and the host
+ * GMMU walk of the client's page tables). Either can be the page
+ * the client actually polls: the r536 fix preferred the walk and
+ * the r538 init-stall boot showed the pin and the walk flapping
+ * every resolve while a client sat in uvm channel_pool_add ->
+ * uvm_push_end_and_wait. A release written only to the losing page
+ * strands the tracker. Keep BOTH pages and fan every pool release
+ * out to the secondary as well: the word is a forward-only
+ * monotonic counter, so a duplicate write is absorbed. The
+ * secondary is dropped when it stops being readable and ages out
+ * after SVC_POOL_DUAL_TTL_MS of not being re-proven by any source
+ * (a page the guest has since freed must not be written).
+ */
+ u64 pa2_page;
+ int ap2;
+ unsigned long seen; /* last proof time of the primary */
+ unsigned long seen2; /* last proof time of the secondary */
};
static struct svc_pool_boot_pa svc_pool_boot[SVC_POOL_PA_MAX];
@@ -3567,6 +3592,12 @@ static u32 svc_pool_boot_n;
static u32 svc_pool_boot_next;
static u32 svc_pool_boot_conflicts;
static u32 svc_pool_boot_hits;
+/* r539: fresh-secondary window; the flapping sources (the watcher's ~2 s
+ * pool re-inject, the per-resolve walk) re-prove both candidates well inside
+ * this, while a page the guest has since freed and reused stops being
+ * re-proven and ages out before any write can land on it (the r372
+ * stale-page corruption rule). */
+#define SVC_POOL_DUAL_TTL_MS 10000u
static int svc_pool_boot_lookup(u64 va, u64 *pa, int *ap)
{
@@ -3597,17 +3628,45 @@ static void svc_pool_boot_store(u64 va, u64 pa, int ap)
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);
+ if (svc_pool_boot[i].pa_page == pa_page) {
+ svc_pool_boot[i].ap = ap;
+ svc_pool_boot[i].seen = jiffies;
+ return;
+ }
+ if (svc_pool_boot[i].pa2_page == pa_page) {
+ /* The previously-secondary page is the
+ * freshest proof: promote it, demote the
+ * old primary. No conflict line: both
+ * candidates stay live and both receive
+ * the fan-out. */
+ u64 op = svc_pool_boot[i].pa_page;
+ int oap = svc_pool_boot[i].ap;
+ unsigned long oseen = svc_pool_boot[i].seen;
+
+ svc_pool_boot[i].pa_page = pa_page;
+ svc_pool_boot[i].ap = ap;
+ svc_pool_boot[i].seen = jiffies;
+ svc_pool_boot[i].pa2_page = op;
+ svc_pool_boot[i].ap2 = oap;
+ svc_pool_boot[i].seen2 = oseen;
+ return;
}
+ /*
+ * r539: a second, DIFFERENT page. Keep the old
+ * primary as the secondary candidate instead of
+ * discarding it - the r538 init-stall boot had the
+ * pin and the walk flapping forever, and the client
+ * polls whichever page its own mapping names.
+ */
+ svc_pool_boot_conflicts++;
+ pr_info_ratelimited("sema-svc: pool-boot second candidate va=0x%llx primary=0x%llx -> 0x%llx (old primary kept as secondary)\n",
+ page, svc_pool_boot[i].pa_page, pa_page);
+ svc_pool_boot[i].pa2_page = svc_pool_boot[i].pa_page;
+ svc_pool_boot[i].ap2 = svc_pool_boot[i].ap;
+ svc_pool_boot[i].seen2 = svc_pool_boot[i].seen;
svc_pool_boot[i].pa_page = pa_page;
svc_pool_boot[i].ap = ap;
+ svc_pool_boot[i].seen = jiffies;
return;
}
}
@@ -3619,6 +3678,62 @@ static void svc_pool_boot_store(u64 va, u64 pa, int ap)
svc_pool_boot[i].va_page = page;
svc_pool_boot[i].pa_page = pa_page;
svc_pool_boot[i].ap = ap;
+ svc_pool_boot[i].pa2_page = 0;
+ svc_pool_boot[i].ap2 = 0;
+ svc_pool_boot[i].seen = jiffies;
+ svc_pool_boot[i].seen2 = 0;
+}
+
+/*
+ * r539: fan one applied pool release out to the secondary candidate page
+ * (see struct svc_pool_boot_pa). Forward-only and read-back proven, exactly
+ * like the primary write; a failure is counted, never propagated - the
+ * primary write already landed. A secondary that stopped being readable is
+ * dropped; one that was not re-proven by any source for
+ * SVC_POOL_DUAL_TTL_MS is left alone (the guest may have reused the page).
+ */
+static void svc_pool_boot_fanout(struct nv_vgpu_device *vgpu, u32 h, u64 va,
+ u32 val)
+{
+ u64 page = va & ~(u64)(SEMA_PAGE_SIZE - 1);
+ u64 pa = 0;
+ int ap = SVC_APERTURE_SYS;
+ u32 cur = 0;
+ u32 back = 0;
+ u32 i;
+
+ for (i = 0; i < svc_pool_boot_n; i++)
+ if (svc_pool_boot[i].va_page == page)
+ break;
+ if (i == svc_pool_boot_n || !svc_pool_boot[i].pa2_page)
+ return;
+ if (!svc_pool_boot[i].seen2 ||
+ time_after(jiffies, svc_pool_boot[i].seen2 +
+ msecs_to_jiffies(SVC_POOL_DUAL_TTL_MS)))
+ return;
+ pa = svc_pool_boot[i].pa2_page | (va & (SEMA_PAGE_SIZE - 1));
+ ap = svc_pool_boot[i].ap2;
+ if (svc_pool_pa_identity(va, pa))
+ return;
+ if (svc_read_ap(vgpu, pa, svc_ap_of(ap), &cur, sizeof(cur))) {
+ /* The page is gone: drop the stale secondary. */
+ svc_pool_boot[i].pa2_page = 0;
+ return;
+ }
+ if (cur >= val)
+ return; /* forward-only: nothing to add */
+ if (svc_write_ap(vgpu, pa, svc_ap_of(ap), &val, sizeof(val)))
+ return;
+ if (svc_read_ap(vgpu, pa, svc_ap_of(ap), &back, sizeof(back)))
+ return;
+ if (back != val)
+ return;
+ svc.pool_dual_applied++;
+ if (svc.pool_dual_log < 128) {
+ svc.pool_dual_log++;
+ pr_info("vgsp-sema: pool-fanout ch=0x%x va=0x%llx pa2=0x%llx cur=%u -> val=%u (secondary candidate received the release)\n",
+ h, va, pa, cur, val);
+ }
}
static void svc_pool_boot_forget(u64 va)
@@ -3697,6 +3812,18 @@ static int svc_pool_pa_resolve(struct nv_vgpu_device *vgpu, u32 h, u64 va,
if (!svc_read_ap(vgpu, exact, svc_ap_of(wap),
&wprobe, sizeof(wprobe))) {
+ /*
+ * r539: record the PIN as the
+ * secondary candidate before the walk
+ * page becomes the primary. The walk
+ * is not infallible (the r538 boot:
+ * pin and walk flapped on every
+ * resolve and the client polled the
+ * pin's page); keeping both means the
+ * release fan-out reaches whichever
+ * page the client actually polls.
+ */
+ svc_pool_boot_store(va, pa, a);
svc_pool_pa_store(va, exact, wap);
svc_tr_clear_failure(page);
if (src)
@@ -4008,6 +4135,7 @@ static int svc_sem_write(struct nv_vgpu_device *vgpu, u32 h, u64 va, u32 val)
if (is_pool) {
svc_pool_wr_note(h, va, addr, src, cur, val, "ok");
svc_pool_alt_apply(vgpu, h, va, val);
+ svc_pool_boot_fanout(vgpu, h, va, val);
}
return 0;
}
@@ -4034,6 +4162,7 @@ static int svc_sem_write(struct nv_vgpu_device *vgpu, u32 h, u64 va, u32 val)
svc_pool_wr_note(h, va, addr, "targeted", cur,
val, "reresolve");
svc_pool_alt_apply(vgpu, h, va, val);
+ svc_pool_boot_fanout(vgpu, h, va, val);
}
svc.sem_write_reresolve++;
pr_info_ratelimited("vgsp-sema: sem-write re-resolve va=0x%llx pa=0x%llx old=0x%llx new=0x%x\n",
@@ -4046,6 +4175,7 @@ static int svc_sem_write(struct nv_vgpu_device *vgpu, u32 h, u64 va, u32 val)
svc_pool_wr_note(h, va, old_addr, src, cur, val,
"advisory");
svc_pool_alt_apply(vgpu, h, va, val);
+ svc_pool_boot_fanout(vgpu, h, va, val);
}
return 0; /* the original write landed; read-back is advisory */
}
@@ -4588,12 +4718,13 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu)
* 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",
+ 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 dual=%llu/%u\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);
+ svc_ledger_pin_repairs, svc_pool_alt_skipped,
+ svc.pool_dual_applied, svc_pool_boot_conflicts);
/*
* P6b wave 1: guest-forwarding summary. Printed whenever any counter
* moved (not only on requests) so a run that captured no candidate is
@@ -16096,9 +16227,15 @@ static bool svc_fake_release(struct nv_vgpu_device *vgpu, struct svc_channel *ch
sizeof(back)) &&
back == nv) {
delivered = true;
- if (svc_pool_sem_va(pool_va))
+ if (svc_pool_sem_va(pool_va)) {
svc_pool_pa_store(pool_va, spa,
svc_ap_of(sap));
+ /* r539: reach the secondary candidate
+ * page too (pin/walk disagreement). */
+ svc_pool_boot_fanout(vgpu,
+ ch->h_object,
+ pool_va, nv);
+ }
svc.releases_applied++;
if (!gsp_sema_fake_rel)
svc.releases_recovered++;
@@ -24237,6 +24374,83 @@ static void svc_scrub_pass(struct nv_vgpu_device *vgpu)
s->authored = s->submitted;
}
+/*
+ * r539: init-stall watchdog. The r530-r538 stall boots all share one shape:
+ * the pump keeps looping (rebuilds, pending retries) but the entry loop stops
+ * consuming anything, a client spins at DEVICE (uvm channel_pool_add ->
+ * uvm_push_end_and_wait) and every counter the guest waits on is frozen. The
+ * historical evidence for that state was spread over many bounded logs; this
+ * dump collects the whole stall state in one place when the entry loop makes
+ * no progress for SVC_STALL_WATCH_MS while withheld work exists. Bounded:
+ * SVC_STALL_WATCH_DUMPS per boot, one per re-armed window.
+ */
+#define SVC_STALL_WATCH_MS 20000u
+#define SVC_STALL_WATCH_DUMPS 4u
+
+static unsigned long svc_stall_watch_last;
+static u64 svc_stall_watch_done;
+static u32 svc_stall_watch_dumps;
+
+static void svc_stall_watch_dump(void)
+{
+ u32 i;
+
+ pr_err("vgsp-sema: STALL-DUMP #%u: no GPFIFO entry consumed for %us (done=%llu held=%llu opdef=%llu/%llu/%llu pending=%u starved=%llu)\n",
+ svc_stall_watch_dumps, SVC_STALL_WATCH_MS / 1000u,
+ svc.gpfifo_entries_done, svc.gpfifo_held,
+ svc.opdef_held, svc.opdef_applied, svc.opdef_expired,
+ svc_pending_n, svc.gpfifo_starved);
+ for (i = 0; i < svc_pending_n && i < 16; i++)
+ pr_err("vgsp-sema: STALL-DUMP pending[%u] h=0x%x va=0x%llx pay=%u op=%u tries=%u\n",
+ i, svc_pending[i].h, svc_pending[i].va,
+ svc_pending[i].payload, svc_pending[i].op,
+ svc_pending[i].tries);
+ for (i = 0; i < SVC_OPDEF_SLOTS; i++) {
+ struct svc_opdef_ent *e = &svc_opdef[i];
+
+ if (!e->active)
+ continue;
+ pr_err("vgsp-sema: STALL-DUMP opdef[%u] h=0x%x entry=%u n=%u age_ms=%u first_va=0x%llx pay=%u\n",
+ i, e->h_object, e->entry_idx, (unsigned)e->n,
+ jiffies_to_msecs(jiffies - e->since),
+ e->n ? e->rel[0].va : 0ull,
+ e->n ? e->rel[0].payload : 0u);
+ }
+ for (i = 0; i < svc_pool_boot_n; i++) {
+ struct svc_pool_boot_pa *b = &svc_pool_boot[i];
+
+ if (!b->va_page)
+ continue;
+ pr_err("vgsp-sema: STALL-DUMP pool[%u] va=0x%llx pa=0x%llx ap=%d pa2=0x%llx ap2=%d age_ms=%u\n",
+ i, b->va_page, b->pa_page, b->ap, b->pa2_page,
+ b->ap2, jiffies_to_msecs(jiffies - b->seen));
+ }
+ pr_err("vgsp-sema: STALL-DUMP pool_dual=%llu alt=%llu conflicts=%u residue=%llu\n",
+ svc.pool_dual_applied, svc.pool_alt_applied,
+ svc_pool_boot_conflicts, svc.pool_residue);
+}
+
+static void svc_stall_watch_check(void)
+{
+ if (svc.gpfifo_entries_done != svc_stall_watch_done) {
+ svc_stall_watch_done = svc.gpfifo_entries_done;
+ svc_stall_watch_last = jiffies;
+ return;
+ }
+ if (!svc_stall_watch_last)
+ svc_stall_watch_last = jiffies;
+ if (!time_after(jiffies, svc_stall_watch_last +
+ msecs_to_jiffies(SVC_STALL_WATCH_MS)))
+ return;
+ svc_stall_watch_last = jiffies; /* re-arm the window */
+ if (svc_stall_watch_dumps >= SVC_STALL_WATCH_DUMPS)
+ return;
+ if (!svc_pending_n && !svc.opdef_held)
+ return; /* idle boot, not a stall */
+ svc_stall_watch_dumps++;
+ svc_stall_watch_dump();
+}
+
static int svc_thread(void *arg)
{
struct nv_vgpu_device *vgpus[SVC_MAX_GUESTS];
@@ -24384,6 +24598,9 @@ static int svc_thread(void *arg)
svc_apply_sweep_slice(vgpu);
}
+ /* r539: one stall dump when the entry loop stops progressing. */
+ svc_stall_watch_check();
+
/*
* While the pump is making progress the client is streaming
* pushes and waiting on their releases: loop tightly and skip