From 2a0ede1d19bad325f1836dd3ef12c91fcfbd22f7 Mon Sep 17 00:00:00 2001 From: Zac Lanzon Date: Fri, 4 Sep 2026 22:18:32 -0400 Subject: [PATCH 1/6] Bind Blackwell window ownership to validated physical assignments --- src/nvidia-modeset/src/nvkms-evo4.c | 116 +++++++++++++++++-- src/nvidia-modeset/src/nvkms-modeset.c | 31 ++++- tests/phywin-allocation.py | 153 +++++++++++++++++++++++++ tests/phywin-sequencing.py | 85 ++++++++++++++ tests/window-ownership.py | 116 +++++++++++++++++++ 5 files changed, 491 insertions(+), 10 deletions(-) create mode 100644 tests/phywin-allocation.py create mode 100644 tests/phywin-sequencing.py create mode 100644 tests/window-ownership.py diff --git a/src/nvidia-modeset/src/nvkms-evo4.c b/src/nvidia-modeset/src/nvkms-evo4.c index 1221a51a62..559f44a2bc 100644 --- a/src/nvidia-modeset/src/nvkms-evo4.c +++ b/src/nvidia-modeset/src/nvkms-evo4.c @@ -2026,6 +2026,7 @@ static void UnassignExtraOrIncompatibleTiles( const NVDispEvoRec *pDispEvo, NVHwHeadMultiTileConfigRec *pMultiTileConfig, const NVHwModeTimingsEvo *pTimings, + const struct NvKmsUsageBounds *pUsage, const NvU32 numRequiredTiles) { const NVDevEvoRec *pDevEvo = pDispEvo->pDevEvo; @@ -2057,6 +2058,11 @@ static void UnassignExtraOrIncompatibleTiles( layer < ARRAY_LEN(pMultiTileConfig->phywinsMask); layer++) { NvU32 numReusedPhywins = 0; NvU32 phywin; + + /* IMP excludes unusable layers; release their physical windows too. */ + if (!pUsage->layer[layer].usable) { + continue; + } FOR_EACH_INDEX_IN_MASK(32, phywin, pMultiTileConfig->phywinsMask[layer]) { nvAssert(phywin < pDevEvo->numHwPhywins); @@ -2130,6 +2136,7 @@ static NvBool AssignNewPhywinsIfNeeded(const NVDispEvoRec *pDispEvo, NVHwHeadMultiTileConfigRec *pMultiTileConfig, const NvU32 head, + const struct NvKmsUsageBounds *pUsage, NvU32 *pFreePhywinsMask) { const NVDevEvoRec *pDevEvo = pDispEvo->pDevEvo; @@ -2138,6 +2145,12 @@ AssignNewPhywinsIfNeeded(const NVDispEvoRec *pDispEvo, for (NvU32 layer = 0; layer < pDevEvo->head[head].numLayers; layer++) { NvU32 phywin; + + if (!pUsage->layer[layer].usable) { + nvAssert(outMultiTileConfig.phywinsMask[layer] == 0); + continue; + } + FOR_EACH_INDEX_IN_MASK(32, phywin, outFreePhywinsMask) { nvAssert(phywin < pDevEvo->numHwPhywins); @@ -2165,6 +2178,7 @@ static NvBool AssignNewTilesToHeadIfNeeded( NVHwHeadMultiTileConfigRec *pMultiTileConfig, const NvU32 head, const NVHwModeTimingsEvo *pTimings, + const struct NvKmsUsageBounds *pUsage, NvU32 numRequiredTiles, NvU32 *pFreeTilesMask, NvU32 *pFreePhywinsMask) @@ -2179,21 +2193,20 @@ static NvBool AssignNewTilesToHeadIfNeeded( nvAssert(nvPopCount32(outMultiTileConfig.tilesMask) <= numRequiredTiles); nvAssert((outMultiTileConfig.tilesMask & outFreeTilesMask) == 0x0); - /* Return early if we already assigned sufficient tiles. */ - if (deltaNumRequiredTiles == 0) { - return TRUE; + if (deltaNumRequiredTiles != 0) { + outMultiTileConfig.tilesMask |= + GetFreeTiles(pDispEvo, deltaNumRequiredTiles, requiredTileType, + &outFreeTilesMask); } - outMultiTileConfig.tilesMask |= - GetFreeTiles(pDispEvo, deltaNumRequiredTiles, requiredTileType, - &outFreeTilesMask); + /* Layer usage can change even when the tile count stays the same. */ if (nvPopCount32(outMultiTileConfig.tilesMask) < numRequiredTiles) { return FALSE; } if (!AssignNewPhywinsIfNeeded(pDispEvo, &outMultiTileConfig, head, - &outFreePhywinsMask)) { + pUsage, &outFreePhywinsMask)) { return FALSE; } @@ -2310,6 +2323,7 @@ AssignNewTilesToHeadsIfNeeded( pOutputMultiTileConfig, head, pTimings, + pInput->head[head].pUsage, numRequiredTiles[head], &freeTilesMask, &freePhywinsMask)) { @@ -2377,7 +2391,18 @@ EvoAssignHwHeadMultiTileConfigDispOutputCA( UnassignExtraOrIncompatibleTiles(pDispEvo, &pOutput->head[head].multiTileConfig, pInput->head[head].pTimings, + pInput->head[head].pUsage, numRequiredTiles[head]); + } else { + /* A flip cannot bind a window or grow its physical assignment. */ + for (NvU32 layer = 0; + layer < pDevEvo->head[head].numLayers; layer++) { + if (pInput->head[head].pUsage->layer[layer].usable && + nvPopCount32(pOutput->head[head].multiTileConfig. + phywinsMask[layer]) < numRequiredTiles[head]) { + return FALSE; + } + } } freeTilesMask &= ~pOutput->head[head].multiTileConfig.tilesMask; @@ -2765,6 +2790,64 @@ static void SetTileSize(NVEvoChannel *pCoreChannel, nvAssert(tileStart == hActive); } +/* Physical window assignment changes need the same interlock handling as + * logical window assignment changes in EvoInitWindowMapping3(). */ +static void EvoTrackPhysicalWindowChangeCA( + NVDevEvoRec *pDevEvo, + const NVEvoChannel *pWindowChannel, + const NvU32 oldMask, + const NvU32 newMask, + NVEvoModesetUpdateState *pModesetUpdateState) +{ + if (oldMask == newMask) { + return; + } + + pModesetUpdateState->windowMappingChanged = TRUE; + nvDisableCoreInterlockUpdateState(pDevEvo, + &pModesetUpdateState->updateState, + pWindowChannel); +} + +/* + * Keep the software window-to-head mapping and advertised layers unchanged. + * Hardware ownership follows the physical assignment for the current mode: + * an unusable layer has neither an owner nor physical windows. This matches + * the unassigned-window state established by overlay-off initialization. + * + * The caller has shut down heads whose layer ownership changes. During the + * shutdown update itself, retain ownership; it is changed in the subsequent + * modeset update, while the head is inactive. + */ +static void EvoSetWindowOwnerCA( + NVDevEvoRec *pDevEvo, + const NvU32 sd, + const NVEvoChannel *pWindowChannel, + const NvU32 head, + const NvU32 phywinsMask, + NVEvoModesetUpdateState *pModesetUpdateState) +{ + const NvU32 win = NV_EVO_CHANNEL_MASK_WINDOW_NUMBER( + pWindowChannel->channelMask); + const void *pCoreDma = pDevEvo->pSubDevices[sd]->pCoreDma; + const NvU32 oldControl = + nvDmaLoadPioMethod(pCoreDma, NVCA7D_WINDOW_SET_CONTROL(win)); + const NvU32 oldOwner = DRF_VAL(CA7D, _WINDOW_SET_CONTROL, _OWNER, oldControl); + const NvU32 owner = (phywinsMask != 0) ? head : + NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE; + + if (oldOwner != owner) { + pModesetUpdateState->windowMappingChanged = TRUE; + nvDisableCoreInterlockUpdateState(pDevEvo, + &pModesetUpdateState->updateState, + pWindowChannel); + } + + nvDmaSetStartEvoMethod(pDevEvo->core, NVCA7D_WINDOW_SET_CONTROL(win), 1); + nvDmaSetEvoMethodData(pDevEvo->core, + FLD_SET_DRF_NUM(CA7D, _WINDOW_SET_CONTROL, _OWNER, owner, oldControl)); +} + static void EvoSetMultiTileConfigCA(const NVDispEvoRec *pDispEvo, const NvU32 head, const NVHwModeTimingsEvo *pTimings, @@ -2787,7 +2870,24 @@ static void EvoSetMultiTileConfigCA(const NVDispEvoRec *pDispEvo, const NvU32 win = NV_EVO_CHANNEL_MASK_WINDOW_NUMBER( pWindowChannel->channelMask); - nvAssert(nvPopCount32(pConfig->tilesMask) == nvPopCount32(phywinsMask)); + const void *pCoreDma = + pDevEvo->pSubDevices[pDispEvo->displayOwner]->pCoreDma; + const NvU32 oldPhywinsMask = + nvDmaLoadPioMethod(pCoreDma, NVCA7D_WINDOW_SET_PHYSICAL(win)); + + EvoTrackPhysicalWindowChangeCA(pDevEvo, pWindowChannel, + oldPhywinsMask, phywinsMask, + pModesetUpdateState); + + if (pTimings != NULL) { + EvoSetWindowOwnerCA(pDevEvo, pDispEvo->displayOwner, + pWindowChannel, head, phywinsMask, + pModesetUpdateState); + } + + /* Unusable layers have no physical windows assigned. */ + nvAssert((phywinsMask == 0) || + (nvPopCount32(pConfig->tilesMask) == nvPopCount32(phywinsMask))); nvDmaSetStartEvoMethod(pCoreChannel, NVCA7D_WINDOW_SET_PHYSICAL(win), 1); diff --git a/src/nvidia-modeset/src/nvkms-modeset.c b/src/nvidia-modeset/src/nvkms-modeset.c index c5aa302ee5..4ed66db7ff 100644 --- a/src/nvidia-modeset/src/nvkms-modeset.c +++ b/src/nvidia-modeset/src/nvkms-modeset.c @@ -2416,9 +2416,34 @@ static NvBool IsCurrentMultiTileConfigOneApiHeadIncompatible( NvU32 phywinsMask = 0x0; for (NvU32 layer = 0; layer < pDevEvo->head[head].numLayers; layer++) { + const NvU32 proposedMask = + pProposedDisp->head[head].multiTileConfig.phywinsMask[layer]; + + /* + * Adding or removing a layer's physical assignment also binds or + * unbinds its window channel. Shut the head down before changing + * that ownership, even if no resource moves to another window. + */ + if ((pMultiTileConfig->phywinsMask[layer] == 0) != + (proposedMask == 0)) { + return TRUE; + } phywinsMask |= pMultiTileConfig->phywinsMask[layer]; } + /* Physical windows may also move between layers on the same head. */ + for (NvU32 layer = 0; layer < pDevEvo->head[head].numLayers; layer++) { + for (NvU32 otherLayer = 0; + otherLayer < pDevEvo->head[head].numLayers; otherLayer++) { + if ((layer != otherLayer) && + ((pMultiTileConfig->phywinsMask[layer] & + pProposedDisp->head[head].multiTileConfig. + phywinsMask[otherLayer]) != 0x0)) { + return TRUE; + } + } + } + for (NvU32 tmpHead = 0; tmpHead < pDevEvo->numHeads; tmpHead++) { const NVHwHeadMultiTileConfigRec *pTmpMultiTileConfig = &pProposedDisp->head[tmpHead].multiTileConfig; @@ -3483,8 +3508,7 @@ KickoffProposedModeSetHwState( * See comment about NVDisplay error code 37, in * function EvoInitWindowMapping3(). */ - const NvBool decoupleFlipUpdates = - pModesetUpdateState->windowMappingChanged; + NvBool decoupleFlipUpdates; /* Send methods to shut down any other unused heads, but don't update yet. */ for (NvU32 apiHead = 0; apiHead < pDevEvo->numApiHeads; apiHead++) { @@ -3506,6 +3530,9 @@ KickoffProposedModeSetHwState( bypassComposition); } + /* PreUpdate may change physical window assignments on Blackwell. */ + decoupleFlipUpdates = pModesetUpdateState->windowMappingChanged; + if (!decoupleFlipUpdates) { /* Merge modeset and flip state updates together */ ApplyProposedModeSetStateOneDispFlip( diff --git a/tests/phywin-allocation.py b/tests/phywin-allocation.py new file mode 100644 index 0000000000..fac79b35b5 --- /dev/null +++ b/tests/phywin-allocation.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +"""Exercise the production CA allocator with a small standalone hardware model.""" +from pathlib import Path +import subprocess +import tempfile + +source = (Path(__file__).resolve().parents[1] / + 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() +start = source.index('static NvBool RequiredScalerTiles(') +end = source.index('static void\nEvoIsModePossibleCA(', start) +modeset = (Path(__file__).resolve().parents[1] / + 'src/nvidia-modeset/src/nvkms-modeset.c').read_text() +move_start = modeset.index('static NvBool IsCurrentMultiTileConfigOneApiHeadIncompatible(') +move_end = modeset.index('\n/*!', move_start) +preamble = r''' +#include +#include +#include +#include +typedef uint32_t NvU32; +typedef int NvBool; +#define TRUE 1 +#define FALSE 0 +#define NVKMS_MAX_HEADS_PER_DISP 4 +#define NV_YUV420_MODE_HW 1 +#define NVBIT(x) (1U << (x)) +#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) +#define nvAssert(x) assert(x) +#define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) +#define FOR_EACH_INDEX_IN_MASK(bits, i, mask) for ((i)=0; (i)<(bits); (i)++) if ((mask) & NVBIT(i)) +#define FOR_EACH_INDEX_IN_MASK_END +typedef enum { NV_EVO_HW_TILE_TYPE_0, NV_EVO_HW_TILE_TYPE_1, + NV_EVO_HW_TILE_TYPE_FIRST=0, NV_EVO_HW_TILE_TYPE_LAST=1 } NVEvoHwTileType; +typedef struct { NvU32 tilesMask, phywinsMask[2]; } NVHwHeadMultiTileConfigRec; +struct NvKmsUsageBounds { struct { NvBool usable; } layer[2]; }; +typedef struct { struct { struct { NvU32 width,height; } in,out; } viewPort; + NvU32 yuv420Mode; } NVHwModeTimingsEvo; +typedef struct { struct { NVEvoHwTileType type; } hwTile[8]; } NVEvoCapabilities; +typedef struct { NvU32 numHeads, numHwTiles, numHwPhywins; + struct { NvU32 numLayers; } head[4]; + struct { NVEvoCapabilities capabilities; } gpus[1]; } NVDevEvoRec; +typedef struct { NvU32 hwHeadsMask; } NVDispApiHeadStateEvoRec; +typedef struct { NVHwHeadMultiTileConfigRec multiTileConfig; } TestHead; +typedef struct { NVDevEvoRec *pDevEvo; NvU32 displayOwner; + NVDispApiHeadStateEvoRec apiHeadState[4]; TestHead headState[4]; } NVDispEvoRec; +typedef NVDispEvoRec *NVDispEvoPtr; +typedef struct { TestHead head[4]; } NVProposedModeSetHwStateOneDisp; +#define FOR_EACH_EVO_HW_HEAD_IN_MASK(mask, head) FOR_EACH_INDEX_IN_MASK(32, head, mask) +typedef struct { struct { NVHwModeTimingsEvo *pTimings; + struct NvKmsUsageBounds *pUsage; NvBool modesetRequested; + NVHwHeadMultiTileConfigRec multiTileConfig; } head[4]; } NVEvoIsModePossibleDispInput; +typedef struct { struct { NVHwHeadMultiTileConfigRec multiTileConfig; } head[4]; } NVEvoIsModePossibleDispOutput; +''' +tests = r''' +static NVDevEvoRec dev; +static NVDispEvoRec disp; +static NVEvoIsModePossibleDispInput input; +static NVHwModeTimingsEvo timings[4]; +static struct NvKmsUsageBounds usage[4]; +static void setup(void) { + memset(&dev,0,sizeof(dev)); memset(&input,0,sizeof(input)); + memset(usage,0,sizeof(usage)); + dev.numHeads=4; dev.numHwTiles=8; dev.numHwPhywins=8; + disp.pDevEvo=&dev; disp.displayOwner=0; + for (NvU32 h=0;h<4;h++) { + dev.head[h].numLayers=2; + input.head[h].pTimings=&timings[h]; input.head[h].pUsage=&usage[h]; + input.head[h].modesetRequested=TRUE; + input.head[h].multiTileConfig.tilesMask=NVBIT(7-h); + input.head[h].multiTileConfig.phywinsMask[0]=NVBIT(2*h); + input.head[h].multiTileConfig.phywinsMask[1]=NVBIT(2*h+1); + usage[h].layer[0].usable=TRUE; + } +} +static void checkUnique(NVEvoIsModePossibleDispOutput *out) { + NvU32 tiles=0, windows=0; + for (NvU32 h=0;h<4;h++) { + NVHwHeadMultiTileConfigRec *c=&out->head[h].multiTileConfig; + assert(!(tiles & c->tilesMask)); tiles |= c->tilesMask; + for (NvU32 l=0;l<2;l++) { + assert(!(windows & c->phywinsMask[l])); windows |= c->phywinsMask[l]; + if (usage[h].layer[l].usable) + assert(nvPopCount32(c->phywinsMask[l])==nvPopCount32(c->tilesMask)); + else assert(c->phywinsMask[l]==0); + } + } +} +int main(void) { + NvU32 required[4]={1,1,2,2}; + NVEvoIsModePossibleDispOutput out={0}; + setup(); + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); + checkUnique(&out); + puts("PASS: four heads, six tiles, unused overlays released"); + setup(); + for (NvU32 h=0;h<4;h++) usage[h].layer[1].usable=TRUE; + assert(!EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); + puts("PASS: twelve required physical windows still rejected"); + setup(); + for (NvU32 h=0;h<4;h++) { + required[h]=1; input.head[h].multiTileConfig.phywinsMask[1]=0; + } + usage[0].layer[1].usable=TRUE; + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); + checkUnique(&out); + puts("PASS: enabling a layer allocates windows without changing tile count"); + setup(); + usage[0].layer[1].usable=TRUE; input.head[0].modesetRequested=FALSE; + required[2]=2; required[3]=2; + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); + assert(!memcmp(&out.head[0].multiTileConfig,&input.head[0].multiTileConfig, + sizeof(NVHwHeadMultiTileConfigRec))); + checkUnique(&out); + puts("PASS: unchanged head retains its assignments"); + input.head[0].multiTileConfig.phywinsMask[1]=0; + assert(!EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); + puts("PASS: unchanged head cannot enable an unassigned overlay without a modeset"); + + NVProposedModeSetHwStateOneDisp proposed={0}; + memset(disp.headState,0,sizeof(disp.headState)); + disp.apiHeadState[0].hwHeadsMask=1; + disp.headState[0].multiTileConfig.phywinsMask[1]=2; + proposed.head[0].multiTileConfig.phywinsMask[1]=2; + assert(!IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); + proposed.head[0].multiTileConfig.phywinsMask[1]=0; + proposed.head[0].multiTileConfig.phywinsMask[0]=2; + assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); + proposed.head[0].multiTileConfig.phywinsMask[0]=0; + proposed.head[1].multiTileConfig.phywinsMask[0]=2; + assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); + puts("PASS: window ownership changes require shutdown, retained ownership does not"); + memset(disp.headState,0,sizeof(disp.headState)); + memset(&proposed,0,sizeof(proposed)); + disp.headState[0].multiTileConfig.phywinsMask[0]=1; + proposed.head[0].multiTileConfig.phywinsMask[0]=1; + proposed.head[0].multiTileConfig.phywinsMask[1]=2; + assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); + disp.headState[0].multiTileConfig.phywinsMask[1]=2; + assert(!IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); + proposed.head[0].multiTileConfig.phywinsMask[1]=0; + assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); + puts("PASS: binding or unbinding an overlay requires shutdown even without resource transfer"); + + return 0; +} +''' +with tempfile.TemporaryDirectory() as tmp: + c = Path(tmp) / 'test.c' + exe = Path(tmp) / 'test' + c.write_text(preamble + source[start:end] + modeset[move_start:move_end] + tests) + subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', + '-fsanitize=undefined,address', str(c), '-o', str(exe)], check=True) + subprocess.run([str(exe)], check=True) diff --git a/tests/phywin-sequencing.py b/tests/phywin-sequencing.py new file mode 100644 index 0000000000..f33ba29f16 --- /dev/null +++ b/tests/phywin-sequencing.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Test production physical-window tracking and modeset/flip ordering.""" +from pathlib import Path +import subprocess +import tempfile +root = Path(__file__).resolve().parents[1] +evo = (root/'src/nvidia-modeset/src/nvkms-evo4.c').read_text() +a = evo.index('static void EvoTrackPhysicalWindowChangeCA(') +b = evo.index('static void EvoSetWindowOwnerCA(', a) +modeset = (root/'src/nvidia-modeset/src/nvkms-modeset.c').read_text() +c = modeset.index('static void\nKickoffProposedModeSetHwState(') +d = modeset.index('static void AllocatePostModesetDispBandwidth(', c) +pre = r''' +#include +#include +#include +typedef unsigned NvU32; +typedef int NvBool; +#define TRUE 1 +#define FALSE 0 +#define EVO_LOG_INFO 0 +#define nvDoDebugLogging() 0 +#define nvEvoLogDev(...) ((void)0) +#define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) +typedef struct { NvU32 channelMask; } NVEvoChannel; +typedef struct { NvU32 noCoreInterlockMask; } NVEvoUpdateState; +typedef struct { NvBool windowMappingChanged; NVEvoUpdateState updateState; } NVEvoModesetUpdateState; +struct Dev; +typedef struct { void (*Update)(struct Dev *, const NVEvoUpdateState *, NvBool); } Hal; +typedef struct Dev { NvU32 numApiHeads; Hal *hal; } NVDevEvoRec; +typedef struct { NVDevEvoRec *pDevEvo; } Disp; +typedef Disp *NVDispEvoPtr; +typedef struct { int dummy; } NVProposedModeSetHwState; +typedef struct { int apiHead[4]; } NVProposedModeSetHwStateOneDisp; +typedef struct { NVEvoModesetUpdateState modesetUpdateState; } NVModeSetWorkArea; +static void nvDisableCoreInterlockUpdateState(NVDevEvoRec *dev, NVEvoUpdateState *u, const NVEvoChannel *ch) { + (void)dev; u->noCoreInterlockMask |= ch->channelMask; +} +static char events[16]; static int n, changeInPre; +static void event(char c) { events[n++]=c; } +static void update(NVDevEvoRec *dev,const NVEvoUpdateState *u,NvBool release) { + (void)dev; (void)u; (void)release; event('U'); +} +#define ApplyProposedModeSetStateOneApiHeadShutDown(...) ((void)0) +#define ApplyProposedModeSetStateOneApiHeadPreUpdate(d,h,p,w,b) do { if (changeInPre) (w)->modesetUpdateState.windowMappingChanged=TRUE; } while (0) +#define ApplyProposedModeSetStateOneDispFlip(...) event('F') +#define KickoffModesetUpdateState(d,s) do { event('C'); memset(s,0,sizeof(*(s))); } while (0) +#define nvRemoveUnusedHdmiDpAudioDevice(...) ((void)0) +#define ApplyProposedModeSetStateOneApiHeadPostModesetUpdate(...) ((void)0) +#define nvIsUpdateStateEmpty(...) TRUE +''' +post = r''' +int main(void) { + Hal hal={update}; NVDevEvoRec dev={1,&hal}; Disp disp={&dev}; + NVEvoChannel channel={4}; NVModeSetWorkArea work={0}; + NVProposedModeSetHwState proposed={0}; NVProposedModeSetHwStateOneDisp one={0}; + EvoTrackPhysicalWindowChangeCA(&dev,&channel,1,1,&work.modesetUpdateState); + assert(!work.modesetUpdateState.windowMappingChanged); + assert(!work.modesetUpdateState.updateState.noCoreInterlockMask); + EvoTrackPhysicalWindowChangeCA(&dev,&channel,1,0,&work.modesetUpdateState); + assert(work.modesetUpdateState.windowMappingChanged); + assert(work.modesetUpdateState.updateState.noCoreInterlockMask==4); + puts("PASS: changed physical assignment disables its core interlock; unchanged is untouched"); + memset(&work,0,sizeof(work)); changeInPre=1; + KickoffProposedModeSetHwState(&disp,&proposed,&one,FALSE,&work); + assert(n==3 && memcmp(events,"CFU",3)==0); + puts("PASS: assignment changed during PreUpdate -> core update, then flip update"); + n=0; changeInPre=0; memset(&work,0,sizeof(work)); + KickoffProposedModeSetHwState(&disp,&proposed,&one,FALSE,&work); + assert(n==2 && memcmp(events,"FC",2)==0); + puts("PASS: unchanged assignment retains combined flip/core update"); + n=0; work.modesetUpdateState.windowMappingChanged=TRUE; + KickoffProposedModeSetHwState(&disp,&proposed,&one,FALSE,&work); + assert(n==3 && memcmp(events,"CFU",3)==0); + puts("PASS: existing logical mapping change still decouples updates"); +} +''' +with tempfile.TemporaryDirectory() as tmp: + source = Path(tmp)/'test.c' + exe = Path(tmp)/'test' + source.write_text(pre+evo[a:b]+modeset[c:d]+post) + subprocess.run(['cc','-std=c99','-Wall','-Wextra','-Werror', + '-Wno-unused-parameter','-Wno-unused-variable', + '-fsanitize=address,undefined',str(source),'-o',str(exe)],check=True) + subprocess.run([str(exe)],check=True) diff --git a/tests/window-ownership.py b/tests/window-ownership.py new file mode 100644 index 0000000000..b2d0bb838b --- /dev/null +++ b/tests/window-ownership.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""Check generated CA ownership/physical methods against allocation invariants. + +This uses the production functions and hardware register definitions, but a +software method recorder. It does not validate real hardware timing or firmware. +""" +from pathlib import Path +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[1] +evo = (root / 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() +start = evo.index('static void EvoTrackPhysicalWindowChangeCA(') +end = evo.index('\nNVEvoHAL nvEvoC9 =', start) +pre = r''' +#include +#include +#include +#include +#include "nvtypes.h" +#include "nvmisc.h" +#include "class/clca7d.h" +#define nvDoDebugLogging() 0 +#define nvEvoLogDev(...) ((void)0) +#define TRUE 1 +#define FALSE 0 +#define nvAssert(x) assert(x) +#define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) +#define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) +typedef struct { NvU32 channelMask; } NVEvoChannel; +typedef struct { NvU32 noCoreInterlockMask; } NVEvoUpdateState; +typedef struct { NvBool windowMappingChanged; NVEvoUpdateState updateState; } NVEvoModesetUpdateState; +typedef struct { void *pCoreDma; } Sub; +typedef struct { + NVEvoChannel *core; + Sub *pSubDevices[1]; + struct { NvU32 numLayers; NVEvoChannel *layer[2]; } head[4]; +} NVDevEvoRec; +typedef struct { NVDevEvoRec *pDevEvo; NvU32 displayOwner; } NVDispEvoRec; +typedef struct { NvU32 tilesMask, phywinsMask[2]; } NVHwHeadMultiTileConfigRec; +typedef struct { int unused; } NVHwModeTimingsEvo; +typedef struct { int unused; } NVDscInfoEvoRec; +static NvU32 cache[65536], pending[65536], currentMethod; +static void nvUpdateUpdateState(NVDevEvoRec *d,NVEvoUpdateState *u,NVEvoChannel *c) {(void)d;(void)u;(void)c;} +static void nvDisableCoreInterlockUpdateState(NVDevEvoRec *d,NVEvoUpdateState *u,const NVEvoChannel *c) { + (void)d;u->noCoreInterlockMask |= 1U<channelMask; +} +static NvU32 nvDmaLoadPioMethod(const void *p,NvU32 method) { + (void)p; assert(method<65536);return cache[method]; +} +static void nvDmaSetStartEvoMethod(NVEvoChannel *c,NvU32 method,NvU32 n) { + (void)c;assert(n==1 && method<65536);currentMethod=method; +} +static void nvDmaSetEvoMethodData(NVEvoChannel *c,NvU32 value) { + (void)c;pending[currentMethod]=value; +} +static void SetTileSize(NVEvoChannel *c,const NVHwModeTimingsEvo *t, + const NVDscInfoEvoRec *d,const NVHwHeadMultiTileConfigRec *m) {(void)c;(void)t;(void)d;(void)m;} +static NvU32 owner(NvU32 win) { + return DRF_VAL(CA7D,_WINDOW_SET_CONTROL,_OWNER,pending[NVCA7D_WINDOW_SET_CONTROL(win)]); +} +static void commit(void) {memcpy(cache,pending,sizeof(cache));} +''' +post = r''' +int main(void) { + NVEvoChannel core={0},base={2},overlay={3};Sub sub={cache}; + NVDevEvoRec dev={.core=&core,.pSubDevices={&sub}}; + dev.head[1].numLayers=2;dev.head[1].layer[0]=&base;dev.head[1].layer[1]=&overlay; + NVDispEvoRec disp={&dev,0};NVEvoModesetUpdateState update={0}; + NVHwModeTimingsEvo timing={0};NVDscInfoEvoRec dsc={0}; + NVHwHeadMultiTileConfigRec config={.tilesMask=3,.phywinsMask={3,0}}; + // Simulate stock window owners, preserving the unrelated HIDE control bit. + cache[NVCA7D_WINDOW_SET_CONTROL(2)]=1; + cache[NVCA7D_WINDOW_SET_CONTROL(3)]=0x101; + cache[NVCA7D_WINDOW_SET_PHYSICAL(2)]=1; + cache[NVCA7D_WINDOW_SET_PHYSICAL(3)]=2; + memcpy(pending,cache,sizeof(cache)); + EvoSetMultiTileConfigCA(&disp,1,&timing,&dsc,&config,&update); + assert(owner(2)==1 && owner(3)==NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); + assert(pending[NVCA7D_WINDOW_SET_CONTROL(3)]==0x10f); + assert(pending[NVCA7D_WINDOW_SET_PHYSICAL(2)]==3); + assert(pending[NVCA7D_WINDOW_SET_PHYSICAL(3)]==0); + assert(update.windowMappingChanged && update.updateState.noCoreInterlockMask==12); + puts("PASS: unused overlay is unowned with no physical windows; base remains bound"); + commit();memset(&update,0,sizeof(update)); + EvoSetMultiTileConfigCA(&disp,1,&timing,&dsc,&config,&update); + assert(!update.windowMappingChanged && !update.updateState.noCoreInterlockMask); + puts("PASS: unchanged allocation/ownership introduces no extra interlock changes"); + config.tilesMask=1;config.phywinsMask[0]=1;config.phywinsMask[1]=2; + EvoSetMultiTileConfigCA(&disp,1,&timing,&dsc,&config,&update); + assert(owner(3)==1 && pending[NVCA7D_WINDOW_SET_PHYSICAL(3)]==2); + assert(update.windowMappingChanged && (update.updateState.noCoreInterlockMask & 8)); + puts("PASS: re-enabled overlay regains its owner and physical assignment"); + commit();memset(&update,0,sizeof(update)); + memset(&config,0,sizeof(config)); + EvoSetMultiTileConfigCA(&disp,1,NULL,NULL,&config,&update); + assert(owner(2)==1 && owner(3)==1); + assert(pending[NVCA7D_WINDOW_SET_PHYSICAL(2)]==0 && pending[NVCA7D_WINDOW_SET_PHYSICAL(3)]==0); + puts("PASS: shutdown clears physical assignments without changing ownership in that update"); + commit();memset(&update,0,sizeof(update)); + config.tilesMask=3;config.phywinsMask[0]=3; + EvoSetMultiTileConfigCA(&disp,1,&timing,&dsc,&config,&update); + assert(owner(2)==1 && owner(3)==NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); + assert(update.windowMappingChanged && (update.updateState.noCoreInterlockMask & 8)); + puts("PASS: subsequent activation unbinds the unused overlay with a non-interlocked update"); +} +''' +with tempfile.TemporaryDirectory() as tmp: + c = Path(tmp) / 'test.c' + exe = Path(tmp) / 'test' + c.write_text(pre + evo[start:end] + post) + subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', + '-fsanitize=address,undefined', + '-I' + str(root / 'src/common/sdk/nvidia/inc'), + str(c), '-o', str(exe)], check=True) + subprocess.run([str(exe)], check=True) From dc908810e663041e39ba3feb943aa00d543f7f47 Mon Sep 17 00:00:00 2001 From: Zac Lanzon Date: Fri, 4 Sep 2026 23:03:46 -0400 Subject: [PATCH 2/6] Fix incomplete CA allocation retries and full-width window masks Retry the failed head after reclaiming scaler-capable tiles instead of advancing to the next tile type with an incomplete assignment. Return failure if the retry cannot satisfy the request. Use a 64-bit intermediate when constructing the physical-window mask, avoiding an undefined 32-bit shift at the capability format's limit. Add exhaustive allocation and boundary checks, and expand the ownership test harness into readable C following the surrounding source style. Validation: 2,359,296 allocation configurations, boundary checks and the existing 16 checks pass under ASan/UBSan; 610.57.04 modeset build passes. These changes have not been installed or hardware-tested. --- src/nvidia-modeset/src/nvkms-evo4.c | 15 +- tests/phywin-allocation.py | 1 + tests/phywin-stress.py | 196 ++++++++++++++++++++ tests/window-ownership.py | 272 +++++++++++++++++++++------- 4 files changed, 415 insertions(+), 69 deletions(-) create mode 100644 tests/phywin-stress.py diff --git a/src/nvidia-modeset/src/nvkms-evo4.c b/src/nvidia-modeset/src/nvkms-evo4.c index 559f44a2bc..51254c5477 100644 --- a/src/nvidia-modeset/src/nvkms-evo4.c +++ b/src/nvidia-modeset/src/nvkms-evo4.c @@ -2342,7 +2342,17 @@ AssignNewTilesToHeadsIfNeeded( pOutput, &freeTilesMask, &freePhywinsMask); - break; + /* Retry this head before moving to the next tile type. */ + if (AssignNewTilesToHeadIfNeeded(pDispEvo, + pOutputMultiTileConfig, + head, + pTimings, + pInput->head[head].pUsage, + numRequiredTiles[head], + &freeTilesMask, + &freePhywinsMask)) { + continue; + } } return FALSE; @@ -2362,7 +2372,8 @@ EvoAssignHwHeadMultiTileConfigDispOutputCA( { const NVDevEvoRec *pDevEvo = pDispEvo->pDevEvo; NvU32 freeTilesMask = NVBIT(pDevEvo->numHwTiles) - 1; - NvU32 freePhywinsMask = NVBIT(pDevEvo->numHwPhywins) - 1; + /* The capability format allows 32 windows; avoid a 32-bit shift by 32. */ + NvU32 freePhywinsMask = (NvU32)(NVBIT64(pDevEvo->numHwPhywins) - 1); for (NvU32 head = 0; head < pDevEvo->numHeads; head++) { if (pInput->head[head].pTimings == NULL) { diff --git a/tests/phywin-allocation.py b/tests/phywin-allocation.py index fac79b35b5..b3a54ecaaa 100644 --- a/tests/phywin-allocation.py +++ b/tests/phywin-allocation.py @@ -24,6 +24,7 @@ #define NVKMS_MAX_HEADS_PER_DISP 4 #define NV_YUV420_MODE_HW 1 #define NVBIT(x) (1U << (x)) +#define NVBIT64(x) (UINT64_C(1) << (x)) #define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) #define nvAssert(x) assert(x) #define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) diff --git a/tests/phywin-stress.py b/tests/phywin-stress.py new file mode 100644 index 0000000000..c1192b7575 --- /dev/null +++ b/tests/phywin-stress.py @@ -0,0 +1,196 @@ +"""Stress production allocation with an independent capacity/uniqueness oracle. + +The model covers four heads, eight tiles, two layers per head, mixed scaler +capabilities, fresh/reused assignments, and 8/32 physical-window limits. +It tests software allocation only, not RM acceptance or hardware execution. +""" +from pathlib import Path +import ast +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[1] +module = ast.parse((root / 'tests/phywin-allocation.py').read_text()) +preamble = next( + ast.literal_eval(node.value) + for node in module.body + if isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) and target.id == 'preamble' + for target in node.targets) +) +# Exercise the API's eight-layer array limit as well as two-layer desktops. +preamble = preamble.replace('[2]', '[8]') +source = (root / 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() +start = source.index('static NvBool RequiredScalerTiles(') +end = source.index('static void\nEvoIsModePossibleCA(', start) +scenarios = r''' +static NvBool TestAllocation( + NvU32 windowCount, + NvBool reuse, + NvU32 scalerTiles, + NvU32 scalingMask, + NvU32 overlayMask, + NvU32 tileChoices) +{ + NVDevEvoRec dev = { 0 }; + NVDispEvoRec disp = { .pDevEvo = &dev }; + NVEvoIsModePossibleDispInput input = { 0 }; + NVEvoIsModePossibleDispOutput output = { 0 }; + NVHwModeTimingsEvo timings[4] = { 0 }; + struct NvKmsUsageBounds usage[4] = { 0 }; + NvU32 required[4]; + NvU32 totalTiles = 0; + NvU32 totalWindows = 0; + NvU32 requiredScalerTiles = 0; + NvU32 assignedTiles = 0; + NvU32 assignedWindows = 0; + + dev.numHeads = 4; + dev.numHwTiles = 8; + dev.numHwPhywins = windowCount; + for (NvU32 tile = 0; tile < 8; tile++) { + dev.gpus[0].capabilities.hwTile[tile].type = + tile < scalerTiles ? NV_EVO_HW_TILE_TYPE_0 : + NV_EVO_HW_TILE_TYPE_1; + } + for (NvU32 head = 0; head < 4; head++) { + dev.head[head].numLayers = 2; + input.head[head].pTimings = &timings[head]; + input.head[head].pUsage = &usage[head]; + input.head[head].modesetRequested = TRUE; + usage[head].layer[0].usable = TRUE; + usage[head].layer[1].usable = !!(overlayMask & NVBIT(head)); + timings[head].viewPort.in.width = 1920; + timings[head].viewPort.out.width = + (scalingMask & NVBIT(head)) ? 1280 : 1920; + required[head] = 1 + ((tileChoices >> (2 * head)) & 3); + if (reuse) { + input.head[head].multiTileConfig.tilesMask = NVBIT(head); + input.head[head].multiTileConfig.phywinsMask[0] = + NVBIT(windowCount - 8 + 2 * head); + input.head[head].multiTileConfig.phywinsMask[1] = + NVBIT(windowCount - 8 + 2 * head + 1); + } + if (scalingMask & NVBIT(head)) { + requiredScalerTiles += required[head]; + } + totalTiles += required[head]; + totalWindows += required[head] * + (1 + usage[head].layer[1].usable); + } + + const NVEvoIsModePossibleDispInput originalInput = input; + NvBool result = EvoAssignHwHeadMultiTileConfigDispOutputCA( + &disp, &input, required, &output); + assert(memcmp(&input, &originalInput, sizeof(input)) == 0); + NvBool expected = totalTiles <= 8 && totalWindows <= windowCount && + requiredScalerTiles <= scalerTiles; + if (result != expected) { + printf("FAIL: windows=%u reuse=%u scalerTiles=%u scaling=%u " + "overlays=%u tiles=%u " + "needTiles=%u needWindows=%u accepted=%d\n", + windowCount, reuse, scalerTiles, scalingMask, + overlayMask, tileChoices, + totalTiles, totalWindows, result); + return FALSE; + } + if (result) { + for (NvU32 head = 0; head < 4; head++) { + const NVHwHeadMultiTileConfigRec *pConfig = + &output.head[head].multiTileConfig; + assert(nvPopCount32(pConfig->tilesMask) == required[head]); + assert(!(pConfig->tilesMask & ~0xffU)); + assert(!(pConfig->tilesMask & assignedTiles)); + assignedTiles |= pConfig->tilesMask; + for (NvU32 layer = 0; layer < 2; layer++) { + NvU32 expectedCount = usage[head].layer[layer].usable ? + required[head] : 0; + assert(nvPopCount32(pConfig->phywinsMask[layer]) == + expectedCount); + assert(!(pConfig->phywinsMask[layer] & + ~(NvU32)(NVBIT64(windowCount) - 1))); + assert(!(pConfig->phywinsMask[layer] & assignedWindows)); + assignedWindows |= pConfig->phywinsMask[layer]; + } + } + } + return TRUE; +} + +static void TestBoundaryConditions(void) +{ + NVDevEvoRec dev = { .numHeads = 4, .numHwTiles = 8, .numHwPhywins = 32 }; + NVDispEvoRec disp = { .pDevEvo = &dev }; + NVEvoIsModePossibleDispInput input = { 0 }; + NVEvoIsModePossibleDispOutput output = { 0 }; + NVHwModeTimingsEvo timing = { 0 }; + struct NvKmsUsageBounds usage = { 0 }; + NvU32 required[4] = { 0 }; + + /* Inactive heads have NULL timing/usage pointers and need no resources. */ + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA( + &disp, &input, required, &output)); + + dev.head[0].numLayers = 8; + input.head[0].pTimings = &timing; + input.head[0].pUsage = &usage; + input.head[0].modesetRequested = TRUE; + input.head[0].multiTileConfig.tilesMask = 1; + input.head[0].multiTileConfig.phywinsMask[0] = NVBIT(31); + required[0] = 1; + for (NvU32 layer = 0; layer < 8; layer++) { + usage.layer[layer].usable = TRUE; + } + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA( + &disp, &input, required, &output)); + assert(output.head[0].multiTileConfig.phywinsMask[0] == NVBIT(31)); + for (NvU32 layer = 0; layer < 8; layer++) { + assert(nvPopCount32(output.head[0].multiTileConfig.phywinsMask[layer]) == 1); + } + + /* Failed helper allocation must not publish its partial result. */ + NVHwHeadMultiTileConfigRec config = { .tilesMask = 3 }; + const NVHwHeadMultiTileConfigRec originalConfig = config; + NvU32 freeWindows = 1; + assert(!AssignNewPhywinsIfNeeded(&disp, &config, 0, &usage, &freeWindows)); + assert(memcmp(&config, &originalConfig, sizeof(config)) == 0); + assert(freeWindows == 1); + puts("PASS: inactive heads, eight layers, bit 31, and allocation failure isolation"); +} + +int main(void) +{ + unsigned cases = 0; + + TestBoundaryConditions(); + + for (NvU32 windowCount = 8; windowCount <= 32; windowCount += 24) { + for (NvU32 reuse = 0; reuse < 2; reuse++) { + for (NvU32 scalerTiles = 0; scalerTiles <= 8; scalerTiles++) { + for (NvU32 scalingMask = 0; scalingMask < 16; scalingMask++) { + for (NvU32 overlayMask = 0; overlayMask < 16; overlayMask++) { + for (NvU32 tileChoices = 0; tileChoices < 256; + tileChoices++) { + assert(TestAllocation(windowCount, reuse, + scalerTiles, scalingMask, + overlayMask, tileChoices)); + cases++; + } + } + } + } + } + } + printf("PASS: %u allocation combinations\n", cases); + return 0; +} +''' +with tempfile.TemporaryDirectory() as directory: + test = Path(directory) / 'test.c' + binary = Path(directory) / 'test' + test.write_text(preamble + source[start:end] + scenarios) + subprocess.run([ + 'cc', '-std=c99', '-Wall', '-Wextra', '-Werror', + '-fsanitize=address,undefined', str(test), '-o', str(binary), + ], check=True) + subprocess.run([str(binary)], check=True) diff --git a/tests/window-ownership.py b/tests/window-ownership.py index b2d0bb838b..607be1851d 100644 --- a/tests/window-ownership.py +++ b/tests/window-ownership.py @@ -17,9 +17,11 @@ #include #include #include + #include "nvtypes.h" #include "nvmisc.h" #include "class/clca7d.h" + #define nvDoDebugLogging() 0 #define nvEvoLogDev(...) ((void)0) #define TRUE 1 @@ -27,82 +29,218 @@ #define nvAssert(x) assert(x) #define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) #define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) -typedef struct { NvU32 channelMask; } NVEvoChannel; -typedef struct { NvU32 noCoreInterlockMask; } NVEvoUpdateState; -typedef struct { NvBool windowMappingChanged; NVEvoUpdateState updateState; } NVEvoModesetUpdateState; -typedef struct { void *pCoreDma; } Sub; + +typedef struct { + NvU32 channelMask; +} NVEvoChannel; + +typedef struct { + NvU32 noCoreInterlockMask; +} NVEvoUpdateState; + +typedef struct { + NvBool windowMappingChanged; + NVEvoUpdateState updateState; +} NVEvoModesetUpdateState; + typedef struct { - NVEvoChannel *core; - Sub *pSubDevices[1]; - struct { NvU32 numLayers; NVEvoChannel *layer[2]; } head[4]; + void *pCoreDma; +} TestSubDevice; + +typedef struct { + NVEvoChannel *core; + TestSubDevice *pSubDevices[1]; + struct { + NvU32 numLayers; + NVEvoChannel *layer[2]; + } head[4]; } NVDevEvoRec; -typedef struct { NVDevEvoRec *pDevEvo; NvU32 displayOwner; } NVDispEvoRec; -typedef struct { NvU32 tilesMask, phywinsMask[2]; } NVHwHeadMultiTileConfigRec; -typedef struct { int unused; } NVHwModeTimingsEvo; -typedef struct { int unused; } NVDscInfoEvoRec; -static NvU32 cache[65536], pending[65536], currentMethod; -static void nvUpdateUpdateState(NVDevEvoRec *d,NVEvoUpdateState *u,NVEvoChannel *c) {(void)d;(void)u;(void)c;} -static void nvDisableCoreInterlockUpdateState(NVDevEvoRec *d,NVEvoUpdateState *u,const NVEvoChannel *c) { - (void)d;u->noCoreInterlockMask |= 1U<channelMask; + +typedef struct { + NVDevEvoRec *pDevEvo; + NvU32 displayOwner; +} NVDispEvoRec; + +typedef struct { + NvU32 tilesMask; + NvU32 phywinsMask[2]; +} NVHwHeadMultiTileConfigRec; + +typedef struct { + int unused; +} NVHwModeTimingsEvo; + +typedef struct { + int unused; +} NVDscInfoEvoRec; + +static NvU32 stateCache[65536]; +static NvU32 pendingMethods[65536]; +static NvU32 currentMethod; + +static void nvUpdateUpdateState( + NVDevEvoRec *pDevEvo, + NVEvoUpdateState *pUpdateState, + NVEvoChannel *pChannel) +{ + (void)pDevEvo; + (void)pUpdateState; + (void)pChannel; +} + +static void nvDisableCoreInterlockUpdateState( + NVDevEvoRec *pDevEvo, + NVEvoUpdateState *pUpdateState, + const NVEvoChannel *pChannel) +{ + (void)pDevEvo; + pUpdateState->noCoreInterlockMask |= 1U << pChannel->channelMask; +} + +static NvU32 nvDmaLoadPioMethod(const void *pCoreDma, NvU32 method) +{ + (void)pCoreDma; + assert(method < 65536); + return stateCache[method]; +} + +static void nvDmaSetStartEvoMethod( + NVEvoChannel *pChannel, + NvU32 method, + NvU32 count) +{ + (void)pChannel; + assert(count == 1 && method < 65536); + currentMethod = method; } -static NvU32 nvDmaLoadPioMethod(const void *p,NvU32 method) { - (void)p; assert(method<65536);return cache[method]; + +static void nvDmaSetEvoMethodData(NVEvoChannel *pChannel, NvU32 value) +{ + (void)pChannel; + pendingMethods[currentMethod] = value; } -static void nvDmaSetStartEvoMethod(NVEvoChannel *c,NvU32 method,NvU32 n) { - (void)c;assert(n==1 && method<65536);currentMethod=method; + +static void SetTileSize( + NVEvoChannel *pChannel, + const NVHwModeTimingsEvo *pTimings, + const NVDscInfoEvoRec *pDscInfo, + const NVHwHeadMultiTileConfigRec *pConfig) +{ + (void)pChannel; + (void)pTimings; + (void)pDscInfo; + (void)pConfig; } -static void nvDmaSetEvoMethodData(NVEvoChannel *c,NvU32 value) { - (void)c;pending[currentMethod]=value; + +static NvU32 GetPendingWindowOwner(NvU32 window) +{ + return DRF_VAL(CA7D, _WINDOW_SET_CONTROL, _OWNER, + pendingMethods[NVCA7D_WINDOW_SET_CONTROL(window)]); } -static void SetTileSize(NVEvoChannel *c,const NVHwModeTimingsEvo *t, - const NVDscInfoEvoRec *d,const NVHwHeadMultiTileConfigRec *m) {(void)c;(void)t;(void)d;(void)m;} -static NvU32 owner(NvU32 win) { - return DRF_VAL(CA7D,_WINDOW_SET_CONTROL,_OWNER,pending[NVCA7D_WINDOW_SET_CONTROL(win)]); + +/* Model completed method execution; this does not simulate hardware timing. */ +static void CommitPendingMethods(void) +{ + memcpy(stateCache, pendingMethods, sizeof(stateCache)); } -static void commit(void) {memcpy(cache,pending,sizeof(cache));} ''' post = r''' -int main(void) { - NVEvoChannel core={0},base={2},overlay={3};Sub sub={cache}; - NVDevEvoRec dev={.core=&core,.pSubDevices={&sub}}; - dev.head[1].numLayers=2;dev.head[1].layer[0]=&base;dev.head[1].layer[1]=&overlay; - NVDispEvoRec disp={&dev,0};NVEvoModesetUpdateState update={0}; - NVHwModeTimingsEvo timing={0};NVDscInfoEvoRec dsc={0}; - NVHwHeadMultiTileConfigRec config={.tilesMask=3,.phywinsMask={3,0}}; - // Simulate stock window owners, preserving the unrelated HIDE control bit. - cache[NVCA7D_WINDOW_SET_CONTROL(2)]=1; - cache[NVCA7D_WINDOW_SET_CONTROL(3)]=0x101; - cache[NVCA7D_WINDOW_SET_PHYSICAL(2)]=1; - cache[NVCA7D_WINDOW_SET_PHYSICAL(3)]=2; - memcpy(pending,cache,sizeof(cache)); - EvoSetMultiTileConfigCA(&disp,1,&timing,&dsc,&config,&update); - assert(owner(2)==1 && owner(3)==NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); - assert(pending[NVCA7D_WINDOW_SET_CONTROL(3)]==0x10f); - assert(pending[NVCA7D_WINDOW_SET_PHYSICAL(2)]==3); - assert(pending[NVCA7D_WINDOW_SET_PHYSICAL(3)]==0); - assert(update.windowMappingChanged && update.updateState.noCoreInterlockMask==12); - puts("PASS: unused overlay is unowned with no physical windows; base remains bound"); - commit();memset(&update,0,sizeof(update)); - EvoSetMultiTileConfigCA(&disp,1,&timing,&dsc,&config,&update); - assert(!update.windowMappingChanged && !update.updateState.noCoreInterlockMask); - puts("PASS: unchanged allocation/ownership introduces no extra interlock changes"); - config.tilesMask=1;config.phywinsMask[0]=1;config.phywinsMask[1]=2; - EvoSetMultiTileConfigCA(&disp,1,&timing,&dsc,&config,&update); - assert(owner(3)==1 && pending[NVCA7D_WINDOW_SET_PHYSICAL(3)]==2); - assert(update.windowMappingChanged && (update.updateState.noCoreInterlockMask & 8)); - puts("PASS: re-enabled overlay regains its owner and physical assignment"); - commit();memset(&update,0,sizeof(update)); - memset(&config,0,sizeof(config)); - EvoSetMultiTileConfigCA(&disp,1,NULL,NULL,&config,&update); - assert(owner(2)==1 && owner(3)==1); - assert(pending[NVCA7D_WINDOW_SET_PHYSICAL(2)]==0 && pending[NVCA7D_WINDOW_SET_PHYSICAL(3)]==0); - puts("PASS: shutdown clears physical assignments without changing ownership in that update"); - commit();memset(&update,0,sizeof(update)); - config.tilesMask=3;config.phywinsMask[0]=3; - EvoSetMultiTileConfigCA(&disp,1,&timing,&dsc,&config,&update); - assert(owner(2)==1 && owner(3)==NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); - assert(update.windowMappingChanged && (update.updateState.noCoreInterlockMask & 8)); - puts("PASS: subsequent activation unbinds the unused overlay with a non-interlocked update"); +int main(void) +{ + NVEvoChannel core = { 0 }; + NVEvoChannel base = { 2 }; + NVEvoChannel overlay = { 3 }; + TestSubDevice subDevice = { stateCache }; + NVDevEvoRec dev = { + .core = &core, + .pSubDevices = { &subDevice }, + }; + NVDispEvoRec disp = { &dev, 0 }; + NVEvoModesetUpdateState update = { 0 }; + NVHwModeTimingsEvo timing = { 0 }; + NVDscInfoEvoRec dsc = { 0 }; + NVHwHeadMultiTileConfigRec config = { + .tilesMask = 3, + .phywinsMask = { 3, 0 }, + }; + + dev.head[1].numLayers = 2; + dev.head[1].layer[0] = &base; + dev.head[1].layer[1] = &overlay; + + /* Simulate stock window owners, including the unrelated HIDE control bit. */ + stateCache[NVCA7D_WINDOW_SET_CONTROL(2)] = 1; + stateCache[NVCA7D_WINDOW_SET_CONTROL(3)] = 0x101; + stateCache[NVCA7D_WINDOW_SET_PHYSICAL(2)] = 1; + stateCache[NVCA7D_WINDOW_SET_PHYSICAL(3)] = 2; + memcpy(pendingMethods, stateCache, sizeof(stateCache)); + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(GetPendingWindowOwner(2) == 1); + assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); + assert(pendingMethods[NVCA7D_WINDOW_SET_CONTROL(3)] == 0x10f); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(2)] == 3); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 0); + assert(update.windowMappingChanged); + assert(update.updateState.noCoreInterlockMask == 12); + puts("PASS: unused overlay is unowned with no physical windows; " + "base remains bound"); + + /* Repeating the same configuration must not introduce an interlock change. */ + CommitPendingMethods(); + memset(&update, 0, sizeof(update)); + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(!update.windowMappingChanged); + assert(!update.updateState.noCoreInterlockMask); + puts("PASS: unchanged allocation/ownership introduces no extra interlock " + "changes"); + + /* Restoring overlay resources also restores its original hardware owner. */ + config.tilesMask = 1; + config.phywinsMask[0] = 1; + config.phywinsMask[1] = 2; + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(GetPendingWindowOwner(3) == 1); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 2); + assert(update.windowMappingChanged); + assert(update.updateState.noCoreInterlockMask & 8); + puts("PASS: re-enabled overlay regains its owner and physical assignment"); + + /* Shutdown releases physical windows while leaving owners unchanged. */ + CommitPendingMethods(); + memset(&update, 0, sizeof(update)); + memset(&config, 0, sizeof(config)); + + EvoSetMultiTileConfigCA(&disp, 1, NULL, NULL, &config, &update); + + assert(GetPendingWindowOwner(2) == 1); + assert(GetPendingWindowOwner(3) == 1); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(2)] == 0); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 0); + puts("PASS: shutdown clears physical assignments without changing " + "ownership in that update"); + + /* Only the following activation update removes the unused window owner. */ + CommitPendingMethods(); + memset(&update, 0, sizeof(update)); + config.tilesMask = 3; + config.phywinsMask[0] = 3; + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(GetPendingWindowOwner(2) == 1); + assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); + assert(update.windowMappingChanged); + assert(update.updateState.noCoreInterlockMask & 8); + puts("PASS: subsequent activation unbinds the unused overlay with a " + "non-interlocked update"); + + return 0; } ''' with tempfile.TemporaryDirectory() as tmp: From 5077cded3506880f3146c1dd1643efe361da7564 Mon Sep 17 00:00:00 2001 From: Zac Lanzon Date: Fri, 4 Sep 2026 23:33:35 -0400 Subject: [PATCH 3/6] Test ordinary CA allocations and queued initialization ownership --- src/nvidia-modeset/src/nvkms-evo4.c | 4 + tests/ordinary-configurations.py | 207 ++++++++++++++++++++++++++++ tests/window-ownership.py | 23 ++++ 3 files changed, 234 insertions(+) create mode 100644 tests/ordinary-configurations.py diff --git a/src/nvidia-modeset/src/nvkms-evo4.c b/src/nvidia-modeset/src/nvkms-evo4.c index 51254c5477..2cac838bbe 100644 --- a/src/nvidia-modeset/src/nvkms-evo4.c +++ b/src/nvidia-modeset/src/nvkms-evo4.c @@ -2854,6 +2854,10 @@ static void EvoSetWindowOwnerCA( pWindowChannel); } + /* + * Initialization may have queued a default binding that is not yet visible + * in PIO. Write the final owner even when the cached owner already matches. + */ nvDmaSetStartEvoMethod(pDevEvo->core, NVCA7D_WINDOW_SET_CONTROL(win), 1); nvDmaSetEvoMethodData(pDevEvo->core, FLD_SET_DRF_NUM(CA7D, _WINDOW_SET_CONTROL, _OWNER, owner, oldControl)); diff --git a/tests/ordinary-configurations.py b/tests/ordinary-configurations.py new file mode 100644 index 0000000000..c266b1c9c3 --- /dev/null +++ b/tests/ordinary-configurations.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +"""Compare ordinary CA allocations with a pinned upstream implementation. + +All exposed layers are usable in these cases. Inputs stay inside the physical +window budget. Known upstream incomplete-retry results are counted separately; +only complete successful reference allocations are compared for exact equality. +Hardware methods and timings are not simulated here. +""" +import ast +from pathlib import Path +import re +import subprocess +import tempfile + +root = Path(__file__).resolve().parents[1] +source_path = 'src/nvidia-modeset/src/nvkms-evo4.c' +reference_revision = 'e4a5faa2567f28c8eabe0ebb6422b6d0abcf37eb' +module = ast.parse((root / 'tests/phywin-allocation.py').read_text()) +preamble = next( + ast.literal_eval(node.value) + for node in module.body + if isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) and target.id == 'preamble' + for target in node.targets) +) + + +def allocator(text): + start = text.index('static NvBool RequiredScalerTiles(') + end = text.index('static void\nEvoIsModePossibleCA(', start) + return text[start:end] + + +current = allocator((root / source_path).read_text()) +reference = allocator(subprocess.check_output( + ['git', 'show', reference_revision + ':' + source_path], cwd=root, text=True)) +function_names = re.findall( + r'(?:static\s+)(?:NvBool|NvU32|void|NVEvoHwTileType)\s+(\w+)\s*\(', reference) +for name in function_names: + reference = re.sub(r'\b' + re.escape(name) + r'\b', 'Reference' + name, reference) + +scenarios = r''' +static NvBool CompleteAllocation( + const NVEvoIsModePossibleDispOutput *pOutput, + NvU32 heads, + NvU32 layers, + const NvU32 required[4]) +{ + NvU32 usedTiles = 0; + NvU32 usedWindows = 0; + + for (NvU32 head = 0; head < heads; head++) { + const NVHwHeadMultiTileConfigRec *pConfig = + &pOutput->head[head].multiTileConfig; + if (nvPopCount32(pConfig->tilesMask) != required[head] || + (pConfig->tilesMask & (usedTiles | ~0xffU))) { + return FALSE; + } + usedTiles |= pConfig->tilesMask; + for (NvU32 layer = 0; layer < layers; layer++) { + NvU32 mask = pConfig->phywinsMask[layer]; + if (nvPopCount32(mask) != required[head] || + (mask & (usedWindows | ~0xffU))) { + return FALSE; + } + usedWindows |= mask; + } + } + return TRUE; +} + +static void SameAllocation( + const NVEvoIsModePossibleDispOutput *pReference, + const NVEvoIsModePossibleDispOutput *pActual, + NvU32 heads, + NvU32 layers) +{ + for (NvU32 head = 0; head < heads; head++) { + assert(pReference->head[head].multiTileConfig.tilesMask == + pActual->head[head].multiTileConfig.tilesMask); + for (NvU32 layer = 0; layer < layers; layer++) { + assert(pReference->head[head].multiTileConfig.phywinsMask[layer] == + pActual->head[head].multiTileConfig.phywinsMask[layer]); + } + } +} + +static unsigned identical; +static unsigned rejected; +static unsigned corrected; +static unsigned unchanged; + +static void TestConfiguration(NvU32 heads, NvU32 layers, NvU32 scalerTiles, + NvU32 scalingMask, NvU32 tileChoices, NvBool reuse) +{ + NVDevEvoRec dev = { .numHeads = heads, .numHwTiles = 8, .numHwPhywins = 8 }; + NVDispEvoRec disp = { .pDevEvo = &dev }; + NVEvoIsModePossibleDispInput input = { 0 }; + NVEvoIsModePossibleDispOutput reference = { 0 }; + NVEvoIsModePossibleDispOutput actual = { 0 }; + NVHwModeTimingsEvo timings[4] = { 0 }; + struct NvKmsUsageBounds usage[4] = { 0 }; + NvU32 required[4] = { 0 }; + NvU32 requiredTiles = 0; + NvU32 requiredScalerTiles = 0; + + for (NvU32 tile = 0; tile < 8; tile++) { + dev.gpus[0].capabilities.hwTile[tile].type = + tile < scalerTiles ? NV_EVO_HW_TILE_TYPE_0 : NV_EVO_HW_TILE_TYPE_1; + } + for (NvU32 head = 0; head < heads; head++) { + dev.head[head].numLayers = layers; + input.head[head].pTimings = &timings[head]; + input.head[head].pUsage = &usage[head]; + input.head[head].modesetRequested = TRUE; + timings[head].viewPort.in.width = 1920; + timings[head].viewPort.out.width = + scalingMask & NVBIT(head) ? 1280 : 1920; + required[head] = 1 + ((tileChoices >> (2 * head)) & 3); + requiredTiles += required[head]; + if (scalingMask & NVBIT(head)) { + requiredScalerTiles += required[head]; + } + if (reuse) { + input.head[head].multiTileConfig.tilesMask = NVBIT(head); + } + for (NvU32 layer = 0; layer < layers; layer++) { + usage[head].layer[layer].usable = TRUE; + if (reuse) { + input.head[head].multiTileConfig.phywinsMask[layer] = + NVBIT(head * layers + layer); + } + } + } + if (requiredTiles * layers > 8) { + return; + } + NvBool expected = requiredScalerTiles <= scalerTiles; + NvBool referenceResult = ReferenceEvoAssignHwHeadMultiTileConfigDispOutputCA( + &disp, &input, required, &reference); + NvBool actualResult = EvoAssignHwHeadMultiTileConfigDispOutputCA( + &disp, &input, required, &actual); + assert(actualResult == expected); + if (actualResult) { + assert(CompleteAllocation(&actual, heads, layers, required)); + } + if (referenceResult && !CompleteAllocation(&reference, heads, layers, required)) { + corrected++; + return; + } + assert(referenceResult == actualResult); + if (!actualResult) { + rejected++; + return; + } + SameAllocation(&reference, &actual, heads, layers); + identical++; + + /* Replay with every subset of heads marked unchanged. */ + for (NvU32 head = 0; head < heads; head++) { + input.head[head].multiTileConfig = actual.head[head].multiTileConfig; + } + for (NvU32 changedMask = 0; changedMask < NVBIT(heads); changedMask++) { + for (NvU32 head = 0; head < heads; head++) { + input.head[head].modesetRequested = !!(changedMask & NVBIT(head)); + } + assert(ReferenceEvoAssignHwHeadMultiTileConfigDispOutputCA( + &disp, &input, required, &reference)); + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA( + &disp, &input, required, &actual)); + SameAllocation(&reference, &actual, heads, layers); + unchanged++; + } +} + +int main(void) +{ + for (NvU32 heads = 1; heads <= 4; heads++) { + for (NvU32 layers = 1; layers <= 2; layers++) { + for (NvU32 scalerTiles = 0; scalerTiles <= 8; scalerTiles++) { + for (NvU32 scalingMask = 0; scalingMask < NVBIT(heads); scalingMask++) { + for (NvU32 tiles = 0; tiles < NVBIT(2 * heads); tiles++) { + TestConfiguration(heads, layers, scalerTiles, + scalingMask, tiles, FALSE); + TestConfiguration(heads, layers, scalerTiles, + scalingMask, tiles, TRUE); + } + } + } + } + } + printf("PASS: %u complete reference allocations exactly match\n", identical); + printf("PASS: %u unchanged-head replays exactly match\n", unchanged); + printf("PASS: %u impossible configurations rejected by both\n", rejected); + printf("PASS: %u incomplete upstream results handled by the retry fix\n", corrected); + return 0; +} +''' +with tempfile.TemporaryDirectory() as directory: + test = Path(directory) / 'comparison.c' + binary = Path(directory) / 'comparison' + test.write_text(preamble + reference + current + scenarios) + subprocess.run([ + 'cc', '-std=c99', '-Wall', '-Wextra', '-Werror', + '-fsanitize=address,undefined', str(test), '-o', str(binary), + ], check=True) + subprocess.run([str(binary)], check=True) diff --git a/tests/window-ownership.py b/tests/window-ownership.py index 607be1851d..192a9b26f4 100644 --- a/tests/window-ownership.py +++ b/tests/window-ownership.py @@ -240,6 +240,29 @@ puts("PASS: subsequent activation unbinds the unused overlay with a " "non-interlocked update"); + /* + * Initialization can queue a default binding before this helper runs. + * PIO still describes the previous unowned state. The final owner write + * must override the queued binding, even though PIO matches our target. + */ + stateCache[NVCA7D_WINDOW_SET_CONTROL(2)] = 1; + stateCache[NVCA7D_WINDOW_SET_CONTROL(3)] = + NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE; + stateCache[NVCA7D_WINDOW_SET_PHYSICAL(2)] = 3; + stateCache[NVCA7D_WINDOW_SET_PHYSICAL(3)] = 0; + memcpy(pendingMethods, stateCache, sizeof(stateCache)); + pendingMethods[NVCA7D_WINDOW_SET_CONTROL(3)] = 1; + memset(&update, 0, sizeof(update)); + update.windowMappingChanged = TRUE; + update.updateState.noCoreInterlockMask = 8; + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); + assert(update.windowMappingChanged); + assert(update.updateState.noCoreInterlockMask & 8); + puts("PASS: an unused overlay overrides a queued initialization binding"); + return 0; } ''' From d6ce47a2296f2f900516d4dd7ad1d67e3a26ca4d Mon Sep 17 00:00:00 2001 From: Zac Lanzon Date: Sat, 5 Sep 2026 00:24:40 -0400 Subject: [PATCH 4/6] Move modeset test C into readable fixture files --- tests/README.md | 17 ++ tests/fixtures/allocation-model.h | 85 ++++++++ tests/fixtures/ordinary-configurations.c | 142 +++++++++++++ tests/fixtures/ownership-model.h | 120 +++++++++++ tests/fixtures/phywin-allocation.c | 106 ++++++++++ tests/fixtures/phywin-sequencing.c | 33 +++ tests/fixtures/phywin-stress.c | 140 +++++++++++++ tests/fixtures/sequencing-model.h | 76 +++++++ tests/fixtures/window-ownership.c | 119 +++++++++++ tests/ordinary-configurations.py | 168 +-------------- tests/phywin-allocation.py | 135 +----------- tests/phywin-sequencing.py | 101 ++------- tests/phywin-stress.py | 173 +-------------- tests/window-ownership.py | 256 +---------------------- 14 files changed, 868 insertions(+), 803 deletions(-) create mode 100644 tests/README.md create mode 100644 tests/fixtures/allocation-model.h create mode 100644 tests/fixtures/ordinary-configurations.c create mode 100644 tests/fixtures/ownership-model.h create mode 100644 tests/fixtures/phywin-allocation.c create mode 100644 tests/fixtures/phywin-sequencing.c create mode 100644 tests/fixtures/phywin-stress.c create mode 100644 tests/fixtures/sequencing-model.h create mode 100644 tests/fixtures/window-ownership.c diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..200bb92e71 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,17 @@ +# Modeset regression tests + +Run these scripts from a checkout with Python 3 and a C compiler supporting AddressSanitizer and UndefinedBehaviorSanitizer: + +```sh +ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/phywin-allocation.py +ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/phywin-sequencing.py +ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/window-ownership.py +ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/phywin-stress.py +ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/ordinary-configurations.py +``` + +The comparison requires base commit `e4a5faa2567f28c8eabe0ebb6422b6d0abcf37eb` in the local Git object database. + +The Python runners extract production functions from the checkout and compile them between the C model headers and scenario files in `fixtures/`. The fixtures are parts of generated translation units, not independently built programs. Keeping the C in separate files makes its types, assertions, and method recording directly reviewable without embedding source in Python strings. The stress and comparison runners share `allocation-model.h`. + +These tests cover a simplified allocator and hardware-method model. They do not load a driver, change displays, run RM, or establish hardware timing and firmware behavior. diff --git a/tests/fixtures/allocation-model.h b/tests/fixtures/allocation-model.h new file mode 100644 index 0000000000..1ee020636a --- /dev/null +++ b/tests/fixtures/allocation-model.h @@ -0,0 +1,85 @@ +#include +#include +#include +#include +typedef uint32_t NvU32; +typedef int NvBool; +#define TRUE 1 +#define FALSE 0 +#define NVKMS_MAX_HEADS_PER_DISP 4 +#define NV_YUV420_MODE_HW 1 +#define NVBIT(x) (1U << (x)) +#define NVBIT64(x) (UINT64_C(1) << (x)) +#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) +#define nvAssert(x) assert(x) +#define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) +#define FOR_EACH_INDEX_IN_MASK(bits, i, mask) \ + for ((i) = 0; (i) < (bits); (i)++) \ + if ((mask) & NVBIT(i)) +#define FOR_EACH_INDEX_IN_MASK_END +typedef enum { + NV_EVO_HW_TILE_TYPE_0, + NV_EVO_HW_TILE_TYPE_1, + NV_EVO_HW_TILE_TYPE_FIRST = 0, + NV_EVO_HW_TILE_TYPE_LAST = 1 +} NVEvoHwTileType; +typedef struct { + NvU32 tilesMask, phywinsMask[2]; +} NVHwHeadMultiTileConfigRec; +struct NvKmsUsageBounds { + struct { + NvBool usable; + } layer[2]; +}; +typedef struct { + struct { + struct { + NvU32 width, height; + } in, out; + } viewPort; + NvU32 yuv420Mode; +} NVHwModeTimingsEvo; +typedef struct { + struct { + NVEvoHwTileType type; + } hwTile[8]; +} NVEvoCapabilities; +typedef struct { + NvU32 numHeads, numHwTiles, numHwPhywins; + struct { + NvU32 numLayers; + } head[4]; + struct { + NVEvoCapabilities capabilities; + } gpus[1]; +} NVDevEvoRec; +typedef struct { + NvU32 hwHeadsMask; +} NVDispApiHeadStateEvoRec; +typedef struct { + NVHwHeadMultiTileConfigRec multiTileConfig; +} TestHead; +typedef struct { + NVDevEvoRec *pDevEvo; + NvU32 displayOwner; + NVDispApiHeadStateEvoRec apiHeadState[4]; + TestHead headState[4]; +} NVDispEvoRec; +typedef NVDispEvoRec *NVDispEvoPtr; +typedef struct { + TestHead head[4]; +} NVProposedModeSetHwStateOneDisp; +#define FOR_EACH_EVO_HW_HEAD_IN_MASK(mask, head) FOR_EACH_INDEX_IN_MASK(32, head, mask) +typedef struct { + struct { + NVHwModeTimingsEvo *pTimings; + struct NvKmsUsageBounds *pUsage; + NvBool modesetRequested; + NVHwHeadMultiTileConfigRec multiTileConfig; + } head[4]; +} NVEvoIsModePossibleDispInput; +typedef struct { + struct { + NVHwHeadMultiTileConfigRec multiTileConfig; + } head[4]; +} NVEvoIsModePossibleDispOutput; diff --git a/tests/fixtures/ordinary-configurations.c b/tests/fixtures/ordinary-configurations.c new file mode 100644 index 0000000000..2e1df1a65c --- /dev/null +++ b/tests/fixtures/ordinary-configurations.c @@ -0,0 +1,142 @@ +static NvBool CompleteAllocation(const NVEvoIsModePossibleDispOutput *pOutput, NvU32 heads, + NvU32 layers, const NvU32 required[4]) +{ + NvU32 usedTiles = 0; + NvU32 usedWindows = 0; + + for (NvU32 head = 0; head < heads; head++) { + const NVHwHeadMultiTileConfigRec *pConfig = &pOutput->head[head].multiTileConfig; + if (nvPopCount32(pConfig->tilesMask) != required[head] || + (pConfig->tilesMask & (usedTiles | ~0xffU))) { + return FALSE; + } + usedTiles |= pConfig->tilesMask; + for (NvU32 layer = 0; layer < layers; layer++) { + NvU32 mask = pConfig->phywinsMask[layer]; + if (nvPopCount32(mask) != required[head] || (mask & (usedWindows | ~0xffU))) { + return FALSE; + } + usedWindows |= mask; + } + } + return TRUE; +} + +static void SameAllocation(const NVEvoIsModePossibleDispOutput *pReference, + const NVEvoIsModePossibleDispOutput *pActual, NvU32 heads, NvU32 layers) +{ + for (NvU32 head = 0; head < heads; head++) { + assert(pReference->head[head].multiTileConfig.tilesMask == + pActual->head[head].multiTileConfig.tilesMask); + for (NvU32 layer = 0; layer < layers; layer++) { + assert(pReference->head[head].multiTileConfig.phywinsMask[layer] == + pActual->head[head].multiTileConfig.phywinsMask[layer]); + } + } +} + +static unsigned identical; +static unsigned rejected; +static unsigned corrected; +static unsigned unchanged; + +static void TestConfiguration(NvU32 heads, NvU32 layers, NvU32 scalerTiles, NvU32 scalingMask, + NvU32 tileChoices, NvBool reuse) +{ + NVDevEvoRec dev = {.numHeads = heads, .numHwTiles = 8, .numHwPhywins = 8}; + NVDispEvoRec disp = {.pDevEvo = &dev}; + NVEvoIsModePossibleDispInput input = {0}; + NVEvoIsModePossibleDispOutput reference = {0}; + NVEvoIsModePossibleDispOutput actual = {0}; + NVHwModeTimingsEvo timings[4] = {0}; + struct NvKmsUsageBounds usage[4] = {0}; + NvU32 required[4] = {0}; + NvU32 requiredTiles = 0; + NvU32 requiredScalerTiles = 0; + + for (NvU32 tile = 0; tile < 8; tile++) { + dev.gpus[0].capabilities.hwTile[tile].type = + tile < scalerTiles ? NV_EVO_HW_TILE_TYPE_0 : NV_EVO_HW_TILE_TYPE_1; + } + for (NvU32 head = 0; head < heads; head++) { + dev.head[head].numLayers = layers; + input.head[head].pTimings = &timings[head]; + input.head[head].pUsage = &usage[head]; + input.head[head].modesetRequested = TRUE; + timings[head].viewPort.in.width = 1920; + timings[head].viewPort.out.width = scalingMask & NVBIT(head) ? 1280 : 1920; + required[head] = 1 + ((tileChoices >> (2 * head)) & 3); + requiredTiles += required[head]; + if (scalingMask & NVBIT(head)) { + requiredScalerTiles += required[head]; + } + if (reuse) { + input.head[head].multiTileConfig.tilesMask = NVBIT(head); + } + for (NvU32 layer = 0; layer < layers; layer++) { + usage[head].layer[layer].usable = TRUE; + if (reuse) { + input.head[head].multiTileConfig.phywinsMask[layer] = NVBIT(head * layers + layer); + } + } + } + if (requiredTiles * layers > 8) { + return; + } + NvBool expected = requiredScalerTiles <= scalerTiles; + NvBool referenceResult = + ReferenceEvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &reference); + NvBool actualResult = + EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &actual); + assert(actualResult == expected); + if (actualResult) { + assert(CompleteAllocation(&actual, heads, layers, required)); + } + if (referenceResult && !CompleteAllocation(&reference, heads, layers, required)) { + corrected++; + return; + } + assert(referenceResult == actualResult); + if (!actualResult) { + rejected++; + return; + } + SameAllocation(&reference, &actual, heads, layers); + identical++; + + /* Replay with every subset of heads marked unchanged. */ + for (NvU32 head = 0; head < heads; head++) { + input.head[head].multiTileConfig = actual.head[head].multiTileConfig; + } + for (NvU32 changedMask = 0; changedMask < NVBIT(heads); changedMask++) { + for (NvU32 head = 0; head < heads; head++) { + input.head[head].modesetRequested = !!(changedMask & NVBIT(head)); + } + assert(ReferenceEvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, + &reference)); + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &actual)); + SameAllocation(&reference, &actual, heads, layers); + unchanged++; + } +} + +int main(void) +{ + for (NvU32 heads = 1; heads <= 4; heads++) { + for (NvU32 layers = 1; layers <= 2; layers++) { + for (NvU32 scalerTiles = 0; scalerTiles <= 8; scalerTiles++) { + for (NvU32 scalingMask = 0; scalingMask < NVBIT(heads); scalingMask++) { + for (NvU32 tiles = 0; tiles < NVBIT(2 * heads); tiles++) { + TestConfiguration(heads, layers, scalerTiles, scalingMask, tiles, FALSE); + TestConfiguration(heads, layers, scalerTiles, scalingMask, tiles, TRUE); + } + } + } + } + } + printf("PASS: %u complete reference allocations exactly match\n", identical); + printf("PASS: %u unchanged-head replays exactly match\n", unchanged); + printf("PASS: %u impossible configurations rejected by both\n", rejected); + printf("PASS: %u incomplete upstream results handled by the retry fix\n", corrected); + return 0; +} diff --git a/tests/fixtures/ownership-model.h b/tests/fixtures/ownership-model.h new file mode 100644 index 0000000000..a0c6133b93 --- /dev/null +++ b/tests/fixtures/ownership-model.h @@ -0,0 +1,120 @@ +#include +#include +#include +#include + +#include "nvtypes.h" +#include "nvmisc.h" +#include "class/clca7d.h" + +#define nvDoDebugLogging() 0 +#define nvEvoLogDev(...) ((void)0) +#define TRUE 1 +#define FALSE 0 +#define nvAssert(x) assert(x) +#define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) +#define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) + +typedef struct { + NvU32 channelMask; +} NVEvoChannel; + +typedef struct { + NvU32 noCoreInterlockMask; +} NVEvoUpdateState; + +typedef struct { + NvBool windowMappingChanged; + NVEvoUpdateState updateState; +} NVEvoModesetUpdateState; + +typedef struct { + void *pCoreDma; +} TestSubDevice; + +typedef struct { + NVEvoChannel *core; + TestSubDevice *pSubDevices[1]; + struct { + NvU32 numLayers; + NVEvoChannel *layer[2]; + } head[4]; +} NVDevEvoRec; + +typedef struct { + NVDevEvoRec *pDevEvo; + NvU32 displayOwner; +} NVDispEvoRec; + +typedef struct { + NvU32 tilesMask; + NvU32 phywinsMask[2]; +} NVHwHeadMultiTileConfigRec; + +typedef struct { + int unused; +} NVHwModeTimingsEvo; + +typedef struct { + int unused; +} NVDscInfoEvoRec; + +static NvU32 stateCache[65536]; +static NvU32 pendingMethods[65536]; +static NvU32 currentMethod; + +static void nvUpdateUpdateState(NVDevEvoRec *pDevEvo, NVEvoUpdateState *pUpdateState, + NVEvoChannel *pChannel) +{ + (void)pDevEvo; + (void)pUpdateState; + (void)pChannel; +} + +static void nvDisableCoreInterlockUpdateState(NVDevEvoRec *pDevEvo, NVEvoUpdateState *pUpdateState, + const NVEvoChannel *pChannel) +{ + (void)pDevEvo; + pUpdateState->noCoreInterlockMask |= 1U << pChannel->channelMask; +} + +static NvU32 nvDmaLoadPioMethod(const void *pCoreDma, NvU32 method) +{ + (void)pCoreDma; + assert(method < 65536); + return stateCache[method]; +} + +static void nvDmaSetStartEvoMethod(NVEvoChannel *pChannel, NvU32 method, NvU32 count) +{ + (void)pChannel; + assert(count == 1 && method < 65536); + currentMethod = method; +} + +static void nvDmaSetEvoMethodData(NVEvoChannel *pChannel, NvU32 value) +{ + (void)pChannel; + pendingMethods[currentMethod] = value; +} + +static void SetTileSize(NVEvoChannel *pChannel, const NVHwModeTimingsEvo *pTimings, + const NVDscInfoEvoRec *pDscInfo, const NVHwHeadMultiTileConfigRec *pConfig) +{ + (void)pChannel; + (void)pTimings; + (void)pDscInfo; + (void)pConfig; +} + +static NvU32 GetPendingWindowOwner(NvU32 window) +{ + return DRF_VAL(CA7D, _WINDOW_SET_CONTROL, _OWNER, + pendingMethods[NVCA7D_WINDOW_SET_CONTROL(window)]); +} + +/* Model completed method execution; this does not simulate hardware timing. */ +static void CommitPendingMethods(void) +{ + memcpy(stateCache, pendingMethods, sizeof(stateCache)); +} diff --git a/tests/fixtures/phywin-allocation.c b/tests/fixtures/phywin-allocation.c new file mode 100644 index 0000000000..a5e933ecfe --- /dev/null +++ b/tests/fixtures/phywin-allocation.c @@ -0,0 +1,106 @@ +static NVDevEvoRec dev; +static NVDispEvoRec disp; +static NVEvoIsModePossibleDispInput input; +static NVHwModeTimingsEvo timings[4]; +static struct NvKmsUsageBounds usage[4]; +static void setup(void) +{ + memset(&dev, 0, sizeof(dev)); + memset(&input, 0, sizeof(input)); + memset(usage, 0, sizeof(usage)); + dev.numHeads = 4; + dev.numHwTiles = 8; + dev.numHwPhywins = 8; + disp.pDevEvo = &dev; + disp.displayOwner = 0; + for (NvU32 h = 0; h < 4; h++) { + dev.head[h].numLayers = 2; + input.head[h].pTimings = &timings[h]; + input.head[h].pUsage = &usage[h]; + input.head[h].modesetRequested = TRUE; + input.head[h].multiTileConfig.tilesMask = NVBIT(7 - h); + input.head[h].multiTileConfig.phywinsMask[0] = NVBIT(2 * h); + input.head[h].multiTileConfig.phywinsMask[1] = NVBIT(2 * h + 1); + usage[h].layer[0].usable = TRUE; + } +} +static void checkUnique(NVEvoIsModePossibleDispOutput *out) +{ + NvU32 tiles = 0, windows = 0; + for (NvU32 h = 0; h < 4; h++) { + NVHwHeadMultiTileConfigRec *c = &out->head[h].multiTileConfig; + assert(!(tiles & c->tilesMask)); + tiles |= c->tilesMask; + for (NvU32 l = 0; l < 2; l++) { + assert(!(windows & c->phywinsMask[l])); + windows |= c->phywinsMask[l]; + if (usage[h].layer[l].usable) + assert(nvPopCount32(c->phywinsMask[l]) == nvPopCount32(c->tilesMask)); + else + assert(c->phywinsMask[l] == 0); + } + } +} +int main(void) +{ + NvU32 required[4] = {1, 1, 2, 2}; + NVEvoIsModePossibleDispOutput out = {0}; + setup(); + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); + checkUnique(&out); + puts("PASS: four heads, six tiles, unused overlays released"); + setup(); + for (NvU32 h = 0; h < 4; h++) + usage[h].layer[1].usable = TRUE; + assert(!EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); + puts("PASS: twelve required physical windows still rejected"); + setup(); + for (NvU32 h = 0; h < 4; h++) { + required[h] = 1; + input.head[h].multiTileConfig.phywinsMask[1] = 0; + } + usage[0].layer[1].usable = TRUE; + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); + checkUnique(&out); + puts("PASS: enabling a layer allocates windows without changing tile count"); + setup(); + usage[0].layer[1].usable = TRUE; + input.head[0].modesetRequested = FALSE; + required[2] = 2; + required[3] = 2; + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); + assert(!memcmp(&out.head[0].multiTileConfig, &input.head[0].multiTileConfig, + sizeof(NVHwHeadMultiTileConfigRec))); + checkUnique(&out); + puts("PASS: unchanged head retains its assignments"); + input.head[0].multiTileConfig.phywinsMask[1] = 0; + assert(!EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); + puts("PASS: unchanged head cannot enable an unassigned overlay without a modeset"); + + NVProposedModeSetHwStateOneDisp proposed = {0}; + memset(disp.headState, 0, sizeof(disp.headState)); + disp.apiHeadState[0].hwHeadsMask = 1; + disp.headState[0].multiTileConfig.phywinsMask[1] = 2; + proposed.head[0].multiTileConfig.phywinsMask[1] = 2; + assert(!IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); + proposed.head[0].multiTileConfig.phywinsMask[1] = 0; + proposed.head[0].multiTileConfig.phywinsMask[0] = 2; + assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); + proposed.head[0].multiTileConfig.phywinsMask[0] = 0; + proposed.head[1].multiTileConfig.phywinsMask[0] = 2; + assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); + puts("PASS: window ownership changes require shutdown, retained ownership does not"); + memset(disp.headState, 0, sizeof(disp.headState)); + memset(&proposed, 0, sizeof(proposed)); + disp.headState[0].multiTileConfig.phywinsMask[0] = 1; + proposed.head[0].multiTileConfig.phywinsMask[0] = 1; + proposed.head[0].multiTileConfig.phywinsMask[1] = 2; + assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); + disp.headState[0].multiTileConfig.phywinsMask[1] = 2; + assert(!IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); + proposed.head[0].multiTileConfig.phywinsMask[1] = 0; + assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); + puts("PASS: binding or unbinding an overlay requires shutdown even without resource transfer"); + + return 0; +} diff --git a/tests/fixtures/phywin-sequencing.c b/tests/fixtures/phywin-sequencing.c new file mode 100644 index 0000000000..d0a39674e5 --- /dev/null +++ b/tests/fixtures/phywin-sequencing.c @@ -0,0 +1,33 @@ +int main(void) +{ + Hal hal = {update}; + NVDevEvoRec dev = {1, &hal}; + Disp disp = {&dev}; + NVEvoChannel channel = {4}; + NVModeSetWorkArea work = {0}; + NVProposedModeSetHwState proposed = {0}; + NVProposedModeSetHwStateOneDisp one = {0}; + EvoTrackPhysicalWindowChangeCA(&dev, &channel, 1, 1, &work.modesetUpdateState); + assert(!work.modesetUpdateState.windowMappingChanged); + assert(!work.modesetUpdateState.updateState.noCoreInterlockMask); + EvoTrackPhysicalWindowChangeCA(&dev, &channel, 1, 0, &work.modesetUpdateState); + assert(work.modesetUpdateState.windowMappingChanged); + assert(work.modesetUpdateState.updateState.noCoreInterlockMask == 4); + puts("PASS: changed physical assignment disables its core interlock; unchanged is untouched"); + memset(&work, 0, sizeof(work)); + changeInPre = 1; + KickoffProposedModeSetHwState(&disp, &proposed, &one, FALSE, &work); + assert(n == 3 && memcmp(events, "CFU", 3) == 0); + puts("PASS: assignment changed during PreUpdate -> core update, then flip update"); + n = 0; + changeInPre = 0; + memset(&work, 0, sizeof(work)); + KickoffProposedModeSetHwState(&disp, &proposed, &one, FALSE, &work); + assert(n == 2 && memcmp(events, "FC", 2) == 0); + puts("PASS: unchanged assignment retains combined flip/core update"); + n = 0; + work.modesetUpdateState.windowMappingChanged = TRUE; + KickoffProposedModeSetHwState(&disp, &proposed, &one, FALSE, &work); + assert(n == 3 && memcmp(events, "CFU", 3) == 0); + puts("PASS: existing logical mapping change still decouples updates"); +} diff --git a/tests/fixtures/phywin-stress.c b/tests/fixtures/phywin-stress.c new file mode 100644 index 0000000000..6af3aa29bd --- /dev/null +++ b/tests/fixtures/phywin-stress.c @@ -0,0 +1,140 @@ +static NvBool TestAllocation(NvU32 windowCount, NvBool reuse, NvU32 scalerTiles, NvU32 scalingMask, + NvU32 overlayMask, NvU32 tileChoices) +{ + NVDevEvoRec dev = {0}; + NVDispEvoRec disp = {.pDevEvo = &dev}; + NVEvoIsModePossibleDispInput input = {0}; + NVEvoIsModePossibleDispOutput output = {0}; + NVHwModeTimingsEvo timings[4] = {0}; + struct NvKmsUsageBounds usage[4] = {0}; + NvU32 required[4]; + NvU32 totalTiles = 0; + NvU32 totalWindows = 0; + NvU32 requiredScalerTiles = 0; + NvU32 assignedTiles = 0; + NvU32 assignedWindows = 0; + + dev.numHeads = 4; + dev.numHwTiles = 8; + dev.numHwPhywins = windowCount; + for (NvU32 tile = 0; tile < 8; tile++) { + dev.gpus[0].capabilities.hwTile[tile].type = + tile < scalerTiles ? NV_EVO_HW_TILE_TYPE_0 : NV_EVO_HW_TILE_TYPE_1; + } + for (NvU32 head = 0; head < 4; head++) { + dev.head[head].numLayers = 2; + input.head[head].pTimings = &timings[head]; + input.head[head].pUsage = &usage[head]; + input.head[head].modesetRequested = TRUE; + usage[head].layer[0].usable = TRUE; + usage[head].layer[1].usable = !!(overlayMask & NVBIT(head)); + timings[head].viewPort.in.width = 1920; + timings[head].viewPort.out.width = (scalingMask & NVBIT(head)) ? 1280 : 1920; + required[head] = 1 + ((tileChoices >> (2 * head)) & 3); + if (reuse) { + input.head[head].multiTileConfig.tilesMask = NVBIT(head); + input.head[head].multiTileConfig.phywinsMask[0] = NVBIT(windowCount - 8 + 2 * head); + input.head[head].multiTileConfig.phywinsMask[1] = NVBIT(windowCount - 8 + 2 * head + 1); + } + if (scalingMask & NVBIT(head)) { + requiredScalerTiles += required[head]; + } + totalTiles += required[head]; + totalWindows += required[head] * (1 + usage[head].layer[1].usable); + } + + const NVEvoIsModePossibleDispInput originalInput = input; + NvBool result = EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &output); + assert(memcmp(&input, &originalInput, sizeof(input)) == 0); + NvBool expected = + totalTiles <= 8 && totalWindows <= windowCount && requiredScalerTiles <= scalerTiles; + if (result != expected) { + printf("FAIL: windows=%u reuse=%u scalerTiles=%u scaling=%u " + "overlays=%u tiles=%u " + "needTiles=%u needWindows=%u accepted=%d\n", + windowCount, reuse, scalerTiles, scalingMask, overlayMask, tileChoices, totalTiles, + totalWindows, result); + return FALSE; + } + if (result) { + for (NvU32 head = 0; head < 4; head++) { + const NVHwHeadMultiTileConfigRec *pConfig = &output.head[head].multiTileConfig; + assert(nvPopCount32(pConfig->tilesMask) == required[head]); + assert(!(pConfig->tilesMask & ~0xffU)); + assert(!(pConfig->tilesMask & assignedTiles)); + assignedTiles |= pConfig->tilesMask; + for (NvU32 layer = 0; layer < 2; layer++) { + NvU32 expectedCount = usage[head].layer[layer].usable ? required[head] : 0; + assert(nvPopCount32(pConfig->phywinsMask[layer]) == expectedCount); + assert(!(pConfig->phywinsMask[layer] & ~(NvU32)(NVBIT64(windowCount) - 1))); + assert(!(pConfig->phywinsMask[layer] & assignedWindows)); + assignedWindows |= pConfig->phywinsMask[layer]; + } + } + } + return TRUE; +} + +static void TestBoundaryConditions(void) +{ + NVDevEvoRec dev = {.numHeads = 4, .numHwTiles = 8, .numHwPhywins = 32}; + NVDispEvoRec disp = {.pDevEvo = &dev}; + NVEvoIsModePossibleDispInput input = {0}; + NVEvoIsModePossibleDispOutput output = {0}; + NVHwModeTimingsEvo timing = {0}; + struct NvKmsUsageBounds usage = {0}; + NvU32 required[4] = {0}; + + /* Inactive heads have NULL timing/usage pointers and need no resources. */ + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &output)); + + dev.head[0].numLayers = 8; + input.head[0].pTimings = &timing; + input.head[0].pUsage = &usage; + input.head[0].modesetRequested = TRUE; + input.head[0].multiTileConfig.tilesMask = 1; + input.head[0].multiTileConfig.phywinsMask[0] = NVBIT(31); + required[0] = 1; + for (NvU32 layer = 0; layer < 8; layer++) { + usage.layer[layer].usable = TRUE; + } + assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &output)); + assert(output.head[0].multiTileConfig.phywinsMask[0] == NVBIT(31)); + for (NvU32 layer = 0; layer < 8; layer++) { + assert(nvPopCount32(output.head[0].multiTileConfig.phywinsMask[layer]) == 1); + } + + /* Failed helper allocation must not publish its partial result. */ + NVHwHeadMultiTileConfigRec config = {.tilesMask = 3}; + const NVHwHeadMultiTileConfigRec originalConfig = config; + NvU32 freeWindows = 1; + assert(!AssignNewPhywinsIfNeeded(&disp, &config, 0, &usage, &freeWindows)); + assert(memcmp(&config, &originalConfig, sizeof(config)) == 0); + assert(freeWindows == 1); + puts("PASS: inactive heads, eight layers, bit 31, and allocation failure isolation"); +} + +int main(void) +{ + unsigned cases = 0; + + TestBoundaryConditions(); + + for (NvU32 windowCount = 8; windowCount <= 32; windowCount += 24) { + for (NvU32 reuse = 0; reuse < 2; reuse++) { + for (NvU32 scalerTiles = 0; scalerTiles <= 8; scalerTiles++) { + for (NvU32 scalingMask = 0; scalingMask < 16; scalingMask++) { + for (NvU32 overlayMask = 0; overlayMask < 16; overlayMask++) { + for (NvU32 tileChoices = 0; tileChoices < 256; tileChoices++) { + assert(TestAllocation(windowCount, reuse, scalerTiles, scalingMask, + overlayMask, tileChoices)); + cases++; + } + } + } + } + } + } + printf("PASS: %u allocation combinations\n", cases); + return 0; +} diff --git a/tests/fixtures/sequencing-model.h b/tests/fixtures/sequencing-model.h new file mode 100644 index 0000000000..221859fb8b --- /dev/null +++ b/tests/fixtures/sequencing-model.h @@ -0,0 +1,76 @@ +#include +#include +#include +typedef unsigned NvU32; +typedef int NvBool; +#define TRUE 1 +#define FALSE 0 +#define EVO_LOG_INFO 0 +#define nvDoDebugLogging() 0 +#define nvEvoLogDev(...) ((void)0) +#define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) +typedef struct { + NvU32 channelMask; +} NVEvoChannel; +typedef struct { + NvU32 noCoreInterlockMask; +} NVEvoUpdateState; +typedef struct { + NvBool windowMappingChanged; + NVEvoUpdateState updateState; +} NVEvoModesetUpdateState; +struct Dev; +typedef struct { + void (*Update)(struct Dev *, const NVEvoUpdateState *, NvBool); +} Hal; +typedef struct Dev { + NvU32 numApiHeads; + Hal *hal; +} NVDevEvoRec; +typedef struct { + NVDevEvoRec *pDevEvo; +} Disp; +typedef Disp *NVDispEvoPtr; +typedef struct { + int dummy; +} NVProposedModeSetHwState; +typedef struct { + int apiHead[4]; +} NVProposedModeSetHwStateOneDisp; +typedef struct { + NVEvoModesetUpdateState modesetUpdateState; +} NVModeSetWorkArea; +static void nvDisableCoreInterlockUpdateState(NVDevEvoRec *dev, NVEvoUpdateState *u, + const NVEvoChannel *ch) +{ + (void)dev; + u->noCoreInterlockMask |= ch->channelMask; +} +static char events[16]; +static int n, changeInPre; +static void event(char c) +{ + events[n++] = c; +} +static void update(NVDevEvoRec *dev, const NVEvoUpdateState *u, NvBool release) +{ + (void)dev; + (void)u; + (void)release; + event('U'); +} +#define ApplyProposedModeSetStateOneApiHeadShutDown(...) ((void)0) +#define ApplyProposedModeSetStateOneApiHeadPreUpdate(d, h, p, w, b) \ + do { \ + if (changeInPre) \ + (w)->modesetUpdateState.windowMappingChanged = TRUE; \ + } while (0) +#define ApplyProposedModeSetStateOneDispFlip(...) event('F') +#define KickoffModesetUpdateState(d, s) \ + do { \ + event('C'); \ + memset(s, 0, sizeof(*(s))); \ + } while (0) +#define nvRemoveUnusedHdmiDpAudioDevice(...) ((void)0) +#define ApplyProposedModeSetStateOneApiHeadPostModesetUpdate(...) ((void)0) +#define nvIsUpdateStateEmpty(...) TRUE diff --git a/tests/fixtures/window-ownership.c b/tests/fixtures/window-ownership.c new file mode 100644 index 0000000000..5788f95c15 --- /dev/null +++ b/tests/fixtures/window-ownership.c @@ -0,0 +1,119 @@ +int main(void) +{ + NVEvoChannel core = {0}; + NVEvoChannel base = {2}; + NVEvoChannel overlay = {3}; + TestSubDevice subDevice = {stateCache}; + NVDevEvoRec dev = { + .core = &core, + .pSubDevices = {&subDevice}, + }; + NVDispEvoRec disp = {&dev, 0}; + NVEvoModesetUpdateState update = {0}; + NVHwModeTimingsEvo timing = {0}; + NVDscInfoEvoRec dsc = {0}; + NVHwHeadMultiTileConfigRec config = { + .tilesMask = 3, + .phywinsMask = {3, 0}, + }; + + dev.head[1].numLayers = 2; + dev.head[1].layer[0] = &base; + dev.head[1].layer[1] = &overlay; + + /* Simulate stock window owners, including the unrelated HIDE control bit. */ + stateCache[NVCA7D_WINDOW_SET_CONTROL(2)] = 1; + stateCache[NVCA7D_WINDOW_SET_CONTROL(3)] = 0x101; + stateCache[NVCA7D_WINDOW_SET_PHYSICAL(2)] = 1; + stateCache[NVCA7D_WINDOW_SET_PHYSICAL(3)] = 2; + memcpy(pendingMethods, stateCache, sizeof(stateCache)); + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(GetPendingWindowOwner(2) == 1); + assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); + assert(pendingMethods[NVCA7D_WINDOW_SET_CONTROL(3)] == 0x10f); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(2)] == 3); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 0); + assert(update.windowMappingChanged); + assert(update.updateState.noCoreInterlockMask == 12); + puts("PASS: unused overlay is unowned with no physical windows; " + "base remains bound"); + + /* Repeating the same configuration must not introduce an interlock change. */ + CommitPendingMethods(); + memset(&update, 0, sizeof(update)); + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(!update.windowMappingChanged); + assert(!update.updateState.noCoreInterlockMask); + puts("PASS: unchanged allocation/ownership introduces no extra interlock " + "changes"); + + /* Restoring overlay resources also restores its original hardware owner. */ + config.tilesMask = 1; + config.phywinsMask[0] = 1; + config.phywinsMask[1] = 2; + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(GetPendingWindowOwner(3) == 1); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 2); + assert(update.windowMappingChanged); + assert(update.updateState.noCoreInterlockMask & 8); + puts("PASS: re-enabled overlay regains its owner and physical assignment"); + + /* Shutdown releases physical windows while leaving owners unchanged. */ + CommitPendingMethods(); + memset(&update, 0, sizeof(update)); + memset(&config, 0, sizeof(config)); + + EvoSetMultiTileConfigCA(&disp, 1, NULL, NULL, &config, &update); + + assert(GetPendingWindowOwner(2) == 1); + assert(GetPendingWindowOwner(3) == 1); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(2)] == 0); + assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 0); + puts("PASS: shutdown clears physical assignments without changing " + "ownership in that update"); + + /* Only the following activation update removes the unused window owner. */ + CommitPendingMethods(); + memset(&update, 0, sizeof(update)); + config.tilesMask = 3; + config.phywinsMask[0] = 3; + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(GetPendingWindowOwner(2) == 1); + assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); + assert(update.windowMappingChanged); + assert(update.updateState.noCoreInterlockMask & 8); + puts("PASS: subsequent activation unbinds the unused overlay with a " + "non-interlocked update"); + + /* + * Initialization can queue a default binding before this helper runs. + * PIO still describes the previous unowned state. The final owner write + * must override the queued binding, even though PIO matches our target. + */ + stateCache[NVCA7D_WINDOW_SET_CONTROL(2)] = 1; + stateCache[NVCA7D_WINDOW_SET_CONTROL(3)] = NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE; + stateCache[NVCA7D_WINDOW_SET_PHYSICAL(2)] = 3; + stateCache[NVCA7D_WINDOW_SET_PHYSICAL(3)] = 0; + memcpy(pendingMethods, stateCache, sizeof(stateCache)); + pendingMethods[NVCA7D_WINDOW_SET_CONTROL(3)] = 1; + memset(&update, 0, sizeof(update)); + update.windowMappingChanged = TRUE; + update.updateState.noCoreInterlockMask = 8; + + EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); + + assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); + assert(update.windowMappingChanged); + assert(update.updateState.noCoreInterlockMask & 8); + puts("PASS: an unused overlay overrides a queued initialization binding"); + + return 0; +} diff --git a/tests/ordinary-configurations.py b/tests/ordinary-configurations.py index c266b1c9c3..41069555e7 100644 --- a/tests/ordinary-configurations.py +++ b/tests/ordinary-configurations.py @@ -6,7 +6,6 @@ only complete successful reference allocations are compared for exact equality. Hardware methods and timings are not simulated here. """ -import ast from pathlib import Path import re import subprocess @@ -15,14 +14,7 @@ root = Path(__file__).resolve().parents[1] source_path = 'src/nvidia-modeset/src/nvkms-evo4.c' reference_revision = 'e4a5faa2567f28c8eabe0ebb6422b6d0abcf37eb' -module = ast.parse((root / 'tests/phywin-allocation.py').read_text()) -preamble = next( - ast.literal_eval(node.value) - for node in module.body - if isinstance(node, ast.Assign) - and any(isinstance(target, ast.Name) and target.id == 'preamble' - for target in node.targets) -) +preamble = (Path(__file__).with_name('fixtures') / 'allocation-model.h').read_text() def allocator(text): @@ -39,163 +31,7 @@ def allocator(text): for name in function_names: reference = re.sub(r'\b' + re.escape(name) + r'\b', 'Reference' + name, reference) -scenarios = r''' -static NvBool CompleteAllocation( - const NVEvoIsModePossibleDispOutput *pOutput, - NvU32 heads, - NvU32 layers, - const NvU32 required[4]) -{ - NvU32 usedTiles = 0; - NvU32 usedWindows = 0; - - for (NvU32 head = 0; head < heads; head++) { - const NVHwHeadMultiTileConfigRec *pConfig = - &pOutput->head[head].multiTileConfig; - if (nvPopCount32(pConfig->tilesMask) != required[head] || - (pConfig->tilesMask & (usedTiles | ~0xffU))) { - return FALSE; - } - usedTiles |= pConfig->tilesMask; - for (NvU32 layer = 0; layer < layers; layer++) { - NvU32 mask = pConfig->phywinsMask[layer]; - if (nvPopCount32(mask) != required[head] || - (mask & (usedWindows | ~0xffU))) { - return FALSE; - } - usedWindows |= mask; - } - } - return TRUE; -} - -static void SameAllocation( - const NVEvoIsModePossibleDispOutput *pReference, - const NVEvoIsModePossibleDispOutput *pActual, - NvU32 heads, - NvU32 layers) -{ - for (NvU32 head = 0; head < heads; head++) { - assert(pReference->head[head].multiTileConfig.tilesMask == - pActual->head[head].multiTileConfig.tilesMask); - for (NvU32 layer = 0; layer < layers; layer++) { - assert(pReference->head[head].multiTileConfig.phywinsMask[layer] == - pActual->head[head].multiTileConfig.phywinsMask[layer]); - } - } -} - -static unsigned identical; -static unsigned rejected; -static unsigned corrected; -static unsigned unchanged; - -static void TestConfiguration(NvU32 heads, NvU32 layers, NvU32 scalerTiles, - NvU32 scalingMask, NvU32 tileChoices, NvBool reuse) -{ - NVDevEvoRec dev = { .numHeads = heads, .numHwTiles = 8, .numHwPhywins = 8 }; - NVDispEvoRec disp = { .pDevEvo = &dev }; - NVEvoIsModePossibleDispInput input = { 0 }; - NVEvoIsModePossibleDispOutput reference = { 0 }; - NVEvoIsModePossibleDispOutput actual = { 0 }; - NVHwModeTimingsEvo timings[4] = { 0 }; - struct NvKmsUsageBounds usage[4] = { 0 }; - NvU32 required[4] = { 0 }; - NvU32 requiredTiles = 0; - NvU32 requiredScalerTiles = 0; - - for (NvU32 tile = 0; tile < 8; tile++) { - dev.gpus[0].capabilities.hwTile[tile].type = - tile < scalerTiles ? NV_EVO_HW_TILE_TYPE_0 : NV_EVO_HW_TILE_TYPE_1; - } - for (NvU32 head = 0; head < heads; head++) { - dev.head[head].numLayers = layers; - input.head[head].pTimings = &timings[head]; - input.head[head].pUsage = &usage[head]; - input.head[head].modesetRequested = TRUE; - timings[head].viewPort.in.width = 1920; - timings[head].viewPort.out.width = - scalingMask & NVBIT(head) ? 1280 : 1920; - required[head] = 1 + ((tileChoices >> (2 * head)) & 3); - requiredTiles += required[head]; - if (scalingMask & NVBIT(head)) { - requiredScalerTiles += required[head]; - } - if (reuse) { - input.head[head].multiTileConfig.tilesMask = NVBIT(head); - } - for (NvU32 layer = 0; layer < layers; layer++) { - usage[head].layer[layer].usable = TRUE; - if (reuse) { - input.head[head].multiTileConfig.phywinsMask[layer] = - NVBIT(head * layers + layer); - } - } - } - if (requiredTiles * layers > 8) { - return; - } - NvBool expected = requiredScalerTiles <= scalerTiles; - NvBool referenceResult = ReferenceEvoAssignHwHeadMultiTileConfigDispOutputCA( - &disp, &input, required, &reference); - NvBool actualResult = EvoAssignHwHeadMultiTileConfigDispOutputCA( - &disp, &input, required, &actual); - assert(actualResult == expected); - if (actualResult) { - assert(CompleteAllocation(&actual, heads, layers, required)); - } - if (referenceResult && !CompleteAllocation(&reference, heads, layers, required)) { - corrected++; - return; - } - assert(referenceResult == actualResult); - if (!actualResult) { - rejected++; - return; - } - SameAllocation(&reference, &actual, heads, layers); - identical++; - - /* Replay with every subset of heads marked unchanged. */ - for (NvU32 head = 0; head < heads; head++) { - input.head[head].multiTileConfig = actual.head[head].multiTileConfig; - } - for (NvU32 changedMask = 0; changedMask < NVBIT(heads); changedMask++) { - for (NvU32 head = 0; head < heads; head++) { - input.head[head].modesetRequested = !!(changedMask & NVBIT(head)); - } - assert(ReferenceEvoAssignHwHeadMultiTileConfigDispOutputCA( - &disp, &input, required, &reference)); - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA( - &disp, &input, required, &actual)); - SameAllocation(&reference, &actual, heads, layers); - unchanged++; - } -} - -int main(void) -{ - for (NvU32 heads = 1; heads <= 4; heads++) { - for (NvU32 layers = 1; layers <= 2; layers++) { - for (NvU32 scalerTiles = 0; scalerTiles <= 8; scalerTiles++) { - for (NvU32 scalingMask = 0; scalingMask < NVBIT(heads); scalingMask++) { - for (NvU32 tiles = 0; tiles < NVBIT(2 * heads); tiles++) { - TestConfiguration(heads, layers, scalerTiles, - scalingMask, tiles, FALSE); - TestConfiguration(heads, layers, scalerTiles, - scalingMask, tiles, TRUE); - } - } - } - } - } - printf("PASS: %u complete reference allocations exactly match\n", identical); - printf("PASS: %u unchanged-head replays exactly match\n", unchanged); - printf("PASS: %u impossible configurations rejected by both\n", rejected); - printf("PASS: %u incomplete upstream results handled by the retry fix\n", corrected); - return 0; -} -''' +scenarios = (Path(__file__).with_name('fixtures') / 'ordinary-configurations.c').read_text() with tempfile.TemporaryDirectory() as directory: test = Path(directory) / 'comparison.c' binary = Path(directory) / 'comparison' diff --git a/tests/phywin-allocation.py b/tests/phywin-allocation.py index b3a54ecaaa..442b0beeda 100644 --- a/tests/phywin-allocation.py +++ b/tests/phywin-allocation.py @@ -12,139 +12,8 @@ 'src/nvidia-modeset/src/nvkms-modeset.c').read_text() move_start = modeset.index('static NvBool IsCurrentMultiTileConfigOneApiHeadIncompatible(') move_end = modeset.index('\n/*!', move_start) -preamble = r''' -#include -#include -#include -#include -typedef uint32_t NvU32; -typedef int NvBool; -#define TRUE 1 -#define FALSE 0 -#define NVKMS_MAX_HEADS_PER_DISP 4 -#define NV_YUV420_MODE_HW 1 -#define NVBIT(x) (1U << (x)) -#define NVBIT64(x) (UINT64_C(1) << (x)) -#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) -#define nvAssert(x) assert(x) -#define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) -#define FOR_EACH_INDEX_IN_MASK(bits, i, mask) for ((i)=0; (i)<(bits); (i)++) if ((mask) & NVBIT(i)) -#define FOR_EACH_INDEX_IN_MASK_END -typedef enum { NV_EVO_HW_TILE_TYPE_0, NV_EVO_HW_TILE_TYPE_1, - NV_EVO_HW_TILE_TYPE_FIRST=0, NV_EVO_HW_TILE_TYPE_LAST=1 } NVEvoHwTileType; -typedef struct { NvU32 tilesMask, phywinsMask[2]; } NVHwHeadMultiTileConfigRec; -struct NvKmsUsageBounds { struct { NvBool usable; } layer[2]; }; -typedef struct { struct { struct { NvU32 width,height; } in,out; } viewPort; - NvU32 yuv420Mode; } NVHwModeTimingsEvo; -typedef struct { struct { NVEvoHwTileType type; } hwTile[8]; } NVEvoCapabilities; -typedef struct { NvU32 numHeads, numHwTiles, numHwPhywins; - struct { NvU32 numLayers; } head[4]; - struct { NVEvoCapabilities capabilities; } gpus[1]; } NVDevEvoRec; -typedef struct { NvU32 hwHeadsMask; } NVDispApiHeadStateEvoRec; -typedef struct { NVHwHeadMultiTileConfigRec multiTileConfig; } TestHead; -typedef struct { NVDevEvoRec *pDevEvo; NvU32 displayOwner; - NVDispApiHeadStateEvoRec apiHeadState[4]; TestHead headState[4]; } NVDispEvoRec; -typedef NVDispEvoRec *NVDispEvoPtr; -typedef struct { TestHead head[4]; } NVProposedModeSetHwStateOneDisp; -#define FOR_EACH_EVO_HW_HEAD_IN_MASK(mask, head) FOR_EACH_INDEX_IN_MASK(32, head, mask) -typedef struct { struct { NVHwModeTimingsEvo *pTimings; - struct NvKmsUsageBounds *pUsage; NvBool modesetRequested; - NVHwHeadMultiTileConfigRec multiTileConfig; } head[4]; } NVEvoIsModePossibleDispInput; -typedef struct { struct { NVHwHeadMultiTileConfigRec multiTileConfig; } head[4]; } NVEvoIsModePossibleDispOutput; -''' -tests = r''' -static NVDevEvoRec dev; -static NVDispEvoRec disp; -static NVEvoIsModePossibleDispInput input; -static NVHwModeTimingsEvo timings[4]; -static struct NvKmsUsageBounds usage[4]; -static void setup(void) { - memset(&dev,0,sizeof(dev)); memset(&input,0,sizeof(input)); - memset(usage,0,sizeof(usage)); - dev.numHeads=4; dev.numHwTiles=8; dev.numHwPhywins=8; - disp.pDevEvo=&dev; disp.displayOwner=0; - for (NvU32 h=0;h<4;h++) { - dev.head[h].numLayers=2; - input.head[h].pTimings=&timings[h]; input.head[h].pUsage=&usage[h]; - input.head[h].modesetRequested=TRUE; - input.head[h].multiTileConfig.tilesMask=NVBIT(7-h); - input.head[h].multiTileConfig.phywinsMask[0]=NVBIT(2*h); - input.head[h].multiTileConfig.phywinsMask[1]=NVBIT(2*h+1); - usage[h].layer[0].usable=TRUE; - } -} -static void checkUnique(NVEvoIsModePossibleDispOutput *out) { - NvU32 tiles=0, windows=0; - for (NvU32 h=0;h<4;h++) { - NVHwHeadMultiTileConfigRec *c=&out->head[h].multiTileConfig; - assert(!(tiles & c->tilesMask)); tiles |= c->tilesMask; - for (NvU32 l=0;l<2;l++) { - assert(!(windows & c->phywinsMask[l])); windows |= c->phywinsMask[l]; - if (usage[h].layer[l].usable) - assert(nvPopCount32(c->phywinsMask[l])==nvPopCount32(c->tilesMask)); - else assert(c->phywinsMask[l]==0); - } - } -} -int main(void) { - NvU32 required[4]={1,1,2,2}; - NVEvoIsModePossibleDispOutput out={0}; - setup(); - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); - checkUnique(&out); - puts("PASS: four heads, six tiles, unused overlays released"); - setup(); - for (NvU32 h=0;h<4;h++) usage[h].layer[1].usable=TRUE; - assert(!EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); - puts("PASS: twelve required physical windows still rejected"); - setup(); - for (NvU32 h=0;h<4;h++) { - required[h]=1; input.head[h].multiTileConfig.phywinsMask[1]=0; - } - usage[0].layer[1].usable=TRUE; - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); - checkUnique(&out); - puts("PASS: enabling a layer allocates windows without changing tile count"); - setup(); - usage[0].layer[1].usable=TRUE; input.head[0].modesetRequested=FALSE; - required[2]=2; required[3]=2; - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); - assert(!memcmp(&out.head[0].multiTileConfig,&input.head[0].multiTileConfig, - sizeof(NVHwHeadMultiTileConfigRec))); - checkUnique(&out); - puts("PASS: unchanged head retains its assignments"); - input.head[0].multiTileConfig.phywinsMask[1]=0; - assert(!EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp,&input,required,&out)); - puts("PASS: unchanged head cannot enable an unassigned overlay without a modeset"); - - NVProposedModeSetHwStateOneDisp proposed={0}; - memset(disp.headState,0,sizeof(disp.headState)); - disp.apiHeadState[0].hwHeadsMask=1; - disp.headState[0].multiTileConfig.phywinsMask[1]=2; - proposed.head[0].multiTileConfig.phywinsMask[1]=2; - assert(!IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); - proposed.head[0].multiTileConfig.phywinsMask[1]=0; - proposed.head[0].multiTileConfig.phywinsMask[0]=2; - assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); - proposed.head[0].multiTileConfig.phywinsMask[0]=0; - proposed.head[1].multiTileConfig.phywinsMask[0]=2; - assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); - puts("PASS: window ownership changes require shutdown, retained ownership does not"); - memset(disp.headState,0,sizeof(disp.headState)); - memset(&proposed,0,sizeof(proposed)); - disp.headState[0].multiTileConfig.phywinsMask[0]=1; - proposed.head[0].multiTileConfig.phywinsMask[0]=1; - proposed.head[0].multiTileConfig.phywinsMask[1]=2; - assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); - disp.headState[0].multiTileConfig.phywinsMask[1]=2; - assert(!IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); - proposed.head[0].multiTileConfig.phywinsMask[1]=0; - assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp,0,&proposed)); - puts("PASS: binding or unbinding an overlay requires shutdown even without resource transfer"); - - return 0; -} -''' +preamble = (Path(__file__).with_name('fixtures') / 'allocation-model.h').read_text() +tests = (Path(__file__).with_name('fixtures') / 'phywin-allocation.c').read_text() with tempfile.TemporaryDirectory() as tmp: c = Path(tmp) / 'test.c' exe = Path(tmp) / 'test' diff --git a/tests/phywin-sequencing.py b/tests/phywin-sequencing.py index f33ba29f16..688fc49f20 100644 --- a/tests/phywin-sequencing.py +++ b/tests/phywin-sequencing.py @@ -3,83 +3,26 @@ from pathlib import Path import subprocess import tempfile + root = Path(__file__).resolve().parents[1] -evo = (root/'src/nvidia-modeset/src/nvkms-evo4.c').read_text() -a = evo.index('static void EvoTrackPhysicalWindowChangeCA(') -b = evo.index('static void EvoSetWindowOwnerCA(', a) -modeset = (root/'src/nvidia-modeset/src/nvkms-modeset.c').read_text() -c = modeset.index('static void\nKickoffProposedModeSetHwState(') -d = modeset.index('static void AllocatePostModesetDispBandwidth(', c) -pre = r''' -#include -#include -#include -typedef unsigned NvU32; -typedef int NvBool; -#define TRUE 1 -#define FALSE 0 -#define EVO_LOG_INFO 0 -#define nvDoDebugLogging() 0 -#define nvEvoLogDev(...) ((void)0) -#define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) -typedef struct { NvU32 channelMask; } NVEvoChannel; -typedef struct { NvU32 noCoreInterlockMask; } NVEvoUpdateState; -typedef struct { NvBool windowMappingChanged; NVEvoUpdateState updateState; } NVEvoModesetUpdateState; -struct Dev; -typedef struct { void (*Update)(struct Dev *, const NVEvoUpdateState *, NvBool); } Hal; -typedef struct Dev { NvU32 numApiHeads; Hal *hal; } NVDevEvoRec; -typedef struct { NVDevEvoRec *pDevEvo; } Disp; -typedef Disp *NVDispEvoPtr; -typedef struct { int dummy; } NVProposedModeSetHwState; -typedef struct { int apiHead[4]; } NVProposedModeSetHwStateOneDisp; -typedef struct { NVEvoModesetUpdateState modesetUpdateState; } NVModeSetWorkArea; -static void nvDisableCoreInterlockUpdateState(NVDevEvoRec *dev, NVEvoUpdateState *u, const NVEvoChannel *ch) { - (void)dev; u->noCoreInterlockMask |= ch->channelMask; -} -static char events[16]; static int n, changeInPre; -static void event(char c) { events[n++]=c; } -static void update(NVDevEvoRec *dev,const NVEvoUpdateState *u,NvBool release) { - (void)dev; (void)u; (void)release; event('U'); -} -#define ApplyProposedModeSetStateOneApiHeadShutDown(...) ((void)0) -#define ApplyProposedModeSetStateOneApiHeadPreUpdate(d,h,p,w,b) do { if (changeInPre) (w)->modesetUpdateState.windowMappingChanged=TRUE; } while (0) -#define ApplyProposedModeSetStateOneDispFlip(...) event('F') -#define KickoffModesetUpdateState(d,s) do { event('C'); memset(s,0,sizeof(*(s))); } while (0) -#define nvRemoveUnusedHdmiDpAudioDevice(...) ((void)0) -#define ApplyProposedModeSetStateOneApiHeadPostModesetUpdate(...) ((void)0) -#define nvIsUpdateStateEmpty(...) TRUE -''' -post = r''' -int main(void) { - Hal hal={update}; NVDevEvoRec dev={1,&hal}; Disp disp={&dev}; - NVEvoChannel channel={4}; NVModeSetWorkArea work={0}; - NVProposedModeSetHwState proposed={0}; NVProposedModeSetHwStateOneDisp one={0}; - EvoTrackPhysicalWindowChangeCA(&dev,&channel,1,1,&work.modesetUpdateState); - assert(!work.modesetUpdateState.windowMappingChanged); - assert(!work.modesetUpdateState.updateState.noCoreInterlockMask); - EvoTrackPhysicalWindowChangeCA(&dev,&channel,1,0,&work.modesetUpdateState); - assert(work.modesetUpdateState.windowMappingChanged); - assert(work.modesetUpdateState.updateState.noCoreInterlockMask==4); - puts("PASS: changed physical assignment disables its core interlock; unchanged is untouched"); - memset(&work,0,sizeof(work)); changeInPre=1; - KickoffProposedModeSetHwState(&disp,&proposed,&one,FALSE,&work); - assert(n==3 && memcmp(events,"CFU",3)==0); - puts("PASS: assignment changed during PreUpdate -> core update, then flip update"); - n=0; changeInPre=0; memset(&work,0,sizeof(work)); - KickoffProposedModeSetHwState(&disp,&proposed,&one,FALSE,&work); - assert(n==2 && memcmp(events,"FC",2)==0); - puts("PASS: unchanged assignment retains combined flip/core update"); - n=0; work.modesetUpdateState.windowMappingChanged=TRUE; - KickoffProposedModeSetHwState(&disp,&proposed,&one,FALSE,&work); - assert(n==3 && memcmp(events,"CFU",3)==0); - puts("PASS: existing logical mapping change still decouples updates"); -} -''' -with tempfile.TemporaryDirectory() as tmp: - source = Path(tmp)/'test.c' - exe = Path(tmp)/'test' - source.write_text(pre+evo[a:b]+modeset[c:d]+post) - subprocess.run(['cc','-std=c99','-Wall','-Wextra','-Werror', - '-Wno-unused-parameter','-Wno-unused-variable', - '-fsanitize=address,undefined',str(source),'-o',str(exe)],check=True) - subprocess.run([str(exe)],check=True) +evo = (root / 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() +tracking_start = evo.index('static void EvoTrackPhysicalWindowChangeCA(') +tracking_end = evo.index('static void EvoSetWindowOwnerCA(', tracking_start) +modeset = (root / 'src/nvidia-modeset/src/nvkms-modeset.c').read_text() +update_start = modeset.index('static void\nKickoffProposedModeSetHwState(') +update_end = modeset.index('static void AllocatePostModesetDispBandwidth(', update_start) +fixtures = Path(__file__).with_name('fixtures') +preamble = (fixtures / 'sequencing-model.h').read_text() +scenarios = (fixtures / 'phywin-sequencing.c').read_text() + +with tempfile.TemporaryDirectory() as directory: + source = Path(directory) / 'test.c' + binary = Path(directory) / 'test' + source.write_text(preamble + evo[tracking_start:tracking_end] + + modeset[update_start:update_end] + scenarios) + subprocess.run([ + 'cc', '-std=c99', '-Wall', '-Wextra', '-Werror', + '-Wno-unused-parameter', '-Wno-unused-variable', + '-fsanitize=address,undefined', str(source), '-o', str(binary), + ], check=True) + subprocess.run([str(binary)], check=True) diff --git a/tests/phywin-stress.py b/tests/phywin-stress.py index c1192b7575..c369438993 100644 --- a/tests/phywin-stress.py +++ b/tests/phywin-stress.py @@ -5,186 +5,17 @@ It tests software allocation only, not RM acceptance or hardware execution. """ from pathlib import Path -import ast import subprocess import tempfile root = Path(__file__).resolve().parents[1] -module = ast.parse((root / 'tests/phywin-allocation.py').read_text()) -preamble = next( - ast.literal_eval(node.value) - for node in module.body - if isinstance(node, ast.Assign) - and any(isinstance(target, ast.Name) and target.id == 'preamble' - for target in node.targets) -) +preamble = (Path(__file__).with_name('fixtures') / 'allocation-model.h').read_text() # Exercise the API's eight-layer array limit as well as two-layer desktops. preamble = preamble.replace('[2]', '[8]') source = (root / 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() start = source.index('static NvBool RequiredScalerTiles(') end = source.index('static void\nEvoIsModePossibleCA(', start) -scenarios = r''' -static NvBool TestAllocation( - NvU32 windowCount, - NvBool reuse, - NvU32 scalerTiles, - NvU32 scalingMask, - NvU32 overlayMask, - NvU32 tileChoices) -{ - NVDevEvoRec dev = { 0 }; - NVDispEvoRec disp = { .pDevEvo = &dev }; - NVEvoIsModePossibleDispInput input = { 0 }; - NVEvoIsModePossibleDispOutput output = { 0 }; - NVHwModeTimingsEvo timings[4] = { 0 }; - struct NvKmsUsageBounds usage[4] = { 0 }; - NvU32 required[4]; - NvU32 totalTiles = 0; - NvU32 totalWindows = 0; - NvU32 requiredScalerTiles = 0; - NvU32 assignedTiles = 0; - NvU32 assignedWindows = 0; - - dev.numHeads = 4; - dev.numHwTiles = 8; - dev.numHwPhywins = windowCount; - for (NvU32 tile = 0; tile < 8; tile++) { - dev.gpus[0].capabilities.hwTile[tile].type = - tile < scalerTiles ? NV_EVO_HW_TILE_TYPE_0 : - NV_EVO_HW_TILE_TYPE_1; - } - for (NvU32 head = 0; head < 4; head++) { - dev.head[head].numLayers = 2; - input.head[head].pTimings = &timings[head]; - input.head[head].pUsage = &usage[head]; - input.head[head].modesetRequested = TRUE; - usage[head].layer[0].usable = TRUE; - usage[head].layer[1].usable = !!(overlayMask & NVBIT(head)); - timings[head].viewPort.in.width = 1920; - timings[head].viewPort.out.width = - (scalingMask & NVBIT(head)) ? 1280 : 1920; - required[head] = 1 + ((tileChoices >> (2 * head)) & 3); - if (reuse) { - input.head[head].multiTileConfig.tilesMask = NVBIT(head); - input.head[head].multiTileConfig.phywinsMask[0] = - NVBIT(windowCount - 8 + 2 * head); - input.head[head].multiTileConfig.phywinsMask[1] = - NVBIT(windowCount - 8 + 2 * head + 1); - } - if (scalingMask & NVBIT(head)) { - requiredScalerTiles += required[head]; - } - totalTiles += required[head]; - totalWindows += required[head] * - (1 + usage[head].layer[1].usable); - } - - const NVEvoIsModePossibleDispInput originalInput = input; - NvBool result = EvoAssignHwHeadMultiTileConfigDispOutputCA( - &disp, &input, required, &output); - assert(memcmp(&input, &originalInput, sizeof(input)) == 0); - NvBool expected = totalTiles <= 8 && totalWindows <= windowCount && - requiredScalerTiles <= scalerTiles; - if (result != expected) { - printf("FAIL: windows=%u reuse=%u scalerTiles=%u scaling=%u " - "overlays=%u tiles=%u " - "needTiles=%u needWindows=%u accepted=%d\n", - windowCount, reuse, scalerTiles, scalingMask, - overlayMask, tileChoices, - totalTiles, totalWindows, result); - return FALSE; - } - if (result) { - for (NvU32 head = 0; head < 4; head++) { - const NVHwHeadMultiTileConfigRec *pConfig = - &output.head[head].multiTileConfig; - assert(nvPopCount32(pConfig->tilesMask) == required[head]); - assert(!(pConfig->tilesMask & ~0xffU)); - assert(!(pConfig->tilesMask & assignedTiles)); - assignedTiles |= pConfig->tilesMask; - for (NvU32 layer = 0; layer < 2; layer++) { - NvU32 expectedCount = usage[head].layer[layer].usable ? - required[head] : 0; - assert(nvPopCount32(pConfig->phywinsMask[layer]) == - expectedCount); - assert(!(pConfig->phywinsMask[layer] & - ~(NvU32)(NVBIT64(windowCount) - 1))); - assert(!(pConfig->phywinsMask[layer] & assignedWindows)); - assignedWindows |= pConfig->phywinsMask[layer]; - } - } - } - return TRUE; -} - -static void TestBoundaryConditions(void) -{ - NVDevEvoRec dev = { .numHeads = 4, .numHwTiles = 8, .numHwPhywins = 32 }; - NVDispEvoRec disp = { .pDevEvo = &dev }; - NVEvoIsModePossibleDispInput input = { 0 }; - NVEvoIsModePossibleDispOutput output = { 0 }; - NVHwModeTimingsEvo timing = { 0 }; - struct NvKmsUsageBounds usage = { 0 }; - NvU32 required[4] = { 0 }; - - /* Inactive heads have NULL timing/usage pointers and need no resources. */ - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA( - &disp, &input, required, &output)); - - dev.head[0].numLayers = 8; - input.head[0].pTimings = &timing; - input.head[0].pUsage = &usage; - input.head[0].modesetRequested = TRUE; - input.head[0].multiTileConfig.tilesMask = 1; - input.head[0].multiTileConfig.phywinsMask[0] = NVBIT(31); - required[0] = 1; - for (NvU32 layer = 0; layer < 8; layer++) { - usage.layer[layer].usable = TRUE; - } - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA( - &disp, &input, required, &output)); - assert(output.head[0].multiTileConfig.phywinsMask[0] == NVBIT(31)); - for (NvU32 layer = 0; layer < 8; layer++) { - assert(nvPopCount32(output.head[0].multiTileConfig.phywinsMask[layer]) == 1); - } - - /* Failed helper allocation must not publish its partial result. */ - NVHwHeadMultiTileConfigRec config = { .tilesMask = 3 }; - const NVHwHeadMultiTileConfigRec originalConfig = config; - NvU32 freeWindows = 1; - assert(!AssignNewPhywinsIfNeeded(&disp, &config, 0, &usage, &freeWindows)); - assert(memcmp(&config, &originalConfig, sizeof(config)) == 0); - assert(freeWindows == 1); - puts("PASS: inactive heads, eight layers, bit 31, and allocation failure isolation"); -} - -int main(void) -{ - unsigned cases = 0; - - TestBoundaryConditions(); - - for (NvU32 windowCount = 8; windowCount <= 32; windowCount += 24) { - for (NvU32 reuse = 0; reuse < 2; reuse++) { - for (NvU32 scalerTiles = 0; scalerTiles <= 8; scalerTiles++) { - for (NvU32 scalingMask = 0; scalingMask < 16; scalingMask++) { - for (NvU32 overlayMask = 0; overlayMask < 16; overlayMask++) { - for (NvU32 tileChoices = 0; tileChoices < 256; - tileChoices++) { - assert(TestAllocation(windowCount, reuse, - scalerTiles, scalingMask, - overlayMask, tileChoices)); - cases++; - } - } - } - } - } - } - printf("PASS: %u allocation combinations\n", cases); - return 0; -} -''' +scenarios = (Path(__file__).with_name('fixtures') / 'phywin-stress.c').read_text() with tempfile.TemporaryDirectory() as directory: test = Path(directory) / 'test.c' binary = Path(directory) / 'test' diff --git a/tests/window-ownership.py b/tests/window-ownership.py index 192a9b26f4..c78b5a23ab 100644 --- a/tests/window-ownership.py +++ b/tests/window-ownership.py @@ -12,260 +12,8 @@ evo = (root / 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() start = evo.index('static void EvoTrackPhysicalWindowChangeCA(') end = evo.index('\nNVEvoHAL nvEvoC9 =', start) -pre = r''' -#include -#include -#include -#include - -#include "nvtypes.h" -#include "nvmisc.h" -#include "class/clca7d.h" - -#define nvDoDebugLogging() 0 -#define nvEvoLogDev(...) ((void)0) -#define TRUE 1 -#define FALSE 0 -#define nvAssert(x) assert(x) -#define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) -#define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) - -typedef struct { - NvU32 channelMask; -} NVEvoChannel; - -typedef struct { - NvU32 noCoreInterlockMask; -} NVEvoUpdateState; - -typedef struct { - NvBool windowMappingChanged; - NVEvoUpdateState updateState; -} NVEvoModesetUpdateState; - -typedef struct { - void *pCoreDma; -} TestSubDevice; - -typedef struct { - NVEvoChannel *core; - TestSubDevice *pSubDevices[1]; - struct { - NvU32 numLayers; - NVEvoChannel *layer[2]; - } head[4]; -} NVDevEvoRec; - -typedef struct { - NVDevEvoRec *pDevEvo; - NvU32 displayOwner; -} NVDispEvoRec; - -typedef struct { - NvU32 tilesMask; - NvU32 phywinsMask[2]; -} NVHwHeadMultiTileConfigRec; - -typedef struct { - int unused; -} NVHwModeTimingsEvo; - -typedef struct { - int unused; -} NVDscInfoEvoRec; - -static NvU32 stateCache[65536]; -static NvU32 pendingMethods[65536]; -static NvU32 currentMethod; - -static void nvUpdateUpdateState( - NVDevEvoRec *pDevEvo, - NVEvoUpdateState *pUpdateState, - NVEvoChannel *pChannel) -{ - (void)pDevEvo; - (void)pUpdateState; - (void)pChannel; -} - -static void nvDisableCoreInterlockUpdateState( - NVDevEvoRec *pDevEvo, - NVEvoUpdateState *pUpdateState, - const NVEvoChannel *pChannel) -{ - (void)pDevEvo; - pUpdateState->noCoreInterlockMask |= 1U << pChannel->channelMask; -} - -static NvU32 nvDmaLoadPioMethod(const void *pCoreDma, NvU32 method) -{ - (void)pCoreDma; - assert(method < 65536); - return stateCache[method]; -} - -static void nvDmaSetStartEvoMethod( - NVEvoChannel *pChannel, - NvU32 method, - NvU32 count) -{ - (void)pChannel; - assert(count == 1 && method < 65536); - currentMethod = method; -} - -static void nvDmaSetEvoMethodData(NVEvoChannel *pChannel, NvU32 value) -{ - (void)pChannel; - pendingMethods[currentMethod] = value; -} - -static void SetTileSize( - NVEvoChannel *pChannel, - const NVHwModeTimingsEvo *pTimings, - const NVDscInfoEvoRec *pDscInfo, - const NVHwHeadMultiTileConfigRec *pConfig) -{ - (void)pChannel; - (void)pTimings; - (void)pDscInfo; - (void)pConfig; -} - -static NvU32 GetPendingWindowOwner(NvU32 window) -{ - return DRF_VAL(CA7D, _WINDOW_SET_CONTROL, _OWNER, - pendingMethods[NVCA7D_WINDOW_SET_CONTROL(window)]); -} - -/* Model completed method execution; this does not simulate hardware timing. */ -static void CommitPendingMethods(void) -{ - memcpy(stateCache, pendingMethods, sizeof(stateCache)); -} -''' -post = r''' -int main(void) -{ - NVEvoChannel core = { 0 }; - NVEvoChannel base = { 2 }; - NVEvoChannel overlay = { 3 }; - TestSubDevice subDevice = { stateCache }; - NVDevEvoRec dev = { - .core = &core, - .pSubDevices = { &subDevice }, - }; - NVDispEvoRec disp = { &dev, 0 }; - NVEvoModesetUpdateState update = { 0 }; - NVHwModeTimingsEvo timing = { 0 }; - NVDscInfoEvoRec dsc = { 0 }; - NVHwHeadMultiTileConfigRec config = { - .tilesMask = 3, - .phywinsMask = { 3, 0 }, - }; - - dev.head[1].numLayers = 2; - dev.head[1].layer[0] = &base; - dev.head[1].layer[1] = &overlay; - - /* Simulate stock window owners, including the unrelated HIDE control bit. */ - stateCache[NVCA7D_WINDOW_SET_CONTROL(2)] = 1; - stateCache[NVCA7D_WINDOW_SET_CONTROL(3)] = 0x101; - stateCache[NVCA7D_WINDOW_SET_PHYSICAL(2)] = 1; - stateCache[NVCA7D_WINDOW_SET_PHYSICAL(3)] = 2; - memcpy(pendingMethods, stateCache, sizeof(stateCache)); - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(GetPendingWindowOwner(2) == 1); - assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); - assert(pendingMethods[NVCA7D_WINDOW_SET_CONTROL(3)] == 0x10f); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(2)] == 3); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 0); - assert(update.windowMappingChanged); - assert(update.updateState.noCoreInterlockMask == 12); - puts("PASS: unused overlay is unowned with no physical windows; " - "base remains bound"); - - /* Repeating the same configuration must not introduce an interlock change. */ - CommitPendingMethods(); - memset(&update, 0, sizeof(update)); - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(!update.windowMappingChanged); - assert(!update.updateState.noCoreInterlockMask); - puts("PASS: unchanged allocation/ownership introduces no extra interlock " - "changes"); - - /* Restoring overlay resources also restores its original hardware owner. */ - config.tilesMask = 1; - config.phywinsMask[0] = 1; - config.phywinsMask[1] = 2; - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(GetPendingWindowOwner(3) == 1); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 2); - assert(update.windowMappingChanged); - assert(update.updateState.noCoreInterlockMask & 8); - puts("PASS: re-enabled overlay regains its owner and physical assignment"); - - /* Shutdown releases physical windows while leaving owners unchanged. */ - CommitPendingMethods(); - memset(&update, 0, sizeof(update)); - memset(&config, 0, sizeof(config)); - - EvoSetMultiTileConfigCA(&disp, 1, NULL, NULL, &config, &update); - - assert(GetPendingWindowOwner(2) == 1); - assert(GetPendingWindowOwner(3) == 1); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(2)] == 0); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 0); - puts("PASS: shutdown clears physical assignments without changing " - "ownership in that update"); - - /* Only the following activation update removes the unused window owner. */ - CommitPendingMethods(); - memset(&update, 0, sizeof(update)); - config.tilesMask = 3; - config.phywinsMask[0] = 3; - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(GetPendingWindowOwner(2) == 1); - assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); - assert(update.windowMappingChanged); - assert(update.updateState.noCoreInterlockMask & 8); - puts("PASS: subsequent activation unbinds the unused overlay with a " - "non-interlocked update"); - - /* - * Initialization can queue a default binding before this helper runs. - * PIO still describes the previous unowned state. The final owner write - * must override the queued binding, even though PIO matches our target. - */ - stateCache[NVCA7D_WINDOW_SET_CONTROL(2)] = 1; - stateCache[NVCA7D_WINDOW_SET_CONTROL(3)] = - NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE; - stateCache[NVCA7D_WINDOW_SET_PHYSICAL(2)] = 3; - stateCache[NVCA7D_WINDOW_SET_PHYSICAL(3)] = 0; - memcpy(pendingMethods, stateCache, sizeof(stateCache)); - pendingMethods[NVCA7D_WINDOW_SET_CONTROL(3)] = 1; - memset(&update, 0, sizeof(update)); - update.windowMappingChanged = TRUE; - update.updateState.noCoreInterlockMask = 8; - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); - assert(update.windowMappingChanged); - assert(update.updateState.noCoreInterlockMask & 8); - puts("PASS: an unused overlay overrides a queued initialization binding"); - - return 0; -} -''' +pre = (Path(__file__).with_name('fixtures') / 'ownership-model.h').read_text() +post = (Path(__file__).with_name('fixtures') / 'window-ownership.c').read_text() with tempfile.TemporaryDirectory() as tmp: c = Path(tmp) / 'test.c' exe = Path(tmp) / 'test' From 7b09f2b73b06d8c974e8eadd3c74f0da77ffd945 Mon Sep 17 00:00:00 2001 From: Zac Lanzon Date: Sat, 5 Sep 2026 00:28:53 -0400 Subject: [PATCH 5/6] Keep standalone regression harness outside the upstream fix --- tests/README.md | 17 --- tests/fixtures/allocation-model.h | 85 -------------- tests/fixtures/ordinary-configurations.c | 142 ----------------------- tests/fixtures/ownership-model.h | 120 ------------------- tests/fixtures/phywin-allocation.c | 106 ----------------- tests/fixtures/phywin-sequencing.c | 33 ------ tests/fixtures/phywin-stress.c | 140 ---------------------- tests/fixtures/sequencing-model.h | 76 ------------ tests/fixtures/window-ownership.c | 119 ------------------- tests/ordinary-configurations.py | 43 ------- tests/phywin-allocation.py | 23 ---- tests/phywin-sequencing.py | 28 ----- tests/phywin-stress.py | 27 ----- tests/window-ownership.py | 25 ---- 14 files changed, 984 deletions(-) delete mode 100644 tests/README.md delete mode 100644 tests/fixtures/allocation-model.h delete mode 100644 tests/fixtures/ordinary-configurations.c delete mode 100644 tests/fixtures/ownership-model.h delete mode 100644 tests/fixtures/phywin-allocation.c delete mode 100644 tests/fixtures/phywin-sequencing.c delete mode 100644 tests/fixtures/phywin-stress.c delete mode 100644 tests/fixtures/sequencing-model.h delete mode 100644 tests/fixtures/window-ownership.c delete mode 100644 tests/ordinary-configurations.py delete mode 100644 tests/phywin-allocation.py delete mode 100644 tests/phywin-sequencing.py delete mode 100644 tests/phywin-stress.py delete mode 100644 tests/window-ownership.py diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index 200bb92e71..0000000000 --- a/tests/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Modeset regression tests - -Run these scripts from a checkout with Python 3 and a C compiler supporting AddressSanitizer and UndefinedBehaviorSanitizer: - -```sh -ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/phywin-allocation.py -ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/phywin-sequencing.py -ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/window-ownership.py -ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/phywin-stress.py -ASAN_OPTIONS=detect_leaks=0 UBSAN_OPTIONS=halt_on_error=1 python3 tests/ordinary-configurations.py -``` - -The comparison requires base commit `e4a5faa2567f28c8eabe0ebb6422b6d0abcf37eb` in the local Git object database. - -The Python runners extract production functions from the checkout and compile them between the C model headers and scenario files in `fixtures/`. The fixtures are parts of generated translation units, not independently built programs. Keeping the C in separate files makes its types, assertions, and method recording directly reviewable without embedding source in Python strings. The stress and comparison runners share `allocation-model.h`. - -These tests cover a simplified allocator and hardware-method model. They do not load a driver, change displays, run RM, or establish hardware timing and firmware behavior. diff --git a/tests/fixtures/allocation-model.h b/tests/fixtures/allocation-model.h deleted file mode 100644 index 1ee020636a..0000000000 --- a/tests/fixtures/allocation-model.h +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include -#include -typedef uint32_t NvU32; -typedef int NvBool; -#define TRUE 1 -#define FALSE 0 -#define NVKMS_MAX_HEADS_PER_DISP 4 -#define NV_YUV420_MODE_HW 1 -#define NVBIT(x) (1U << (x)) -#define NVBIT64(x) (UINT64_C(1) << (x)) -#define ARRAY_LEN(x) (sizeof(x) / sizeof((x)[0])) -#define nvAssert(x) assert(x) -#define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) -#define FOR_EACH_INDEX_IN_MASK(bits, i, mask) \ - for ((i) = 0; (i) < (bits); (i)++) \ - if ((mask) & NVBIT(i)) -#define FOR_EACH_INDEX_IN_MASK_END -typedef enum { - NV_EVO_HW_TILE_TYPE_0, - NV_EVO_HW_TILE_TYPE_1, - NV_EVO_HW_TILE_TYPE_FIRST = 0, - NV_EVO_HW_TILE_TYPE_LAST = 1 -} NVEvoHwTileType; -typedef struct { - NvU32 tilesMask, phywinsMask[2]; -} NVHwHeadMultiTileConfigRec; -struct NvKmsUsageBounds { - struct { - NvBool usable; - } layer[2]; -}; -typedef struct { - struct { - struct { - NvU32 width, height; - } in, out; - } viewPort; - NvU32 yuv420Mode; -} NVHwModeTimingsEvo; -typedef struct { - struct { - NVEvoHwTileType type; - } hwTile[8]; -} NVEvoCapabilities; -typedef struct { - NvU32 numHeads, numHwTiles, numHwPhywins; - struct { - NvU32 numLayers; - } head[4]; - struct { - NVEvoCapabilities capabilities; - } gpus[1]; -} NVDevEvoRec; -typedef struct { - NvU32 hwHeadsMask; -} NVDispApiHeadStateEvoRec; -typedef struct { - NVHwHeadMultiTileConfigRec multiTileConfig; -} TestHead; -typedef struct { - NVDevEvoRec *pDevEvo; - NvU32 displayOwner; - NVDispApiHeadStateEvoRec apiHeadState[4]; - TestHead headState[4]; -} NVDispEvoRec; -typedef NVDispEvoRec *NVDispEvoPtr; -typedef struct { - TestHead head[4]; -} NVProposedModeSetHwStateOneDisp; -#define FOR_EACH_EVO_HW_HEAD_IN_MASK(mask, head) FOR_EACH_INDEX_IN_MASK(32, head, mask) -typedef struct { - struct { - NVHwModeTimingsEvo *pTimings; - struct NvKmsUsageBounds *pUsage; - NvBool modesetRequested; - NVHwHeadMultiTileConfigRec multiTileConfig; - } head[4]; -} NVEvoIsModePossibleDispInput; -typedef struct { - struct { - NVHwHeadMultiTileConfigRec multiTileConfig; - } head[4]; -} NVEvoIsModePossibleDispOutput; diff --git a/tests/fixtures/ordinary-configurations.c b/tests/fixtures/ordinary-configurations.c deleted file mode 100644 index 2e1df1a65c..0000000000 --- a/tests/fixtures/ordinary-configurations.c +++ /dev/null @@ -1,142 +0,0 @@ -static NvBool CompleteAllocation(const NVEvoIsModePossibleDispOutput *pOutput, NvU32 heads, - NvU32 layers, const NvU32 required[4]) -{ - NvU32 usedTiles = 0; - NvU32 usedWindows = 0; - - for (NvU32 head = 0; head < heads; head++) { - const NVHwHeadMultiTileConfigRec *pConfig = &pOutput->head[head].multiTileConfig; - if (nvPopCount32(pConfig->tilesMask) != required[head] || - (pConfig->tilesMask & (usedTiles | ~0xffU))) { - return FALSE; - } - usedTiles |= pConfig->tilesMask; - for (NvU32 layer = 0; layer < layers; layer++) { - NvU32 mask = pConfig->phywinsMask[layer]; - if (nvPopCount32(mask) != required[head] || (mask & (usedWindows | ~0xffU))) { - return FALSE; - } - usedWindows |= mask; - } - } - return TRUE; -} - -static void SameAllocation(const NVEvoIsModePossibleDispOutput *pReference, - const NVEvoIsModePossibleDispOutput *pActual, NvU32 heads, NvU32 layers) -{ - for (NvU32 head = 0; head < heads; head++) { - assert(pReference->head[head].multiTileConfig.tilesMask == - pActual->head[head].multiTileConfig.tilesMask); - for (NvU32 layer = 0; layer < layers; layer++) { - assert(pReference->head[head].multiTileConfig.phywinsMask[layer] == - pActual->head[head].multiTileConfig.phywinsMask[layer]); - } - } -} - -static unsigned identical; -static unsigned rejected; -static unsigned corrected; -static unsigned unchanged; - -static void TestConfiguration(NvU32 heads, NvU32 layers, NvU32 scalerTiles, NvU32 scalingMask, - NvU32 tileChoices, NvBool reuse) -{ - NVDevEvoRec dev = {.numHeads = heads, .numHwTiles = 8, .numHwPhywins = 8}; - NVDispEvoRec disp = {.pDevEvo = &dev}; - NVEvoIsModePossibleDispInput input = {0}; - NVEvoIsModePossibleDispOutput reference = {0}; - NVEvoIsModePossibleDispOutput actual = {0}; - NVHwModeTimingsEvo timings[4] = {0}; - struct NvKmsUsageBounds usage[4] = {0}; - NvU32 required[4] = {0}; - NvU32 requiredTiles = 0; - NvU32 requiredScalerTiles = 0; - - for (NvU32 tile = 0; tile < 8; tile++) { - dev.gpus[0].capabilities.hwTile[tile].type = - tile < scalerTiles ? NV_EVO_HW_TILE_TYPE_0 : NV_EVO_HW_TILE_TYPE_1; - } - for (NvU32 head = 0; head < heads; head++) { - dev.head[head].numLayers = layers; - input.head[head].pTimings = &timings[head]; - input.head[head].pUsage = &usage[head]; - input.head[head].modesetRequested = TRUE; - timings[head].viewPort.in.width = 1920; - timings[head].viewPort.out.width = scalingMask & NVBIT(head) ? 1280 : 1920; - required[head] = 1 + ((tileChoices >> (2 * head)) & 3); - requiredTiles += required[head]; - if (scalingMask & NVBIT(head)) { - requiredScalerTiles += required[head]; - } - if (reuse) { - input.head[head].multiTileConfig.tilesMask = NVBIT(head); - } - for (NvU32 layer = 0; layer < layers; layer++) { - usage[head].layer[layer].usable = TRUE; - if (reuse) { - input.head[head].multiTileConfig.phywinsMask[layer] = NVBIT(head * layers + layer); - } - } - } - if (requiredTiles * layers > 8) { - return; - } - NvBool expected = requiredScalerTiles <= scalerTiles; - NvBool referenceResult = - ReferenceEvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &reference); - NvBool actualResult = - EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &actual); - assert(actualResult == expected); - if (actualResult) { - assert(CompleteAllocation(&actual, heads, layers, required)); - } - if (referenceResult && !CompleteAllocation(&reference, heads, layers, required)) { - corrected++; - return; - } - assert(referenceResult == actualResult); - if (!actualResult) { - rejected++; - return; - } - SameAllocation(&reference, &actual, heads, layers); - identical++; - - /* Replay with every subset of heads marked unchanged. */ - for (NvU32 head = 0; head < heads; head++) { - input.head[head].multiTileConfig = actual.head[head].multiTileConfig; - } - for (NvU32 changedMask = 0; changedMask < NVBIT(heads); changedMask++) { - for (NvU32 head = 0; head < heads; head++) { - input.head[head].modesetRequested = !!(changedMask & NVBIT(head)); - } - assert(ReferenceEvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, - &reference)); - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &actual)); - SameAllocation(&reference, &actual, heads, layers); - unchanged++; - } -} - -int main(void) -{ - for (NvU32 heads = 1; heads <= 4; heads++) { - for (NvU32 layers = 1; layers <= 2; layers++) { - for (NvU32 scalerTiles = 0; scalerTiles <= 8; scalerTiles++) { - for (NvU32 scalingMask = 0; scalingMask < NVBIT(heads); scalingMask++) { - for (NvU32 tiles = 0; tiles < NVBIT(2 * heads); tiles++) { - TestConfiguration(heads, layers, scalerTiles, scalingMask, tiles, FALSE); - TestConfiguration(heads, layers, scalerTiles, scalingMask, tiles, TRUE); - } - } - } - } - } - printf("PASS: %u complete reference allocations exactly match\n", identical); - printf("PASS: %u unchanged-head replays exactly match\n", unchanged); - printf("PASS: %u impossible configurations rejected by both\n", rejected); - printf("PASS: %u incomplete upstream results handled by the retry fix\n", corrected); - return 0; -} diff --git a/tests/fixtures/ownership-model.h b/tests/fixtures/ownership-model.h deleted file mode 100644 index a0c6133b93..0000000000 --- a/tests/fixtures/ownership-model.h +++ /dev/null @@ -1,120 +0,0 @@ -#include -#include -#include -#include - -#include "nvtypes.h" -#include "nvmisc.h" -#include "class/clca7d.h" - -#define nvDoDebugLogging() 0 -#define nvEvoLogDev(...) ((void)0) -#define TRUE 1 -#define FALSE 0 -#define nvAssert(x) assert(x) -#define nvPopCount32(x) ((NvU32)__builtin_popcount(x)) -#define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) - -typedef struct { - NvU32 channelMask; -} NVEvoChannel; - -typedef struct { - NvU32 noCoreInterlockMask; -} NVEvoUpdateState; - -typedef struct { - NvBool windowMappingChanged; - NVEvoUpdateState updateState; -} NVEvoModesetUpdateState; - -typedef struct { - void *pCoreDma; -} TestSubDevice; - -typedef struct { - NVEvoChannel *core; - TestSubDevice *pSubDevices[1]; - struct { - NvU32 numLayers; - NVEvoChannel *layer[2]; - } head[4]; -} NVDevEvoRec; - -typedef struct { - NVDevEvoRec *pDevEvo; - NvU32 displayOwner; -} NVDispEvoRec; - -typedef struct { - NvU32 tilesMask; - NvU32 phywinsMask[2]; -} NVHwHeadMultiTileConfigRec; - -typedef struct { - int unused; -} NVHwModeTimingsEvo; - -typedef struct { - int unused; -} NVDscInfoEvoRec; - -static NvU32 stateCache[65536]; -static NvU32 pendingMethods[65536]; -static NvU32 currentMethod; - -static void nvUpdateUpdateState(NVDevEvoRec *pDevEvo, NVEvoUpdateState *pUpdateState, - NVEvoChannel *pChannel) -{ - (void)pDevEvo; - (void)pUpdateState; - (void)pChannel; -} - -static void nvDisableCoreInterlockUpdateState(NVDevEvoRec *pDevEvo, NVEvoUpdateState *pUpdateState, - const NVEvoChannel *pChannel) -{ - (void)pDevEvo; - pUpdateState->noCoreInterlockMask |= 1U << pChannel->channelMask; -} - -static NvU32 nvDmaLoadPioMethod(const void *pCoreDma, NvU32 method) -{ - (void)pCoreDma; - assert(method < 65536); - return stateCache[method]; -} - -static void nvDmaSetStartEvoMethod(NVEvoChannel *pChannel, NvU32 method, NvU32 count) -{ - (void)pChannel; - assert(count == 1 && method < 65536); - currentMethod = method; -} - -static void nvDmaSetEvoMethodData(NVEvoChannel *pChannel, NvU32 value) -{ - (void)pChannel; - pendingMethods[currentMethod] = value; -} - -static void SetTileSize(NVEvoChannel *pChannel, const NVHwModeTimingsEvo *pTimings, - const NVDscInfoEvoRec *pDscInfo, const NVHwHeadMultiTileConfigRec *pConfig) -{ - (void)pChannel; - (void)pTimings; - (void)pDscInfo; - (void)pConfig; -} - -static NvU32 GetPendingWindowOwner(NvU32 window) -{ - return DRF_VAL(CA7D, _WINDOW_SET_CONTROL, _OWNER, - pendingMethods[NVCA7D_WINDOW_SET_CONTROL(window)]); -} - -/* Model completed method execution; this does not simulate hardware timing. */ -static void CommitPendingMethods(void) -{ - memcpy(stateCache, pendingMethods, sizeof(stateCache)); -} diff --git a/tests/fixtures/phywin-allocation.c b/tests/fixtures/phywin-allocation.c deleted file mode 100644 index a5e933ecfe..0000000000 --- a/tests/fixtures/phywin-allocation.c +++ /dev/null @@ -1,106 +0,0 @@ -static NVDevEvoRec dev; -static NVDispEvoRec disp; -static NVEvoIsModePossibleDispInput input; -static NVHwModeTimingsEvo timings[4]; -static struct NvKmsUsageBounds usage[4]; -static void setup(void) -{ - memset(&dev, 0, sizeof(dev)); - memset(&input, 0, sizeof(input)); - memset(usage, 0, sizeof(usage)); - dev.numHeads = 4; - dev.numHwTiles = 8; - dev.numHwPhywins = 8; - disp.pDevEvo = &dev; - disp.displayOwner = 0; - for (NvU32 h = 0; h < 4; h++) { - dev.head[h].numLayers = 2; - input.head[h].pTimings = &timings[h]; - input.head[h].pUsage = &usage[h]; - input.head[h].modesetRequested = TRUE; - input.head[h].multiTileConfig.tilesMask = NVBIT(7 - h); - input.head[h].multiTileConfig.phywinsMask[0] = NVBIT(2 * h); - input.head[h].multiTileConfig.phywinsMask[1] = NVBIT(2 * h + 1); - usage[h].layer[0].usable = TRUE; - } -} -static void checkUnique(NVEvoIsModePossibleDispOutput *out) -{ - NvU32 tiles = 0, windows = 0; - for (NvU32 h = 0; h < 4; h++) { - NVHwHeadMultiTileConfigRec *c = &out->head[h].multiTileConfig; - assert(!(tiles & c->tilesMask)); - tiles |= c->tilesMask; - for (NvU32 l = 0; l < 2; l++) { - assert(!(windows & c->phywinsMask[l])); - windows |= c->phywinsMask[l]; - if (usage[h].layer[l].usable) - assert(nvPopCount32(c->phywinsMask[l]) == nvPopCount32(c->tilesMask)); - else - assert(c->phywinsMask[l] == 0); - } - } -} -int main(void) -{ - NvU32 required[4] = {1, 1, 2, 2}; - NVEvoIsModePossibleDispOutput out = {0}; - setup(); - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); - checkUnique(&out); - puts("PASS: four heads, six tiles, unused overlays released"); - setup(); - for (NvU32 h = 0; h < 4; h++) - usage[h].layer[1].usable = TRUE; - assert(!EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); - puts("PASS: twelve required physical windows still rejected"); - setup(); - for (NvU32 h = 0; h < 4; h++) { - required[h] = 1; - input.head[h].multiTileConfig.phywinsMask[1] = 0; - } - usage[0].layer[1].usable = TRUE; - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); - checkUnique(&out); - puts("PASS: enabling a layer allocates windows without changing tile count"); - setup(); - usage[0].layer[1].usable = TRUE; - input.head[0].modesetRequested = FALSE; - required[2] = 2; - required[3] = 2; - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); - assert(!memcmp(&out.head[0].multiTileConfig, &input.head[0].multiTileConfig, - sizeof(NVHwHeadMultiTileConfigRec))); - checkUnique(&out); - puts("PASS: unchanged head retains its assignments"); - input.head[0].multiTileConfig.phywinsMask[1] = 0; - assert(!EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &out)); - puts("PASS: unchanged head cannot enable an unassigned overlay without a modeset"); - - NVProposedModeSetHwStateOneDisp proposed = {0}; - memset(disp.headState, 0, sizeof(disp.headState)); - disp.apiHeadState[0].hwHeadsMask = 1; - disp.headState[0].multiTileConfig.phywinsMask[1] = 2; - proposed.head[0].multiTileConfig.phywinsMask[1] = 2; - assert(!IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); - proposed.head[0].multiTileConfig.phywinsMask[1] = 0; - proposed.head[0].multiTileConfig.phywinsMask[0] = 2; - assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); - proposed.head[0].multiTileConfig.phywinsMask[0] = 0; - proposed.head[1].multiTileConfig.phywinsMask[0] = 2; - assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); - puts("PASS: window ownership changes require shutdown, retained ownership does not"); - memset(disp.headState, 0, sizeof(disp.headState)); - memset(&proposed, 0, sizeof(proposed)); - disp.headState[0].multiTileConfig.phywinsMask[0] = 1; - proposed.head[0].multiTileConfig.phywinsMask[0] = 1; - proposed.head[0].multiTileConfig.phywinsMask[1] = 2; - assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); - disp.headState[0].multiTileConfig.phywinsMask[1] = 2; - assert(!IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); - proposed.head[0].multiTileConfig.phywinsMask[1] = 0; - assert(IsCurrentMultiTileConfigOneApiHeadIncompatible(&disp, 0, &proposed)); - puts("PASS: binding or unbinding an overlay requires shutdown even without resource transfer"); - - return 0; -} diff --git a/tests/fixtures/phywin-sequencing.c b/tests/fixtures/phywin-sequencing.c deleted file mode 100644 index d0a39674e5..0000000000 --- a/tests/fixtures/phywin-sequencing.c +++ /dev/null @@ -1,33 +0,0 @@ -int main(void) -{ - Hal hal = {update}; - NVDevEvoRec dev = {1, &hal}; - Disp disp = {&dev}; - NVEvoChannel channel = {4}; - NVModeSetWorkArea work = {0}; - NVProposedModeSetHwState proposed = {0}; - NVProposedModeSetHwStateOneDisp one = {0}; - EvoTrackPhysicalWindowChangeCA(&dev, &channel, 1, 1, &work.modesetUpdateState); - assert(!work.modesetUpdateState.windowMappingChanged); - assert(!work.modesetUpdateState.updateState.noCoreInterlockMask); - EvoTrackPhysicalWindowChangeCA(&dev, &channel, 1, 0, &work.modesetUpdateState); - assert(work.modesetUpdateState.windowMappingChanged); - assert(work.modesetUpdateState.updateState.noCoreInterlockMask == 4); - puts("PASS: changed physical assignment disables its core interlock; unchanged is untouched"); - memset(&work, 0, sizeof(work)); - changeInPre = 1; - KickoffProposedModeSetHwState(&disp, &proposed, &one, FALSE, &work); - assert(n == 3 && memcmp(events, "CFU", 3) == 0); - puts("PASS: assignment changed during PreUpdate -> core update, then flip update"); - n = 0; - changeInPre = 0; - memset(&work, 0, sizeof(work)); - KickoffProposedModeSetHwState(&disp, &proposed, &one, FALSE, &work); - assert(n == 2 && memcmp(events, "FC", 2) == 0); - puts("PASS: unchanged assignment retains combined flip/core update"); - n = 0; - work.modesetUpdateState.windowMappingChanged = TRUE; - KickoffProposedModeSetHwState(&disp, &proposed, &one, FALSE, &work); - assert(n == 3 && memcmp(events, "CFU", 3) == 0); - puts("PASS: existing logical mapping change still decouples updates"); -} diff --git a/tests/fixtures/phywin-stress.c b/tests/fixtures/phywin-stress.c deleted file mode 100644 index 6af3aa29bd..0000000000 --- a/tests/fixtures/phywin-stress.c +++ /dev/null @@ -1,140 +0,0 @@ -static NvBool TestAllocation(NvU32 windowCount, NvBool reuse, NvU32 scalerTiles, NvU32 scalingMask, - NvU32 overlayMask, NvU32 tileChoices) -{ - NVDevEvoRec dev = {0}; - NVDispEvoRec disp = {.pDevEvo = &dev}; - NVEvoIsModePossibleDispInput input = {0}; - NVEvoIsModePossibleDispOutput output = {0}; - NVHwModeTimingsEvo timings[4] = {0}; - struct NvKmsUsageBounds usage[4] = {0}; - NvU32 required[4]; - NvU32 totalTiles = 0; - NvU32 totalWindows = 0; - NvU32 requiredScalerTiles = 0; - NvU32 assignedTiles = 0; - NvU32 assignedWindows = 0; - - dev.numHeads = 4; - dev.numHwTiles = 8; - dev.numHwPhywins = windowCount; - for (NvU32 tile = 0; tile < 8; tile++) { - dev.gpus[0].capabilities.hwTile[tile].type = - tile < scalerTiles ? NV_EVO_HW_TILE_TYPE_0 : NV_EVO_HW_TILE_TYPE_1; - } - for (NvU32 head = 0; head < 4; head++) { - dev.head[head].numLayers = 2; - input.head[head].pTimings = &timings[head]; - input.head[head].pUsage = &usage[head]; - input.head[head].modesetRequested = TRUE; - usage[head].layer[0].usable = TRUE; - usage[head].layer[1].usable = !!(overlayMask & NVBIT(head)); - timings[head].viewPort.in.width = 1920; - timings[head].viewPort.out.width = (scalingMask & NVBIT(head)) ? 1280 : 1920; - required[head] = 1 + ((tileChoices >> (2 * head)) & 3); - if (reuse) { - input.head[head].multiTileConfig.tilesMask = NVBIT(head); - input.head[head].multiTileConfig.phywinsMask[0] = NVBIT(windowCount - 8 + 2 * head); - input.head[head].multiTileConfig.phywinsMask[1] = NVBIT(windowCount - 8 + 2 * head + 1); - } - if (scalingMask & NVBIT(head)) { - requiredScalerTiles += required[head]; - } - totalTiles += required[head]; - totalWindows += required[head] * (1 + usage[head].layer[1].usable); - } - - const NVEvoIsModePossibleDispInput originalInput = input; - NvBool result = EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &output); - assert(memcmp(&input, &originalInput, sizeof(input)) == 0); - NvBool expected = - totalTiles <= 8 && totalWindows <= windowCount && requiredScalerTiles <= scalerTiles; - if (result != expected) { - printf("FAIL: windows=%u reuse=%u scalerTiles=%u scaling=%u " - "overlays=%u tiles=%u " - "needTiles=%u needWindows=%u accepted=%d\n", - windowCount, reuse, scalerTiles, scalingMask, overlayMask, tileChoices, totalTiles, - totalWindows, result); - return FALSE; - } - if (result) { - for (NvU32 head = 0; head < 4; head++) { - const NVHwHeadMultiTileConfigRec *pConfig = &output.head[head].multiTileConfig; - assert(nvPopCount32(pConfig->tilesMask) == required[head]); - assert(!(pConfig->tilesMask & ~0xffU)); - assert(!(pConfig->tilesMask & assignedTiles)); - assignedTiles |= pConfig->tilesMask; - for (NvU32 layer = 0; layer < 2; layer++) { - NvU32 expectedCount = usage[head].layer[layer].usable ? required[head] : 0; - assert(nvPopCount32(pConfig->phywinsMask[layer]) == expectedCount); - assert(!(pConfig->phywinsMask[layer] & ~(NvU32)(NVBIT64(windowCount) - 1))); - assert(!(pConfig->phywinsMask[layer] & assignedWindows)); - assignedWindows |= pConfig->phywinsMask[layer]; - } - } - } - return TRUE; -} - -static void TestBoundaryConditions(void) -{ - NVDevEvoRec dev = {.numHeads = 4, .numHwTiles = 8, .numHwPhywins = 32}; - NVDispEvoRec disp = {.pDevEvo = &dev}; - NVEvoIsModePossibleDispInput input = {0}; - NVEvoIsModePossibleDispOutput output = {0}; - NVHwModeTimingsEvo timing = {0}; - struct NvKmsUsageBounds usage = {0}; - NvU32 required[4] = {0}; - - /* Inactive heads have NULL timing/usage pointers and need no resources. */ - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &output)); - - dev.head[0].numLayers = 8; - input.head[0].pTimings = &timing; - input.head[0].pUsage = &usage; - input.head[0].modesetRequested = TRUE; - input.head[0].multiTileConfig.tilesMask = 1; - input.head[0].multiTileConfig.phywinsMask[0] = NVBIT(31); - required[0] = 1; - for (NvU32 layer = 0; layer < 8; layer++) { - usage.layer[layer].usable = TRUE; - } - assert(EvoAssignHwHeadMultiTileConfigDispOutputCA(&disp, &input, required, &output)); - assert(output.head[0].multiTileConfig.phywinsMask[0] == NVBIT(31)); - for (NvU32 layer = 0; layer < 8; layer++) { - assert(nvPopCount32(output.head[0].multiTileConfig.phywinsMask[layer]) == 1); - } - - /* Failed helper allocation must not publish its partial result. */ - NVHwHeadMultiTileConfigRec config = {.tilesMask = 3}; - const NVHwHeadMultiTileConfigRec originalConfig = config; - NvU32 freeWindows = 1; - assert(!AssignNewPhywinsIfNeeded(&disp, &config, 0, &usage, &freeWindows)); - assert(memcmp(&config, &originalConfig, sizeof(config)) == 0); - assert(freeWindows == 1); - puts("PASS: inactive heads, eight layers, bit 31, and allocation failure isolation"); -} - -int main(void) -{ - unsigned cases = 0; - - TestBoundaryConditions(); - - for (NvU32 windowCount = 8; windowCount <= 32; windowCount += 24) { - for (NvU32 reuse = 0; reuse < 2; reuse++) { - for (NvU32 scalerTiles = 0; scalerTiles <= 8; scalerTiles++) { - for (NvU32 scalingMask = 0; scalingMask < 16; scalingMask++) { - for (NvU32 overlayMask = 0; overlayMask < 16; overlayMask++) { - for (NvU32 tileChoices = 0; tileChoices < 256; tileChoices++) { - assert(TestAllocation(windowCount, reuse, scalerTiles, scalingMask, - overlayMask, tileChoices)); - cases++; - } - } - } - } - } - } - printf("PASS: %u allocation combinations\n", cases); - return 0; -} diff --git a/tests/fixtures/sequencing-model.h b/tests/fixtures/sequencing-model.h deleted file mode 100644 index 221859fb8b..0000000000 --- a/tests/fixtures/sequencing-model.h +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include -#include -typedef unsigned NvU32; -typedef int NvBool; -#define TRUE 1 -#define FALSE 0 -#define EVO_LOG_INFO 0 -#define nvDoDebugLogging() 0 -#define nvEvoLogDev(...) ((void)0) -#define NV_EVO_CHANNEL_MASK_WINDOW_NUMBER(x) (x) -typedef struct { - NvU32 channelMask; -} NVEvoChannel; -typedef struct { - NvU32 noCoreInterlockMask; -} NVEvoUpdateState; -typedef struct { - NvBool windowMappingChanged; - NVEvoUpdateState updateState; -} NVEvoModesetUpdateState; -struct Dev; -typedef struct { - void (*Update)(struct Dev *, const NVEvoUpdateState *, NvBool); -} Hal; -typedef struct Dev { - NvU32 numApiHeads; - Hal *hal; -} NVDevEvoRec; -typedef struct { - NVDevEvoRec *pDevEvo; -} Disp; -typedef Disp *NVDispEvoPtr; -typedef struct { - int dummy; -} NVProposedModeSetHwState; -typedef struct { - int apiHead[4]; -} NVProposedModeSetHwStateOneDisp; -typedef struct { - NVEvoModesetUpdateState modesetUpdateState; -} NVModeSetWorkArea; -static void nvDisableCoreInterlockUpdateState(NVDevEvoRec *dev, NVEvoUpdateState *u, - const NVEvoChannel *ch) -{ - (void)dev; - u->noCoreInterlockMask |= ch->channelMask; -} -static char events[16]; -static int n, changeInPre; -static void event(char c) -{ - events[n++] = c; -} -static void update(NVDevEvoRec *dev, const NVEvoUpdateState *u, NvBool release) -{ - (void)dev; - (void)u; - (void)release; - event('U'); -} -#define ApplyProposedModeSetStateOneApiHeadShutDown(...) ((void)0) -#define ApplyProposedModeSetStateOneApiHeadPreUpdate(d, h, p, w, b) \ - do { \ - if (changeInPre) \ - (w)->modesetUpdateState.windowMappingChanged = TRUE; \ - } while (0) -#define ApplyProposedModeSetStateOneDispFlip(...) event('F') -#define KickoffModesetUpdateState(d, s) \ - do { \ - event('C'); \ - memset(s, 0, sizeof(*(s))); \ - } while (0) -#define nvRemoveUnusedHdmiDpAudioDevice(...) ((void)0) -#define ApplyProposedModeSetStateOneApiHeadPostModesetUpdate(...) ((void)0) -#define nvIsUpdateStateEmpty(...) TRUE diff --git a/tests/fixtures/window-ownership.c b/tests/fixtures/window-ownership.c deleted file mode 100644 index 5788f95c15..0000000000 --- a/tests/fixtures/window-ownership.c +++ /dev/null @@ -1,119 +0,0 @@ -int main(void) -{ - NVEvoChannel core = {0}; - NVEvoChannel base = {2}; - NVEvoChannel overlay = {3}; - TestSubDevice subDevice = {stateCache}; - NVDevEvoRec dev = { - .core = &core, - .pSubDevices = {&subDevice}, - }; - NVDispEvoRec disp = {&dev, 0}; - NVEvoModesetUpdateState update = {0}; - NVHwModeTimingsEvo timing = {0}; - NVDscInfoEvoRec dsc = {0}; - NVHwHeadMultiTileConfigRec config = { - .tilesMask = 3, - .phywinsMask = {3, 0}, - }; - - dev.head[1].numLayers = 2; - dev.head[1].layer[0] = &base; - dev.head[1].layer[1] = &overlay; - - /* Simulate stock window owners, including the unrelated HIDE control bit. */ - stateCache[NVCA7D_WINDOW_SET_CONTROL(2)] = 1; - stateCache[NVCA7D_WINDOW_SET_CONTROL(3)] = 0x101; - stateCache[NVCA7D_WINDOW_SET_PHYSICAL(2)] = 1; - stateCache[NVCA7D_WINDOW_SET_PHYSICAL(3)] = 2; - memcpy(pendingMethods, stateCache, sizeof(stateCache)); - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(GetPendingWindowOwner(2) == 1); - assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); - assert(pendingMethods[NVCA7D_WINDOW_SET_CONTROL(3)] == 0x10f); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(2)] == 3); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 0); - assert(update.windowMappingChanged); - assert(update.updateState.noCoreInterlockMask == 12); - puts("PASS: unused overlay is unowned with no physical windows; " - "base remains bound"); - - /* Repeating the same configuration must not introduce an interlock change. */ - CommitPendingMethods(); - memset(&update, 0, sizeof(update)); - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(!update.windowMappingChanged); - assert(!update.updateState.noCoreInterlockMask); - puts("PASS: unchanged allocation/ownership introduces no extra interlock " - "changes"); - - /* Restoring overlay resources also restores its original hardware owner. */ - config.tilesMask = 1; - config.phywinsMask[0] = 1; - config.phywinsMask[1] = 2; - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(GetPendingWindowOwner(3) == 1); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 2); - assert(update.windowMappingChanged); - assert(update.updateState.noCoreInterlockMask & 8); - puts("PASS: re-enabled overlay regains its owner and physical assignment"); - - /* Shutdown releases physical windows while leaving owners unchanged. */ - CommitPendingMethods(); - memset(&update, 0, sizeof(update)); - memset(&config, 0, sizeof(config)); - - EvoSetMultiTileConfigCA(&disp, 1, NULL, NULL, &config, &update); - - assert(GetPendingWindowOwner(2) == 1); - assert(GetPendingWindowOwner(3) == 1); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(2)] == 0); - assert(pendingMethods[NVCA7D_WINDOW_SET_PHYSICAL(3)] == 0); - puts("PASS: shutdown clears physical assignments without changing " - "ownership in that update"); - - /* Only the following activation update removes the unused window owner. */ - CommitPendingMethods(); - memset(&update, 0, sizeof(update)); - config.tilesMask = 3; - config.phywinsMask[0] = 3; - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(GetPendingWindowOwner(2) == 1); - assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); - assert(update.windowMappingChanged); - assert(update.updateState.noCoreInterlockMask & 8); - puts("PASS: subsequent activation unbinds the unused overlay with a " - "non-interlocked update"); - - /* - * Initialization can queue a default binding before this helper runs. - * PIO still describes the previous unowned state. The final owner write - * must override the queued binding, even though PIO matches our target. - */ - stateCache[NVCA7D_WINDOW_SET_CONTROL(2)] = 1; - stateCache[NVCA7D_WINDOW_SET_CONTROL(3)] = NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE; - stateCache[NVCA7D_WINDOW_SET_PHYSICAL(2)] = 3; - stateCache[NVCA7D_WINDOW_SET_PHYSICAL(3)] = 0; - memcpy(pendingMethods, stateCache, sizeof(stateCache)); - pendingMethods[NVCA7D_WINDOW_SET_CONTROL(3)] = 1; - memset(&update, 0, sizeof(update)); - update.windowMappingChanged = TRUE; - update.updateState.noCoreInterlockMask = 8; - - EvoSetMultiTileConfigCA(&disp, 1, &timing, &dsc, &config, &update); - - assert(GetPendingWindowOwner(3) == NVCA7D_WINDOW_SET_CONTROL_OWNER_NONE); - assert(update.windowMappingChanged); - assert(update.updateState.noCoreInterlockMask & 8); - puts("PASS: an unused overlay overrides a queued initialization binding"); - - return 0; -} diff --git a/tests/ordinary-configurations.py b/tests/ordinary-configurations.py deleted file mode 100644 index 41069555e7..0000000000 --- a/tests/ordinary-configurations.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -"""Compare ordinary CA allocations with a pinned upstream implementation. - -All exposed layers are usable in these cases. Inputs stay inside the physical -window budget. Known upstream incomplete-retry results are counted separately; -only complete successful reference allocations are compared for exact equality. -Hardware methods and timings are not simulated here. -""" -from pathlib import Path -import re -import subprocess -import tempfile - -root = Path(__file__).resolve().parents[1] -source_path = 'src/nvidia-modeset/src/nvkms-evo4.c' -reference_revision = 'e4a5faa2567f28c8eabe0ebb6422b6d0abcf37eb' -preamble = (Path(__file__).with_name('fixtures') / 'allocation-model.h').read_text() - - -def allocator(text): - start = text.index('static NvBool RequiredScalerTiles(') - end = text.index('static void\nEvoIsModePossibleCA(', start) - return text[start:end] - - -current = allocator((root / source_path).read_text()) -reference = allocator(subprocess.check_output( - ['git', 'show', reference_revision + ':' + source_path], cwd=root, text=True)) -function_names = re.findall( - r'(?:static\s+)(?:NvBool|NvU32|void|NVEvoHwTileType)\s+(\w+)\s*\(', reference) -for name in function_names: - reference = re.sub(r'\b' + re.escape(name) + r'\b', 'Reference' + name, reference) - -scenarios = (Path(__file__).with_name('fixtures') / 'ordinary-configurations.c').read_text() -with tempfile.TemporaryDirectory() as directory: - test = Path(directory) / 'comparison.c' - binary = Path(directory) / 'comparison' - test.write_text(preamble + reference + current + scenarios) - subprocess.run([ - 'cc', '-std=c99', '-Wall', '-Wextra', '-Werror', - '-fsanitize=address,undefined', str(test), '-o', str(binary), - ], check=True) - subprocess.run([str(binary)], check=True) diff --git a/tests/phywin-allocation.py b/tests/phywin-allocation.py deleted file mode 100644 index 442b0beeda..0000000000 --- a/tests/phywin-allocation.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -"""Exercise the production CA allocator with a small standalone hardware model.""" -from pathlib import Path -import subprocess -import tempfile - -source = (Path(__file__).resolve().parents[1] / - 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() -start = source.index('static NvBool RequiredScalerTiles(') -end = source.index('static void\nEvoIsModePossibleCA(', start) -modeset = (Path(__file__).resolve().parents[1] / - 'src/nvidia-modeset/src/nvkms-modeset.c').read_text() -move_start = modeset.index('static NvBool IsCurrentMultiTileConfigOneApiHeadIncompatible(') -move_end = modeset.index('\n/*!', move_start) -preamble = (Path(__file__).with_name('fixtures') / 'allocation-model.h').read_text() -tests = (Path(__file__).with_name('fixtures') / 'phywin-allocation.c').read_text() -with tempfile.TemporaryDirectory() as tmp: - c = Path(tmp) / 'test.c' - exe = Path(tmp) / 'test' - c.write_text(preamble + source[start:end] + modeset[move_start:move_end] + tests) - subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', - '-fsanitize=undefined,address', str(c), '-o', str(exe)], check=True) - subprocess.run([str(exe)], check=True) diff --git a/tests/phywin-sequencing.py b/tests/phywin-sequencing.py deleted file mode 100644 index 688fc49f20..0000000000 --- a/tests/phywin-sequencing.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python3 -"""Test production physical-window tracking and modeset/flip ordering.""" -from pathlib import Path -import subprocess -import tempfile - -root = Path(__file__).resolve().parents[1] -evo = (root / 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() -tracking_start = evo.index('static void EvoTrackPhysicalWindowChangeCA(') -tracking_end = evo.index('static void EvoSetWindowOwnerCA(', tracking_start) -modeset = (root / 'src/nvidia-modeset/src/nvkms-modeset.c').read_text() -update_start = modeset.index('static void\nKickoffProposedModeSetHwState(') -update_end = modeset.index('static void AllocatePostModesetDispBandwidth(', update_start) -fixtures = Path(__file__).with_name('fixtures') -preamble = (fixtures / 'sequencing-model.h').read_text() -scenarios = (fixtures / 'phywin-sequencing.c').read_text() - -with tempfile.TemporaryDirectory() as directory: - source = Path(directory) / 'test.c' - binary = Path(directory) / 'test' - source.write_text(preamble + evo[tracking_start:tracking_end] + - modeset[update_start:update_end] + scenarios) - subprocess.run([ - 'cc', '-std=c99', '-Wall', '-Wextra', '-Werror', - '-Wno-unused-parameter', '-Wno-unused-variable', - '-fsanitize=address,undefined', str(source), '-o', str(binary), - ], check=True) - subprocess.run([str(binary)], check=True) diff --git a/tests/phywin-stress.py b/tests/phywin-stress.py deleted file mode 100644 index c369438993..0000000000 --- a/tests/phywin-stress.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Stress production allocation with an independent capacity/uniqueness oracle. - -The model covers four heads, eight tiles, two layers per head, mixed scaler -capabilities, fresh/reused assignments, and 8/32 physical-window limits. -It tests software allocation only, not RM acceptance or hardware execution. -""" -from pathlib import Path -import subprocess -import tempfile - -root = Path(__file__).resolve().parents[1] -preamble = (Path(__file__).with_name('fixtures') / 'allocation-model.h').read_text() -# Exercise the API's eight-layer array limit as well as two-layer desktops. -preamble = preamble.replace('[2]', '[8]') -source = (root / 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() -start = source.index('static NvBool RequiredScalerTiles(') -end = source.index('static void\nEvoIsModePossibleCA(', start) -scenarios = (Path(__file__).with_name('fixtures') / 'phywin-stress.c').read_text() -with tempfile.TemporaryDirectory() as directory: - test = Path(directory) / 'test.c' - binary = Path(directory) / 'test' - test.write_text(preamble + source[start:end] + scenarios) - subprocess.run([ - 'cc', '-std=c99', '-Wall', '-Wextra', '-Werror', - '-fsanitize=address,undefined', str(test), '-o', str(binary), - ], check=True) - subprocess.run([str(binary)], check=True) diff --git a/tests/window-ownership.py b/tests/window-ownership.py deleted file mode 100644 index c78b5a23ab..0000000000 --- a/tests/window-ownership.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 -"""Check generated CA ownership/physical methods against allocation invariants. - -This uses the production functions and hardware register definitions, but a -software method recorder. It does not validate real hardware timing or firmware. -""" -from pathlib import Path -import subprocess -import tempfile - -root = Path(__file__).resolve().parents[1] -evo = (root / 'src/nvidia-modeset/src/nvkms-evo4.c').read_text() -start = evo.index('static void EvoTrackPhysicalWindowChangeCA(') -end = evo.index('\nNVEvoHAL nvEvoC9 =', start) -pre = (Path(__file__).with_name('fixtures') / 'ownership-model.h').read_text() -post = (Path(__file__).with_name('fixtures') / 'window-ownership.c').read_text() -with tempfile.TemporaryDirectory() as tmp: - c = Path(tmp) / 'test.c' - exe = Path(tmp) / 'test' - c.write_text(pre + evo[start:end] + post) - subprocess.run(['cc', '-std=c99', '-Wall', '-Wextra', '-Werror', - '-fsanitize=address,undefined', - '-I' + str(root / 'src/common/sdk/nvidia/inc'), - str(c), '-o', str(exe)], check=True) - subprocess.run([str(exe)], check=True) From 84507d9214bb881d447dfced1c0483bf9d445a7e Mon Sep 17 00:00:00 2001 From: Zac Lanzon Date: Sat, 5 Sep 2026 00:48:48 -0400 Subject: [PATCH 6/6] Align modeset comments with allocation and update decisions --- src/nvidia-modeset/src/nvkms-evo4.c | 19 ++++++++++--------- src/nvidia-modeset/src/nvkms-modeset.c | 24 ++++++++++++------------ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/nvidia-modeset/src/nvkms-evo4.c b/src/nvidia-modeset/src/nvkms-evo4.c index 2cac838bbe..6ecf7f3cd3 100644 --- a/src/nvidia-modeset/src/nvkms-evo4.c +++ b/src/nvidia-modeset/src/nvkms-evo4.c @@ -2059,7 +2059,7 @@ static void UnassignExtraOrIncompatibleTiles( NvU32 numReusedPhywins = 0; NvU32 phywin; - /* IMP excludes unusable layers; release their physical windows too. */ + /* Do not retain physical windows for layers excluded by IMP. */ if (!pUsage->layer[layer].usable) { continue; } @@ -2199,12 +2199,11 @@ static NvBool AssignNewTilesToHeadIfNeeded( &outFreeTilesMask); } - /* Layer usage can change even when the tile count stays the same. */ - if (nvPopCount32(outMultiTileConfig.tilesMask) < numRequiredTiles) { return FALSE; } + /* Layer usage can change even when the tile count stays the same. */ if (!AssignNewPhywinsIfNeeded(pDispEvo, &outMultiTileConfig, head, pUsage, &outFreePhywinsMask)) { return FALSE; @@ -2342,7 +2341,8 @@ AssignNewTilesToHeadsIfNeeded( pOutput, &freeTilesMask, &freePhywinsMask); - /* Retry this head before moving to the next tile type. */ + + /* Retry the failed head with the reclaimed resources. */ if (AssignNewTilesToHeadIfNeeded(pDispEvo, pOutputMultiTileConfig, head, @@ -2801,8 +2801,10 @@ static void SetTileSize(NVEvoChannel *pCoreChannel, nvAssert(tileStart == hActive); } -/* Physical window assignment changes need the same interlock handling as - * logical window assignment changes in EvoInitWindowMapping3(). */ +/* + * Physical window assignment changes need the same interlock handling as + * logical window assignment changes in EvoInitWindowMapping3(). + */ static void EvoTrackPhysicalWindowChangeCA( NVDevEvoRec *pDevEvo, const NVEvoChannel *pWindowChannel, @@ -2826,9 +2828,7 @@ static void EvoTrackPhysicalWindowChangeCA( * an unusable layer has neither an owner nor physical windows. This matches * the unassigned-window state established by overlay-off initialization. * - * The caller has shut down heads whose layer ownership changes. During the - * shutdown update itself, retain ownership; it is changed in the subsequent - * modeset update, while the head is inactive. + * The caller has shut down heads whose layer ownership changes. */ static void EvoSetWindowOwnerCA( NVDevEvoRec *pDevEvo, @@ -2894,6 +2894,7 @@ static void EvoSetMultiTileConfigCA(const NVDispEvoRec *pDispEvo, oldPhywinsMask, phywinsMask, pModesetUpdateState); + /* Retain ownership during shutdown; change it in the next modeset. */ if (pTimings != NULL) { EvoSetWindowOwnerCA(pDevEvo, pDispEvo->displayOwner, pWindowChannel, head, phywinsMask, diff --git a/src/nvidia-modeset/src/nvkms-modeset.c b/src/nvidia-modeset/src/nvkms-modeset.c index 4ed66db7ff..b07d35493c 100644 --- a/src/nvidia-modeset/src/nvkms-modeset.c +++ b/src/nvidia-modeset/src/nvkms-modeset.c @@ -3497,17 +3497,6 @@ KickoffProposedModeSetHwState( { NVDevEvoRec *pDevEvo = pDispEvo->pDevEvo; NVEvoModesetUpdateState *pModesetUpdateState = &pWorkArea->modesetUpdateState; - /* - * If there is a change in window ownership, decouple window channel flips - * and the core channel update that performs a modeset. - * - * This allows window channel flips to be instead interlocked with the core - * channel update that sets the window usage bounds, avoiding window - * invalid usage exceptions. - * - * See comment about NVDisplay error code 37, in - * function EvoInitWindowMapping3(). - */ NvBool decoupleFlipUpdates; /* Send methods to shut down any other unused heads, but don't update yet. */ @@ -3530,7 +3519,18 @@ KickoffProposedModeSetHwState( bypassComposition); } - /* PreUpdate may change physical window assignments on Blackwell. */ + /* + * If window ownership or physical assignments change, decouple window + * channel flips from the core channel update that performs a modeset. + * Check after PreUpdate, which may change physical assignments on Blackwell. + * + * This allows window channel flips to be instead interlocked with the core + * channel update that sets the window usage bounds, avoiding window + * invalid usage exceptions. + * + * See comment about NVDisplay error code 37, in + * function EvoInitWindowMapping3(). + */ decoupleFlipUpdates = pModesetUpdateState->windowMappingChanged; if (!decoupleFlipUpdates) {