diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-27 06:36:00 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-27 06:36:00 -0400 |
| commit | 8898bd00b7c2e6385ceb7238f29d3672308b949a (patch) | |
| tree | 79dc6fa714d22749134dcd8073a18f1585ae9ebb /kernel/gsp_sema_service.c | |
| parent | fffae7b19383373a95032645c583ccf76add23df (diff) | |
| download | open-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.
Diffstat (limited to 'kernel/gsp_sema_service.c')
| -rw-r--r-- | kernel/gsp_sema_service.c | 183 |
1 files changed, 177 insertions, 6 deletions
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; |
