Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions src/common/unix/nvidia-push/src/nvidia-push.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ static NvBool nvWriteGpEntry(
NvU32 *gpPointer;
const NvU32 entriesNeeded = NV_PUSH_NUM_GPFIFO_ENTRIES_PER_KICKOFF;
NvPushDevicePtr pDevice = push_buffer->pDevice;
NvU64 baseTime, currentTime;

FillGpEntry(&push_buffer->main, putOffset, &gpEntry0, &gpEntry1);

Expand All @@ -358,12 +359,32 @@ static NvBool nvWriteGpEntry(

nvAssert((nextGpPut % 2) == 0);

// Wait for a free entry in the buffer
while (nextGpPut == ReadGpGetOffset(push_buffer)) {
/*
* Wait for a free entry in the buffer.
*
* Bail out if the channel faults, but also if GET simply stops
* advancing: a channel that is never serviced does not necessarily
* raise an error notifier, and without a deadline this loop would spin
* in kernel context indefinitely.
*/
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 (!push_buffer->noTimeout &&
(currentTime > (baseTime + NV_PUSH_NOTIFIER_SHORT_TIMEOUT))) {
nvPushImportLogError(pDevice,
"Timed out waiting for a free GPFIFO entry.");
nvAssert(!"Timed out waiting for a free GPFIFO entry");
return FALSE;
}

nvPushImportYield(pDevice);
}
gpPointer[0] = gpEntry0;
gpPointer[1] = gpEntry1;
Expand Down
54 changes: 54 additions & 0 deletions src/nvidia-modeset/src/nvkms-difr.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ typedef struct _NVDIFRStateEvoRec {
/* Copy engine instance for DIFR prefetches. */
NvU32 prefetchEngine;

/* Set if the prefetch channel could not be reallocated after a fault. */
NvBool channelBroken;

/* For tracking which surfaces have been prefetched already. */
NvU32 prefetchPass;
} NVDIFRStateEvoRec;
Expand All @@ -162,6 +165,7 @@ static NvBool AllocDIFRPushChannel(NVDIFRStateEvoPtr pDifr);
static void FreeDIFRPushChannel(NVDIFRStateEvoPtr pDifr);
static NvBool AllocDIFRCopyEngine(NVDIFRStateEvoPtr pDifr);
static void FreeDIFRCopyEngine(NVDIFRStateEvoPtr pDifr);
static NvBool ResetDIFRPushChannel(NVDIFRStateEvoPtr pDifr);

static NvU32 PrefetchSingleSurface(NVDIFRStateEvoPtr pDifr,
NVDIFRPrefetchParams *pParams,
Expand Down Expand Up @@ -298,6 +302,15 @@ NvU32 nvDIFRPrefetchSurfaces(NVDIFRStateEvoPtr pDifr, size_t l2CacheSize)
return NV2080_CTRL_LPWR_DIFR_PREFETCH_FAIL_OS_FLIPS_ENABLED;
}

/*
* We no longer have a usable prefetch channel. As above, despite its
* wording this is the code that tells RM (and further PMU) to stop
* requesting prefetches until the next modeset.
*/
if (pDifr->channelBroken) {
return NV2080_CTRL_LPWR_DIFR_PREFETCH_FAIL_INSUFFICIENT_L2_SIZE;
}

status = NV2080_CTRL_LPWR_DIFR_PREFETCH_SUCCESS;

pSubDev = &pDevEvo->gpus[0];
Expand Down Expand Up @@ -372,6 +385,22 @@ NvU32 nvDIFRPrefetchSurfaces(NVDIFRStateEvoPtr pDifr, size_t l2CacheSize)
}

out:
/*
* A CE error means we kicked off a prefetch that was never consumed,
* leaving GPFIFO entries queued on the channel for good. Reset the
* channel so that the failure doesn't cost us ring space permanently:
* otherwise repeated failures eventually exhaust the GPFIFO and the
* next kickoff has nothing left to wait for.
*/
if (status == NV2080_CTRL_LPWR_DIFR_PREFETCH_FAIL_CE_HW_ERROR) {
if (!ResetDIFRPushChannel(pDifr)) {
pDifr->channelBroken = TRUE;

nvEvoLogDev(pDevEvo, EVO_LOG_WARN,
"Failed to reset the DIFR prefetch channel.");
}
}

return status;
}

Expand Down Expand Up @@ -437,6 +466,31 @@ static NvBool AllocDIFRPushChannel(NVDIFRStateEvoPtr pDifr)
return TRUE;
}

/*
* Tear down and reallocate the prefetch channel.
*
* A prefetch that fails with a CE error has already written GPFIFO entries
* that the copy engine never consumed. Nothing else reclaims them, so GET
* stays where it is and the channel permanently loses that much of its
* (small) GPFIFO ring. Reallocating the channel puts GET and PUT back in
* sync so a later prefetch can start from a clean slate.
*/
static NvBool ResetDIFRPushChannel(NVDIFRStateEvoPtr pDifr)
{
FreeDIFRCopyEngine(pDifr);
FreeDIFRPushChannel(pDifr);

if (!AllocDIFRPushChannel(pDifr) ||
!AllocDIFRCopyEngine(pDifr)) {
FreeDIFRCopyEngine(pDifr);
FreeDIFRPushChannel(pDifr);

return FALSE;
}

return TRUE;
}

static void FreeDIFRPushChannel(NVDIFRStateEvoPtr pDifr)
{
NVDevEvoPtr pDevEvo = pDifr->pDevEvo;
Expand Down