diff options
Diffstat (limited to 'drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_command.c')
-rw-r--r-- | drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_command.c | 4509 |
1 files changed, 4509 insertions, 0 deletions
diff --git a/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_command.c b/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_command.c new file mode 100644 index 000000000000..324b945d2aa1 --- /dev/null +++ b/drivers/mxc/gpu-viv/hal/kernel/gc_hal_kernel_command.c @@ -0,0 +1,4509 @@ +/**************************************************************************** +* +* The MIT License (MIT) +* +* Copyright (c) 2014 - 2020 Vivante Corporation +* +* Permission is hereby granted, free of charge, to any person obtaining a +* copy of this software and associated documentation files (the "Software"), +* to deal in the Software without restriction, including without limitation +* the rights to use, copy, modify, merge, publish, distribute, sublicense, +* and/or sell copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +* DEALINGS IN THE SOFTWARE. +* +***************************************************************************** +* +* The GPL License (GPL) +* +* Copyright (C) 2014 - 2020 Vivante Corporation +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software Foundation, +* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +***************************************************************************** +* +* Note: This software is released under dual MIT and GPL licenses. A +* recipient may use this file under the terms of either the MIT license or +* GPL License. If you wish to use only one license not the other, you can +* indicate your decision by deleting one of the above license notices in your +* version of this file. +* +*****************************************************************************/ + + +#include "gc_hal_kernel_precomp.h" +#include "gc_hal_kernel_context.h" + +#define _GC_OBJ_ZONE gcvZONE_COMMAND + +/******************************************************************************\ +********************************* Support Code ********************************* +\******************************************************************************/ + +/******************************************************************************* +** +** _NewQueue +** +** Allocate a new command queue. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object. +** +** gctBOOL Stalled +** Indicate if hardware is stalled already. +** +** OUTPUT: +** +** gckCOMMAND Command +** gckCOMMAND object has been updated with a new command queue. +*/ +static gceSTATUS +_NewQueue( + IN OUT gckCOMMAND Command, + IN gctBOOL Stalled + ) +{ + gceSTATUS status; + gctINT currentIndex, newIndex; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Switch to the next command buffer. */ + currentIndex = Command->index; + newIndex = (currentIndex + 1) % gcdCOMMAND_QUEUES; + + /* Wait for availability. */ + gcmkDUMP(Command->os, "#[kernel.waitsignal]"); + + gcmkONERROR(gckOS_WaitSignal( + Command->os, + Command->queues[newIndex].signal, + gcvFALSE, + gcvINFINITE + )); + +#if gcmIS_DEBUG(gcdDEBUG_TRACE) + if (newIndex < currentIndex) + { + Command->wrapCount += 1; + + gcmkTRACE_ZONE_N( + gcvLEVEL_INFO, gcvZONE_COMMAND, + 2 * 4, + "%s(%d): queue array wrapped around.\n", + __FUNCTION__, __LINE__ + ); + } + + gcmkTRACE_ZONE_N( + gcvLEVEL_INFO, gcvZONE_COMMAND, + 3 * 4, + "%s(%d): total queue wrap arounds %d.\n", + __FUNCTION__, __LINE__, Command->wrapCount + ); + + gcmkTRACE_ZONE_N( + gcvLEVEL_INFO, gcvZONE_COMMAND, + 3 * 4, + "%s(%d): switched to queue %d.\n", + __FUNCTION__, __LINE__, newIndex + ); +#endif + + /* Update gckCOMMAND object with new command queue. */ + Command->index = newIndex; + Command->newQueue = gcvTRUE; + Command->videoMem = Command->queues[newIndex].videoMem; + Command->logical = Command->queues[newIndex].logical; + Command->address = Command->queues[newIndex].address; + Command->offset = 0; + + if (currentIndex != -1) + { + if (Stalled) + { + gckOS_Signal( + Command->os, + Command->queues[currentIndex].signal, + gcvTRUE + ); + } + else + { + /* Mark the command queue as available. */ + gcmkONERROR(gckEVENT_Signal( + Command->kernel->eventObj, + Command->queues[currentIndex].signal, + gcvKERNEL_COMMAND + )); + } + } + + /* Success. */ + gcmkFOOTER_ARG("Command->index=%d", Command->index); + return gcvSTATUS_OK; + +OnError: + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_IncrementCommitAtom( + IN gckCOMMAND Command, + IN gctBOOL Increment + ) +{ + gceSTATUS status; + gckHARDWARE hardware; + gctINT32 atomValue; + gctBOOL powerAcquired = gcvFALSE; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Extract the gckHARDWARE and gckEVENT objects. */ + hardware = Command->kernel->hardware; + gcmkVERIFY_OBJECT(hardware, gcvOBJ_HARDWARE); + + /* Grab the power mutex. */ + gcmkONERROR(gckOS_AcquireMutex( + Command->os, hardware->powerMutex, gcvINFINITE + )); + powerAcquired = gcvTRUE; + + /* Increment the commit atom. */ + if (Increment) + { + gcmkONERROR(gckOS_AtomIncrement( + Command->os, Command->atomCommit, &atomValue + )); + } + else + { + gcmkONERROR(gckOS_AtomDecrement( + Command->os, Command->atomCommit, &atomValue + )); + } + + /* Release the power mutex. */ + gcmkONERROR(gckOS_ReleaseMutex( + Command->os, hardware->powerMutex + )); + powerAcquired = gcvFALSE; + + /* Success. */ + gcmkFOOTER(); + return gcvSTATUS_OK; + +OnError: + if (powerAcquired) + { + /* Release the power mutex. */ + gcmkVERIFY_OK(gckOS_ReleaseMutex( + Command->os, hardware->powerMutex + )); + } + + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_CheckFlushMMU( + IN gckCOMMAND Command, + IN gckHARDWARE Hardware + ) +{ +#if gcdSECURITY + return gcvSTATUS_OK; +#else + gceSTATUS status; + gctUINT32 oldValue; + gctBOOL pause = gcvFALSE; + + gctUINT8_PTR pointer; + gctUINT32 address; + gctUINT32 eventBytes; + gctUINT32 endBytes; + gctUINT32 bufferSize; + gctUINT32 executeBytes; + + gckOS_AtomicExchange(Command->os, + Hardware->pageTableDirty[gcvENGINE_RENDER], + 0, + &oldValue); + + if (oldValue) + { + /* Page Table is upated, flush mmu before commit. */ + gctUINT32 flushBytes; + + gcmkONERROR(gckHARDWARE_FlushMMU( + Hardware, + gcvNULL, + gcvINVALID_ADDRESS, + 0, + &flushBytes + )); + + gcmkONERROR(gckCOMMAND_Reserve( + Command, + flushBytes, + (gctPOINTER *)&pointer, + &bufferSize + )); + + /* Pointer to reserved address. */ + address = Command->address + Command->offset; + + /* + * subsequent 8 bytes are wait-link commands. + * Set more existed bytes for now. + */ + gcmkONERROR(gckHARDWARE_FlushMMU( + Hardware, + pointer, + address, + (bufferSize - flushBytes), + &flushBytes + )); + + gcmkONERROR(gckCOMMAND_Execute(Command, flushBytes)); + + if ((oldValue & gcvPAGE_TABLE_DIRTY_BIT_FE) + && (!Hardware->stallFEPrefetch) + ) + { + pause = gcvTRUE; + } + } + + if (pause) + { + /* Query size. */ + gcmkONERROR(gckWLFE_Event(Hardware, gcvNULL, 0, gcvKERNEL_PIXEL, &eventBytes)); + gcmkONERROR(gckWLFE_End(Hardware, gcvNULL, ~0U, &endBytes)); + + executeBytes = eventBytes + endBytes; + + /* Reserve space. */ + gcmkONERROR(gckCOMMAND_Reserve( + Command, + executeBytes, + (gctPOINTER *)&pointer, + &bufferSize + )); + + /* Pointer to reserved address. */ + address = Command->address + Command->offset; + + /* Append EVENT(29). */ + gcmkONERROR(gckWLFE_Event( + Hardware, + pointer, + 29, + gcvKERNEL_PIXEL, + &eventBytes + )); + + /* Append END. */ + pointer += eventBytes; + address += eventBytes; + + gcmkONERROR(gckWLFE_End(Hardware, pointer, address, &endBytes)); + + gcmkONERROR(gckCOMMAND_Execute(Command, executeBytes)); + } + + return gcvSTATUS_OK; +OnError: + return status; +#endif +} + +/* WaitLink FE only. */ +static gceSTATUS +_DummyDraw( + IN gckCOMMAND Command + ) +{ +#if gcdSECURITY + return gcvSTATUS_OK; +#else + gceSTATUS status; + gckHARDWARE hardware = Command->kernel->hardware; + + gctUINT8_PTR pointer; + gctUINT32 bufferSize; + + gctUINT32 dummyDrawBytes; + gceDUMMY_DRAW_TYPE dummyDrawType = gcvDUMMY_DRAW_INVALID; + + if (gckHARDWARE_IsFeatureAvailable(hardware, gcvFEATURE_FE_NEED_DUMMYDRAW)) + { + dummyDrawType = gcvDUMMY_DRAW_GC400; + } + + if (!gckHARDWARE_IsFeatureAvailable(hardware, gcvFEATURE_USC_DEFER_FILL_FIX) && + gckHARDWARE_IsFeatureAvailable(hardware, gcvFEATURE_USC)) + { + dummyDrawType = gcvDUMMY_DRAW_V60; + } + + if (dummyDrawType != gcvDUMMY_DRAW_INVALID) + { + gckHARDWARE_DummyDraw(hardware, gcvNULL, Command->queues[0].address, dummyDrawType, &dummyDrawBytes); + + /* Reserve space. */ + gcmkONERROR(gckCOMMAND_Reserve( + Command, + dummyDrawBytes, + (gctPOINTER *)&pointer, + &bufferSize + )); + + gckHARDWARE_DummyDraw(hardware, pointer, Command->queues[0].address, dummyDrawType, &dummyDrawBytes); + + gcmkONERROR(gckCOMMAND_Execute(Command, dummyDrawBytes)); + } + + return gcvSTATUS_OK; +OnError: + return status; +#endif +} + +/* + * Wait pending semaphores. + * + * next == free: full, no more semaphores. + * (free + 1) = next: empty + */ +static gcmINLINE gceSTATUS +_WaitPendingMcfeSema( + gckCOMMAND Command + ) +{ + gceSTATUS status; + const gctUINT count = gcmCOUNTOF(Command->pendingSema); + gctUINT32 nextFreePos; + gctUINT32 timeout = gcvINFINITE; + + gcmkHEADER_ARG("freePendingPos=%u nextPendingPos=%u", + Command->freePendingPos, Command->nextPendingPos); + + nextFreePos = (Command->freePendingPos + 1) % count; + + if (nextFreePos == Command->nextPendingPos) + { + /* No pendings. */ + gcmkONERROR(gcvSTATUS_NOT_FOUND); + } + + while (nextFreePos != Command->nextPendingPos) + { + /* Timeout is infinite in the first to at least free one slot. */ + status = gckOS_WaitSignal(Command->os, + Command->pendingSema[nextFreePos].signal, + gcvFALSE, + timeout); + + if (status == gcvSTATUS_TIMEOUT) + { + /* Timeout out is OK for later pendings. */ + break; + } + + gcmkONERROR(status); + + /* Do not wait for the later slots. */ + timeout = 0; + + /* More free semaphores can be used. */ + Command->freeSemaId = Command->pendingSema[nextFreePos].semaId; + + /* Advance free pos. */ + Command->freePendingPos = nextFreePos; + nextFreePos = (nextFreePos + 1) % count; + } + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + gcmkFOOTER(); + return status; +} + +static gctUINT32 +_GetFreeMcfeSemaNum( + gckCOMMAND Command + ) +{ + gctUINT32 num = 0; + + if (Command->nextSemaId <= Command->freeSemaId) + { + num = Command->freeSemaId - Command->nextSemaId; + } + else + { + num = Command->totalSemaId - Command->nextSemaId + Command->freeSemaId; + } + + return num; +} + +/* + * Get next semaphore id in semaphore ring. + * + * There are semaMinThreshhold semaphores are reserved for system operations, + * such as _SyncToSystemChannel etc. + * The rest semaphores are regular ones. + */ +static gcmINLINE gceSTATUS +_GetNextMcfeSemaId( + gckCOMMAND Command, + gctBOOL regularSema, + gctUINT32 * SemaId + ) +{ + gctUINT32 freeSemaNum = 0; + + gceSTATUS status = gcvSTATUS_OK; + + /* + * See the comments in struct definition. + * wait when run out of semaphores. + */ + freeSemaNum = _GetFreeMcfeSemaNum(Command); + + if ((regularSema && (freeSemaNum <= Command->semaMinThreshhold)) || + (!regularSema && (freeSemaNum == 0))) + { + gcmkONERROR(_WaitPendingMcfeSema(Command)); + } + + gcmkASSERT(Command->nextSemaId != Command->freeSemaId); + + /* Output the semaphore ID. */ + *SemaId = Command->nextSemaId; + + /* Advance to next. */ + if (++Command->nextSemaId == Command->totalSemaId) + { + Command->nextSemaId = 0; + } + +OnError: + return status; +} + +/* + * Get next pending pos in pending semaphore tracking ring. + */ +static gcmINLINE gceSTATUS +_GetNextPendingPos( + gckCOMMAND Command, + gctUINT32 * Pos + ) +{ + gceSTATUS status = gcvSTATUS_OK; + + /* Wait when out of pending ring. */ + if (Command->nextPendingPos == Command->freePendingPos) + { + /* Run out of pending semaphore tracking ring. */ + gcmkONERROR(_WaitPendingMcfeSema(Command)); + } + + gcmkASSERT(Command->nextPendingPos != Command->freePendingPos); + + *Pos = Command->nextPendingPos; + + /* Advance to next. */ + if (++Command->nextPendingPos == gcmCOUNTOF(Command->pendingSema)) + { + Command->nextPendingPos = 0; + } + +OnError: + return status; +} + +/* + * Sync specific channels to system channel. + * Record semaphores to pendingSema structure. + * 'SyncChannel' is cleared upon function return. + */ +static gceSTATUS +_SyncToSystemChannel( + gckCOMMAND Command, + gctUINT64 SyncChannel[2] + ) +{ + gceSTATUS status; + gckKERNEL kernel = Command->kernel; + gckHARDWARE hardware = kernel->hardware; + gctUINT8 semaId[128]; + gctUINT32 semaCount = 0; + gctUINT32 reqBytes = 0; + gctUINT32 bytes = 0; + gctUINT8_PTR buffer; + gctUINT32 i; + gctUINT32 pri; + + /* Ignore system channel. */ + SyncChannel[0] &= ~((gctUINT64)1ull); + SyncChannel[1] &= ~((gctUINT64)1ull); + + if (!SyncChannel[0] && !SyncChannel[1]) + { + return gcvSTATUS_OK; + } + + /* Query SendSemaphore command size. */ + gckMCFE_SendSemaphore(hardware, gcvNULL, 0, &reqBytes); + + for (pri = 0; pri < 2; pri++) + { + for (i = 1; i < 64 && SyncChannel[pri]; i++) + { + gctUINT32 id; + + if (!(SyncChannel[pri] & (1ull << i))) + { + continue; + } + + /* Get a free semaphore id. */ + gcmkONERROR(_GetNextMcfeSemaId(Command, gcvFALSE, &id)); + semaId[semaCount++] = (gctUINT8)id; + + gcmkONERROR(gckCOMMAND_Reserve(Command, reqBytes, (gctPOINTER *)&buffer, &bytes)); + + /* Send semaphore executed in specified channel. */ + gckMCFE_SendSemaphore(hardware, buffer, id, &bytes); + + gcmkONERROR(gckCOMMAND_ExecuteMultiChannel(Command, pri, i, reqBytes)); + + /* Remove the sync'ed channel. */ + SyncChannel[pri] &= ~(1ull << i); + } + } + + if (semaCount > 0) + { + gctUINT32 pos = 0; + gckEVENT eventObj = kernel->eventObj; + gctUINT32 bufferLen = 0; + + /* Query WaitSemaphore command size. */ + gckMCFE_WaitSemaphore(hardware, gcvNULL, 0, &reqBytes); + reqBytes *= semaCount; + + gcmkONERROR(gckCOMMAND_Reserve(Command, reqBytes, (gctPOINTER *)&buffer, &bufferLen)); + + for (i = 0; i < semaCount; i++) + { + bytes = bufferLen; + + /* Wait semaphores executed in fixed system channel. */ + gckMCFE_WaitSemaphore(hardware, buffer, semaId[i], &bytes); + + buffer += bytes; + bufferLen -= bytes; + } + + gcmkONERROR(gckCOMMAND_ExecuteMultiChannel(Command, 0, 0, reqBytes)); + + /* Now upload the pending semaphore tracking ring. */ + gcmkONERROR(_GetNextPendingPos(Command, &pos)); + + /* Update latest pending semaphore id. */ + Command->pendingSema[pos].semaId = (gctUINT32)semaId[semaCount - 1]; + + /* Send the signal by event. */ + gcmkONERROR(gckEVENT_Signal( + eventObj, + Command->pendingSema[pos].signal, + gcvKERNEL_PIXEL + )); + + gcmkONERROR(gckEVENT_Submit( + eventObj, + gcvTRUE, + gcvFALSE + )); + } + + return gcvSTATUS_OK; + +OnError: + return status; +} + +static gcmINLINE gceSTATUS +_SyncFromSystemChannel( + gckCOMMAND Command, + gctBOOL Priority, + gctUINT32 ChannelId + ) +{ + gceSTATUS status; + gckHARDWARE hardware = Command->kernel->hardware; + gctUINT32 reqBytes = 0; + gctUINT32 bytes = 0; + gctUINT8_PTR buffer; + gctUINT32 id; + + if (!(Command->syncChannel[Priority ? 1 : 0] & (1ull << ChannelId))) + { + /* No need to sync. */ + return gcvSTATUS_OK; + } + + /* Get a semaphore. */ + gcmkONERROR(_GetNextMcfeSemaId(Command, gcvFALSE, &id)); + + /* Send the semaphore in system channel. */ + gckMCFE_SendSemaphore(hardware, gcvNULL, 0, &reqBytes); + + gcmkONERROR(gckCOMMAND_Reserve( + Command, + reqBytes, + (gctPOINTER *)&buffer, + &bytes + )); + + gckMCFE_SendSemaphore(hardware, buffer, id, &bytes); + + gcmkONERROR(gckCOMMAND_ExecuteMultiChannel(Command, gcvFALSE, 0, reqBytes)); + + /* Wait the semaphore in specific channel. */ + gckMCFE_WaitSemaphore(hardware, gcvNULL, 0, &reqBytes); + + gcmkONERROR(gckCOMMAND_Reserve( + Command, + reqBytes, + (gctPOINTER *)&buffer, + &bytes + )); + + gckMCFE_WaitSemaphore(hardware, buffer, id, &bytes); + + gcmkONERROR(gckCOMMAND_ExecuteMultiChannel( + Command, + Priority, + ChannelId, + reqBytes + )); + + /* Clear the sync flag. */ + Command->syncChannel[Priority ? 1 : 0] &= ~(1ull << ChannelId); + + /* Can not track the semaphore here. */ + return gcvSTATUS_OK; + +OnError: + return status; +} + +static gceSTATUS +_CheckFlushMcfeMMU( + IN gckCOMMAND Command, + IN gckHARDWARE Hardware + ) +{ + gceSTATUS status = gcvSTATUS_OK; + gctUINT32 oldValue; + gctUINT32 reqBytes; + gctUINT32 bytes; + gctUINT8_PTR buffer; + gctUINT32 id = 0; + + gckOS_AtomicExchange(Command->os, + Hardware->pageTableDirty[gcvENGINE_RENDER], + 0, + &oldValue); + + if (!oldValue) + { + return gcvSTATUS_OK; + } + + /* + * We had sync'ed to system channel in every commit, see comments in Commit. + * It should not run into sync again here, unless there's some other place + * causes channels dirty. Let's check it here. + */ + gcmkONERROR(_SyncToSystemChannel(Command, Command->dirtyChannel)); + + /* Query flush Mcfe MMU cache command bytes. */ + gcmkONERROR(gckHARDWARE_FlushMcfeMMU(Hardware, gcvNULL, &reqBytes)); + + /* Query semaphore command bytes. */ + gcmkONERROR( + gckMCFE_SendSemaphore(Hardware, gcvNULL, 0, &bytes)); + reqBytes += bytes; + + gcmkONERROR( + gckMCFE_WaitSemaphore(Hardware, gcvNULL, 0, &bytes)); + reqBytes += bytes; + + /* Get a semaphore. */ + gcmkONERROR(_GetNextMcfeSemaId(Command, gcvFALSE, &id)); + + /* Request command buffer for system channel. */ + gcmkONERROR(gckCOMMAND_Reserve( + Command, + reqBytes, + (gctPOINTER *)&buffer, + &bytes + )); + + /* Do flush mmu. */ + gckHARDWARE_FlushMcfeMMU(Hardware, buffer, &bytes); + buffer += bytes; + + /* Send and wait semaphore in the system channel itself. */ + gcmkONERROR(gckMCFE_SendSemaphore(Hardware, buffer, id, &bytes)); + buffer += bytes; + + gcmkONERROR(gckMCFE_WaitSemaphore(Hardware, buffer, id, &bytes)); + + /* Execute flush mmu and send semaphores. */ + gcmkONERROR(gckCOMMAND_ExecuteMultiChannel(Command, 0, 0, reqBytes)); + + /* Need sync from system channel. */ + Command->syncChannel[0] = ~1ull; + Command->syncChannel[1] = ~1ull; + + return gcvSTATUS_OK; + +OnError: + return status; +} + +/* + * Find sema id from the map. + * Returns semaphore Id, ie the array index of semaHandleMap. + * -1 if not found. + */ +static gcmINLINE gctINT32 +_FindSemaIdFromMap( + IN gckCOMMAND Command, + IN gctUINT32 SemaHandle + ) +{ + gctUINT32 semaId = Command->nextSemaId; + + do + { + /* + * Only need to check semaId between signaledId (inclusive) and + * nextSemaId (exclusive). + */ + semaId = (semaId == 0) ? (Command->totalSemaId - 1) : (semaId - 1); + + if (Command->semaHandleMap[semaId] == SemaHandle) + { + return (gctINT32)semaId; + } + } + while (semaId != Command->freeSemaId); + + return -1; +} + +/* Put together patch list handling variables. */ +typedef struct _gcsPATCH_LIST_VARIABLE +{ + /* gcvHAL_PATCH_VIDMEM_TIMESTAMP. */ + gctUINT64 maxAsyncTimestamp; + + /* gcvHAL_PATCH_MCFE_SEMAPHORE. */ + gctBOOL semaUsed; +} +gcsPATCH_LIST_VARIABLE; + +/* Patch item handler typedef. */ +typedef gceSTATUS +(* PATCH_ITEM_HANDLER)( + IN gckCOMMAND Command, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer, + IN gctPOINTER Patch, + IN gcsPATCH_LIST_VARIABLE * PatchListVar + ); + +static const gctUINT32 _PatchItemSize[] = +{ + 0, + (gctUINT32)sizeof(gcsHAL_PATCH_VIDMEM_ADDRESS), + (gctUINT32)sizeof(gcsHAL_PATCH_MCFE_SEMAPHORE), + (gctUINT32)sizeof(gcsHAL_PATCH_VIDMEM_TIMESTAMP), +}; + +static gceSTATUS +_HandleVidmemAddressPatch( + IN gckCOMMAND Command, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer, + IN gctPOINTER Patch, + IN gcsPATCH_LIST_VARIABLE * PatchListVar + ) +{ + gceSTATUS status = gcvSTATUS_OK; + gcsHAL_PATCH_VIDMEM_ADDRESS * patch = Patch; + + gcmkHEADER_ARG("Command=%p location=0x%x node=0x%x offset=%x", + Command, patch->location, patch->node, patch->offset); + + (void)status; + (void)patch; + + gcmkFOOTER(); + return gcvSTATUS_OK; +} + +static gceSTATUS +_HandleMCFESemaphorePatch( + IN gckCOMMAND Command, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer, + IN gctPOINTER Patch, + IN gcsPATCH_LIST_VARIABLE * PatchListVar + ) +{ + gckHARDWARE hardware = Command->kernel->hardware; + gctINT32 index; + gctUINT32 semaId; + gceSTATUS status; + gctUINT32 bytes = 8; + gctUINT32 buffer[2]; + gctUINT8_PTR location; + gcsHAL_PATCH_MCFE_SEMAPHORE * patch = (gcsHAL_PATCH_MCFE_SEMAPHORE *)Patch; + + gcmkHEADER_ARG("Command=%p location=0x%x semaHandle=%d", + Command, patch->location, patch->semaHandle); + + index = _FindSemaIdFromMap(Command, patch->semaHandle); + + if (index < 0) + { + status = _GetNextMcfeSemaId(Command, gcvTRUE, &semaId); + + if (gcmIS_ERROR(status)) + { + gcmkONERROR(_SyncToSystemChannel(Command, Command->dirtyChannel)); + gcmkONERROR(_GetNextMcfeSemaId(Command, gcvTRUE, &semaId)); + } + + Command->semaHandleMap[semaId] = patch->semaHandle; + } + else + { + semaId = (gctUINT32)index; + + /* One send must match one wait, will assign new id next time. */ + Command->semaHandleMap[semaId] = 0; + } + + if (patch->sendSema) + { + gcmkONERROR(gckMCFE_SendSemaphore(hardware, buffer, semaId, &bytes)); + } + else + { + gcmkONERROR(gckMCFE_WaitSemaphore(hardware, buffer, semaId, &bytes)); + } + + gcmkASSERT(bytes == 8); + + location = gcmUINT64_TO_PTR(CommandBuffer->logical + patch->location); + + /* Patch the command buffer. */ + gckOS_WriteMemory(Command->os, location, buffer[0]); + gckOS_WriteMemory(Command->os, location + 4, buffer[1]); + + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_HandleTimestampPatch( + IN gckCOMMAND Command, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer, + IN gctPOINTER Patch, + IN gcsPATCH_LIST_VARIABLE * PatchListVar + ) +{ + gceSTATUS status; + gctUINT32 processID; + gckVIDMEM_NODE videoMem = gcvNULL; + gcsHAL_PATCH_VIDMEM_TIMESTAMP * patch = Patch; + gceENGINE engine = Command->feType == gcvHW_FE_ASYNC ? gcvENGINE_BLT + : gcvENGINE_RENDER; + + gcmkHEADER_ARG("Command=%p node=0x%x", Command, patch->handle); + + /* Get the current process ID. */ + gcmkONERROR(gckOS_GetProcessID(&processID)); + + gcmkONERROR( + gckVIDMEM_HANDLE_Lookup(Command->kernel, + processID, + patch->handle, + &videoMem)); + + gcmkVERIFY_OK(gckVIDMEM_NODE_Reference(Command->kernel, videoMem)); + + /* Touch video memory node. */ + gcmkVERIFY_OK( + gckVIDMEM_NODE_SetCommitStamp(Command->kernel, + engine, + videoMem, + Command->commitStamp)); + + if ((engine == gcvENGINE_RENDER) && Command->kernel->asyncCommand) + { + /* Find the latest timestamp of the nodes used in async FE. */ + gctUINT64 stamp = 0; + + /* Get stamp touched async command buffer. */ + gcmkVERIFY_OK( + gckVIDMEM_NODE_GetCommitStamp(Command->kernel, + gcvENGINE_BLT, + videoMem, + &stamp)); + + /* Find latest one. */ + if (PatchListVar->maxAsyncTimestamp < stamp) + { + PatchListVar->maxAsyncTimestamp = stamp; + } + } + +OnError: + if (videoMem) + { + gckVIDMEM_NODE_Dereference(Command->kernel, videoMem); + } + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; +} + +static gceSTATUS +_HandlePatchListSingle( + IN gckCOMMAND Command, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer, + IN gcsHAL_PATCH_LIST * PatchList, + IN gctBOOL NeedCopy, + IN gcsPATCH_LIST_VARIABLE * PatchListVar + ) +{ + gceSTATUS status; + /* 256 bytes for storage. */ + gctUINT64 storage[32]; + gctPOINTER kArray = gcvNULL; + gctPOINTER userPtr = gcvNULL; + gctUINT32 index = 0; + gctUINT32 count = 0; + gctUINT32 itemSize = 0; + gctUINT32 batchCount = 0; + + static const PATCH_ITEM_HANDLER patchHandler[] = + { + gcvNULL, + _HandleVidmemAddressPatch, + _HandleMCFESemaphorePatch, + _HandleTimestampPatch, + }; + PATCH_ITEM_HANDLER handler; + + gcmkHEADER_ARG("Command=%p CommandBuffer=%p PatchList=%p type=%d", + Command, CommandBuffer, PatchList, PatchList->type); + + if (PatchList->type >= gcmCOUNTOF(_PatchItemSize) || PatchList->type >= gcmCOUNTOF(patchHandler)) + { + /* Exceeds buffer max size. */ + gcmkONERROR(gcvSTATUS_INVALID_ARGUMENT); + } + + itemSize = _PatchItemSize[PatchList->type]; + + batchCount = (gctUINT32)(sizeof(storage) / itemSize); + + handler = patchHandler[PatchList->type]; + + while (index < PatchList->count) + { + gctUINT i; + gctUINT8_PTR ptr; + + /* Determine batch count, don't handle too many in one batch. */ + count = PatchList->count - index; + + if (count > batchCount) + { + count = batchCount; + } + + userPtr = gcmUINT64_TO_PTR(PatchList->patchArray + itemSize * index); + + /* Copy/map a patch array batch from user. */ + if (NeedCopy) + { + kArray = storage; + + status = gckOS_CopyFromUserData( + Command->os, + kArray, + userPtr, + itemSize * count + ); + } + else + { + status = gckOS_MapUserPointer( + Command->os, + userPtr, + itemSize * count, + (gctPOINTER *)&kArray + ); + } + + if (gcmIS_ERROR(status)) + { + userPtr = gcvNULL; + gcmkONERROR(status); + } + + /* Advance to next batch. */ + index += count; + + ptr = (gctUINT8_PTR)kArray; + + for (i = 0; i < count; i++) + { + /* Call handler. */ + gcmkONERROR( + handler(Command, CommandBuffer, ptr, PatchListVar)); + + /* Advance to next patch. */ + ptr += itemSize; + } + + /* Unmap user pointer if mapped. */ + if (!NeedCopy) + { + gcmkVERIFY_OK(gckOS_UnmapUserPointer( + Command->os, + userPtr, + itemSize * count, + kArray + )); + } + } + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + if (!NeedCopy && userPtr) + { + gcmkVERIFY_OK(gckOS_UnmapUserPointer( + Command->os, + userPtr, + itemSize * count, + kArray + )); + + userPtr = gcvNULL; + } + + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_HandlePatchList( + IN gckCOMMAND Command, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer, + OUT gcsPATCH_LIST_VARIABLE * PatchListVar + ) +{ + gceSTATUS status; + gctBOOL needCopy = gcvFALSE; + gcsHAL_PATCH_LIST storage; + gcsHAL_PATCH_LIST * kPatchList = gcvNULL; + gctPOINTER userPtr = gcmUINT64_TO_PTR(CommandBuffer->patchHead); + + gcmkHEADER_ARG("Command=%p CommandBuffer=%p", Command, CommandBuffer); + + /* Check wehther we need to copy the structures or not. */ + gcmkONERROR(gckOS_QueryNeedCopy(Command->os, 0, &needCopy)); + + while (userPtr) + { + gctUINT64 next; + + /* Copy/map a patch from user. */ + if (needCopy) + { + kPatchList = &storage; + + status = gckOS_CopyFromUserData( + Command->os, + kPatchList, + userPtr, + sizeof(gcsHAL_PATCH_LIST) + ); + } + else + { + status = gckOS_MapUserPointer( + Command->os, + userPtr, + sizeof(gcsHAL_PATCH_LIST), + (gctPOINTER *)&kPatchList + ); + } + + if (gcmIS_ERROR(status)) + { + userPtr = gcvNULL; + gcmkONERROR(status); + } + + /* Do handle patch. */ + gcmkASSERT(kPatchList->type < gcvHAL_PATCH_TYPE_COUNT); + + gcmkONERROR( + _HandlePatchListSingle(Command, + CommandBuffer, + kPatchList, + needCopy, + PatchListVar)); + + next = kPatchList->next; + + /* Unmap user pointer if mapped. */ + if (!needCopy) + { + gcmkVERIFY_OK(gckOS_UnmapUserPointer( + Command->os, + userPtr, + sizeof(gcsHAL_PATCH_LIST), + kPatchList + )); + } + + /* Advance to next patch from user. */ + userPtr = gcmUINT64_TO_PTR(next); + } + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + if (!needCopy && userPtr) + { + gcmkVERIFY_OK(gckOS_UnmapUserPointer( + Command->os, + userPtr, + sizeof(gcsHAL_PATCH_LIST), + kPatchList + )); + } + + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_WaitForAsyncCommandStamp( + IN gckCOMMAND Command, + IN gctUINT64 Stamp + ) +{ + gctUINT32 bytes; + gceSTATUS status; + gctUINT32 fenceAddress; + gctUINT32 bufferSize; + gctPOINTER pointer; + gckCOMMAND asyncCommand = Command->kernel->asyncCommand; + + gcmkHEADER_ARG("Stamp = 0x%llx", Stamp); + + if (*(gctUINT64 *)asyncCommand->fence->logical >= Stamp) + { + /* Already satisfied, skip. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + } + + fenceAddress = asyncCommand->fence->address; + + gcmkONERROR(gckHARDWARE_WaitFence( + Command->kernel->hardware, + gcvNULL, + Stamp, + fenceAddress, + &bytes + )); + + gcmkONERROR(gckCOMMAND_Reserve( + Command, + bytes, + &pointer, + &bufferSize + )); + + gcmkONERROR(gckHARDWARE_WaitFence( + Command->kernel->hardware, + pointer, + Stamp, + fenceAddress, + &bytes + )); + + gcmkONERROR(gckCOMMAND_Execute(Command, bytes)); + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + gcmkFOOTER(); + return status; +} + +/******************************************************************************\ +****************************** gckCOMMAND API Code ****************************** +\******************************************************************************/ + +/******************************************************************************* +** +** gckCOMMAND_Construct +** +** Construct a new gckCOMMAND object. +** +** INPUT: +** +** gckKERNEL Kernel +** Pointer to an gckKERNEL object. +** +** OUTPUT: +** +** gckCOMMAND * Command +** Pointer to a variable that will hold the pointer to the gckCOMMAND +** object. +*/ +gceSTATUS +gckCOMMAND_Construct( + IN gckKERNEL Kernel, + IN gceHW_FE_TYPE FeType, + OUT gckCOMMAND * Command + ) +{ + gckOS os; + gckCOMMAND command = gcvNULL; + gceSTATUS status; + gctINT i; + gctPOINTER pointer = gcvNULL; + gctSIZE_T pageSize; + + gcmkHEADER_ARG("Kernel=%p", Kernel); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL); + gcmkVERIFY_ARGUMENT(Command != gcvNULL); + + /* Extract the gckOS object. */ + os = Kernel->os; + + /* Allocate the gckCOMMAND structure. */ + gcmkONERROR(gckOS_Allocate(os, gcmSIZEOF(struct _gckCOMMAND), &pointer)); + command = pointer; + + /* Reset the entire object. */ + gcmkONERROR(gckOS_ZeroMemory(command, gcmSIZEOF(struct _gckCOMMAND))); + + /* Initialize the gckCOMMAND object.*/ + command->object.type = gcvOBJ_COMMAND; + command->kernel = Kernel; + command->os = os; + + command->feType = FeType; + + /* Get the command buffer requirements. */ + gcmkONERROR(gckHARDWARE_QueryCommandBuffer( + Kernel->hardware, + gcvENGINE_RENDER, + &command->alignment, + gcvNULL, + gcvNULL + )); + + /* Create the command queue mutex. */ + gcmkONERROR(gckOS_CreateMutex(os, &command->mutexQueue)); + + /* Create the context switching mutex. */ + gcmkONERROR(gckOS_CreateMutex(os, &command->mutexContext)); + + /* Create the context switching mutex. */ + gcmkONERROR(gckOS_CreateMutex(os, &command->mutexContextSeq)); + + /* Create the power management semaphore. */ + gcmkONERROR(gckOS_CreateSemaphore(os, &command->powerSemaphore)); + + /* Create the commit atom. */ + gcmkONERROR(gckOS_AtomConstruct(os, &command->atomCommit)); + + /* Get the page size from teh OS. */ + gcmkONERROR(gckOS_GetPageSize(os, &pageSize)); + + gcmkSAFECASTSIZET(command->pageSize, pageSize); + + /* Get process ID. */ + gcmkONERROR(gckOS_GetProcessID(&command->kernelProcessID)); + + /* Set hardware to pipe 0. */ + command->pipeSelect = gcvPIPE_INVALID; + + /* Pre-allocate the command queues. */ + for (i = 0; i < gcdCOMMAND_QUEUES; ++i) + { +#if !gcdCAPTURE_ONLY_MODE + gcePOOL pool = gcvPOOL_DEFAULT; +#else + gcePOOL pool = gcvPOOL_VIRTUAL; +#endif + + gctSIZE_T size = pageSize; + gckVIDMEM_NODE videoMem = gcvNULL; + gctUINT32 allocFlag = 0; + +#if gcdENABLE_CACHEABLE_COMMAND_BUFFER + allocFlag = gcvALLOC_FLAG_CACHEABLE; +#endif + + /* Allocate video memory node for command buffers. */ + gcmkONERROR(gckKERNEL_AllocateVideoMemory( + Kernel, + 64, + gcvVIDMEM_TYPE_COMMAND, + allocFlag, + &size, + &pool, + &videoMem + )); + + command->queues[i].videoMem = videoMem; + + /* Lock for GPU access. */ + gcmkONERROR(gckVIDMEM_NODE_Lock( + Kernel, + videoMem, + &command->queues[i].address + )); + + /* Lock for kernel side CPU access. */ + gcmkONERROR(gckVIDMEM_NODE_LockCPU( + Kernel, + videoMem, + gcvFALSE, + gcvFALSE, + &command->queues[i].logical + )); + + gcmkONERROR(gckOS_CreateSignal( + os, gcvFALSE, &command->queues[i].signal + )); + + gcmkONERROR(gckOS_Signal( + os, command->queues[i].signal, gcvTRUE + )); + } + +#if gcdRECORD_COMMAND + gcmkONERROR(gckRECORDER_Construct(os, Kernel->hardware, &command->recorder)); +#endif + + gcmkONERROR(gckFENCE_Create( + os, Kernel, &command->fence + )); + + /* No command queue in use yet. */ + command->index = -1; + command->logical = gcvNULL; + command->newQueue = gcvFALSE; + + /* Query mcfe semaphore count. */ + if (FeType == gcvHW_FE_MULTI_CHANNEL) + { + command->totalSemaId = 128; + + /* Empty sema id ring. */ + command->nextSemaId = 0; + command->freeSemaId = command->totalSemaId - 1; + + command->semaMinThreshhold = 16; + + /* Create signals. */ + for (i = 0; i < (gctINT)gcmCOUNTOF(command->pendingSema); i++) + { + gcmkONERROR(gckOS_CreateSignal( + os, + gcvFALSE, + &command->pendingSema[i].signal + )); + } + + /* Empty pending sema tracking ring. */ + command->nextPendingPos = 0; + command->freePendingPos = gcmCOUNTOF(command->pendingSema) - 1; + + /* Allocate sema handle mapping. */ + gcmkONERROR(gckOS_Allocate( + os, + command->totalSemaId * sizeof(gctUINT32), + &pointer + )); + + command->semaHandleMap = (gctUINT32 *)pointer; + + gcmkVERIFY_OK(gckOS_ZeroMemory( + command->semaHandleMap, + command->totalSemaId * sizeof(gctUINT32) + )); + } + + /* Command is not yet running. */ + command->running = gcvFALSE; + + /* Command queue is idle. */ + command->idle = gcvTRUE; + + /* Commit stamp start from 1. */ + command->commitStamp = 1; + + command->dummyDraw = gcvTRUE; + + /* Return pointer to the gckCOMMAND object. */ + *Command = command; + + /* Success. */ + gcmkFOOTER_ARG("*Command=0x%x", *Command); + return gcvSTATUS_OK; + +OnError: + /* Roll back. */ + if (command != gcvNULL) + { + gcmkVERIFY_OK(gckCOMMAND_Destroy(command)); + } + + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +/******************************************************************************* +** +** gckCOMMAND_Destroy +** +** Destroy an gckCOMMAND object. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object to destroy. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_Destroy( + IN gckCOMMAND Command + ) +{ + gctINT i; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + /* Stop the command queue. */ + gcmkVERIFY_OK(gckCOMMAND_Stop(Command)); + + for (i = 0; i < gcdCOMMAND_QUEUES; ++i) + { + if (Command->queues[i].signal) + { + gcmkVERIFY_OK(gckOS_DestroySignal( + Command->os, Command->queues[i].signal + )); + } + + if (Command->queues[i].logical) + { + gcmkVERIFY_OK(gckVIDMEM_NODE_UnlockCPU( + Command->kernel, + Command->queues[i].videoMem, + 0, + gcvFALSE, + gcvFALSE + )); + + gcmkVERIFY_OK(gckVIDMEM_NODE_Unlock( + Command->kernel, + Command->queues[i].videoMem, + 0, + gcvNULL + )); + + gcmkVERIFY_OK(gckVIDMEM_NODE_Dereference( + Command->kernel, + Command->queues[i].videoMem + )); + + Command->queues[i].videoMem = gcvNULL; + Command->queues[i].logical = gcvNULL; + } + } + + if (Command->mutexContext) + { + /* Delete the context switching mutex. */ + gcmkVERIFY_OK(gckOS_DeleteMutex(Command->os, Command->mutexContext)); + } + + if (Command->mutexContextSeq != gcvNULL) + gcmkVERIFY_OK(gckOS_DeleteMutex(Command->os, Command->mutexContextSeq)); + + if (Command->mutexQueue) + { + /* Delete the command queue mutex. */ + gcmkVERIFY_OK(gckOS_DeleteMutex(Command->os, Command->mutexQueue)); + } + + if (Command->powerSemaphore) + { + /* Destroy the power management semaphore. */ + gcmkVERIFY_OK(gckOS_DestroySemaphore(Command->os, Command->powerSemaphore)); + } + + if (Command->atomCommit) + { + /* Destroy the commit atom. */ + gcmkVERIFY_OK(gckOS_AtomDestroy(Command->os, Command->atomCommit)); + } + +#if gcdRECORD_COMMAND + gckRECORDER_Destory(Command->os, Command->recorder); +#endif + + if (Command->stateMap) + { + gcmkOS_SAFE_FREE(Command->os, Command->stateMap); + } + + if (Command->semaHandleMap) + { + gcmkOS_SAFE_FREE(Command->os, Command->semaHandleMap); + } + + if (Command->fence) + { + gcmkVERIFY_OK(gckFENCE_Destory(Command->os, Command->fence)); + } + + /* Mark object as unknown. */ + Command->object.type = gcvOBJ_UNKNOWN; + + /* Free the gckCOMMAND object. */ + gcmkVERIFY_OK(gcmkOS_SAFE_FREE(Command->os, Command)); + + /* Success. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; +} + +/******************************************************************************* +** +** gckCOMMAND_EnterCommit +** +** Acquire command queue synchronization objects. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object to destroy. +** +** gctBOOL FromPower +** Determines whether the call originates from inside the power +** management or not. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_EnterCommit( + IN gckCOMMAND Command, + IN gctBOOL FromPower + ) +{ + gceSTATUS status; + gckHARDWARE hardware; + gctBOOL atomIncremented = gcvFALSE; + gctBOOL semaAcquired = gcvFALSE; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Extract the gckHARDWARE and gckEVENT objects. */ + hardware = Command->kernel->hardware; + gcmkVERIFY_OBJECT(hardware, gcvOBJ_HARDWARE); + + if (!FromPower) + { + /* Increment COMMIT atom to let power management know that a commit is + ** in progress. */ + gcmkONERROR(_IncrementCommitAtom(Command, gcvTRUE)); + atomIncremented = gcvTRUE; + + /* Notify the system the GPU has a commit. */ + gcmkONERROR(gckOS_Broadcast(Command->os, + hardware, + gcvBROADCAST_GPU_COMMIT)); + + /* Acquire the power management semaphore. */ + gcmkONERROR(gckOS_AcquireSemaphore(Command->os, + Command->powerSemaphore)); + semaAcquired = gcvTRUE; + } + + /* Grab the conmmand queue mutex. */ + gcmkONERROR(gckOS_AcquireMutex(Command->os, + Command->mutexQueue, + gcvINFINITE)); + + /* Success. */ + gcmkFOOTER(); + return gcvSTATUS_OK; + +OnError: + if (semaAcquired) + { + /* Release the power management semaphore. */ + gcmkVERIFY_OK(gckOS_ReleaseSemaphore( + Command->os, Command->powerSemaphore + )); + } + + if (atomIncremented) + { + /* Decrement the commit atom. */ + gcmkVERIFY_OK(_IncrementCommitAtom( + Command, gcvFALSE + )); + } + + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +/******************************************************************************* +** +** gckCOMMAND_ExitCommit +** +** Release command queue synchronization objects. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object to destroy. +** +** gctBOOL FromPower +** Determines whether the call originates from inside the power +** management or not. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_ExitCommit( + IN gckCOMMAND Command, + IN gctBOOL FromPower + ) +{ + gceSTATUS status; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Release the power mutex. */ + gcmkONERROR(gckOS_ReleaseMutex(Command->os, Command->mutexQueue)); + + if (!FromPower) + { + /* Release the power management semaphore. */ + gcmkONERROR(gckOS_ReleaseSemaphore(Command->os, + Command->powerSemaphore)); + + /* Decrement the commit atom. */ + gcmkONERROR(_IncrementCommitAtom(Command, gcvFALSE)); + } + + /* Success. */ + gcmkFOOTER(); + return gcvSTATUS_OK; + +OnError: + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_StartWaitLinkFE( + IN gckCOMMAND Command + ) +{ + gceSTATUS status; + gckHARDWARE hardware; + gctUINT32 waitOffset = 0; + gctUINT32 waitLinkBytes; + gctPOINTER logical; + gctUINT32 address; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + if (Command->running) + { + /* Command queue already running. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + } + + /* Extract the gckHARDWARE object. */ + hardware = Command->kernel->hardware; + gcmkVERIFY_OBJECT(hardware, gcvOBJ_HARDWARE); + + /* Query the size of WAIT/LINK command sequence. */ + gcmkONERROR(gckWLFE_WaitLink( + hardware, + gcvNULL, + ~0U, + Command->offset, + &waitLinkBytes, + gcvNULL, + gcvNULL + )); + + if ((Command->pageSize - Command->offset < waitLinkBytes) + || (Command->logical == gcvNULL) + ) + { + /* Start at beginning of a new queue. */ + gcmkONERROR(_NewQueue(Command, gcvTRUE)); + } + + logical = (gctUINT8_PTR) Command->logical + Command->offset; + address = Command->address + Command->offset; + + /* Append WAIT/LINK. */ + gcmkONERROR(gckWLFE_WaitLink( + hardware, + logical, + address, + 0, + &waitLinkBytes, + &waitOffset, + &Command->waitPos.size + )); + + /* Update wait command position. */ + Command->waitPos.videoMem = Command->videoMem; + Command->waitPos.offset = Command->offset + waitOffset; + Command->waitPos.logical = (gctUINT8_PTR) logical + waitOffset; + Command->waitPos.address = address + waitOffset; + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + Command->offset, + logical, + waitLinkBytes + )); + + /* Adjust offset. */ + Command->offset += waitLinkBytes; + Command->newQueue = gcvFALSE; + + gcmkDUMP(Command->os, "#[wait-link: fe start]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_COMMAND, + logical, + address, + waitLinkBytes + ); + +#if gcdSECURITY + /* Start FE by calling security service. */ + gckKERNEL_SecurityStartCommand( + Command->kernel + ); +#else + +#if !gcdCAPTURE_ONLY_MODE + /* Enable command processor. */ + gcmkONERROR(gckWLFE_Execute( + hardware, + address, + waitLinkBytes + )); +#endif + +#endif + + /* Command queue is running. */ + Command->running = gcvTRUE; + + /* Success. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_StartAsyncFE( + IN gckCOMMAND Command + ) +{ + if ((Command->pageSize <= Command->offset) || + (Command->logical == gcvNULL)) + { + /* Start at beginning of a new queue. */ + gcmkVERIFY_OK(_NewQueue(Command, gcvTRUE)); + } + + /* Command queue is running. */ + Command->running = gcvTRUE; + + /* Nothing to do. */ + return gcvSTATUS_OK; +} + +static gceSTATUS +_StartMCFE( + IN gckCOMMAND Command + ) +{ + if ((Command->pageSize <= Command->offset) || + (Command->logical == gcvNULL)) + { + /* Start at beginning of a new queue. */ + gcmkVERIFY_OK(_NewQueue(Command, gcvTRUE)); + } + + /* Command queue is running. */ + Command->running = gcvTRUE; + + /* Nothing to do. */ + return gcvSTATUS_OK; +} + +/******************************************************************************* +** +** gckCOMMAND_Start +** +** Start up the command queue. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object to start. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_Start( + IN gckCOMMAND Command + ) +{ + gceSTATUS status; + + gcmkHEADER_ARG("Command=%p", Command); + + if (Command->feType == gcvHW_FE_WAIT_LINK) + { + gcmkONERROR(_StartWaitLinkFE(Command)); + } + else if (Command->feType == gcvHW_FE_MULTI_CHANNEL) + { + gcmkONERROR(_StartMCFE(Command)); + } + else + { + gcmkONERROR(_StartAsyncFE(Command)); + } + + /* Success. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; +OnError: + /* Return the status. */ + gcmkFOOTER(); + return status; + +} + +static gceSTATUS +_StopWaitLinkFE( + IN gckCOMMAND Command + ) +{ + gckHARDWARE hardware; + gceSTATUS status; + gctUINT32 idle; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + /* Extract the gckHARDWARE object. */ + hardware = Command->kernel->hardware; + gcmkVERIFY_OBJECT(hardware, gcvOBJ_HARDWARE); + + /* Replace last WAIT with END. */ + gcmkONERROR(gckWLFE_End( + hardware, + Command->waitPos.logical, + Command->waitPos.address, + &Command->waitPos.size + )); + + gcmkDUMP(Command->os, "#[end: fe stop]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_COMMAND, + Command->waitPos.logical, + Command->waitPos.address, + Command->waitPos.size + ); + +#if gcdSECURITY + gcmkONERROR(gckKERNEL_SecurityExecute( + Command->kernel, Command->waitPos.logical, 8 + )); +#endif + + /* Update queue tail pointer. */ + gcmkONERROR(gckHARDWARE_UpdateQueueTail(Command->kernel->hardware, + Command->logical, + Command->offset)); + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->waitPos.videoMem, + Command->waitPos.offset, + Command->waitPos.logical, + Command->waitPos.size + )); + + /* Wait for idle. */ + gcmkONERROR(gckHARDWARE_GetIdle(hardware, gcvTRUE, &idle)); + + /* Command queue is no longer running. */ + Command->running = gcvFALSE; + + /* Success. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_StopAsyncFE( + IN gckCOMMAND Command + ) +{ + gckHARDWARE hardware; + gceSTATUS status; + gctUINT32 idle; + + gcmkHEADER_ARG("Command=%p", Command); + + hardware = Command->kernel->hardware; + + /* Update queue tail pointer. */ + gcmkONERROR(gckHARDWARE_UpdateQueueTail(hardware, + Command->logical, + Command->offset)); + + /* Wait for idle. */ + gcmkONERROR(gckHARDWARE_GetIdle(hardware, gcvTRUE, &idle)); + + /* Command queue is no longer running. */ + Command->running = gcvFALSE; + + /* Success. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_StopMCFE( + IN gckCOMMAND Command + ) +{ + gceSTATUS status; + gckHARDWARE hardware; + + gcmkHEADER_ARG("Command=%p", Command); + + hardware = Command->kernel->hardware; + + /* Update queue tail pointer. */ + gcmkONERROR(gckHARDWARE_UpdateQueueTail(hardware, + Command->logical, + Command->offset)); + + /* Command queue is no longer running. */ + Command->running = gcvFALSE; + + /* Success. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +/******************************************************************************* +** +** gckCOMMAND_Stop +** +** Stop the command queue. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object to stop. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_Stop( + IN gckCOMMAND Command + ) +{ + if (!Command->running) + { + /* Command queue is not running. */ + return gcvSTATUS_OK; + } + + if (Command->feType == gcvHW_FE_WAIT_LINK) + { + return _StopWaitLinkFE(Command); + } + else if (Command->feType == gcvHW_FE_MULTI_CHANNEL) + { + return _StopMCFE(Command); + } + else + { + return _StopAsyncFE(Command); + } +} + +static gceSTATUS +_CommitWaitLinkOnce( + IN gckCOMMAND Command, + IN gckCONTEXT Context, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer, + IN gcsSTATE_DELTA_PTR StateDelta, + IN gctUINT32 ProcessID, + IN gctBOOL Shared, + INOUT gctBOOL *contextSwitched + ) +{ + gceSTATUS status; + gctBOOL commitEntered = gcvFALSE; + gctBOOL contextAcquired = gcvFALSE; + gckHARDWARE hardware; + gcsPATCH_LIST_VARIABLE patchListVar = {0, 0}; + + gcsCONTEXT_PTR contextBuffer; + gctUINT8_PTR commandBufferLogical = gcvNULL; + gctUINT32 commandBufferAddress = 0; + gckVIDMEM_NODE commandBufferVideoMem = gcvNULL; + gctUINT8_PTR commandBufferTail = gcvNULL; + gctUINT commandBufferSize; + gctUINT32 linkBytes; + gctSIZE_T bytes; + gctUINT32 offset; + gctPOINTER entryLogical; + gctUINT32 entryAddress; + gctUINT32 entryBytes; + gctUINT32 exitAddress; + gctUINT32 exitBytes; + gctPOINTER waitLinkLogical; + gctUINT32 waitLinkAddress; + gctUINT32 waitLinkBytes; + gctUINT32 waitOffset; + gctUINT32 waitSize; + +#if gcdCAPTURE_ONLY_MODE + gctINT i; +#endif + +#ifdef __QNXNTO__ + gctPOINTER userCommandBufferLogical = gcvNULL; + gctBOOL userCommandBufferLogicalMapped = gcvFALSE; +#endif + +#if gcdDUMP_IN_KERNEL + gctPOINTER contextDumpLogical = gcvNULL; +# endif + gctUINT32 exitLinkLow = 0, exitLinkHigh = 0; + gctUINT32 entryLinkLow = 0, entryLinkHigh = 0; + gctUINT32 commandLinkLow = 0, commandLinkHigh = 0; + + gcmkHEADER_ARG("Command=%p CommandBuffer=%p ProcessID=%d", + Command, CommandBuffer, ProcessID + ); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + gcmkASSERT(Command->feType == gcvHW_FE_WAIT_LINK); + + /* Acquire the command queue. */ + gcmkONERROR(gckCOMMAND_EnterCommit(Command, gcvFALSE)); + commitEntered = gcvTRUE; + + /* Acquire the context switching mutex. */ + gcmkONERROR(gckOS_AcquireMutex( + Command->os, Command->mutexContext, gcvINFINITE + )); + contextAcquired = gcvTRUE; + + /* Extract the gckHARDWARE and gckEVENT objects. */ + hardware = Command->kernel->hardware; + + + gcmkONERROR( + _HandlePatchList(Command, CommandBuffer, &patchListVar)); + + /* Query the size of LINK command. */ + gcmkONERROR(gckWLFE_Link( + hardware, gcvNULL, 0, 0, &linkBytes, gcvNULL, gcvNULL + )); + + /* Compute the command buffer entry and the size. */ + commandBufferLogical + = (gctUINT8_PTR) gcmUINT64_TO_PTR(CommandBuffer->logical) + + CommandBuffer->startOffset; + + commandBufferAddress = CommandBuffer->address + + CommandBuffer->startOffset; + +#ifdef __QNXNTO__ + gcmkONERROR(gckVIDMEM_HANDLE_Lookup( + Command->kernel, + ProcessID, + CommandBuffer->videoMemNode, + &commandBufferVideoMem + )); + + gcmkONERROR(gckVIDMEM_NODE_LockCPU( + Command->kernel, + commandBufferVideoMem, + gcvFALSE, + gcvFALSE, + &userCommandBufferLogical + )); + + commandBufferLogical = (gctUINT8_PTR)userCommandBufferLogical + CommandBuffer->startOffset; + userCommandBufferLogicalMapped =gcvTRUE; +#endif + + commandBufferSize = CommandBuffer->size; + + gcmkONERROR(_CheckFlushMMU(Command, hardware)); + + if (Command->dummyDraw == gcvTRUE && + Context != gcvNULL) + { + Command->dummyDraw = gcvFALSE; + gcmkONERROR(_DummyDraw(Command)); + } + + if (gckHARDWARE_IsFeatureAvailable(hardware, gcvFEATURE_FENCE_64BIT) && + Command->kernel->asyncCommand && + patchListVar.maxAsyncTimestamp != 0) + { + gcmkONERROR(_WaitForAsyncCommandStamp( + Command, + patchListVar.maxAsyncTimestamp + )); + } + + /* Get the current offset. */ + offset = Command->offset; + + /* Compute number of bytes left in current kernel command queue. */ + bytes = Command->pageSize - offset; + + /* Query the size of WAIT/LINK command sequence. */ + gcmkONERROR(gckWLFE_WaitLink( + hardware, + gcvNULL, + ~0U, + offset, + &waitLinkBytes, + gcvNULL, + gcvNULL + )); + + /* Is there enough space in the current command queue? */ + if (bytes < waitLinkBytes) + { + /* No, create a new one. */ + gcmkONERROR(_NewQueue(Command, gcvFALSE)); + + /* Get the new current offset. */ + offset = Command->offset; + + /* Recompute the number of bytes in the new kernel command queue. */ + bytes = Command->pageSize - offset; + gcmkASSERT(bytes >= waitLinkBytes); + } + + /* Compute the location if WAIT/LINK command sequence. */ + waitLinkLogical = (gctUINT8_PTR) Command->logical + offset; + waitLinkAddress = Command->address + offset; + + /* Context switch required? */ + if (Context == gcvNULL) + { + /* See if we have to switch pipes for the command buffer. */ + if (CommandBuffer->entryPipe == (gctUINT32)(Command->pipeSelect)) + { + /* Skip reserved head bytes. */ + offset = CommandBuffer->reservedHead; + } + else + { + gctUINT32 pipeBytes = CommandBuffer->reservedHead; + + /* The current hardware and the entry command buffer pipes + ** are different, switch to the correct pipe. */ + gcmkONERROR(gckHARDWARE_PipeSelect( + Command->kernel->hardware, + commandBufferLogical, + CommandBuffer->entryPipe, + &pipeBytes + )); + + /* Do not skip pipe switching sequence. */ + offset = 0; + + /* Reserved bytes in userspace must be exact for a pipeSelect. */ + gcmkASSERT(pipeBytes == CommandBuffer->reservedHead); + } + + /* Compute the entry. */ + entryLogical = commandBufferLogical + offset; + entryAddress = commandBufferAddress + offset; + entryBytes = commandBufferSize - offset; + + Command->currContext = gcvNULL; + } +#if gcdDEBUG_OPTION && gcdDEBUG_FORCE_CONTEXT_UPDATE + else if (1) +#else + else if (Command->currContext != Context) +#endif + { + /* Get the current context buffer. */ + contextBuffer = Context->buffer; + + /* Yes, merge in the deltas. */ + gcmkONERROR(gckCONTEXT_Update(Context, ProcessID, StateDelta)); + + /*************************************************************** + ** SWITCHING CONTEXT. + */ + + /* Determine context buffer entry offset. */ + offset = (Command->pipeSelect == gcvPIPE_3D) + + /* Skip pipe switching sequence. */ + ? Context->entryOffset3D + Context->pipeSelectBytes + + /* Do not skip pipe switching sequence. */ + : Context->entryOffset3D; + + /* Compute the entry. */ + entryLogical = (gctUINT8_PTR) contextBuffer->logical + offset; + entryAddress = contextBuffer->address + offset; + entryBytes = Context->bufferSize - offset; + + /* See if we have to switch pipes between the context + and command buffers. */ + if (CommandBuffer->entryPipe == gcvPIPE_3D) + { + /* Skip reserved head bytes. */ + offset = CommandBuffer->reservedHead; + } + else + { + gctUINT32 pipeBytes = CommandBuffer->reservedHead; + + /* The current hardware and the initial context pipes are + different, switch to the correct pipe. */ + gcmkONERROR(gckHARDWARE_PipeSelect( + Command->kernel->hardware, + commandBufferLogical, + CommandBuffer->entryPipe, + &pipeBytes + )); + + /* Do not skip pipe switching sequence. */ + offset = 0; + + /* Reserved bytes in userspace must be exact for a pipeSelect. */ + gcmkASSERT(pipeBytes == CommandBuffer->reservedHead); + } + + /* Generate a LINK from the context buffer to + the command buffer. */ + gcmkONERROR(gckWLFE_Link( + hardware, + contextBuffer->link3D, + commandBufferAddress + offset, + commandBufferSize - offset, + &linkBytes, + &commandLinkLow, + &commandLinkHigh + )); + +#if gcdCAPTURE_ONLY_MODE + for (i = 0; i < gcdCONTEXT_BUFFER_COUNT; ++i) + { + gcsCONTEXT_PTR buffer = contextBuffer; + + gckOS_CopyToUserData(Command->os, buffer->logical, CommandBuffer->contextLogical[i], Context->bufferSize); + + buffer = buffer->next; + } +#endif + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + contextBuffer->videoMem, + entryAddress - contextBuffer->address, + entryLogical, + entryBytes + )); + + /* Update the current context. */ + Command->currContext = Context; + + if (contextSwitched) + { + *contextSwitched = gcvTRUE; + } + +#if gcdDUMP_IN_KERNEL + contextDumpLogical = entryLogical; +#endif + +#if gcdSECURITY + /* Commit context buffer to trust zone. */ + gckKERNEL_SecurityExecute( + Command->kernel, + entryLogical, + entryBytes - 8 + ); +#endif + +#if gcdRECORD_COMMAND + gckRECORDER_Record( + Command->recorder, + gcvNULL, + 0xFFFFFFFF, + entryLogical, + entryBytes + ); +#endif + } + + /* Same context. */ + else + { + /* See if we have to switch pipes for the command buffer. */ + if (CommandBuffer->entryPipe == (gctUINT32)(Command->pipeSelect)) + { + /* Skip reserved head bytes. */ + offset = CommandBuffer->reservedHead; + } + else + { + gctUINT32 pipeBytes = CommandBuffer->reservedHead; + + /* The current hardware and the entry command buffer pipes + ** are different, switch to the correct pipe. */ + gcmkONERROR(gckHARDWARE_PipeSelect( + Command->kernel->hardware, + commandBufferLogical, + CommandBuffer->entryPipe, + &pipeBytes + )); + + /* Do not skip pipe switching sequence. */ + offset = 0; + + /* Reserved bytes in userspace must be exact for a pipeSelect. */ + gcmkASSERT(pipeBytes == CommandBuffer->reservedHead); + } + + /* Compute the entry. */ + entryLogical = commandBufferLogical + offset; + entryAddress = commandBufferAddress + offset; + entryBytes = commandBufferSize - offset; + } + + (void)entryLogical; + + /* Determine the location to jump to for the command buffer being + ** scheduled. */ + if (Command->newQueue) + { + /* New command queue, jump to the beginning of it. */ + /* Some extra commands (at beginning) are required for new queue. */ + exitAddress = Command->address; + exitBytes = Command->offset + waitLinkBytes; + } + else + { + /* Still within the preexisting command queue, jump to the new + WAIT/LINK command sequence. */ + exitAddress = waitLinkAddress; + exitBytes = waitLinkBytes; + } + + /* Add a new WAIT/LINK command sequence. When the command buffer which is + currently being scheduled is fully executed by the GPU, the FE will + jump to this WAIT/LINK sequence. */ + gcmkONERROR(gckWLFE_WaitLink( + hardware, + waitLinkLogical, + waitLinkAddress, + offset, + &waitLinkBytes, + &waitOffset, + &waitSize + )); + + if (Command->newQueue) + { + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + 0, + Command->logical, + exitBytes + )); + } + else + { + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + Command->offset, + waitLinkLogical, + exitBytes + )); + } + + /* Determine the location of the TAIL in the command buffer. */ + commandBufferTail + = commandBufferLogical + + commandBufferSize + - CommandBuffer->reservedTail; + + /* Generate command which writes out commit stamp. */ + if (gckHARDWARE_IsFeatureAvailable(hardware, gcvFEATURE_FENCE_64BIT)) + { + gctUINT32 bytes; + + gcmkONERROR(gckHARDWARE_Fence( + hardware, + gcvENGINE_RENDER, + commandBufferTail, + Command->fence->address, + Command->commitStamp, + &bytes + )); + + commandBufferTail += bytes; + } + + /* Generate a LINK from the end of the command buffer being scheduled + back to the kernel command queue. */ +#if !gcdSECURITY + if (Shared == gcvFALSE) + { + gcmkONERROR(gckWLFE_Link( + hardware, + commandBufferTail, + exitAddress, + exitBytes, + &linkBytes, + &exitLinkLow, + &exitLinkHigh + )); + } + else + { + gctUINT8_PTR link = commandBufferTail + CommandBuffer->exitIndex * 16; + gctSIZE_T bytes = 8; + + gcmkONERROR(gckWLFE_ChipEnable( + hardware, + link, + (gceCORE_3D_MASK)(1 << hardware->kernel->chipID), + &bytes + )); + + link += bytes; + + gcmkONERROR(gckWLFE_Link( + hardware, + link, + exitAddress, + exitBytes, + &linkBytes, + &exitLinkLow, + &exitLinkHigh + )); + + link += linkBytes; + } +#endif + + gcmkONERROR(gckVIDMEM_HANDLE_Lookup( + Command->kernel, + ProcessID, + CommandBuffer->videoMemNode, + &commandBufferVideoMem + )); + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + commandBufferVideoMem, + CommandBuffer->startOffset, + commandBufferLogical, + commandBufferSize + )); + +#if gcdRECORD_COMMAND + gckRECORDER_Record( + Command->recorder, + commandBufferLogical + offset, + commandBufferSize - offset, + gcvNULL, + 0xFFFFFFFF + ); + + gckRECORDER_AdvanceIndex(Command->recorder, Command->commitStamp); +#endif + +#if gcdSECURITY + /* Submit command buffer to trust zone. */ + gckKERNEL_SecurityExecute( + Command->kernel, + commandBufferLogical + offset, + commandBufferSize - offset - 8 + ); +#else +#if gcdNULL_DRIVER || gcdCAPTURE_ONLY_MODE + /* + * Skip link to entryAddress. + * Instead, we directly link to final wait link position. + */ + gcmkONERROR(gckWLFE_Link( + hardware, + Command->waitPos.logical, + waitLinkAddress, + waitLinkBytes, + &Command->waitPos.size, + &entryLinkLow, + &entryLinkHigh + )); +# else + /* Generate a LINK from the previous WAIT/LINK command sequence to the + entry determined above (either the context or the command buffer). + This LINK replaces the WAIT instruction from the previous WAIT/LINK + pair, therefore we use WAIT metrics for generation of this LINK. + This action will execute the entire sequence. */ + gcmkONERROR(gckWLFE_Link( + hardware, + Command->waitPos.logical, + entryAddress, + entryBytes, + &Command->waitPos.size, + &entryLinkLow, + &entryLinkHigh + )); +# endif +#endif + +#if gcdLINK_QUEUE_SIZE + /* TODO: What's it? */ + if (Command->kernel->stuckDump >= gcvSTUCK_DUMP_USER_COMMAND) + { + gcuQUEUEDATA data; + + gcmkVERIFY_OK(gckOS_GetProcessID(&data.linkData.pid)); + + data.linkData.start = entryAddress; + data.linkData.end = entryAddress + entryBytes; + data.linkData.linkLow = entryLinkLow; + data.linkData.linkHigh = entryLinkHigh; + + gckQUEUE_Enqueue(&hardware->linkQueue, &data); + + if (commandBufferAddress + offset != entryAddress) + { + data.linkData.start = commandBufferAddress + offset; + data.linkData.end = commandBufferAddress + commandBufferSize; + data.linkData.linkLow = commandLinkLow; + data.linkData.linkHigh = commandLinkHigh; + + gckQUEUE_Enqueue(&hardware->linkQueue, &data); + } + + if (Command->kernel->stuckDump >= gcvSTUCK_DUMP_ALL_COMMAND) + { + data.linkData.start = exitAddress; + data.linkData.end = exitAddress + exitBytes; + data.linkData.linkLow = exitLinkLow; + data.linkData.linkHigh = exitLinkHigh; + + /* Dump kernel command.*/ + gckQUEUE_Enqueue(&hardware->linkQueue, &data); + } + } +#endif + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->waitPos.videoMem, + Command->waitPos.offset, + Command->waitPos.logical, + Command->waitPos.size + )); + + if (entryAddress != commandBufferAddress + offset) + { + gcmkDUMP(Command->os, "#[context]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_CONTEXT, + contextDumpLogical, + entryAddress, + entryBytes + ); + + /* execute context. */ + gcmkDUMP(Command->os, + "@[execute 0 0 0x%08X 0x%08X]", + entryAddress + offset, + entryBytes - offset - 8 + ); + } + + gcmkDUMP(Command->os, "#[command: user]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_COMMAND, + commandBufferLogical + offset, + commandBufferAddress + offset, + commandBufferSize - offset + ); + + /* execute user commands. */ + gcmkDUMP( + Command->os, + "@[execute 0 0 0x%08X 0x%08X]", + commandBufferAddress + + CommandBuffer->reservedHead, + commandBufferSize + - CommandBuffer->reservedHead + - CommandBuffer->reservedTail + ); + + gcmkDUMP(Command->os, "#[wait-link]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_COMMAND, + waitLinkLogical, + waitLinkAddress, + waitLinkBytes + ); + +#if gcdNULL_DRIVER || gcdCAPTURE_ONLY_MODE + gcmkDUMP( + Command->os, + "#[null driver: below command skipped link to 0x%08X 0x%08X]", + entryAddress, + entryBytes + ); +#endif + + gcmkDUMP(Command->os, "#[link: break prev wait-link]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_COMMAND, + Command->waitPos.logical, + Command->waitPos.address, + Command->waitPos.size + ); + + /* Update the current pipe. */ + Command->pipeSelect = CommandBuffer->exitPipe; + + /* Update command queue offset. */ + Command->offset += waitLinkBytes; + Command->newQueue = gcvFALSE; + + /* Update address of last WAIT. */ + Command->waitPos.videoMem = Command->videoMem; + Command->waitPos.offset = Command->offset - waitLinkBytes + waitOffset; + Command->waitPos.logical = (gctUINT8_PTR)waitLinkLogical + waitOffset; + Command->waitPos.address = waitLinkAddress + waitOffset; + Command->waitPos.size = waitSize; + + /* Update queue tail pointer. */ + gcmkONERROR(gckHARDWARE_UpdateQueueTail( + hardware, Command->logical, Command->offset + )); + + /* Release the context switching mutex. */ + gcmkONERROR(gckOS_ReleaseMutex(Command->os, Command->mutexContext)); + contextAcquired = gcvFALSE; + + /* Release the command queue. */ + gcmkONERROR(gckCOMMAND_ExitCommit(Command, gcvFALSE)); + commitEntered = gcvFALSE; + + if (status == gcvSTATUS_INTERRUPTED) + { + gcmkTRACE( + gcvLEVEL_INFO, + "%s(%d): Intterupted in gckEVENT_Submit", + __FUNCTION__, __LINE__ + ); + status = gcvSTATUS_OK; + } + else + { + gcmkONERROR(status); + } + +#ifdef __QNXNTO__ + if(userCommandBufferLogicalMapped) + { + gcmkVERIFY_OK(gckVIDMEM_NODE_UnlockCPU( + Command->kernel, + commandBufferVideoMem, + ProcessID, + gcvFALSE, + gcvFALSE + )); + + userCommandBufferLogicalMapped =gcvFALSE; + } +#endif + + /* Return status. */ + gcmkFOOTER(); + return gcvSTATUS_OK; + +OnError: + if (contextAcquired) + { + /* Release the context switching mutex. */ + gcmkVERIFY_OK(gckOS_ReleaseMutex(Command->os, Command->mutexContext)); + } + + if (commitEntered) + { + /* Release the command queue mutex. */ + gcmkVERIFY_OK(gckCOMMAND_ExitCommit(Command, gcvFALSE)); + } + +#ifdef __QNXNTO__ + if (userCommandBufferLogicalMapped) + { + gcmkVERIFY_OK(gckOS_UnmapUserPointer( + Command->os, + userCommandBufferLogical, + 0, + commandBufferLogical)); + } +#endif + + /* Return status. */ + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_CommitAsyncOnce( + IN gckCOMMAND Command, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer + ) +{ + gceSTATUS status; + gckHARDWARE hardware = Command->kernel->hardware; + gctBOOL available = gcvFALSE; + gctBOOL acquired = gcvFALSE; + gctUINT8_PTR commandBufferLogical; + gctUINT8_PTR commandBufferTail; + gctUINT commandBufferSize; + gctUINT32 commandBufferAddress; + gctUINT32 fenceBytes; + gctUINT32 oldValue; + gctUINT32 flushBytes; + gcsPATCH_LIST_VARIABLE patchListVar = {0, 0}; + + gcmkHEADER(); + + gcmkVERIFY_OK( + _HandlePatchList(Command, CommandBuffer, &patchListVar)); + + gckOS_AtomicExchange(Command->os, + hardware->pageTableDirty[gcvENGINE_BLT], + 0, + &oldValue); + + if (oldValue) + { + gckHARDWARE_FlushAsyncMMU(hardware, gcvNULL, &flushBytes); + + gcmkASSERT(flushBytes <= CommandBuffer->reservedHead); + + /* Compute the command buffer entry to insert the flushMMU commands. */ + commandBufferLogical = (gctUINT8_PTR)gcmUINT64_TO_PTR(CommandBuffer->logical) + + CommandBuffer->startOffset + + CommandBuffer->reservedHead + - flushBytes; + + commandBufferAddress = CommandBuffer->address + + CommandBuffer->startOffset + + CommandBuffer->reservedHead + - flushBytes; + + gckHARDWARE_FlushAsyncMMU(hardware, commandBufferLogical, &flushBytes); + } + else + { + /* Compute the command buffer entry. */ + commandBufferLogical = (gctUINT8_PTR)gcmUINT64_TO_PTR(CommandBuffer->logical) + + CommandBuffer->startOffset + + CommandBuffer->reservedHead; + + commandBufferAddress = CommandBuffer->address + + CommandBuffer->startOffset + + CommandBuffer->reservedHead; + + flushBytes = 0; + } + + commandBufferTail = (gctUINT8_PTR)gcmUINT64_TO_PTR(CommandBuffer->logical) + + CommandBuffer->startOffset + + CommandBuffer->size + - CommandBuffer->reservedTail; + + gcmkONERROR(gckHARDWARE_Fence( + hardware, + gcvENGINE_BLT, + commandBufferTail, + Command->fence->address, + Command->commitStamp, + &fenceBytes + )); + + gcmkASSERT(fenceBytes <= CommandBuffer->reservedTail); + + commandBufferSize = CommandBuffer->size + - CommandBuffer->reservedHead + - CommandBuffer->reservedTail + + flushBytes + + fenceBytes; + + gckOS_AcquireMutex(Command->os, Command->mutexContext, gcvINFINITE); + acquired = gcvTRUE; + + /* Acquire a slot. */ + for (;;) + { + gcmkONERROR(gckASYNC_FE_ReserveSlot(hardware, &available)); + + if (available) + { + break; + } + else + { + gcmkTRACE_ZONE(gcvLEVEL_INFO, _GC_OBJ_ZONE, "No available slot, have to wait"); + + gckOS_Delay(Command->os, 1); + } + } + +#if gcdNULL_DRIVER || gcdCAPTURE_ONLY_MODE + /* Skip submit to hardware for NULL driver. */ + gcmkDUMP(Command->os, "#[null driver: below command is skipped]"); +#endif + + gcmkDUMP(Command->os, "#[async-command: user]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_ASYNC_COMMAND, + commandBufferLogical, + commandBufferAddress, + commandBufferSize + ); + + gcmkDUMP( + Command->os, + "@[execute 1 0 0x%08X 0x%08X]", + commandBufferAddress + + CommandBuffer->reservedHead, + CommandBuffer->size + - CommandBuffer->reservedHead + - CommandBuffer->reservedTail + ); + +#if !gcdNULL_DRIVER + /* Execute command buffer. */ + gckASYNC_FE_Execute(hardware, commandBufferAddress, commandBufferSize); +#endif + + gckOS_ReleaseMutex(Command->os, Command->mutexContext); + acquired = gcvFALSE; + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + if (acquired) + { + gckOS_ReleaseMutex(Command->os, Command->mutexContext); + } + + gcmkFOOTER(); + return status; +} + +static gceSTATUS +_CommitMultiChannelOnce( + IN gckCOMMAND Command, + IN gckCONTEXT Context, + IN gcsHAL_COMMAND_LOCATION * CommandBuffer + ) +{ + gceSTATUS status; + gctBOOL acquired = gcvFALSE; + gctUINT8_PTR commandBufferLogical; + gctUINT commandBufferSize; + gctUINT32 commandBufferAddress; + gckHARDWARE hardware; + gctUINT64 bit; + gcsPATCH_LIST_VARIABLE patchListVar = {0, 0}; + + gcmkHEADER_ARG("priority=%d channelId=%d videoMemNode=%u size=0x%x patchHead=%p", + CommandBuffer->priority, CommandBuffer->channelId, + CommandBuffer->videoMemNode, CommandBuffer->size, + gcmUINT64_TO_PTR(CommandBuffer->patchHead)); + + gcmkASSERT(Command->feType == gcvHW_FE_MULTI_CHANNEL); + + hardware = Command->kernel->hardware; + + gcmkVERIFY_OK( + _HandlePatchList(Command, CommandBuffer, &patchListVar)); + + /* Check flush mcfe MMU cache. */ + gcmkONERROR(_CheckFlushMcfeMMU(Command, hardware)); + + /* Compute the command buffer entry and the size. */ + commandBufferLogical + = (gctUINT8_PTR) gcmUINT64_TO_PTR(CommandBuffer->logical) + + CommandBuffer->startOffset + + CommandBuffer->reservedHead; + + commandBufferAddress = CommandBuffer->address + + CommandBuffer->startOffset + + CommandBuffer->reservedHead; + + /* reservedTail bytes are not used, because fence not enable. */ + commandBufferSize + = CommandBuffer->size + - CommandBuffer->reservedHead + - CommandBuffer->reservedTail; + + if (commandBufferSize & 8) + { + /* + * Need 16 byte alignment for MCFE command size. + * command is already 8 byte aligned, if not 16 byte aligned, + * we need append 8 bytes. + */ + gctUINT32 nop[2]; + gctSIZE_T bytes = 8; + gctUINT8_PTR tail = commandBufferLogical + commandBufferSize; + + gckMCFE_Nop(hardware, nop, &bytes); + gcmkASSERT(bytes == 8); + + gckOS_WriteMemory(Command->os, tail, nop[0]); + gckOS_WriteMemory(Command->os, tail + 4, nop[1]); + + commandBufferSize += 8; + } + + /* Large command buffer size does not make sense. */ + gcmkASSERT(commandBufferSize < 0x800000); + + + if (CommandBuffer->channelId != 0) + { + /* Sync from the system channel. */ + gcmkONERROR(_SyncFromSystemChannel( + Command, + (gctBOOL)CommandBuffer->priority, + (gctUINT32)CommandBuffer->channelId + )); + } + + Command->currContext = Context; + + gckOS_AcquireMutex(Command->os, Command->mutexQueue, gcvINFINITE); + acquired = gcvTRUE; + +#if gcdNULL_DRIVER || gcdCAPTURE_ONLY_MODE + /* Skip submit to hardware for NULL driver. */ + gcmkDUMP(Command->os, "#[null driver: below command is skipped]"); +#endif + + gcmkDUMP(Command->os, "#[mcfe-command: user]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_COMMAND, + commandBufferLogical, + commandBufferAddress, + commandBufferSize + ); + + gcmkDUMP(Command->os, + "@[execute %d %d 0x%08X 0x%08X]", + CommandBuffer->channelId, + CommandBuffer->priority, + commandBufferAddress, + commandBufferSize); + +#if !gcdNULL_DRIVER + /* Execute command buffer. */ + gcmkONERROR(gckMCFE_Execute( + hardware, + (gctBOOL)CommandBuffer->priority, + (gctUINT32)CommandBuffer->channelId, + commandBufferAddress, + commandBufferSize + )); +#endif + + bit = 1ull << CommandBuffer->channelId; + + /* This channel is dirty. */ + Command->dirtyChannel[CommandBuffer->priority ? 1 : 0] |= bit; + + gckOS_ReleaseMutex(Command->os, Command->mutexQueue); + acquired = gcvFALSE; + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + if (acquired) + { + gckOS_ReleaseMutex(Command->os, Command->mutexQueue); + } + + gcmkFOOTER(); + return status; +} + + +/******************************************************************************* +** +** gckCOMMAND_Commit +** +** Commit command buffers to the command queue. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to a gckCOMMAND object. +** +** gcsHAL_SUBCOMMIT * SubCommit +** Commit information, includes context, delta, and command buffer +* locations. +** +** gctUINT32 ProcessID +** Current process ID. +** +** OUTPUT: +** gctBOOL *contextSwitched +** pass context Switch flag to upper +*/ +gceSTATUS +gckCOMMAND_Commit( + IN gckCOMMAND Command, + IN gcsHAL_SUBCOMMIT * SubCommit, + IN gctUINT32 ProcessId, + IN gctBOOL Shared, + OUT gctUINT64_PTR CommitStamp, + INOUT gctBOOL *contextSwitched + ) +{ + gceSTATUS status; + gcsSTATE_DELTA_PTR delta = gcmUINT64_TO_PTR(SubCommit->delta); + gckCONTEXT context = gcvNULL; + gcsHAL_COMMAND_LOCATION *cmdLoc = &SubCommit->commandBuffer; + gcsHAL_COMMAND_LOCATION _cmdLoc; + gctPOINTER userPtr = gcvNULL; + gctBOOL needCopy = gcvFALSE; + + gcmkHEADER_ARG("Command=%p SubCommit=%p delta=%p context=%u pid=%u", + Command, SubCommit, delta, SubCommit->context, ProcessId); + + gcmkVERIFY_OK(gckOS_QueryNeedCopy(Command->os, ProcessId, &needCopy)); + + if (SubCommit->context) + { + context = gckKERNEL_QueryPointerFromName( + Command->kernel, + (gctUINT32)(SubCommit->context) + ); + } + + do + { + gctUINT64 next; + + /* Skip the first nested one. */ + if (userPtr) + { + /* Copy/map command buffer location from user. */ + if (needCopy) + { + cmdLoc = &_cmdLoc; + + status = gckOS_CopyFromUserData( + Command->os, + cmdLoc, + userPtr, + gcmSIZEOF(gcsHAL_COMMAND_LOCATION) + ); + } + else + { + status = gckOS_MapUserPointer( + Command->os, + userPtr, + gcmSIZEOF(gcsHAL_COMMAND_LOCATION), + (gctPOINTER *)&cmdLoc + ); + } + + if (gcmIS_ERROR(status)) + { + userPtr = gcvNULL; + + gcmkONERROR(status); + } + } + + if (Command->feType == gcvHW_FE_WAIT_LINK) + { + /* Commit command buffers. */ + status = _CommitWaitLinkOnce(Command, + context, + cmdLoc, + delta, + ProcessId, + Shared, + contextSwitched); + } + else if (Command->feType == gcvHW_FE_MULTI_CHANNEL) + { + status = _CommitMultiChannelOnce(Command, context, cmdLoc); + } + else + { + gcmkASSERT(Command->feType == gcvHW_FE_ASYNC); + + status = _CommitAsyncOnce(Command, cmdLoc); + } + + if (status != gcvSTATUS_INTERRUPTED) + { + gcmkONERROR(status); + } + + /* Do not need context or delta for later commands. */ + context = gcvNULL; + delta = gcvNULL; + + next = cmdLoc->next; + + /* Unmap user pointer if mapped. */ + if (!needCopy && userPtr) + { + gcmkVERIFY_OK(gckOS_UnmapUserPointer( + Command->os, + userPtr, + gcmSIZEOF(gcsHAL_COMMAND_LOCATION), + cmdLoc + )); + } + + /* Advance to next command buffer location from user. */ + userPtr = gcmUINT64_TO_PTR(next); + } + while (userPtr); + + if (Command->feType == gcvHW_FE_MULTI_CHANNEL) + { + /* + * Semphore synchronization. + * + * Here we blindly sync dirty other channels to the system channel. + * The scenario to sync channels to the system channel: + * 1. Need to sync channels who sent semaphores. + * 2. Need to sync dirty channels when event(interrupt) is to sent. + * 3. Need to sync dirty channels when system channel need run something + * such as flush mmu. + * + * When power management is on, blindly sync dirty channels is OK because + * there's always a event(intrrupt). + * + * The only condition we sync more than needed is: + * a. power manangement is off. + * b. no user event is attached when commit. + * c. no user event is to be submitted in next ioctl. + * That's a rare condition. + * + * Conclusion is that, blindly sync dirty channels is a good choice for + * now. + */ + gcmkONERROR(_SyncToSystemChannel(Command, Command->dirtyChannel)); + } + + /* Output commit stamp. */ + *CommitStamp = Command->commitStamp; + Command->commitStamp++; + + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + if (!needCopy && userPtr) + { + gckOS_UnmapUserPointer( + Command->os, + userPtr, + gcmSIZEOF(gcsHAL_COMMAND_LOCATION), + cmdLoc + ); + } + + gcmkFOOTER(); + return status; +} + + +/******************************************************************************* +** +** gckCOMMAND_Reserve +** +** Reserve space in the command queue. Also acquire the command queue mutex. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object. +** +** gctSIZE_T RequestedBytes +** Number of bytes previously reserved. +** +** OUTPUT: +** +** gctPOINTER * Buffer +** Pointer to a variable that will receive the address of the reserved +** space. +** +** gctSIZE_T * BufferSize +** Pointer to a variable that will receive the number of bytes +** available in the command queue. +*/ +gceSTATUS +gckCOMMAND_Reserve( + IN gckCOMMAND Command, + IN gctUINT32 RequestedBytes, + OUT gctPOINTER * Buffer, + OUT gctUINT32 * BufferSize + ) +{ + gceSTATUS status; + gctUINT32 bytes; + gctUINT32 requiredBytes; + gctUINT32 requestedAligned; + + gcmkHEADER_ARG("Command=%p RequestedBytes=%lu", Command, RequestedBytes); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + if (Command->feType == gcvHW_FE_WAIT_LINK) + { + /* Compute aligned number of reuested bytes. */ + requestedAligned = gcmALIGN(RequestedBytes, Command->alignment); + + /* Another WAIT/LINK command sequence will have to be appended after + the requested area being reserved. Compute the number of bytes + required for WAIT/LINK at the location after the reserved area. */ + gcmkONERROR(gckWLFE_WaitLink( + Command->kernel->hardware, + gcvNULL, + ~0U, + Command->offset + requestedAligned, + &requiredBytes, + gcvNULL, + gcvNULL + )); + + /* Compute total number of bytes required. */ + requiredBytes += requestedAligned; + } + else + { + requiredBytes = gcmALIGN(RequestedBytes, 8); + } + + /* Compute number of bytes available in command queue. */ + bytes = Command->pageSize - Command->offset; + + /* Is there enough space in the current command queue? */ + if (bytes < requiredBytes) + { + /* Create a new command queue. */ + gcmkONERROR(_NewQueue(Command, gcvFALSE)); + + /* Recompute the number of bytes in the new kernel command queue. */ + bytes = Command->pageSize - Command->offset; + + /* Still not enough space? */ + if (bytes < requiredBytes) + { + /* Rare case, not enough room in command queue. */ + gcmkONERROR(gcvSTATUS_BUFFER_TOO_SMALL); + } + } + + /* Return pointer to empty slot command queue. */ + *Buffer = (gctUINT8 *) Command->logical + Command->offset; + + /* Return number of bytes left in command queue. */ + *BufferSize = bytes; + + /* Success. */ + gcmkFOOTER_ARG("*Buffer=0x%x *BufferSize=%lu", *Buffer, *BufferSize); + return gcvSTATUS_OK; + +OnError: + /* Return status. */ + gcmkFOOTER(); + return status; +} + +/******************************************************************************* +** +** gckCOMMAND_Execute +** +** Execute a previously reserved command queue by appending a WAIT/LINK command +** sequence after it and modifying the last WAIT into a LINK command. The +** command FIFO mutex will be released whether this function succeeds or not. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object. +** +** gctSIZE_T RequestedBytes +** Number of bytes previously reserved. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_Execute( + IN gckCOMMAND Command, + IN gctUINT32 RequestedBytes + ) +{ + gceSTATUS status; + + gctUINT8_PTR waitLinkLogical; + gctUINT32 waitLinkAddress; + gctUINT32 waitLinkOffset; + gctUINT32 waitLinkBytes; + + gctUINT32 waitOffset; + gctUINT32 waitBytes; + + gctUINT32 linkLow, linkHigh; + + gctPOINTER execLogical; + gctUINT32 execAddress; + gctUINT32 execBytes; + + gcmkHEADER_ARG("Command=%p RequestedBytes=%lu", Command, RequestedBytes); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + /* Compute offset for WAIT/LINK. */ + waitLinkOffset = Command->offset + RequestedBytes; + + /* Compute number of bytes left in command queue. */ + waitLinkBytes = Command->pageSize - waitLinkOffset; + + /* Compute the location if WAIT/LINK command sequence. */ + waitLinkLogical = (gctUINT8_PTR) Command->logical + waitLinkOffset; + waitLinkAddress = Command->address + waitLinkOffset; + + /* Append WAIT/LINK in command queue. */ + gcmkONERROR(gckWLFE_WaitLink( + Command->kernel->hardware, + waitLinkLogical, + waitLinkAddress, + waitLinkOffset, + &waitLinkBytes, + &waitOffset, + &waitBytes + )); + + + /* Determine the location to jump to for the command buffer being + ** scheduled. */ + if (Command->newQueue) + { + /* New command queue, jump to the beginning of it. */ + execLogical = Command->logical; + execAddress = Command->address; + execBytes = Command->offset + RequestedBytes + waitLinkBytes; + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + 0, + execLogical, + execBytes + )); + } + else + { + /* Still within the preexisting command queue, jump directly to the + reserved area. */ + execLogical = (gctUINT8 *) Command->logical + Command->offset; + execAddress = Command->address + Command->offset; + execBytes = RequestedBytes + waitLinkBytes; + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + Command->offset, + execLogical, + execBytes + )); + } + +#if gcdNULL_DRIVER || gcdCAPTURE_ONLY_MODE + /* + * Skip link to execAddress. + * Instead, we directly link to final wait link position. + */ + gcmkONERROR(gckWLFE_Link( + Command->kernel->hardware, + Command->waitPos.logical, + waitLinkAddress, + waitLinkBytes, + &Command->waitPos.size, + &linkLow, + &linkHigh + )); +#else + /* Convert the last WAIT into a LINK. */ + gcmkONERROR(gckWLFE_Link( + Command->kernel->hardware, + Command->waitPos.logical, + execAddress, + execBytes, + &Command->waitPos.size, + &linkLow, + &linkHigh + )); +#endif + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->waitPos.videoMem, + Command->waitPos.offset, + Command->waitPos.logical, + Command->waitPos.size + )); + +#if gcdLINK_QUEUE_SIZE + if (Command->kernel->stuckDump >= gcvSTUCK_DUMP_ALL_COMMAND) + { + gcuQUEUEDATA data; + + gcmkVERIFY_OK(gckOS_GetProcessID(&data.linkData.pid)); + + data.linkData.start = execAddress; + data.linkData.end = execAddress + execBytes; + data.linkData.linkLow = linkLow; + data.linkData.linkHigh = linkHigh; + + gckQUEUE_Enqueue(&Command->kernel->hardware->linkQueue, &data); + } +#endif + + gcmkDUMP(Command->os, "#[command: kernel execute]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_COMMAND, + execLogical, + execAddress, + execBytes + ); + +#if gcdNULL_DRIVER || gcdCAPTURE_ONLY_MODE + gcmkDUMP(Command->os, + "#[null driver: below command skipped link to 0x%08X 0x%08X]", + execAddress, + execBytes); +#endif + + gcmkDUMP(Command->os, "#[link: break prev wait-link]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_COMMAND, + Command->waitPos.logical, + Command->waitPos.address, + Command->waitPos.size + ); + + /* Update the pointer to the last WAIT. */ + Command->waitPos.videoMem = Command->videoMem; + Command->waitPos.offset = waitLinkOffset + waitOffset; + Command->waitPos.logical = (gctUINT8_PTR)waitLinkLogical + waitOffset; + Command->waitPos.address = waitLinkAddress + waitOffset; + Command->waitPos.size = waitBytes; + + /* Update the command queue. */ + Command->offset += RequestedBytes + waitLinkBytes; + Command->newQueue = gcvFALSE; + + /* Update queue tail pointer. */ + gcmkONERROR(gckHARDWARE_UpdateQueueTail( + Command->kernel->hardware, Command->logical, Command->offset + )); + + /* Success. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +gceSTATUS +gckCOMMAND_ExecuteAsync( + IN gckCOMMAND Command, + IN gctUINT32 RequestedBytes + ) +{ + gceSTATUS status; + gckHARDWARE hardware; + gctBOOL available; + gctPOINTER execLogical; + gctUINT32 execAddress; + gctUINT32 execBytes; + + hardware = Command->kernel->hardware; + + /* Determine the location to jump to for the command buffer being + ** scheduled. */ + if (Command->newQueue) + { + /* New command queue, jump to the beginning of it. */ + execLogical = Command->logical; + execAddress = Command->address; + execBytes = Command->offset + RequestedBytes; + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + 0, + execLogical, + execBytes + )); + } + else + { + /* Still within the preexisting command queue, jump directly to the + reserved area. */ + execLogical = (gctUINT8 *) Command->logical + Command->offset; + execAddress = Command->address + Command->offset; + execBytes = RequestedBytes; + + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + Command->offset, + execLogical, + execBytes + )); + } + + /* Acquire a slot. */ + for (;;) + { + gcmkONERROR(gckASYNC_FE_ReserveSlot(hardware, &available)); + + if (available) + { + break; + } + else + { + gckOS_Delay(Command->os, 1); + } + } + +#if gcdNULL_DRIVER || gcdCAPTURE_ONLY_MODE + /* Skip submit to hardware for NULL driver. */ + gcmkDUMP(Command->os, "#[null driver: below command is skipped]"); +#endif + + gcmkDUMP(Command->os, "#[async-command: kernel execute]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_COMMAND, + execLogical, + execAddress, + execBytes + ); + +#if !gcdNULL_DRIVER + /* Send descriptor. */ + gckASYNC_FE_Execute(hardware, execAddress, execBytes); +#endif + + /* Update the command queue. */ + Command->offset += RequestedBytes; + Command->newQueue = gcvFALSE; + + return gcvSTATUS_OK; + +OnError: + return status; +} + +gceSTATUS +gckCOMMAND_ExecuteMultiChannel( + IN gckCOMMAND Command, + IN gctBOOL Priority, + IN gctUINT32 ChannelId, + IN gctUINT32 RequestedBytes + ) +{ + gceSTATUS status; + gctPOINTER execLogical; + gctUINT32 execAddress; + gctUINT32 execBytes; + + /* Determine the location to jump to for the command buffer being + ** scheduled. */ + if (Command->newQueue) + { + /* New command queue, jump to the beginning of it. */ + execLogical = Command->logical; + execAddress = Command->address; + execBytes = Command->offset + RequestedBytes; + } + else + { + /* Still within the preexisting command queue, jump directly to the + reserved area. */ + execLogical = (gctUINT8 *) Command->logical + Command->offset; + execAddress = Command->address + Command->offset; + execBytes = RequestedBytes; + } + + if (execBytes & 8) + { + gctSIZE_T bytes = 8; + gctUINT8_PTR tail = (gctUINT8_PTR)execLogical + execBytes; + + gckMCFE_Nop(Command->kernel->hardware, tail, &bytes); + gcmkASSERT(bytes == 8); + + execBytes += 8; + RequestedBytes += 8; + } + + if (Command->newQueue) + { + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + 0, + execLogical, + execBytes + )); + } + else + { + gcmkONERROR(gckVIDMEM_NODE_CleanCache( + Command->kernel, + Command->videoMem, + Command->offset, + execLogical, + execBytes + )); + } + +#if gcdNULL_DRIVER || gcdCAPTURE_ONLY_MODE + /* Skip submit to hardware for NULL driver. */ + gcmkDUMP(Command->os, "#[null driver: below command is skipped]"); +#endif + + gcmkDUMP(Command->os, "#[mcfe-command: kernel execute]"); + gcmkDUMP_BUFFER( + Command->os, + gcvDUMP_BUFFER_KERNEL_COMMAND, + execLogical, + execAddress, + execBytes + ); + + gcmkDUMP(Command->os, + "@[execute %u %u 0x%08X 0x%08X]", + ChannelId, Priority, execAddress, execBytes); + +#if !gcdNULL_DRIVER + /* Send descriptor. */ + gcmkONERROR( + gckMCFE_Execute(Command->kernel->hardware, + Priority, + ChannelId, + execAddress, + execBytes)); +#endif + + /* Update the command queue. */ + Command->offset += RequestedBytes; + Command->newQueue = gcvFALSE; + + return gcvSTATUS_OK; + +OnError: + return status; +} + +/******************************************************************************* +** +** gckCOMMAND_Stall +** +** The calling thread will be suspended until the command queue has been +** completed. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to an gckCOMMAND object. +** +** gctBOOL FromPower +** Determines whether the call originates from inside the power +** management or not. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_Stall( + IN gckCOMMAND Command, + IN gctBOOL FromPower + ) +{ + gckOS os; + gckHARDWARE hardware; + gckEVENT eventObject; + gceSTATUS status; + gctSIGNAL signal = gcvNULL; + gctUINT timer = 0; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + /* Extract the gckOS object pointer. */ + os = Command->os; + gcmkVERIFY_OBJECT(os, gcvOBJ_OS); + + /* Extract the gckHARDWARE object pointer. */ + hardware = Command->kernel->hardware; + gcmkVERIFY_OBJECT(hardware, gcvOBJ_HARDWARE); + + /* Extract the gckEVENT object pointer. */ + eventObject = Command->kernel->eventObj; + gcmkVERIFY_OBJECT(eventObject, gcvOBJ_EVENT); + + /* Allocate the signal. */ + gcmkONERROR(gckOS_CreateSignal(os, gcvTRUE, &signal)); + + /* Append the EVENT command to trigger the signal. */ + gcmkONERROR(gckEVENT_Signal(eventObject, signal, gcvKERNEL_PIXEL)); + + /* Submit the event queue. */ + gcmkONERROR(gckEVENT_Submit(eventObject, gcvTRUE, FromPower)); + + gcmkDUMP(Command->os, "#[kernel.stall]"); + + if (status == gcvSTATUS_CHIP_NOT_READY) + { + /* Error. */ + goto OnError; + } + + do + { + /* Wait for the signal. */ + status = gckOS_WaitSignal(os, signal, !FromPower, gcdGPU_ADVANCETIMER); + + if (status == gcvSTATUS_TIMEOUT) + { +#if gcmIS_DEBUG(gcdDEBUG_CODE) + gctUINT32 idle; + + /* Read idle register. */ + gcmkVERIFY_OK(gckHARDWARE_GetIdle( + hardware, gcvFALSE, &idle + )); + + gcmkTRACE( + gcvLEVEL_ERROR, + "%s(%d): idle=%08x", + __FUNCTION__, __LINE__, idle + ); + + gcmkVERIFY_OK(gckOS_MemoryBarrier(os, gcvNULL)); +#endif + + /* Advance timer. */ + timer += gcdGPU_ADVANCETIMER; + } + else if (status == gcvSTATUS_INTERRUPTED) + { + gcmkONERROR(gcvSTATUS_INTERRUPTED); + } + + } + while (gcmIS_ERROR(status)); + + /* Bail out on timeout. */ + if (gcmIS_ERROR(status)) + { + /* Broadcast the stuck GPU. */ + gcmkONERROR(gckOS_Broadcast( + os, hardware, gcvBROADCAST_GPU_STUCK + )); + } + + /* Delete the signal. */ + gcmkVERIFY_OK(gckOS_DestroySignal(os, signal)); + + /* Success. */ + gcmkFOOTER_NO(); + return gcvSTATUS_OK; + +OnError: + if (signal != gcvNULL) + { + /* Free the signal. */ + gcmkVERIFY_OK(gckOS_DestroySignal(os, signal)); + } + + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +#if (gcdENABLE_3D || gcdENABLE_2D) +static gceSTATUS +_AttachWaitLinkFECommand( + IN gckCOMMAND Command, + OUT gckCONTEXT * Context, + OUT gctSIZE_T * MaxState, + OUT gctUINT32 * NumStates, + IN gctUINT32 ProcessID + ) +{ + gceSTATUS status; + gctBOOL acquired = gcvFALSE; + + gcmkHEADER_ARG("Command=%p", Command); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + /* Acquire the context switching mutex. */ + gcmkONERROR(gckOS_AcquireMutex( + Command->os, Command->mutexContext, gcvINFINITE + )); + acquired = gcvTRUE; + + /* Construct a gckCONTEXT object. */ + gcmkONERROR(gckCONTEXT_Construct( + Command->os, + Command->kernel->hardware, + ProcessID, + Context + )); + + /* Return the number of states in the context. */ + * MaxState = (* Context)->maxState; + * NumStates = (* Context)->numStates; + + /* Release the context switching mutex. */ + gcmkONERROR(gckOS_ReleaseMutex(Command->os, Command->mutexContext)); + acquired = gcvFALSE; + + /* Success. */ + gcmkFOOTER_ARG("*Context=0x%x", *Context); + return gcvSTATUS_OK; + +OnError: + /* Release mutex. */ + if (acquired) + { + /* Release the context switching mutex. */ + gcmkVERIFY_OK(gckOS_ReleaseMutex(Command->os, Command->mutexContext)); + acquired = gcvFALSE; + } + + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +/******************************************************************************* +** +** gckCOMMAND_Attach +** +** Attach user process. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to a gckCOMMAND object. +** +** gctUINT32 ProcessID +** Current process ID. +** +** OUTPUT: +** +** gckCONTEXT * Context +** Pointer to a variable that will receive a pointer to a new +** gckCONTEXT object. +** +** gctSIZE_T * StateCount +** Pointer to a variable that will receive the number of states +** in the context buffer. +*/ +gceSTATUS +gckCOMMAND_Attach( + IN gckCOMMAND Command, + OUT gckCONTEXT * Context, + OUT gctSIZE_T * MaxState, + OUT gctUINT32 * NumStates, + IN gctUINT32 ProcessID + ) +{ + gctUINT32 allocationSize; + gctPOINTER pointer; + gceSTATUS status; + + if (Command->feType == gcvHW_FE_WAIT_LINK) + { + status = _AttachWaitLinkFECommand(Command, + Context, + MaxState, + NumStates, + ProcessID); + } + else if (Command->feType == gcvHW_FE_MULTI_CHANNEL) + { + /* + * For mcfe, we only allocate context which is used to + * store profile counters. + */ + allocationSize = gcmSIZEOF(struct _gckCONTEXT); + + /* Allocate the object. */ + gckOS_Allocate(Command->os, allocationSize, &pointer); + if (!pointer) + { + return gcvSTATUS_OUT_OF_MEMORY; + } + *Context = pointer; + /* Reset the entire object. */ + gckOS_ZeroMemory(*Context, allocationSize); + + /* Initialize the gckCONTEXT object. */ + (*Context)->object.type = gcvOBJ_CONTEXT; + (*Context)->os = Command->os; + (*Context)->hardware = Command->kernel->hardware; + *MaxState = 0; + *NumStates = 0; + + status = gcvSTATUS_OK; + } + else + { + /* Nothing to do. */ + *Context = gcvNULL; + *MaxState = 0; + *NumStates = 0; + + status = gcvSTATUS_OK; + } + + return status; +} +#endif + +static gceSTATUS +_DetachWaitLinkFECommand( + IN gckCOMMAND Command, + IN gckCONTEXT Context + ) +{ + gceSTATUS status; + gctBOOL acquired = gcvFALSE; + + gcmkHEADER_ARG("Command=%p Context=%p", Command, Context); + + /* Verify the arguments. */ + gcmkVERIFY_OBJECT(Command, gcvOBJ_COMMAND); + + /* Acquire the context switching mutex. */ + gcmkONERROR(gckOS_AcquireMutex( + Command->os, Command->mutexContext, gcvINFINITE + )); + acquired = gcvTRUE; + + /* Construct a gckCONTEXT object. */ + gcmkONERROR(gckCONTEXT_Destroy(Context)); + + if (Command->currContext == Context) + { + /* Detach from gckCOMMAND object if the destoryed context is current context. */ + Command->currContext = gcvNULL; + } + + /* Release the context switching mutex. */ + gcmkONERROR(gckOS_ReleaseMutex(Command->os, Command->mutexContext)); + acquired = gcvFALSE; + + /* Return the status. */ + gcmkFOOTER(); + return gcvSTATUS_OK; + +OnError: + /* Release mutex. */ + if (acquired) + { + /* Release the context switching mutex. */ + gcmkVERIFY_OK(gckOS_ReleaseMutex(Command->os, Command->mutexContext)); + acquired = gcvFALSE; + } + + /* Return the status. */ + gcmkFOOTER(); + return status; +} + +/******************************************************************************* +** +** gckCOMMAND_Detach +** +** Detach user process. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to a gckCOMMAND object. +** +** gckCONTEXT Context +** Pointer to a gckCONTEXT object to be destroyed. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_Detach( + IN gckCOMMAND Command, + IN gckCONTEXT Context + ) +{ + if (Command->feType == gcvHW_FE_WAIT_LINK) + { + return _DetachWaitLinkFECommand(Command, Context); + } + else if (Command->feType == gcvHW_FE_MULTI_CHANNEL) + { + gcmkOS_SAFE_FREE(Context->os, Context); + return gcvSTATUS_OK; + } + else + { + /* Nothing to do. */ + return gcvSTATUS_OK; + } +} + +static void +_DumpBuffer( + IN gctPOINTER Buffer, + IN gctUINT32 GpuAddress, + IN gctSIZE_T Size + ) +{ + gctSIZE_T i, line, left; + gctUINT32_PTR data = Buffer; + + line = Size / 32; + left = Size % 32; + + for (i = 0; i < line; i++) + { + gcmkPRINT("%08X : %08X %08X %08X %08X %08X %08X %08X %08X", + GpuAddress, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); + data += 8; + GpuAddress += 8 * 4; + } + + switch(left) + { + case 28: + gcmkPRINT("%08X : %08X %08X %08X %08X %08X %08X %08X", + GpuAddress, data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + break; + case 24: + gcmkPRINT("%08X : %08X %08X %08X %08X %08X %08X", + GpuAddress, data[0], data[1], data[2], data[3], data[4], data[5]); + break; + case 20: + gcmkPRINT("%08X : %08X %08X %08X %08X %08X", + GpuAddress, data[0], data[1], data[2], data[3], data[4]); + break; + case 16: + gcmkPRINT("%08X : %08X %08X %08X %08X", + GpuAddress, data[0], data[1], data[2], data[3]); + break; + case 12: + gcmkPRINT("%08X : %08X %08X %08X", + GpuAddress, data[0], data[1], data[2]); + break; + case 8: + gcmkPRINT("%08X : %08X %08X", + GpuAddress, data[0], data[1]); + break; + case 4: + gcmkPRINT("%08X : %08X", + GpuAddress, data[0]); + break; + default: + break; + } +} + +/******************************************************************************* +** +** gckCOMMAND_DumpExecutingBuffer +** +** Dump the command buffer which GPU is executing. +** +** INPUT: +** +** gckCOMMAND Command +** Pointer to a gckCOMMAND object. +** +** OUTPUT: +** +** Nothing. +*/ +gceSTATUS +gckCOMMAND_DumpExecutingBuffer( + IN gckCOMMAND Command + ) +{ + gceSTATUS status; + gckVIDMEM_NODE nodeObject = gcvNULL; + gctUINT32 gpuAddress; + gctPOINTER entry = gcvNULL; + gckOS os = Command->os; + gckKERNEL kernel = Command->kernel; + gctUINT32 i; + gckQUEUE queue = &kernel->hardware->linkQueue; + gctSIZE_T bytes; + gctUINT32 offset; + gctPOINTER entryDump; + gctUINT8 processName[24] = {0}; + + gcmkPRINT("**************************\n"); + gcmkPRINT("**** COMMAND BUF DUMP ****\n"); + gcmkPRINT("**************************\n"); + + gcmkPRINT(" Submitted commit stamp = %lld", Command->commitStamp - 1); + gcmkPRINT(" Executed commit stamp = %lld", *(gctUINT64_PTR)Command->fence->logical); + + gcmkVERIFY_OK(gckOS_ReadRegisterEx(os, kernel->core, 0x664, &gpuAddress)); + gcmkVERIFY_OK(gckOS_ReadRegisterEx(os, kernel->core, 0x664, &gpuAddress)); + + gcmkPRINT("DMA Address 0x%08X", gpuAddress); + + /* Find GPU address in video memory list. */ + status = gckVIDMEM_NODE_Find(kernel, gpuAddress, &nodeObject, &offset); + + if (gcmIS_SUCCESS(status) && nodeObject->type == gcvVIDMEM_TYPE_COMMAND) + { + gcmkONERROR(gckVIDMEM_NODE_LockCPU( + kernel, + nodeObject, + gcvFALSE, + gcvFALSE, + &entryDump + )); + + gcmkVERIFY_OK(gckVIDMEM_NODE_GetSize( + kernel, + nodeObject, + &bytes + )); + + gcmkPRINT("Command buffer around 0x%08X:", gpuAddress); + + /* Align to 4096. */ + offset &= 0xfffff000; + gpuAddress &= 0xfffff000; + + /* Dump max 4096 bytes. */ + bytes = (bytes - offset) > 4096 ? 4096 : (bytes - offset); + + /* Kernel address of page where stall point stay. */ + entryDump = (gctUINT8_PTR)entryDump + offset; + + _DumpBuffer(entryDump, gpuAddress, bytes); + + gcmkVERIFY_OK(gckVIDMEM_NODE_UnlockCPU( + kernel, + nodeObject, + 0, + gcvFALSE, + gcvFALSE + )); + } + else + { + gcmkPRINT("Can not find command buffer around 0x%08X.\n", gpuAddress); + } + + /* new line. */ + gcmkPRINT(""); + + gcmkPRINT("Kernel command buffers:"); + + for (i = 0; i < gcdCOMMAND_QUEUES; i++) + { + entry = Command->queues[i].logical; + gpuAddress = Command->queues[i].address; + + gcmkPRINT("command buffer %d at 0x%08X size %u", + i, gpuAddress, Command->pageSize); + _DumpBuffer(entry, gpuAddress, Command->pageSize); + } + + /* new line. */ + gcmkPRINT(""); + + if (queue->count) + { + gcmkPRINT("Dump Level is %d, dump %d valid record in link queue:", + Command->kernel->stuckDump, queue->count); + } + + for (i = 0; i < queue->count; i++) + { + gcuQUEUEDATA * queueData; + gckLINKDATA linkData; + + gckQUEUE_GetData(queue, i, &queueData); + + linkData = &queueData->linkData; + + /* Get gpu address of this command buffer. */ + gpuAddress = linkData->start; + bytes = linkData->end - gpuAddress; + + processName[0] = '\0'; + gckOS_GetProcessNameByPid(linkData->pid, sizeof(processName), processName); + + gcmkPRINT("Link record %d: [%08X - %08X] from command %08X %08X pid %u (%s):", + i, + linkData->start, + linkData->end, + linkData->linkLow, + linkData->linkHigh, + linkData->pid, + processName); + + /* Find GPU address in video memory list. */ + status = gckVIDMEM_NODE_Find(kernel, gpuAddress, &nodeObject, &offset); + + if (gcmIS_SUCCESS(status) && nodeObject->type == gcvVIDMEM_TYPE_COMMAND) + { + gcmkONERROR(gckVIDMEM_NODE_LockCPU( + kernel, + nodeObject, + gcvFALSE, + gcvFALSE, + &entryDump + )); + + /* Kernel address of page where stall point stay. */ + entryDump = (gctUINT8_PTR)entryDump + offset; + + _DumpBuffer(entryDump, gpuAddress, bytes); + + gcmkVERIFY_OK(gckVIDMEM_NODE_UnlockCPU( + kernel, + nodeObject, + 0, + gcvFALSE, + gcvFALSE + )); + } + else + { + gcmkPRINT("Not found"); + } + + /* new line. */ + gcmkPRINT(""); + } + + return gcvSTATUS_OK; + +OnError: + return status; +} + |