From b345bc644520a7537b48a30bd03b4ca65587a2a3 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 8 Oct 2026 11:58:47 -0400 Subject: vgsp-sema: reachable GR counter publish + injected-pin override at autoreg (FORENSICS-v2 H3 + anomaly 6) FORENSICS-v2 H3: svc_counter_publish() was reachable only at the end of a fully pumped channel; when a GR channel's ring or USERD cannot be read the per-channel loop early-`continue`s above that call (USERD not resolvable, GP_PUT unreadable), so pub_calls stayed 0 even with all 20 GR counter slots registered and the client's tracker waiting on them. Duplicate the publish into the safety-net block at the top of the per-channel loop, next to svc_counter_autoreg/svc_counter_flush_pending: - the value is a real GPFIFO put read from the channel's registered USERD; when the USERD is not readable nothing is written, so a guessed put can never advance the client past work the pump cannot see; - the target is the proof-gated slot registration (injected pin or the read-back-validated derive), never an unproven page; - the same relay-hold guard as the end-of-pass call applies. The end-of-pass site now publishes for CE channels only, so a GR pass does not publish twice. Anomaly 6: an earlier-derived counter_pa could disagree with a later-arriving injected pin (r1: derived 0x123a43ff0 vs pin-backed 0x123a28ff0) and nothing re-checked the pin while counter_pa was already registered - a publish would then advance a page the client never polls. svc_counter_autoreg() now re-checks the rank's slot against the injected pin on every pass and re-registers (under chan_lock) on a mismatch, which also makes the new publish proof-gated through the pin. Evidence: FORENSICS-v2 H3 + anomaly 6; 11-hostdmesg-v2-r1.log:2429-2714 (20x "not at fb ... probe rc=-11", pub_calls=0, gr_rel=0)." --- kernel/gsp_sema_service.c | 78 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index 50639e3..ddc7078 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -4703,13 +4703,50 @@ static void svc_counter_autoreg(struct nv_vgpu_device *vgpu, if (ch->engine_type || n_gr <= 0) return; + if (ch->gr_index >= n_gr) + return; + va = 0x20400fec0ULL + (u64)(n_gr - 1 - ch->gr_index) * 0x10ULL; if (ch->counter_pa) { + /* + * FORENSICS-v2 anomaly 6: a slot derived in an earlier pass + * can disagree with a later-arriving injected pin (r1: + * derived 0x123a43ff0 vs pin-backed 0x123a28ff0, off by + * 0x1b000). Injected pins are the authoritative guest truth - + * svc_counter_learn re-checks them when a release decodes - + * but nothing re-checked them while counter_pa was already + * registered and no push had been decoded yet, so a publish + * would have advanced a page the client never polls. Re-check + * the rank's slot on every pass and re-register on a mismatch + * so the publish is proof-gated through the pin. + */ + u64 ipa = 0; + int iap = SVC_APERTURE_SYS; + + if (!svc_ipin_lookup(va, &ipa, &iap) && + iap == SVC_APERTURE_SYS && + (ipa & ~0x3ULL) != ch->counter_pa) { + unsigned long flags; + u64 old = ch->counter_pa; + int j; + + spin_lock_irqsave(&svc.chan_lock, flags); + ch->counter_pa = ipa & ~0x3ULL; + for (j = 0; j < SVC_MAX_CHANNELS; j++) { + if (svc.chan[j].valid && + svc.chan[j].h_object == ch->h_object) { + svc.chan[j].counter_pa = + ch->counter_pa; + break; + } + } + spin_unlock_irqrestore(&svc.chan_lock, flags); + pr_info("sema-chan: counter pin override h=0x%x rank=%u pa=0x%llx (was 0x%llx; injected pin is authoritative)\n", + ch->h_object, ch->gr_index, + ch->counter_pa, old); + } svc_counter_flush_pending(vgpu, ch); return; } - if (ch->gr_index >= n_gr) - return; - va = 0x20400fec0ULL + (u64)(n_gr - 1 - ch->gr_index) * 0x10ULL; /* * The injected-pin table is the authoritative source; only it may * shadow a self-discovered map entry (which can hold a stale @@ -11193,8 +11230,34 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) * remains. */ if (!ch->engine_type) { + u32 pput = 0; + svc_counter_autoreg(vgpu, ch, n_gr); svc_counter_flush_pending(vgpu, ch); + /* + * H3 (FORENSICS-v2): the end-of-pass publish below is + * unreachable on the passes that early-`continue` + * before it (USERD not resolvable, GP_PUT unreadable) + * even though the counter slot is registered and the + * client's tracker waits on it - v2 had pub_calls=0 + * with 20/20 slots registered and the GR ctx-init + * releases unread. Publish here, next to the safety + * net, from the registered USERD when it is readable. + * The value is a real GPFIFO put read from the + * channel's own control block, never a guess, and the + * target is the proof-gated slot registration + * (injected pin, or the read-back-validated derive of + * svc_counter_autoreg). Skip while a relay hold owns + * the channel, exactly like the end-of-pass call. + */ + if (ch->counter_pa && ch->userd_pa && + !(svc_fwd_hold.active && + svc_fwd_hold.vgpu == vgpu && + svc_fwd_hold.ch_handle == ch->h_object) && + !svc_read_ap(vgpu, + ch->userd_pa + SVC_USERD_GP_PUT, + ch->userd_as, &pput, sizeof(pput))) + svc_counter_publish(vgpu, ch, pput); } /* @@ -12385,8 +12448,15 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) * safety net at the top of the pass, so this must not * re-register: once per pass is enough and the derivation is * not free. + * + * H3 (FORENSICS-v2): GR channels publish in the safety-net + * block at the top of the pass - the early `continue`s above + * make this call unreachable exactly when the ring or USERD + * cannot be read, which is the case the publish must still + * cover. Keep this site for the CE channels only, so a GR + * pass does not publish twice. */ - if (!held) + if (!held && ch->engine_type) svc_counter_publish(vgpu, ch, put); spin_lock_irqsave(&svc.chan_lock, flags); -- cgit v1.2.3