Skip to content

Fix system hang from DIFR prefetch GPFIFO exhaustion - #1286

Open
runiter wants to merge 2 commits into
NVIDIA:mainfrom
runiter:fix-unbounded-gpfifo-wait
Open

Fix system hang from DIFR prefetch GPFIFO exhaustion#1286
runiter wants to merge 2 commits into
NVIDIA:mainfrom
runiter:fix-unbounded-gpfifo-wait

Conversation

@runiter

@runiter runiter commented Aug 11, 2026

Copy link
Copy Markdown

Two related fixes for a deterministic system hang in the DIFR prefetch path. The first stops a stalled channel from taking down the machine; the second stops the channel getting into that state.

Context and the full per-boot data are in #1205.

1. nvidia-push: bound the wait for a free GPFIFO entry

nvWriteGpEntry() waits for a free GPFIFO entry here:

// Wait for a free entry in the buffer
while (nextGpPut == ReadGpGetOffset(push_buffer)) {
    if (nvPushCheckChannelError(push_buffer)) {
        nvAssert(!"A channel error occurred in nvWriteGpEntry()");
        return FALSE;
    }
}

No deadline, and the only exit is nvPushCheckChannelError(), which returns TRUE only once RM has written 0xFFFF into the error notifier. A channel that stalls without faulting — never serviced, no RC event, no notifier write — leaves that check returning FALSE forever. It is also a busy spin with no yield, so the calling kernel thread is lost and the watchdog escalates to a system hang.

Every other wait in this file is bounded: IdleChannel() takes a timeoutMSec, and the notifier wait has short and long timeouts plus an nvPushImportYield(). This loop is the exception.

The change gives it a deadline using IdleChannel()'s idiom, honours the existing noTimeout opt-out, yields between polls, and returns FALSE on expiry — a path Kickoff() already handles by leaving putOffset unchanged. No new helpers or constants.

I chose SHORT_TIMEOUT (3s) over LONG_TIMEOUT (10s) deliberately: the soft-lockup watchdog reports at ~20s, and I wanted this to surface as a logged error rather than a lockup splat. Happy to switch it for consistency with the other waits.

2. nvkms-difr: reset the prefetch channel after a CE fault

This is what makes the channel stall in the first place.

PrefetchSingleSurface() kicks off a copy and waits DIFR_PREFETCH_WAIT_PERIOD_US (10ms) for the semaphore. On expiry it returns FAIL_CE_HW_ERROR — but the GPFIFO entries it already wrote stay queued on a channel the CE never drained. Nothing reclaims them, so GET stops advancing and the channel permanently loses one kickoff's worth of ring.

That ring is small: pushBufferSizeInBytes = 1024 gives numGpFifoEntries = 16 (nvidia-push-init.c), 2 entries per kickoff, and InitGpFifoExtendedBase() consumes 2 more at alloc on Hopper+ without a matching progress-tracker release. So there is very little headroom, and a handful of faults exhausts it — after which the next kickoff waits on a ring that can never drain, which is fix 1's loop.

The change resets the channel on exactly that status, reusing the existing Free/Alloc helpers, so a fault costs nothing permanent and a later prefetch starts with GET and PUT in sync. Only FAIL_CE_HW_ERROR can leak — the other two failure codes return before any kickoff. If reallocation itself fails, that is remembered and FAIL_INSUFFICIENT_L2_SIZE is returned from then on (the code which, per the existing comment, tells RM and PMU to stop requesting prefetches) rather than leaving a freed channel for the next prefetch to dereference.

I did not attempt to make DIFR retry within a boot cycle. subdeviceCtrlCmdLpwrDifrCtrl_IMPL has no implementation in the open tree, so the "disable until next driver load" decision after a CE fault is yours, not NVKMS's, and I did not want to guess at it. In practice a resume re-enables DIFR anyway, so with this fix the next cycle gets a clean channel and can succeed if the stall was transient.

How I hit this

RTX 5070 Ti, Ubuntu 24.04, kernel 6.17.0-1028-oem, driver 595.71.05-open, GNOME Wayland. The machine hard-hangs on exactly the 6th suspend/resume cycle of every boot — 7 boots out of 7, on both deep (S3) and s2idle, never once surviving a 6th. Always:

watchdog: BUG: soft lockup - CPU#1 stuck for 26s! [nvidia-modeset/]
RIP: nvWriteGpEntry+0xf9
  nvPushKickoff
  PrefetchHelperSurfaceEvo
  nvDIFRPrefetchSurfaces
  DifrPrefetchEventDeferredWork
  nvkms_kthread_q_callback

The fixed count is what pointed at a small ring being consumed one kickoff per resume rather than anything probabilistic.

Worth noting the user-visible consequence of the leak beyond the hang: because a CE fault disables DIFR until the next driver load, and each resume re-enables it for exactly one more doomed attempt, DIFR appears to be silently non-functional on this machine after the first suspend of any boot — while still accumulating the damage that eventually hangs it.

#1205 also has a report of the same deadlock signature triggered by plain display idle with no S3 involved (RTX 4060 Max-Q, 580.173.02), which fits: any repeated prefetch failure fills the ring, and suspend/resume is just a reliable way to produce one.

Testing

Both patches are derived from source analysis and have been compile-tested only. They build clean against 6.17.0-1028-oem with no new warnings. I have not run them on hardware, so I can't claim they resolve the hang — only that fix 1 removes the code path's ability to spin forever, and fix 2 removes the accumulation that leads there.

Two review questions I can't answer from outside:

  1. nvPushImportYield() has precedent in nvidia-push.c, but you know better than I do whether every Kickoff() caller is in a context where yielding is legal. Happy to gate or drop the yield and keep only the deadline.
  2. Is channel alloc/free legal from DifrPrefetchEventDeferredWork()'s kthread-queue context? nvkms-difr.c already makes RM control calls from timer and kthread callbacks, but full channel reallocation from there is an assumption on my part. If it isn't safe, the reset would need deferring to a worker.

Both files are byte-identical between 595.71.05, 595.91.07 and 610.57.04, so nothing released contains a fix. I have a 100% reproducible case in six suspend cycles and am glad to build and test any alternative patch you'd prefer, on either branch.

nvWriteGpEntry() waits for the GPU to consume a GPFIFO entry in a loop
whose only exit is nvPushCheckChannelError(), which reports an error only
once RM has written 0xFFFF into the channel's error notifier. A channel
that stalls without faulting -- one that is simply never serviced --
leaves the notifier clean, so the loop never terminates. Because it is a
busy spin with no yield, this hangs the calling kernel thread, and with
it the machine.

Give the loop a deadline using the same idiom as IdleChannel(), honouring
the existing noTimeout opt-out, and yield between polls as the notifier
wait already does. On expiry return FALSE, which Kickoff() already
handles by leaving putOffset unchanged.
@CLAassistant

CLAassistant commented Aug 11, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

When PrefetchSingleSurface() gives up waiting for the prefetch semaphore
it returns FAIL_CE_HW_ERROR, but the GPFIFO entries it already wrote stay
queued on a channel the copy engine never drained. Nothing reclaims them,
so GET stops advancing and the channel permanently loses one kickoff's
worth of its ring. The DIFR channel's pushbuffer is 1024 bytes, giving
only 16 GPFIFO entries, so a handful of such faults exhausts it and the
next kickoff waits on a ring that can never drain.

Reset the channel on that status so a fault costs nothing permanent, and
a later prefetch starts with GET and PUT back in sync. Only
FAIL_CE_HW_ERROR can leak: the other failure paths return before any
kickoff. If the channel cannot be reallocated, remember that and report
FAIL_INSUFFICIENT_L2_SIZE from then on, which stops RM requesting further
prefetches, rather than leaving a freed channel for the next one to use.
@runiter runiter changed the title nvidia-push: bound the wait for a free GPFIFO entry Fix system hang from DIFR prefetch GPFIFO exhaustion Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants