blob: 684e397b8b9ec18f24a5b5a3df63a524ad1cbf07 [file] [log] [blame]
/****************************************************************************
*
* 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_NUM; ++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)
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;
}