summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-09 14:50:03 -0500
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-09 14:50:03 -0500
commitad93b227526f42fa4ea7e529de883a9fa600c34d (patch)
tree8b76a1a3993c289e5e380e1c548a505db32e0292
parent3701f985edb89224f6a5c1237b21ddb8e527e126 (diff)
downloadopen-iov-ad93b227526f42fa4ea7e529de883a9fa600c34d.tar.gz
open-iov-ad93b227526f42fa4ea7e529de883a9fa600c34d.zip
vgsp-sema: keep the scrubber release alive while the event pump blocks
The guest's PMA scrub wait and the host's status-queue pump can deadlock: the X server's scrub wait holds the guest RM lock the status consumer needs, the ring fills, and every critical event post then waits out its full 200 ms ring-full budget. A service pass can take many seconds, so the next pass's scrub step - the only thing that can release the scrub wait - is starved and the guest never makes progress. Run the scrub step from inside the send path's ring-full wait (service thread only), and re-resolve the scrubber's finish/host-sema words on every pass instead of once: the guest re-creates its RM kernel VAS and re-maps the channel buffer under the same GPU VA (observed: gpfifo VA 0x120064000 moved from PA 0x127e6a000 to 0x1122e9000), so a once-only resolution authored a page the guest no longer polled. Prefer the RM kernel VAS (h=0xc) RAM mapping over a client VAS vidmem mapping, and force re-resolution when a scrubber re-registers.
-rw-r--r--kernel/gsp_rm_emulation.c11
-rw-r--r--kernel/gsp_sema_service.c132
-rw-r--r--kernel/gsp_sema_service.h9
3 files changed, 136 insertions, 16 deletions
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index 37b2200..c14a767 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -639,6 +639,17 @@ static int gsp_rm_send_flags(struct gsp_emu_context *ctx, u32 function,
if (w == wait_iters)
break;
mutex_unlock(&rm->stat_lock);
+ /*
+ * r1668: a full status ring means the guest's RM
+ * consumer is blocked, and the usual cause is the
+ * CeUtils scrub wait holding the RM lock the event
+ * callback needs. Authoring the scrubber's finish
+ * payload is the only way out, but the pump cannot
+ * reach its next pass while this post waits, so run
+ * the scrub step here (service thread only; no-op
+ * elsewhere).
+ */
+ gsp_sema_service_scrub_pump(ctx->vgpu);
msleep(1);
mutex_lock(&rm->stat_lock);
}
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 7f61c57..e156339 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -1064,6 +1064,7 @@ struct svc_scrub {
bool pa_ok; /* scanner registered fin/hsem PAs */
u64 fin_pa; /* finish payload word (u32) */
u64 hsem_pa; /* channel host semaphore word (u32) */
+ unsigned long pa_at; /* last successful walk re-resolution */
bool seeded;
u32 put_last;
u64 submitted; /* blocks submitted since seeding */
@@ -2374,6 +2375,14 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
s->gpfifo_va != desc->gpfifo_va) {
s->seeded = false;
s->submitted = 0;
+ /*
+ * r1668: a re-registered scrubber may reuse the
+ * same GPU VA with a new backing page. Force the
+ * finish-word resolution to run again instead of
+ * authoring the previous allocation's page.
+ */
+ s->pa_ok = false;
+ s->authored_gen = 0;
}
s->session_gen = svc_sess_gen;
s->userd_ok = true;
@@ -45302,6 +45311,62 @@ int gsp_sema_service_scrubbuf_add(u64 fin_pa, u64 hsem_pa)
}
/*
+ * Resolve a scrubber channel-buffer VA to guest RAM, preferring the RM
+ * kernel VAS (h=0xc, the address space the guest's own CPU mapping of the
+ * channel buffer lives in). Several VA spaces can map the same VA: the
+ * client's VAS (e.g. beef0300) maps it to its own emulated vidmem object,
+ * and the kernel VAS maps the real channel buffer in RAM. Picking the first
+ * walk success (newest VAS first) therefore picked a vidmem page and the
+ * authored finish word never reached the guest. Returns true on a RAM hit.
+ */
+static bool svc_scrub_resolve_va(struct nv_vgpu_device *vgpu, u64 va,
+ u64 *pa, int *ap, u32 *hvas)
+{
+ struct svc_vaspace local[SVC_MAX_VASPACES];
+ unsigned long flags;
+ u64 best_pa = 0;
+ int best_ap = SVC_APERTURE_SYS;
+ u32 best_h = 0;
+ bool found = false;
+ int i, n = 0;
+
+ spin_lock_irqsave(&svc.vas_lock, flags);
+ for (i = 0; i < SVC_MAX_VASPACES; i++)
+ if (svc.vas[i].valid)
+ local[n++] = svc.vas[i];
+ spin_unlock_irqrestore(&svc.vas_lock, flags);
+
+ for (i = 0; i < n; i++) {
+ u64 wpa = 0;
+ int wap = SVC_APERTURE_SYS;
+
+ if (svc_gmmu_walk(vgpu, local[i].pdb, local[i].entries, va,
+ local[i].ap, &wpa, &wap, false) || !wpa)
+ continue;
+ if (svc_ap_of(wap) != SVC_APERTURE_SYS)
+ continue;
+ if (local[i].h_vaspace == 0xc) {
+ *pa = wpa;
+ *ap = wap;
+ *hvas = local[i].h_vaspace;
+ return true;
+ }
+ if (!found) {
+ best_pa = wpa;
+ best_ap = wap;
+ best_h = local[i].h_vaspace;
+ found = true;
+ }
+ }
+ if (found) {
+ *pa = best_pa;
+ *ap = best_ap;
+ *hvas = best_h;
+ }
+ return found;
+}
+
+/*
* Author the CeUtils scrubber channel's progress once per service pass.
*
* The physical CE would advance, per completed scrub block, the channel's
@@ -45340,10 +45405,11 @@ static void svc_scrub_pass(struct nv_vgpu_device *vgpu)
* entries*8 and fin = hsem + 4. Resolve them through the learned map
* or the VAS walk and register in place.
*/
- if (s->userd_ok && !s->pa_ok && s->gpfifo_va && s->entries) {
+ if (s->userd_ok && s->gpfifo_va && s->entries) {
u64 hva = s->gpfifo_va + (u64)s->entries * 8;
u64 pa = 0;
int ap = SVC_APERTURE_SYS;
+ u32 hv = 0;
/*
* r1667: resolve through the GPU page tables FIRST. The learned
@@ -45353,23 +45419,37 @@ static void svc_scrub_pass(struct nv_vgpu_device *vgpu)
* old page from a previous allocation and the scrub wait never
* completed). Fall back to the pair map only when no walkable VAS
* maps the VA.
+ *
+ * r1668: re-resolve on EVERY pass, not just the first. The guest
+ * re-creates the RM kernel VAS (h=0xc) and re-maps the scrubber
+ * buffer under the same GPU VA when its RM resets (observed after
+ * the first GZDoom run: the same gpfifo VA 0x120064000 moved from
+ * PA 0x127e6a000 to 0x1122e9000). A once-only resolution then
+ * authored a page the guest no longer polls and the scrub wait
+ * never completed, which in turn kept the guest's RM lock held and
+ * froze the status-queue consumer (the pump's event posts then
+ * blocked for 200 ms each, starving this very pass).
*/
- if (svc_walk_vaspaces(vgpu, hva, &pa, &ap) || !pa) {
- if (svc_lookup(vgpu, hva, &pa, &ap) || !pa)
- pa = 0;
- }
- /* The scrub words are guest RAM; an FB translation is not. */
- if (pa && svc_ap_of(ap) != SVC_APERTURE_SYS)
- pa = 0;
- if (pa) {
- s->hsem_pa = pa;
- s->fin_pa = pa + 4;
+ if (svc_scrub_resolve_va(vgpu, hva, &pa, &ap, &hv) && pa) {
+ if (!s->pa_ok || pa != s->hsem_pa) {
+ if (s->log_budget < 24) {
+ s->log_budget++;
+ pr_info("sema-svc: scrub geometry hsem_va=0x%llx fin_va=0x%llx -> hsem_pa=0x%llx fin_pa=0x%llx vas=0x%x\n",
+ hva, hva + 4, pa, pa + 4, hv);
+ }
+ s->hsem_pa = pa;
+ s->fin_pa = pa + 4;
+ }
s->pa_ok = true;
- if (s->log_budget < 16) {
- s->log_budget++;
- pr_info("sema-svc: scrub geometry hsem_va=0x%llx fin_va=0x%llx -> hsem_pa=0x%llx fin_pa=0x%llx\n",
- hva, hva + 4, s->hsem_pa,
- s->fin_pa);
+ s->pa_at = jiffies;
+ } else if (!s->pa_ok) {
+ /* Last resort: the learned pair map, RAM only. */
+ if (!svc_lookup(vgpu, hva, &pa, &ap) && pa &&
+ svc_ap_of(ap) == SVC_APERTURE_SYS) {
+ s->hsem_pa = pa;
+ s->fin_pa = pa + 4;
+ s->pa_ok = true;
+ s->pa_at = jiffies;
}
}
}
@@ -45453,6 +45533,26 @@ static void svc_scrub_pass(struct nv_vgpu_device *vgpu)
}
/*
+ * r1668: run the scrub step from inside a blocked event post.
+ *
+ * When the guest's status-queue consumer is frozen (typically because the
+ * X server's PMA scrub wait holds the RM lock the event callback needs),
+ * the pump's critical-event posts wait out their full ring-full budget
+ * (200 ms each) before dropping. A pass can then take many seconds, and
+ * the next pass's scrub step - the only thing that can release the scrub
+ * wait - is starved. This hook is called from the send path's wait loop,
+ * on the service thread itself, so it runs the same state machine without
+ * any concurrency. It is a no-op from any other thread (RPC replies run
+ * on the GSP emulation thread).
+ */
+void gsp_sema_service_scrub_pump(struct nv_vgpu_device *vgpu)
+{
+ if (!svc.task || !vgpu || current != svc.task)
+ return;
+ svc_scrub_pass(vgpu);
+}
+
+/*
* 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 ->
diff --git a/kernel/gsp_sema_service.h b/kernel/gsp_sema_service.h
index 8fec320..7228cd3 100644
--- a/kernel/gsp_sema_service.h
+++ b/kernel/gsp_sema_service.h
@@ -73,6 +73,15 @@ void gsp_sema_service_vgpu_cleanup(struct nv_vgpu_device *vgpu);
int gsp_sema_service_scrubbuf_add(u64 fin_pa, u64 hsem_pa);
/*
+ * Author the CeUtils scrubber's progress from the GSP-RM send path's
+ * ring-full wait loop (r1668). Called on the sema service thread only; it
+ * lets the scrub completion run while the pump is blocked posting a
+ * critical event into a status ring the guest cannot drain (the guest's RM
+ * consumer is frozen behind the very scrub wait this releases).
+ */
+void gsp_sema_service_scrub_pump(struct nv_vgpu_device *vgpu);
+
+/*
* A GPU VA space whose top-level page directory the driver has published (the
* driver builds its own page tables for the address spaces it owns, then hands
* the directory's physical address to the GPU through SET_PAGE_DIRECTORY).