From 2658fedd556f872f11dd3aeefa88ff6eb76c5342 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Sun, 18 Oct 2026 12:06:43 -0400 Subject: vgsp: bounded GR launch completion (GR-STALL), r220 hold storm fix, churn caps Every GR launch now either completes or fails fast with a distinguishable dmesg line; no host wait in the launch/consume path can run forever. - gsp_phys_gr_wait_max_ms (new param, default 60000, clamp [10000,600000], 0 = built-in per-ladder bounds only): absolute per-launch submit budget. Checked in the GR poll loop (after the release acks) and in the retirement-retry loop; on expiry the submit fails out ('vgsp-gr: GR-STALL-wait-max ... action=fail') or the retry loop stops ('GR-STALL-retry ... action=proceed'), so the chain of individually bounded ladder phases can no longer hold the single-inflight work item (and the client's launch wait) for the ladder sum. - gsp_sema_service: pass-top svc_fwd_outcome_check() completes a forwarded GR request that no hold owns. A GR push with no push-level release records engages no release hold, so a backend failure (ladder timeout, abort) left the QMD completion word unwritten and the client waited to its own timeout (r219/r222, qmd_rel frozen at the previous value). The check authors the word in software (full resolver, forward-only, latched once) and logs 'GR-STALL-backend ... action=proceed'. It also covers a *successful* submit whose release0 could not target the guest page (engine completion landed on the module semaphore only) - the work item records gr_rel_unobserved for that. - Consume tail: a GR capture consumed without ever being forwarded (a data-stall / busy-defer / no-capture hold fell through) now authors its QMD completion release, latched per capture, and logs 'GR-STALL-hold'. - r220 hold-storm fix (196 s re-hold): the m150/m156 data-stall hold window is keyed to the GPFIFO slot index, and a pin-batch rewind replays the same launch at different slots, so the window re-armed on every pass; the absolute per-launch backstop could never fire because its record fields were excluded from the pump snapshot write-back list (the GRFIX NOTE record leak) and the fill gate defaulted off. The four bound fields are now written back and svc_gr_hold_bound_expired is effective on the default path (cap gsp_phys_gr_hold_max_ms, default 3000 ms, keyed to the launch push VA via svc_gr_launch_va so nothing re-arms it across rewinds); the busy-defer hold site consults the same bound. On expiry the entry is consumed with the software completion and logs 'GR-STALL-hold-bound'. - Hold expiry/failure edges log 'GR-STALL-fwd-hold' / 'GR-STALL-fwd-fail' with waited_ms for held forwarded launches. - Anti-degradation under pin churn/leftover clients: svc_rescue_scan_range (up to 6 GB of guest-RAM reads on the service thread) is capped at 3 s per range with RESCUE-SCAN-CAP logging; the ledger eviction burst triggers a logged wholesale cache reset (LEDGER-RESET cap-and-degrade, 256 evictions); pending-ring overflow is counted and surfaced (PENDING-OVF, pend_ovf in the drain line). New diag tokens: gr_swrel/pend_ovf, ledger resets, rescue scan_capped, work_gr stall_wait_max/stall_retry/wait_max_ms. Green behavior: normal launches complete in ~0.2-11 s and never approach the budget; the hold bound only fires where the old path could stall forever. make kernel green; only pre-existing warnings. (cherry picked from commit dc3650105f163631bb826ae3685a7d6e9025bfe8) --- kernel/gsp_phys_booter.c | 82 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) (limited to 'kernel/gsp_phys_booter.c') diff --git a/kernel/gsp_phys_booter.c b/kernel/gsp_phys_booter.c index 5e16630..6a66a16 100644 --- a/kernel/gsp_phys_booter.c +++ b/kernel/gsp_phys_booter.c @@ -16202,6 +16202,16 @@ static u64 gsp_phys_gr_pool_exhausted; /* m156 (E5): zero-write retries where the warm sem push proved the engine * wedged (the persistent-lottery class); further retries are skipped. */ static u64 gsp_phys_gr_sem_wedged; +/* + * STALLFREE: bounded-completion telemetry. stall_wait_max counts launches + * that crossed gsp_phys_gr_wait_max_ms and failed out of the submit ladder + * (the sema service then authors the QMD release in software); stall_retry + * counts launches whose retirement retry loop stopped early on the same + * budget (the launch proceeds with its current outcome). Both are zero on a + * green run. + */ +static u64 gsp_phys_gr_stall_wait_max; +static u64 gsp_phys_gr_stall_retry; /* m86: program-page override blob (see gsp-phys-gr-override). */ static u8 gsp_phys_gr_override[GSP_PHYS_PAGE_SIZE]; static u32 gsp_phys_gr_override_len; @@ -18249,6 +18259,37 @@ gsp_phys_gr_code_src(const struct gsp_phys_gr_submit *req, return mode; } +/* + * STALLFREE: the absolute per-launch wait budget. Every ladder phase is + * individually bounded, but a launch can chain them (fast re-arm + fresh + * entries + retirement retries), so the sum - and with it the time the + * single-inflight work item holds the client's completion - is not. This + * predicate bounds the whole submit: once it is true, no further re-arm, + * re-publish or re-execution runs. Green launches complete in ~0.2-11 s and + * never approach the default 60 s. + */ +static bool gsp_phys_gr_wait_budget_over(unsigned long start) +{ + unsigned int budget = gsp_phys_gr_wait_max_ms_value(); + + return budget && time_after_eq(jiffies, + start + msecs_to_jiffies(budget)); +} + +/* + * STALLFREE: one distinguishable bounded-completion line per stall episode. + * `action=fail` means the submit gives up (the sema service's failure edge + * authors the QMD completion release in software, so the guest proceeds); + * `action=proceed` means the phase gave up but the launch continues with its + * current outcome. + */ +static void gsp_phys_gr_stall_log(const char *reason, u64 push_va, + unsigned long start, const char *action) +{ + pr_warn("vgsp-gr: GR-STALL-%s va=0x%llx waited_ms=%u action=%s\n", + reason, push_va, jiffies_to_msecs(jiffies - start), action); +} + int gsp_phys_backend_guest_gr_submit(const struct gsp_phys_gr_submit *req, struct gsp_phys_backend_copy_result *out) { @@ -19554,6 +19595,24 @@ gr_page_unmapped: poll_start_us, &sub); break; } + /* + * STALLFREE: the whole-launch budget. Checked after the release + * acks (a completed launch always wins) and before any further + * ladder work; on expiry the submit fails out now and the sema + * service's failure edge authors the QMD completion release in + * software, so the guest client is never left waiting on a + * launch this host will not finish. + */ + if (gsp_phys_gr_wait_budget_over(start)) { + gsp_phys_gr_stall_wait_max++; + gsp_phys_gr_stall_log("wait-max", req->push_va, start, + "fail"); + pr_err("vgsp-gr: GR LAUNCH FAILED: wait budget crossed (qmd_rel=0x%08x want=0x%08x ch_rel=0x%08x want=0x%08x) h=0x%x push=0x%llx; ladder stopped\n", + w0, want_qmd, w1, want_ch, req->h_object, + req->push_va); + ret = -ETIMEDOUT; + goto out; + } if ((++grpoll_n % 100) == 0) pr_info("vgsp-gr: poll +%ums qmd_rel=0x%08x ch_rel=0x%08x\n", grpoll_n * GSP_PHYS_RMCALL_POLL_MS, w0, w1); @@ -20066,6 +20125,22 @@ gr_page_unmapped: (u32)gsp_phys_gr_nowrite_retries_value(); while (wrote == 0 && nw_attempts < nw_retries) { + /* + * STALLFREE: the same whole-launch budget caps + * the retirement retry chain. When it is crossed + * the loop stops and the launch PROCEEDS with + * its current outcome (the zero-write accept + * below restores the release words and logs + * loudly), so a chain of slow dud retries can + * never hold the client past the budget. + */ + if (gsp_phys_gr_wait_budget_over(start)) { + gsp_phys_gr_stall_retry++; + gsp_phys_gr_stall_log("retry", + req->push_va, start, + "proceed"); + break; + } nw_attempts++; pr_info("vgsp-gr: outcome retry %u/%u (wrote=%u tracked=%u): refill + re-publish\n", nw_attempts, nw_retries, @@ -21756,12 +21831,15 @@ static int gsp_phys_state_show(struct seq_file *m, void *v) gsp_phys_backend_gr_counters(&b_sub, &b_ok, &b_refused, &b_timeout, &b_pool); seq_printf(m, - "work_gr: req=%llu fallback=%llu ok=%llu refused=%llu timeout=%llu fault=%llu submitted=%llu backend_ok=%llu backend_refused=%llu backend_timeout=%llu pool_exhausted=%llu forward_param=%u exec_ok=%u sem_wedged=%llu\n", + "work_gr: req=%llu fallback=%llu ok=%llu refused=%llu timeout=%llu fault=%llu submitted=%llu backend_ok=%llu backend_refused=%llu backend_timeout=%llu pool_exhausted=%llu forward_param=%u exec_ok=%u sem_wedged=%llu stall_wait_max=%llu stall_retry=%llu wait_max_ms=%u\n", gr_req, gr_fallback, gr_ok, gr_refused, gr_timeout, gr_fault, b_sub, b_ok, b_refused, b_timeout, b_pool, gsp_phys_gr_forward_enabled(), boot_stage.rmcall_gr_exec_ok, - READ_ONCE(gsp_phys_gr_sem_wedged)); + READ_ONCE(gsp_phys_gr_sem_wedged), + READ_ONCE(gsp_phys_gr_stall_wait_max), + READ_ONCE(gsp_phys_gr_stall_retry), + gsp_phys_gr_wait_max_ms_value()); } { /* -- cgit v1.2.3