From 2fc027ea603d19ebca934032b707ba1b872dba55 Mon Sep 17 00:00:00 2001 From: Lukas Date: Thu, 11 Jun 2026 14:12:23 +0300 Subject: [PATCH 1/2] Bound the GPFIFO-space wait in nvWriteGpEntry() --- src/common/unix/nvidia-push/src/nvidia-push.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/common/unix/nvidia-push/src/nvidia-push.c b/src/common/unix/nvidia-push/src/nvidia-push.c index 58b3dca8cc..74d973eeeb 100644 --- a/src/common/unix/nvidia-push/src/nvidia-push.c +++ b/src/common/unix/nvidia-push/src/nvidia-push.c @@ -347,6 +347,7 @@ static NvBool nvWriteGpEntry( NvU32 nextGpPut; NvU32 *gpPointer; + NvU64 baseTime, currentTime; const NvU32 entriesNeeded = NV_PUSH_NUM_GPFIFO_ENTRIES_PER_KICKOFF; NvPushDevicePtr pDevice = push_buffer->pDevice; @@ -359,11 +360,21 @@ static NvBool nvWriteGpEntry( nvAssert((nextGpPut % 2) == 0); // Wait for a free entry in the buffer - while (nextGpPut == ReadGpGetOffset(push_buffer)) { + for (baseTime = currentTime = nvPushImportGetMilliSeconds(pDevice); + nextGpPut == ReadGpGetOffset(push_buffer); + currentTime = nvPushImportGetMilliSeconds(pDevice)) { + if (nvPushCheckChannelError(push_buffer)) { nvAssert(!"A channel error occurred in nvWriteGpEntry()"); return FALSE; } + + if (currentTime > (baseTime + NV_PUSH_NOTIFIER_SHORT_TIMEOUT) && + !push_buffer->noTimeout) { + nvPushImportLogError(pDevice, + "Timed out waiting for a free GPFIFO entry."); + return FALSE; + } } gpPointer[0] = gpEntry0; gpPointer[1] = gpEntry1; From 72494147a411568c169718f7f4eaed910a334773 Mon Sep 17 00:00:00 2001 From: Lukas Date: Sat, 11 Jul 2026 10:07:24 +0300 Subject: [PATCH 2/2] Bound the pushbuffer-space wait in __nvPushMakeRoom() With nvWriteGpEntry() bounded, the same stalled-channel scenario spins in __nvPushMakeRoom() instead, one call earlier on the same path. Bound it with the same NV_PUSH_NOTIFIER_SHORT_TIMEOUT / noTimeout logic. Since this function returns void and callers write methods unconditionally after it returns, on timeout wrap the write pointer back to the buffer base and mark the whole buffer free so the writes stay in bounds; the following kickoff then fails and the caller gives up. --- src/common/unix/nvidia-push/src/nvidia-push.c | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/common/unix/nvidia-push/src/nvidia-push.c b/src/common/unix/nvidia-push/src/nvidia-push.c index 74d973eeeb..f1267db5cc 100644 --- a/src/common/unix/nvidia-push/src/nvidia-push.c +++ b/src/common/unix/nvidia-push/src/nvidia-push.c @@ -627,6 +627,7 @@ void __nvPushMakeRoom(NvPushChannelPtr push_buffer, NvU32 count) NvU32 getOffset; NvU32 putOffset; NvBool fenceToEnd = FALSE; + NvU64 baseTime, currentTime; putOffset = (NvU32) ((char *)push_buffer->main.buffer - (char *)push_buffer->main.base); @@ -639,7 +640,11 @@ void __nvPushMakeRoom(NvPushChannelPtr push_buffer, NvU32 count) } nvAssert(putOffset == push_buffer->main.putOffset); - while (count >= push_buffer->main.freeDwords) { + for (baseTime = currentTime = + nvPushImportGetMilliSeconds(push_buffer->pDevice); + count >= push_buffer->main.freeDwords; + currentTime = nvPushImportGetMilliSeconds(push_buffer->pDevice)) { + if (nvPushCheckChannelError(push_buffer)) { nvAssert(!"A channel error occurred in __nvPushMakeRoom()"); // Unlike with non-gpfifo channels, RC recovery can't reset GET to @@ -647,6 +652,25 @@ void __nvPushMakeRoom(NvPushChannelPtr push_buffer, NvU32 count) return __nvPushMakeRoom(push_buffer, count); } + if (currentTime > (baseTime + NV_PUSH_NOTIFIER_SHORT_TIMEOUT) && + !push_buffer->noTimeout) { + // GET has stopped advancing and no channel error was raised, so no + // room will ever free up. Don't spin forever: this function is + // void and its callers write unconditionally once it returns, so + // give them in-bounds space by wrapping the write pointer back to + // the start of the pushbuffer and declaring it free. Each method + // write is asserted smaller than sizeInBytes/8, so a full-buffer + // grant is always enough. The methods land only in this channel's + // own pushbuffer, which the stalled GPU never reads; the ensuing + // kickoff fails and the caller (e.g. DIFR prefetch) gives up. + nvPushImportLogError(push_buffer->pDevice, + "Timed out waiting for room in the pushbuffer."); + push_buffer->main.putOffset = 0; + push_buffer->main.buffer = push_buffer->main.base; + push_buffer->main.freeDwords = push_buffer->main.sizeInBytes >> 2; + return; + } + getOffset = nvPushReadGetOffset(push_buffer, TRUE); nvAssert(getOffset <= push_buffer->main.sizeInBytes);