From eee9223116692285dece42568defbecb2e4e0cdf Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 15 Oct 2026 17:50:57 -0400 Subject: vgsp-sema: m156 fail-closed release gate - withhold unwitnessed GR completion Companion to the backend witness gate. A data-bearing forwarded GR launch's completion release is now authored only when an executor was witnessed (backend success = retirement wait observed writes, or the CUDA bridge owns it, or an engine fault was delivered as the loud edge). When the hold resolves with no witness, the release is withheld and the channel's error notifier + event are delivered so the client observes a launch failure instead of stale/zero output (r150 big-copy, r155 streams2 STREAMS bad=32768). - svc_gr_rel_witnessed() gate at the two hold-resolution sites (svc_fwd_hold_expire_check, svc_fwd_hold_poll failure branch); - svc_gr_failclosed_deliver() reuses the fault-deliver notifier tail (record shape + notifier_err latch) with the host-detected marker; - a queued data-bearing GR launch now takes the hold even when the pass recorded no release of its own, so the deferred completion release always has an author (its success edge) and a withholder (its failure edge); - the GR hold bound is raised to 40 s under the gate so the cold-context recovery chain (5 s + re-arm + 5 s + 3x2 s retries) can publish its outcome before the watchdog fires; - new gr_failclosed counter in the work_guard summary line. gsp_phys_rmcall_gr_failclosed=0 restores the historical behavior with one switch. Build: make -j8 (kernel), green. --- kernel/gsp_sema_service.c | 159 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 149 insertions(+), 10 deletions(-) diff --git a/kernel/gsp_sema_service.c b/kernel/gsp_sema_service.c index f8925df..373da64 100644 --- a/kernel/gsp_sema_service.c +++ b/kernel/gsp_sema_service.c @@ -815,6 +815,13 @@ struct sema_svc { u64 fwd_gr_timeout; u64 fwd_gr_fault; u64 fwd_gr_bridge; + /* + * m156 fail-closed releases: data-bearing forwarded GR launches whose + * completion release was withheld because no executor was witnessed + * (the engine acked but wrote nothing), with a channel error + * delivered instead. Counts the gate firing, not backend failures. + */ + u64 fwd_gr_failclosed; /* * GR launch completion releases (the QMD-level semaphore the physical * engine authors on success) that the emulation had to write itself @@ -3507,15 +3514,15 @@ static void svc_rebuild(struct nv_vgpu_device *vgpu) svc.fwd_rel_fallback || svc.fwd_rel_pending || svc.fwd_rel_overflow || svc.fwd_hold_engaged || svc.fwd_hold_timeouts || svc.fwd_hold_canceled || - svc.fwd_gr_sw_rel) - pr_info("sema-svc: work_guard deferred=%llu applied=%llu fallback=%llu pending=%llu overflow=%llu engaged=%llu timeouts=%llu canceled=%llu copy_skipped=%llu copy_soft=%llu copy_fail=%llu direct=%llu gr_sw_rel=%llu\n", + svc.fwd_gr_sw_rel || svc.fwd_gr_failclosed) + pr_info("sema-svc: work_guard deferred=%llu applied=%llu fallback=%llu pending=%llu overflow=%llu engaged=%llu timeouts=%llu canceled=%llu copy_skipped=%llu copy_soft=%llu copy_fail=%llu direct=%llu gr_sw_rel=%llu gr_failclosed=%llu\n", svc.fwd_rel_deferred, svc.fwd_rel_applied, svc.fwd_rel_fallback, svc.fwd_rel_pending, svc.fwd_rel_overflow, svc.fwd_hold_engaged, svc.fwd_hold_timeouts, svc.fwd_hold_canceled, svc.fwd_copy_skipped, svc.fwd_copy_soft, svc.fwd_copy_fail, svc.rel_hold_direct, - svc.fwd_gr_sw_rel); + svc.fwd_gr_sw_rel, svc.fwd_gr_failclosed); /* show a few entries so mappings can be verified from dmesg */ if (gsp_sema_dbg) { u32 shown; @@ -10907,6 +10914,17 @@ static void svc_fwd_soft_copy_inline(struct nv_vgpu_device *vgpu) * hardware has not produced yet. */ #define SVC_FWD_GR_HOLD_TIMEOUT_MS 12000u +/* + * m156: a data-bearing GR launch under the fail-closed gate can legitimately + * spend the whole cold-context recovery chain before its outcome is + * published (5 s initial poll + re-arm + 5 s + up to three 2 s fresh-entry + * retries plus the retirement waits). Expiring the hold inside that window + * would fail a launch that is still retrying, so the gate raises the GR + * hold bound to this value; the backend is itself bounded, so the work + * item's done always arrives before it, and the value remains the + * last-resort watchdog. + */ +#define SVC_FWD_GR_HOLD_GATED_MS 40000u struct svc_fwd_hold { bool active; @@ -11416,11 +11434,98 @@ static void svc_fwd_hold_gr_release(struct nv_vgpu_device *vgpu, svc_fwd_req.gr_rel_payload); } +/* + * m156 fail-closed witness. A data-bearing forwarded GR launch's completion + * release may only be authored when an executor witnessed the launch: the + * backend returns success only after the retirement wait observed engine + * writes (gsp_phys_rmcall_gr_failclosed), and the CUDA bridge owns a launch + * it executed. The engine's own ack (qmd_rel/ch_rel) is a front-end + * retirement, not an execution witness - r155 live: streams2's launch + * logged qmd_rel=6/ch_rel=0xc0ffee02 with "0/205 pages changed" on the + * initial attempt and all three outcome retries, the backend returned + * success, and the client read all-zero output (STREAMS bad=32768). A + * launch with no resolved data pages has no output to lose and keeps the + * historical release behavior. + */ +static bool svc_gr_rel_witnessed(bool done) +{ + if (!gsp_phys_rmcall_gr_failclosed_value()) + return true; /* gate disabled: historical behavior */ + if (!svc_fwd_req.gr || !svc_fwd_req.gr_n_data) + return true; + /* + * A delivered engine fault is itself a loud failure edge (notifier + + * channel event); the release then wakes the client's tracker so it + * observes the error instead of hanging. + */ + if (svc_fwd_hold.fault_delivered) + return true; + if (gsp_cuda_bridge_enabled() && svc_fwd_req.gr_bridge_done) + return true; + return done && svc_fwd_req.hw_rc == 0; +} + +/* + * m156 fail-closed delivery. When the release is withheld because no + * executor was witnessed, write the channel's error notifier and post the + * channel event so the guest driver observes a launch failure instead of + * waiting forever on a word nothing will write (the ISSUE-0002 wedge). The + * record shape and the notifier_err latch mirror svc_fwd_fault_deliver's + * notifier tail; there is no decoded fault record here, so info32 carries + * the host-detected failure marker 0x1. + */ +static void svc_gr_failclosed_deliver(struct nv_vgpu_device *vgpu, + struct svc_channel *ch) +{ + u8 notif[SVC_NOTIFY_SIZE] = {0}; + u32 info32 = 0x1u; + u16 info16, status = SVC_NOTIFY_ERROR_STATUS; + int ret; + + if (!vgpu || !ch || !ch->notifier_pa || ch->notifier_err) + return; + info16 = (u16)ch->engine_type; + memcpy(notif + SVC_NOTIFY_INFO32_OFF, &info32, 4); + memcpy(notif + SVC_NOTIFY_INFO16_OFF, &info16, 2); + memcpy(notif + SVC_NOTIFY_STATUS_OFF, &status, 2); + ret = svc_write_ap(vgpu, ch->notifier_pa, ch->notifier_as, notif, + sizeof(notif)); + if (ret) { + pr_err_ratelimited("vgsp-gr: FAIL-CLOSED notifier write failed ch=0x%x pa=0x%llx as=%u ret=%d\n", + ch->h_object, ch->notifier_pa, + ch->notifier_as, ret); + return; + } + ch->notifier_err = true; + ch->notifier_err_until = jiffies + SVC_NOTIFIER_ERR_HOLD; + ch->notifier_err_put = ch->put_prev; + svc.fwd_gr_failclosed++; + pr_info("vgsp-gr: FAIL-CLOSED: completion release withheld for data-bearing launch h=0x%x (no executor witnessed; guest release va=0x%llx pay=0x%x not authored); channel error delivered\n", + ch->h_object, svc_fwd_req.gr_rel_va, + svc_fwd_req.gr_rel_payload); + ret = gsp_rm_post_channel_event(vgpu, ch->h_object); + if (ret) + pr_info("vgsp-gr: FAIL-CLOSED channel event post failed ch=0x%x ret=%d\n", + ch->h_object, ret); +} + static bool svc_fwd_hold_expired(void) { - if (svc_fwd_req.gr && !smp_load_acquire(&svc_fwd_req.done)) + if (svc_fwd_req.gr && !smp_load_acquire(&svc_fwd_req.done)) { + unsigned long ms = SVC_FWD_GR_HOLD_TIMEOUT_MS; + + /* + * m156: a data-bearing launch under the fail-closed gate must + * not have its hold resolved while the backend is still + * running its bounded recovery chain (the outcome decides + * whether the release is authored or withheld). + */ + if (svc_fwd_req.gr_n_data > 0 && + gsp_phys_rmcall_gr_failclosed_value()) + ms = SVC_FWD_GR_HOLD_GATED_MS; return time_after(jiffies, svc_fwd_hold.since + - msecs_to_jiffies(SVC_FWD_GR_HOLD_TIMEOUT_MS)); + msecs_to_jiffies(ms)); + } return time_after(jiffies, svc_fwd_hold.since + msecs_to_jiffies(SVC_FWD_HOLD_TIMEOUT_MS)); } @@ -11646,7 +11751,15 @@ static void svc_fwd_hold_expire_check(struct nv_vgpu_device *vgpu, shim.engine_type = svc_fwd_hold.engine_type; svc_fwd_rel_apply_all(hv, &shim, svc_fwd_hold.rel, svc_fwd_hold.nrel); - svc_fwd_hold_gr_release(hv, &shim); + /* + * m156: a data-bearing launch whose hold resolved with no + * executor witness keeps its completion release withheld and + * gets a channel error instead (fail closed). + */ + if (svc_gr_rel_witnessed(done)) + svc_fwd_hold_gr_release(hv, &shim); + else + svc_gr_failclosed_deliver(hv, ch); } if (!(done && svc_fwd_req.hw_rc == 0)) svc_fwd_hold_direct_note(); @@ -11751,7 +11864,17 @@ static bool svc_fwd_hold_poll(struct nv_vgpu_device *vgpu, svc.fwd_relay_fallback++; svc_fwd_rel_apply_all(hv, ch, svc_fwd_hold.rel, svc_fwd_hold.nrel); - svc_fwd_hold_gr_release(hv, ch); + /* + * m156 fail-closed: a data-bearing forwarded launch with no + * executor witness must not have its completion release + * authored here (the r150 big-copy / r155 streams2 class: + * the client would read stale/zero output as a successful + * kernel). Withhold the release and deliver a channel error. + */ + if (svc_gr_rel_witnessed(done)) + svc_fwd_hold_gr_release(hv, ch); + else + svc_gr_failclosed_deliver(hv, ch); svc_fwd_hold_direct_note(); svc.fwd_rel_fallback++; if (!done) @@ -14316,10 +14439,26 @@ static void svc_pump_channels(struct nv_vgpu_device *vgpu) * request leaves the * authorship to the * hardware (or its hold - * fallback). + * fallback). m156: a + * queued data-bearing GR + * launch under the + * fail-closed gate needs + * the hold even when this + * pass recorded no release + * of its own - the hold's + * success edge is the only + * author of the deferred + * completion release and + * its failure edge withholds + * it. */ - if (svc_fwd_defer_armed && - svc_fwd_rel_n > 0) { + if ((fwd_guest_queued && + !ch->engine_type && + gsp_phys_rmcall_gr_failclosed_value() && + svc_fwd_req.gr && + svc_fwd_req.gr_n_data > 0) || + (svc_fwd_defer_armed && + svc_fwd_rel_n > 0)) { if (fwd_guest_queued && !svc_fwd_hold.active) { svc_fwd_hold_engage(vgpu, -- cgit v1.2.3