Fix system hang from DIFR prefetch GPFIFO exhaustion - #1286
Open
runiter wants to merge 2 commits into
Open
Conversation
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.
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 entrynvWriteGpEntry()waits for a free GPFIFO entry here:No deadline, and the only exit is
nvPushCheckChannelError(), which returns TRUE only once RM has written0xFFFFinto 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 atimeoutMSec, and the notifier wait has short and long timeouts plus annvPushImportYield(). This loop is the exception.The change gives it a deadline using
IdleChannel()'s idiom, honours the existingnoTimeoutopt-out, yields between polls, and returnsFALSEon expiry — a pathKickoff()already handles by leavingputOffsetunchanged. No new helpers or constants.I chose
SHORT_TIMEOUT(3s) overLONG_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 faultThis is what makes the channel stall in the first place.
PrefetchSingleSurface()kicks off a copy and waitsDIFR_PREFETCH_WAIT_PERIOD_US(10ms) for the semaphore. On expiry it returnsFAIL_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 = 1024givesnumGpFifoEntries = 16(nvidia-push-init.c), 2 entries per kickoff, andInitGpFifoExtendedBase()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/Allochelpers, so a fault costs nothing permanent and a later prefetch starts with GET and PUT in sync. OnlyFAIL_CE_HW_ERRORcan leak — the other two failure codes return before any kickoff. If reallocation itself fails, that is remembered andFAIL_INSUFFICIENT_L2_SIZEis 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_IMPLhas 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) ands2idle, never once surviving a 6th. Always: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:
nvPushImportYield()has precedent innvidia-push.c, but you know better than I do whether everyKickoff()caller is in a context where yielding is legal. Happy to gate or drop the yield and keep only the deadline.DifrPrefetchEventDeferredWork()'s kthread-queue context?nvkms-difr.calready 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.