summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-27 06:36:00 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-10-27 06:36:00 -0400
commit8898bd00b7c2e6385ceb7238f29d3672308b949a (patch)
tree79dc6fa714d22749134dcd8073a18f1585ae9ebb
parentfffae7b19383373a95032645c583ccf76add23df (diff)
downloadopen-iov-8898bd00b7c2e6385ceb7238f29d3672308b949a.tar.gz
open-iov-8898bd00b7c2e6385ceb7238f29d3672308b949a.zip
vgsp: service the guest RC watchdog channel (Volta GPFIFO class + class2d notifier ack)
The guest kernel's krcWatchdog posts a notifier cycle on its own channel (object 0x31415900) every second and declares 'GPU is probably locked' when the zeroed notification never comes back within 7 s. Observed as the sustained-GL-load wedge: once a GL client (GZDOOM) enabled robust channels the errors started and Xorg eventually stalled on screen readback. - rm: register the Volta GPFIFO class 0xc36f (plus the later generations) as a channel class. The watchdog's channel allocates 0xc36f on this device - the driver picks the first class the gpu reports supported - and without it the channel never reached the progress service. It now registers and latches (put visible, entries walked). - sema: decode the class2d notifier cycle on subchannel 3 (SET_NOTIFY_A/B name the notifier VA, NOTIFY WRITE_ONLY writes the zeroed NvNotification) and author the write, like svc_notify_complete does for the work-submit token. Proven live: 'sema-notify2d: va=... zeroed (RC watchdog acked)'. - sema: also ack on every pass that sees the channel's put != get, so the ack does not depend on the ring reads (its VA is known from the first decode); idempotent. - sema: never consume a zero-length/zero-VA GPFIFO entry as empty work. Two bogus zero entries consumed through a mis-resolved ring page pushed the watchdog userd's GPGet past the driver's own GPPut and every later userd probe then read get>put, permanently rejecting the channel's real userd. Hold such entries (bounded, per-channel counter persists through the pass snapshot). - sema: a registered userd value that is a real guest PA (the watchdog's own block) outranks the ring+0x2000 convention guess the ring auto-pin applies for the CUDA GR layout. - rm: log channel-class allocs whose params are too short for registration instead of skipping silently. Remaining (next): under heavy GL traffic the watchdog ring's translation can go stale for stretches (content-scanned map answering where a pin used to), which starves the ack; pin the push/notifier pages when the first real decode delivers them. The frame capture (xshm_pub -> XShmGetImage) still stalls while that starvation is active.
-rw-r--r--kernel/gsp_rm_emulation.c27
-rw-r--r--kernel/gsp_sema_service.c183
2 files changed, 202 insertions, 8 deletions
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index d6412c3..fc08b9d 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -3052,8 +3052,21 @@ static bool gsp_rm_is_channel_class(u32 class_id)
case 0x906f: /* MAXWELL_CHANNEL_GPFIFO_A */
case 0xa06f: /* KEPLER_CHANNEL_GPFIFO_A */
case 0xb06f: /* PASCAL_CHANNEL_GPFIFO_A */
- case 0xc06f: /* VOLTA_CHANNEL_GPFIFO_A */
+ case 0xc06f: /* VOLTA_CHANNEL_GPFIFO_A (PRE-TURING NAME) */
+ case 0xc36f: /* VOLTA_CHANNEL_GPFIFO_A */
+ /*
+ * r828: the RC watchdog's channel allocates 0xc36f on this
+ * device (the driver's gpfifoMapping loop picks the first
+ * class the gpu reports supported) - without it in this set
+ * the channel never reaches the progress service, its class2d
+ * notifier cycle (SET_NOTIFY_A/B + NOTIFY) never executes and
+ * the guest's krcWatchdog declares the GPU locked after its
+ * 7 s timeout as soon as robust channels are in play.
+ */
case 0xc46f: /* TURING_CHANNEL_GPFIFO_A */
+ case 0xc56f: /* AMPERE_CHANNEL_GPFIFO_A */
+ case 0xc86f: /* HOPPER_CHANNEL_GPFIFO_A */
+ case 0xc96f: /* BLACKWELL_CHANNEL_GPFIFO_A */
return true;
default:
return false;
@@ -3075,8 +3088,18 @@ static void gsp_rm_register_channel(struct gsp_emu_context *ctx, u32 h_object,
{
struct sema_channel_desc desc;
- if (params_size < GSPRM_CHAN_NOTIFIER_MEM + 8)
+ if (params_size < GSPRM_CHAN_NOTIFIER_MEM + 8) {
+ /*
+ * r828 diag: a channel-class alloc whose params are shorter
+ * than the notifier field is not registered, silently. The
+ * RC watchdog channel (hObject 0x31415900) never appeared in
+ * the sema table; name every such skip with its size so the
+ * shape question is answered by a log, not inference.
+ */
+ pr_info("vgsp-rm: channel alloc hObject=0x%x params_size=%u too short (< %u); NOT registered\n",
+ h_object, params_size, GSPRM_CHAN_NOTIFIER_MEM + 8);
return;
+ }
memset(&desc, 0, sizeof(desc));
desc.vgpu = ctx->vgpu;
diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c
index 1fa09d0..9d3697f 100644
--- a/kernel/gsp_sema_service.c
+++ b/kernel/gsp_sema_service.c
@@ -506,6 +506,20 @@ struct svc_channel {
*/
u8 userd_scans;
/*
+ * r828: the RC watchdog's notifier VA, captured from the class2d
+ * SET_NOTIFY_A/B methods on subchannel 3 and zeroed at the NOTIFY
+ * (see svc_notify2d_write). Survives the pass snapshot via the
+ * write-back list.
+ */
+ u64 notify2d_va;
+ /* r829: bounded count of zero-entry (bad-translation) holds; see the
+ * zero-entry hold in svc_pump_channels. Persists via the write-back. */
+ u32 zero_held;
+ /* r831: the registered userd value is a real guest PA (the driver's
+ * own declaration; e.g. the RC watchdog channel). The ring auto-pin's
+ * ring+0x2000 convention must not overwrite it. */
+ bool userd_reg;
+ /*
* r471: the completion-counter publish must never advance the
* guest's word past a push whose content was still unwritten when
* the entry was consumed (the streams_events staging-reuse
@@ -1562,6 +1576,16 @@ void gsp_sema_service_channel_add(const struct sema_channel_desc *desc)
ch->gpfifo_entries = desc->gpfifo_entries;
ch->userd_pa = desc->userd_pa;
ch->userd_as = desc->userd_as ? desc->userd_as : SVC_APERTURE_SYS;
+ /*
+ * r831: a registered userd value that looks like a real guest
+ * PA (the RC watchdog's channel carries its own userd block)
+ * is the driver's own declaration and outranks the ring+0x2000
+ * convention guess the ring auto-pin applies (that guess is
+ * right for the CUDA GR layout, where the registered value is
+ * pool-relative - observed 0x10000). Tiny registered values
+ * stay guesses.
+ */
+ ch->userd_reg = desc->userd_pa >= 0x10000000ULL;
ch->instance_pa = desc->instance_pa;
ch->notifier_pa = desc->notifier_pa;
ch->notifier_as = desc->notifier_as ? desc->notifier_as
@@ -3227,6 +3251,8 @@ static u32 svc_rdw_log;
* appeared. Each scan reports its decision, a scan that found nothing
* reportable logs a sweep summary. */
static u32 svc_userdp_log;
+/* r829: bounded witness for the zero-entry holds (bad ring translations). */
+static u32 svc_zero_entry_log;
static u8 svc_read_last_src;
/* r715: PA the winning source of the last push read resolved to. */
static u64 svc_read_last_pa;
@@ -10742,6 +10768,54 @@ static void svc_push_raw_dump(const u8 *buf, u32 len, u64 push_va,
h_object, push_va, off, len);
}
+/*
+ * r828: the guest kernel's RC watchdog (krcWatchdog_IMPL, 7 s notify
+ * timeout) posts a notifier cycle on its own channel (object 0x31415900)
+ * through the class2d subchannel (NVA06F_SUBCHANNEL_2D = 3): SET_NOTIFY_A/B
+ * name the notifier VA, NOTIFY (WRITE_ONLY) is the GPU's "write a zeroed
+ * NvNotification" - the driver set the status word to 0xFFFF and declares
+ * "GPU is probably locked" when it never comes back as 0. Observed: robust
+ * channels enabled by the GL client (GZDOOM), then NVRM krcWatchdog every
+ * 8 s and Xorg dead on its screen readback. The emulation must author the
+ * notification write, exactly like svc_notify_complete does for the
+ * work-submit token.
+ */
+#define SVC_2D_SUBCH 3 /* NVA06F_SUBCHANNEL_2D */
+#define SVC_2D_SET_NOTIFY_A 0x0104 /* payload: addr bits [56:32] */
+#define SVC_2D_SET_NOTIFY_B 0x0108 /* payload: addr bits [31:0] */
+#define SVC_2D_NOTIFY 0x010c /* payload: 0 = WRITE_ONLY */
+
+static u64 svc_notify2d_done;
+static u32 svc_notify2d_log;
+
+static void svc_notify2d_write(struct nv_vgpu_device *vgpu,
+ struct svc_channel *ch)
+{
+ /* 16 bytes = the NvNotification (status word at +14). */
+ u8 zero[16] = { 0 };
+ u64 pa = 0;
+ int ap = SVC_APERTURE_SYS;
+
+ if (!ch->notify2d_va)
+ return;
+ if (svc_lookup(vgpu, ch->notify2d_va, &pa, &ap)) {
+ if (gsp_sema_dbg && svc_notify2d_log < 16) {
+ svc_notify2d_log++;
+ pr_info("sema-notify2d: h=0x%x va=0x%llx unresolved (RC watchdog notifier; guest re-posts every second)\n",
+ ch->h_object, ch->notify2d_va);
+ }
+ return;
+ }
+ if (svc_write_ap(vgpu, pa, svc_ap_of(ap), zero, sizeof(zero)))
+ return;
+ svc_notify2d_done++;
+ if (gsp_sema_dbg && svc_notify2d_log < 16) {
+ svc_notify2d_log++;
+ pr_info("sema-notify2d: h=0x%x va=0x%llx -> pa=0x%llx zeroed (RC watchdog acked)\n",
+ ch->h_object, ch->notify2d_va, pa);
+ }
+}
+
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,
@@ -11292,6 +11366,33 @@ static void svc_decode_stream(struct nv_vgpu_device *vgpu, const u8 *buf,
continue;
}
+ if (subch == SVC_2D_SUBCH && count >= 1 && addr >= 0x0100 &&
+ addr <= 0x010c) {
+ /*
+ * r828: the RC watchdog's notifier cycle (class2d
+ * methods on subchannel 3). See svc_notify2d_write:
+ * without the zeroed notification the guest's
+ * krcWatchdog declares the GPU locked after 7 s and
+ * the X/GL stack wedges on screen readback.
+ */
+ switch (addr) {
+ case SVC_2D_SET_NOTIFY_A:
+ ch->notify2d_va = (u64)(v0 & 0x1ffffff) << 32;
+ break;
+ case SVC_2D_SET_NOTIFY_B:
+ ch->notify2d_va |= (u64)v0;
+ break;
+ case SVC_2D_NOTIFY:
+ if (count == 1 && v0 == 0) /* WRITE_ONLY */
+ svc_notify2d_write(vgpu, ch);
+ break;
+ default:
+ break;
+ }
+ off += 4 + count * 4;
+ continue;
+ }
+
if (subch == 0 && addr == 0) {
/* SET_OBJECT on the channel subchannel: a push starts */
n_ops = 0;
@@ -16997,9 +17098,15 @@ static void svc_gr_ring_autopin(struct nv_vgpu_device *vgpu,
&get);
svc_pin_store(ch->gpfifo_va, pin_pa, pin_ap);
svc_lat_store(ch->gpfifo_va, pin_pa, pin_ap);
- ch->userd_pa = pin_pa + SVC_GR_USERD_OFF;
- ch->userd_as = svc_ap_of(pin_ap);
- ch->userd_ok = true;
+ /* r831: keep a registered real-PA userd. */
+ if (ch->userd_reg) {
+ ch->userd_ok = true;
+ } else {
+ ch->userd_pa =
+ pin_pa + SVC_GR_USERD_OFF;
+ ch->userd_as = svc_ap_of(pin_ap);
+ ch->userd_ok = true;
+ }
svc_gr_ring_base = base;
if (svc_ap_of(pin_ap) == SVC_APERTURE_FB)
svc_fb_claim_add(ch->h_object, pin_pa,
@@ -17036,9 +17143,19 @@ static void svc_gr_ring_autopin(struct nv_vgpu_device *vgpu,
&get)) {
svc_pin_store(ch->gpfifo_va, wpa, wap);
svc_lat_store(ch->gpfifo_va, wpa, wap);
- ch->userd_pa = wpa + SVC_GR_USERD_OFF;
- ch->userd_as = wap;
- ch->userd_ok = true;
+ /*
+ * r831: the ring+0x2000 userd convention is a guess
+ * for the CUDA GR layout; a channel whose REGISTERED
+ * userd is a real PA (the RC watchdog) keeps its own
+ * (the candidate gate above probes it as fallback).
+ */
+ if (ch->userd_reg) {
+ ch->userd_ok = true;
+ } else {
+ ch->userd_pa = wpa + SVC_GR_USERD_OFF;
+ ch->userd_as = wap;
+ ch->userd_ok = true;
+ }
svc_gr_ring_base = wpa;
if (wap == SVC_APERTURE_FB)
svc_fb_claim_add(ch->h_object, wpa,
@@ -26782,6 +26899,23 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
}
/*
+ * r832: the RC watchdog's notifier ack must not depend on the
+ * ring reads. Its VA is known from the channel's first decode
+ * (ch->notify2d_va, captured from SET_NOTIFY_A/B) and the ack
+ * is the same zeroed NvNotification the decode path writes:
+ * the guest re-arms status=0xFFFF every cycle and checks it
+ * back to 0 within 7 s. Under heavy GL traffic the watchdog
+ * ring's translation can go stale for stretches (its entries
+ * then read as zeros and are consumed as empty), which starved
+ * exactly this write and wedged the guest with "GPU is
+ * probably locked" while every other channel kept running.
+ * Writing the ack on every pass that sees work pending is
+ * idempotent.
+ */
+ if (!ch->engine_type && ch->notify2d_va && put != get)
+ svc_notify2d_write(vgpu, ch);
+
+ /*
* ISSUE-0002 diag: put snapshots for CE channels, bounded per
* session. The throttle is indexed by rank (the channel struct
* iterated here is a local copy, so a per-channel stamp never
@@ -27224,6 +27358,40 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
}
/*
+ * r829: a zero-length / zero-VA entry is a bad read
+ * (a stale or wrong translation), never work. The old
+ * path consumed it as an empty push and advanced
+ * GPGet; observed r830 on the RC watchdog channel:
+ * two bogus zero entries consumed through a
+ * mis-resolved ring page pushed the userd's GPGet past
+ * the driver's own GPPut, every later userd probe then
+ * read get>put and the channel's REAL userd was
+ * rejected - the channel stopped being serviced and
+ * the guest declared the GPU locked. Hold the entry
+ * instead (bounded; the per-channel counter persists
+ * through the pass snapshot); a corrected translation
+ * (the ring auto-pin lands within seconds) re-reads
+ * it. Past the cap the old consume-as-empty behavior
+ * resumes so an unresolvable ring cannot livelock the
+ * channel.
+ */
+ if (va == 0 || len == 0) {
+ if (ch->zero_held < 512 && !svc_hold_bypass()) {
+ ch->zero_held++;
+ svc.gpfifo_held++;
+ if (gsp_sema_dbg &&
+ svc_zero_entry_log < 16) {
+ svc_zero_entry_log++;
+ pr_info("sema-chan: zero-entry hold h=0x%x idx=%llu raw=%08x%08x (bad ring translation; not consumed)\n",
+ ch->h_object, idx,
+ e1, e0);
+ }
+ stalled = true;
+ continue;
+ }
+ }
+
+ /*
* Cheap wait for an entry already inside its
* expensive-wait window: read + rescue + hold were
* already paid for this VA, and a pin-batch rewind
@@ -29270,6 +29438,9 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu)
svc.chan[k].gr_ram_logged = ch->gr_ram_logged;
svc.chan[k].gr_ram_ring = ch->gr_ram_ring;
svc.chan[k].userd_scans = ch->userd_scans;
+ svc.chan[k].notify2d_va = ch->notify2d_va;
+ svc.chan[k].zero_held = ch->zero_held;
+ svc.chan[k].userd_reg = ch->userd_reg;
svc.chan[k].logged = ch->logged;
svc.chan[k].logged_expected = ch->logged_expected;
svc.chan[k].notified = ch->notified;