blob: 36ffa46d994374dd0b749b58fbdf5e1cbf539243 [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"
#define _GC_OBJ_ZONE gcvZONE_DATABASE
/*******************************************************************************
***** Private fuctions ********************************************************/
#define _GetSlot(database, x) \
(gctUINT32)(gcmPTR_TO_UINT64(x) % gcmCOUNTOF(database->list))
/*******************************************************************************
** gckKERNEL_FindDatabase
**
** Find a database identified by a process ID and move it to the head of the
** hash list.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gctUINT32 ProcessID
** ProcessID that identifies the database.
**
** gctBOOL LastProcessID
** gcvTRUE if searching for the last known process ID. gcvFALSE if
** we need to search for the process ID specified by the ProcessID
** argument.
**
** OUTPUT:
**
** gcsDATABASE_PTR * Database
** Pointer to a variable receiving the database structure pointer on
** success.
*/
gceSTATUS
gckKERNEL_FindDatabase(
IN gckKERNEL Kernel,
IN gctUINT32 ProcessID,
IN gctBOOL LastProcessID,
OUT gcsDATABASE_PTR * Database
)
{
gceSTATUS status;
gcsDATABASE_PTR database, previous;
gctSIZE_T slot;
gctBOOL acquired = gcvFALSE;
gcmkHEADER_ARG("Kernel=%p ProcessID=%d LastProcessID=%d",
Kernel, ProcessID, LastProcessID);
/* Compute the hash for the database. */
slot = ProcessID % gcmCOUNTOF(Kernel->db->db);
/* Acquire the database mutex. */
gcmkONERROR(
gckOS_AcquireMutex(Kernel->os, Kernel->db->dbMutex, gcvINFINITE));
acquired = gcvTRUE;
/* Check whether we are getting the last known database. */
if (LastProcessID)
{
/* Use last database. */
database = Kernel->db->lastDatabase;
if (database == gcvNULL)
{
/* Database not found. */
gcmkONERROR(gcvSTATUS_INVALID_DATA);
}
}
else
{
/* Walk the hash list. */
for (previous = gcvNULL, database = Kernel->db->db[slot];
database != gcvNULL;
database = database->next)
{
if (database->processID == ProcessID)
{
/* Found it! */
break;
}
previous = database;
}
if (database == gcvNULL)
{
/* Database not found. */
gcmkONERROR(gcvSTATUS_INVALID_DATA);
}
if (previous != gcvNULL)
{
/* Move database to the head of the hash list. */
previous->next = database->next;
database->next = Kernel->db->db[slot];
Kernel->db->db[slot] = database;
}
}
/* Release the database mutex. */
gcmkONERROR(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
/* Return the database. */
*Database = database;
/* Success. */
gcmkFOOTER_ARG("*Database=0x%x", *Database);
return gcvSTATUS_OK;
OnError:
if (acquired)
{
/* Release the database mutex. */
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
}
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
** gckKERNEL_DeinitDatabase
**
** De-init a database structure.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gcsDATABASE_PTR Database
** Pointer to the database structure to deinit.
**
** OUTPUT:
**
** Nothing.
*/
static gceSTATUS
gckKERNEL_DeinitDatabase(
IN gckKERNEL Kernel,
IN gcsDATABASE_PTR Database
)
{
gcmkHEADER_ARG("Kernel=%p Database=%p", Kernel, Database);
if (Database)
{
Database->deleted = gcvFALSE;
/* Destory handle db. */
if (Database->refs)
{
gcmkVERIFY_OK(gckOS_AtomDestroy(Kernel->os, Database->refs));
Database->refs = gcvNULL;
}
if (Database->handleDatabase)
{
gcmkVERIFY_OK(gckKERNEL_DestroyIntegerDatabase(Kernel, Database->handleDatabase));
Database->handleDatabase = gcvNULL;
}
if (Database->handleDatabaseMutex)
{
gcmkVERIFY_OK(gckOS_DeleteMutex(Kernel->os, Database->handleDatabaseMutex));
Database->handleDatabaseMutex = gcvNULL;
}
}
gcmkFOOTER_NO();
return gcvSTATUS_OK;
}
/*******************************************************************************
** gckKERNEL_NewRecord
**
** Create a new database record structure and insert it to the head of the
** database.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gcsDATABASE_PTR Database
** Pointer to a database structure.
**
** OUTPUT:
**
** gcsDATABASE_RECORD_PTR * Record
** Pointer to a variable receiving the database record structure
** pointer on success.
*/
static gceSTATUS
gckKERNEL_NewRecord(
IN gckKERNEL Kernel,
IN gcsDATABASE_PTR Database,
IN gctUINT32 Slot,
OUT gcsDATABASE_RECORD_PTR * Record
)
{
gceSTATUS status;
gctBOOL acquired = gcvFALSE;
gcsDATABASE_RECORD_PTR record = gcvNULL;
gcmkHEADER_ARG("Kernel=%p Database=%p", Kernel, Database);
/* Acquire the database mutex. */
gcmkONERROR(
gckOS_AcquireMutex(Kernel->os, Kernel->db->dbMutex, gcvINFINITE));
acquired = gcvTRUE;
if (Kernel->db->freeRecord != gcvNULL)
{
/* Allocate the record from the free list. */
record = Kernel->db->freeRecord;
Kernel->db->freeRecord = record->next;
}
else
{
gctPOINTER pointer = gcvNULL;
/* Allocate the record from the heap. */
gcmkONERROR(gckOS_Allocate(Kernel->os,
gcmSIZEOF(gcsDATABASE_RECORD),
&pointer));
record = pointer;
}
/* Insert the record in the database. */
record->next = Database->list[Slot];
Database->list[Slot] = record;
/* Release the database mutex. */
gcmkONERROR(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
/* Return the record. */
*Record = record;
/* Success. */
gcmkFOOTER_ARG("*Record=0x%x", *Record);
return gcvSTATUS_OK;
OnError:
if (acquired)
{
/* Release the database mutex. */
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
}
if (record != gcvNULL)
{
gcmkVERIFY_OK(gcmkOS_SAFE_FREE(Kernel->os, record));
}
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
** gckKERNEL_DeleteRecord
**
** Remove a database record from the database and delete its structure.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gcsDATABASE_PTR Database
** Pointer to a database structure.
**
** gceDATABASE_TYPE Type
** Type of the record to remove.
**
** gctPOINTER Data
** Data of the record to remove.
**
** OUTPUT:
**
** gctSIZE_T_PTR Bytes
** Pointer to a variable that receives the size of the record deleted.
** Can be gcvNULL if the size is not required.
*/
static gceSTATUS
gckKERNEL_DeleteRecord(
IN gckKERNEL Kernel,
IN gcsDATABASE_PTR Database,
IN gceDATABASE_TYPE Type,
IN gctPOINTER Data,
OUT gctSIZE_T_PTR Bytes OPTIONAL
)
{
gceSTATUS status;
gctBOOL acquired = gcvFALSE;
gcsDATABASE_RECORD_PTR record, previous;
gctUINT32 slot = _GetSlot(Database, Data);
gcmkHEADER_ARG("Kernel=%p Database=%p Type=%d Data=%p",
Kernel, Database, Type, Data);
/* Acquire the database mutex. */
gcmkONERROR(
gckOS_AcquireMutex(Kernel->os, Kernel->db->dbMutex, gcvINFINITE));
acquired = gcvTRUE;
/* Scan the database for this record. */
for (record = Database->list[slot], previous = gcvNULL;
record != gcvNULL;
record = record->next
)
{
if ((record->type == Type)
&& (record->data == Data)
)
{
/* Found it! */
break;
}
previous = record;
}
if (record == gcvNULL)
{
/* Ouch! This record is not found? */
gcmkONERROR(gcvSTATUS_INVALID_DATA);
}
if (Bytes != gcvNULL)
{
/* Return size of record. */
*Bytes = record->bytes;
}
/* Remove record from database. */
if (previous == gcvNULL)
{
Database->list[slot] = record->next;
}
else
{
previous->next = record->next;
}
/* Insert record in free list. */
record->next = Kernel->db->freeRecord;
Kernel->db->freeRecord = record;
/* Release the database mutex. */
gcmkONERROR(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
/* Success. */
gcmkFOOTER_ARG("*Bytes=%lu", gcmOPT_VALUE(Bytes));
return gcvSTATUS_OK;
OnError:
if (acquired)
{
/* Release the database mutex. */
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
}
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
** gckKERNEL_FindRecord
**
** Find a database record from the database.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gcsDATABASE_PTR Database
** Pointer to a database structure.
**
** gceDATABASE_TYPE Type
** Type of the record to remove.
**
** gctPOINTER Data
** Data of the record to remove.
**
** OUTPUT:
**
** gctSIZE_T_PTR Bytes
** Pointer to a variable that receives the size of the record deleted.
** Can be gcvNULL if the size is not required.
*/
static gceSTATUS
gckKERNEL_FindRecord(
IN gckKERNEL Kernel,
IN gcsDATABASE_PTR Database,
IN gceDATABASE_TYPE Type,
IN gctPOINTER Data,
OUT gcsDATABASE_RECORD_PTR Record
)
{
gceSTATUS status;
gctBOOL acquired = gcvFALSE;
gcsDATABASE_RECORD_PTR record;
gctUINT32 slot = _GetSlot(Database, Data);
gcmkHEADER_ARG("Kernel=%p Database=%p Type=%d Data=%p",
Kernel, Database, Type, Data);
/* Acquire the database mutex. */
gcmkONERROR(
gckOS_AcquireMutex(Kernel->os, Kernel->db->dbMutex, gcvINFINITE));
acquired = gcvTRUE;
/* Scan the database for this record. */
for (record = Database->list[slot];
record != gcvNULL;
record = record->next
)
{
if ((record->type == Type)
&& (record->data == Data)
)
{
/* Found it! */
break;
}
}
if (record == gcvNULL)
{
/* Ouch! This record is not found? */
gcmkONERROR(gcvSTATUS_INVALID_DATA);
}
if (Record != gcvNULL)
{
/* Return information of record. */
gcmkONERROR(
gckOS_MemCopy(Record, record, sizeof(gcsDATABASE_RECORD)));
}
/* Release the database mutex. */
gcmkONERROR(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
/* Success. */
gcmkFOOTER_ARG("Record=0x%x", Record);
return gcvSTATUS_OK;
OnError:
if (acquired)
{
/* Release the database mutex. */
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
}
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
***** Public API **************************************************************/
/*******************************************************************************
** gckKERNEL_CreateProcessDB
**
** Create a new process database.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gctUINT32 ProcessID
** Process ID used to identify the database.
**
** OUTPUT:
**
** Nothing.
*/
gceSTATUS
gckKERNEL_CreateProcessDB(
IN gckKERNEL Kernel,
IN gctUINT32 ProcessID
)
{
gceSTATUS status = gcvSTATUS_OK;
gcsDATABASE_PTR database = gcvNULL;
gctPOINTER pointer = gcvNULL;
gctBOOL acquired = gcvFALSE;
gctSIZE_T slot;
gctUINT32 i;
gcmkHEADER_ARG("Kernel=%p ProcessID=%d", Kernel, ProcessID);
/* Compute the hash for the database. */
slot = ProcessID % gcmCOUNTOF(Kernel->db->db);
/* Acquire the database mutex. */
gcmkONERROR(gckOS_AcquireMutex(Kernel->os, Kernel->db->dbMutex, gcvINFINITE));
acquired = gcvTRUE;
/* Walk the hash list. */
for (database = Kernel->db->db[slot];
database != gcvNULL;
database = database->next)
{
if (database->processID == ProcessID)
{
gctINT32 oldVal = 0;
if (database->deleted)
{
gcmkFATAL("%s(%d): DB of Process=0x%x cannot be reentered since it was in deletion\n",
__FUNCTION__, __LINE__, ProcessID);
gcmkONERROR(gcvSTATUS_INVALID_REQUEST);
}
gcmkVERIFY_OK(gckOS_AtomIncrement(Kernel->os, database->refs, &oldVal));
goto OnExit;
}
}
if (Kernel->db->freeDatabase)
{
/* Allocate a database from the free list. */
database = Kernel->db->freeDatabase;
Kernel->db->freeDatabase = database->next;
}
else
{
/* Allocate a new database from the heap. */
gcmkONERROR(gckOS_Allocate(Kernel->os,
gcmSIZEOF(gcsDATABASE),
&pointer));
gckOS_ZeroMemory(pointer, gcmSIZEOF(gcsDATABASE));
database = pointer;
gcmkONERROR(gckOS_CreateMutex(Kernel->os, &database->counterMutex));
}
/* Initialize the database. */
/* Save the hash slot. */
database->slot = slot;
database->processID = ProcessID;
database->vidMem.bytes = 0;
database->vidMem.maxBytes = 0;
database->vidMem.totalBytes = 0;
database->nonPaged.bytes = 0;
database->nonPaged.maxBytes = 0;
database->nonPaged.totalBytes = 0;
database->mapMemory.bytes = 0;
database->mapMemory.maxBytes = 0;
database->mapMemory.totalBytes = 0;
for (i = 0; i < gcmCOUNTOF(database->list); i++)
{
database->list[i] = gcvNULL;
}
for (i = 0; i < gcvVIDMEM_TYPE_COUNT; i++)
{
database->vidMemType[i].bytes = 0;
database->vidMemType[i].maxBytes = 0;
database->vidMemType[i].totalBytes = 0;
}
for (i = 0; i < gcvPOOL_NUMBER_OF_POOLS; i++)
{
database->vidMemPool[i].bytes = 0;
database->vidMemPool[i].maxBytes = 0;
database->vidMemPool[i].totalBytes = 0;
}
gcmkASSERT(database->refs == gcvNULL);
gcmkONERROR(gckOS_AtomConstruct(Kernel->os, &database->refs));
gcmkONERROR(gckOS_AtomSet(Kernel->os, database->refs, 1));
gcmkASSERT(database->handleDatabase == gcvNULL);
gcmkONERROR(gckKERNEL_CreateIntegerDatabase(Kernel, 64, &database->handleDatabase));
gcmkASSERT(database->handleDatabaseMutex == gcvNULL);
gcmkONERROR(gckOS_CreateMutex(Kernel->os, &database->handleDatabaseMutex));
/* Insert the database into the hash. */
database->next = Kernel->db->db[slot];
Kernel->db->db[slot] = database;
/* Reset idle timer. */
Kernel->db->lastIdle = 0;
OnError:
if (gcmIS_ERROR(status))
{
gcmkVERIFY_OK(gckKERNEL_DeinitDatabase(Kernel, database));
if (pointer)
{
gcmkOS_SAFE_FREE(Kernel->os, pointer);
}
}
OnExit:
if (acquired)
{
/* Release the database mutex. */
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
}
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
** gckKERNEL_AddProcessDB
**
** Add a record to a process database.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gctUINT32 ProcessID
** Process ID used to identify the database.
**
** gceDATABASE_TYPE TYPE
** Type of the record to add.
**
** gctPOINTER Pointer
** Data of the record to add.
**
** gctPHYS_ADDR Physical
** Physical address of the record to add.
**
** gctSIZE_T Size
** Size of the record to add.
**
** OUTPUT:
**
** Nothing.
*/
gceSTATUS
gckKERNEL_AddProcessDB(
IN gckKERNEL Kernel,
IN gctUINT32 ProcessID,
IN gceDATABASE_TYPE Type,
IN gctPOINTER Pointer,
IN gctPHYS_ADDR Physical,
IN gctSIZE_T Size
)
{
gceSTATUS status;
gcsDATABASE_PTR database;
gcsDATABASE_RECORD_PTR record = gcvNULL;
gcsDATABASE_COUNTERS * count;
gctUINT32 vidMemType;
gcePOOL vidMemPool;
gcmkHEADER_ARG("Kernel=%p ProcessID=%d Type=%d Pointer=%p "
"Physical=%p Size=%lu",
Kernel, ProcessID, Type, Pointer, Physical, Size);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
/* Decode type. */
vidMemType = (Type & gcdDB_VIDEO_MEMORY_TYPE_MASK) >> gcdDB_VIDEO_MEMORY_TYPE_SHIFT;
vidMemPool = (Type & gcdDB_VIDEO_MEMORY_POOL_MASK) >> gcdDB_VIDEO_MEMORY_POOL_SHIFT;
Type &= gcdDATABASE_TYPE_MASK;
/* Special case the idle record. */
if (Type == gcvDB_IDLE)
{
gctUINT64 time;
/* Get the current profile time. */
gcmkONERROR(gckOS_GetProfileTick(&time));
if ((ProcessID == 0) && (Kernel->db->lastIdle != 0))
{
/* Out of idle, adjust time it was idle. */
Kernel->db->idleTime += time - Kernel->db->lastIdle;
Kernel->db->lastIdle = 0;
}
else if (ProcessID == 1)
{
/* Save current idle time. */
Kernel->db->lastIdle = time;
}
#if gcdDYNAMIC_SPEED
{
/* Test for first call. */
if (Kernel->db->lastSlowdown == 0)
{
/* Save milliseconds. */
Kernel->db->lastSlowdown = time;
Kernel->db->lastSlowdownIdle = Kernel->db->idleTime;
}
else
{
/* Compute ellapsed time in milliseconds. */
gctUINT delta = gckOS_ProfileToMS(time - Kernel->db->lastSlowdown);
/* Test for end of period. */
if (delta >= gcdDYNAMIC_SPEED)
{
/* Compute number of idle milliseconds. */
gctUINT idle = gckOS_ProfileToMS(
Kernel->db->idleTime - Kernel->db->lastSlowdownIdle);
/* Broadcast to slow down the GPU. */
gcmkONERROR(gckOS_BroadcastCalibrateSpeed(Kernel->os,
Kernel->hardware,
idle,
delta));
/* Save current time. */
Kernel->db->lastSlowdown = time;
Kernel->db->lastSlowdownIdle = Kernel->db->idleTime;
}
}
}
#endif
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
}
/* Verify the arguments. */
gcmkVERIFY_ARGUMENT(Pointer != gcvNULL);
/* Find the database. */
gcmkONERROR(gckKERNEL_FindDatabase(Kernel, ProcessID, gcvFALSE, &database));
/* Create a new record in the database. */
gcmkONERROR(gckKERNEL_NewRecord(Kernel, database, _GetSlot(database, Pointer), &record));
/* Initialize the record. */
record->kernel = Kernel;
record->type = Type;
record->data = Pointer;
record->physical = Physical;
record->bytes = Size;
/* Get pointer to counters. */
switch (Type)
{
case gcvDB_VIDEO_MEMORY:
count = &database->vidMem;
break;
case gcvDB_NON_PAGED:
count = &database->nonPaged;
break;
case gcvDB_CONTIGUOUS:
count = &database->contiguous;
break;
case gcvDB_MAP_MEMORY:
count = &database->mapMemory;
break;
case gcvDB_MAP_USER_MEMORY:
count = &database->mapUserMemory;
break;
default:
count = gcvNULL;
break;
}
gcmkVERIFY_OK(gckOS_AcquireMutex(Kernel->os, database->counterMutex, gcvINFINITE));
if (count != gcvNULL)
{
/* Adjust counters. */
count->totalBytes += Size;
count->bytes += Size;
count->allocCount++;
if (count->bytes > count->maxBytes)
{
count->maxBytes = count->bytes;
}
}
if (Type == gcvDB_VIDEO_MEMORY)
{
count = &database->vidMemType[vidMemType];
/* Adjust counters. */
count->totalBytes += Size;
count->bytes += Size;
count->allocCount++;
if (count->bytes > count->maxBytes)
{
count->maxBytes = count->bytes;
}
count = &database->vidMemPool[vidMemPool];
/* Adjust counters. */
count->totalBytes += Size;
count->bytes += Size;
count->allocCount++;
if (count->bytes > count->maxBytes)
{
count->maxBytes = count->bytes;
}
}
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, database->counterMutex));
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
OnError:
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
** gckKERNEL_RemoveProcessDB
**
** Remove a record from a process database.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gctUINT32 ProcessID
** Process ID used to identify the database.
**
** gceDATABASE_TYPE TYPE
** Type of the record to remove.
**
** gctPOINTER Pointer
** Data of the record to remove.
**
** OUTPUT:
**
** Nothing.
*/
gceSTATUS
gckKERNEL_RemoveProcessDB(
IN gckKERNEL Kernel,
IN gctUINT32 ProcessID,
IN gceDATABASE_TYPE Type,
IN gctPOINTER Pointer
)
{
gceSTATUS status;
gcsDATABASE_PTR database;
gctSIZE_T bytes = 0;
gctUINT32 vidMemType;
gcePOOL vidMemPool;
gcmkHEADER_ARG("Kernel=%p ProcessID=%d Type=%d Pointer=%p",
Kernel, ProcessID, Type, Pointer);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
gcmkVERIFY_ARGUMENT(Pointer != gcvNULL);
/* Decode type. */
vidMemType = (Type & gcdDB_VIDEO_MEMORY_TYPE_MASK) >> gcdDB_VIDEO_MEMORY_TYPE_SHIFT;
vidMemPool = (Type & gcdDB_VIDEO_MEMORY_POOL_MASK) >> gcdDB_VIDEO_MEMORY_POOL_SHIFT;
Type &= gcdDATABASE_TYPE_MASK;
/* Find the database. */
gcmkONERROR(gckKERNEL_FindDatabase(Kernel, ProcessID, gcvFALSE, &database));
/* Delete the record. */
gcmkONERROR(
gckKERNEL_DeleteRecord(Kernel, database, Type, Pointer, &bytes));
gcmkVERIFY_OK(gckOS_AcquireMutex(Kernel->os, database->counterMutex, gcvINFINITE));
/* Update counters. */
switch (Type)
{
case gcvDB_VIDEO_MEMORY:
database->vidMem.bytes -= bytes;
database->vidMem.freeCount++;
database->vidMemType[vidMemType].bytes -= bytes;
database->vidMemType[vidMemType].freeCount++;
database->vidMemPool[vidMemPool].bytes -= bytes;
database->vidMemPool[vidMemPool].freeCount++;
break;
case gcvDB_NON_PAGED:
database->nonPaged.bytes -= bytes;
database->nonPaged.freeCount++;
break;
case gcvDB_CONTIGUOUS:
database->contiguous.bytes -= bytes;
database->contiguous.freeCount++;
break;
case gcvDB_MAP_MEMORY:
database->mapMemory.bytes -= bytes;
database->mapMemory.freeCount++;
break;
case gcvDB_MAP_USER_MEMORY:
database->mapUserMemory.bytes -= bytes;
database->mapUserMemory.freeCount++;
break;
default:
break;
}
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, database->counterMutex));
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
OnError:
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
** gckKERNEL_FindProcessDB
**
** Find a record from a process database.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gctUINT32 ProcessID
** Process ID used to identify the database.
**
** gceDATABASE_TYPE TYPE
** Type of the record to remove.
**
** gctPOINTER Pointer
** Data of the record to remove.
**
** OUTPUT:
**
** gcsDATABASE_RECORD_PTR Record
** Copy of record.
*/
gceSTATUS
gckKERNEL_FindProcessDB(
IN gckKERNEL Kernel,
IN gctUINT32 ProcessID,
IN gctUINT32 ThreadID,
IN gceDATABASE_TYPE Type,
IN gctPOINTER Pointer,
OUT gcsDATABASE_RECORD_PTR Record
)
{
gceSTATUS status;
gcsDATABASE_PTR database;
gcmkHEADER_ARG("Kernel=%p ProcessID=%d Type=%d Pointer=%p",
Kernel, ProcessID, ThreadID, Type, Pointer);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
gcmkVERIFY_ARGUMENT(Pointer != gcvNULL);
/* Find the database. */
gcmkONERROR(gckKERNEL_FindDatabase(Kernel, ProcessID, gcvFALSE, &database));
/* Find the record. */
gcmkONERROR(
gckKERNEL_FindRecord(Kernel, database, Type, Pointer, Record));
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
OnError:
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
** gckKERNEL_DestroyProcessDB
**
** Destroy a process database. If the database contains any records, the data
** inside those records will be deleted as well. This aids in the cleanup if
** a process has died unexpectedly or has memory leaks.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gctUINT32 ProcessID
** Process ID used to identify the database.
**
** OUTPUT:
**
** Nothing.
*/
gceSTATUS
gckKERNEL_DestroyProcessDB(
IN gckKERNEL Kernel,
IN gctUINT32 ProcessID
)
{
gceSTATUS status = gcvSTATUS_OK;
gckKERNEL kernel = Kernel;
gcsDATABASE_PTR previous = gcvNULL;
gcsDATABASE_PTR database = gcvNULL;
gcsDATABASE_PTR db = gcvNULL;
gctBOOL acquired = gcvFALSE;
gctSIZE_T slot;
gctUINT32 i;
gcmkHEADER_ARG("Kernel=%p ProcessID=%d", Kernel, ProcessID);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
/* Compute the hash for the database. */
slot = ProcessID % gcmCOUNTOF(Kernel->db->db);
/* Acquire the database mutex. */
gcmkONERROR(gckOS_AcquireMutex(Kernel->os, Kernel->db->dbMutex, gcvINFINITE));
acquired = gcvTRUE;
/* Walk the hash list. */
for (database = Kernel->db->db[slot];
database != gcvNULL;
database = database->next)
{
if (database->processID == ProcessID)
{
break;
}
}
if (database)
{
gctINT32 oldVal = 0;
gcmkONERROR(gckOS_AtomDecrement(Kernel->os, database->refs, &oldVal));
if (oldVal != 1)
{
goto OnExit;
}
/* Mark it for delete so disallow reenter until really delete it */
gcmkASSERT(!database->deleted);
database->deleted = gcvTRUE;
}
else
{
gcmkFATAL("%s(%d): DB destroy of Process=0x%x cannot match with creation\n",
__FUNCTION__, __LINE__, ProcessID);
gcmkONERROR(gcvSTATUS_NOT_FOUND);
}
#if gcdCAPTURE_ONLY_MODE
gcmkPRINT("Capture only mode: The max allocation from System Pool is %llu bytes", database->vidMemPool[gcvPOOL_SYSTEM].maxBytes);
#endif
/* Cannot remove the database from the hash list
** since later records deinit need to access from the hash
*/
gcmkONERROR(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
acquired = gcvFALSE;
gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_DATABASE,
"DB(%d): VidMem: total=%lu max=%lu",
ProcessID, database->vidMem.totalBytes,
database->vidMem.maxBytes);
gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_DATABASE,
"DB(%d): NonPaged: total=%lu max=%lu",
ProcessID, database->nonPaged.totalBytes,
database->nonPaged.maxBytes);
gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_DATABASE,
"DB(%d): Idle time=%llu",
ProcessID, Kernel->db->idleTime);
gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_DATABASE,
"DB(%d): Map: total=%lu max=%lu",
ProcessID, database->mapMemory.totalBytes,
database->mapMemory.maxBytes);
{
gcmkTRACE_ZONE(gcvLEVEL_WARNING, gcvZONE_DATABASE,
"Process %d has entries in its database:",
ProcessID);
}
for (i = 0; i < gcmCOUNTOF(database->list); i++)
{
gcsDATABASE_RECORD_PTR record, next;
/* Walk all records. */
for (record = database->list[i]; record != gcvNULL; record = next)
{
gctBOOL asynchronous = gcvTRUE;
gckVIDMEM_NODE nodeObject;
gctPHYS_ADDR physical;
gctUINT32 handle;
/* Next next record. */
next = record->next;
/* Dispatch on record type. */
switch (record->type)
{
case gcvDB_VIDEO_MEMORY:
gcmkERR_BREAK(gckVIDMEM_HANDLE_Lookup(record->kernel,
ProcessID,
gcmPTR2INT32(record->data),
&nodeObject));
/* Free the video memory. */
gcmkVERIFY_OK(gckVIDMEM_HANDLE_Dereference(record->kernel,
ProcessID,
gcmPTR2INT32(record->data)));
gcmkVERIFY_OK(gckVIDMEM_NODE_Dereference(record->kernel,
nodeObject));
gcmkTRACE_ZONE(gcvLEVEL_WARNING, gcvZONE_DATABASE,
"DB: VIDEO_MEMORY 0x%x (status=%d)",
record->data, status);
break;
case gcvDB_NON_PAGED:
physical = gcmNAME_TO_PTR(record->physical);
/* Free the non paged memory. */
status = gckOS_FreeNonPagedMemory(Kernel->os,
physical,
record->data,
record->bytes);
gcmRELEASE_NAME(record->physical);
gcmkTRACE_ZONE(gcvLEVEL_WARNING, gcvZONE_DATABASE,
"DB: NON_PAGED 0x%x, bytes=%lu (status=%d)",
record->data, record->bytes, status);
break;
case gcvDB_SIGNAL:
#if USE_NEW_LINUX_SIGNAL
status = gcvSTATUS_NOT_SUPPORTED;
#else
/* Free the user signal. */
status = gckOS_DestroyUserSignal(Kernel->os,
gcmPTR2INT32(record->data));
#endif /* USE_NEW_LINUX_SIGNAL */
gcmkTRACE_ZONE(gcvLEVEL_WARNING, gcvZONE_DATABASE,
"DB: SIGNAL %d (status=%d)",
(gctINT)(gctUINTPTR_T)record->data, status);
break;
case gcvDB_VIDEO_MEMORY_LOCKED:
handle = gcmPTR2INT32(record->data);
gcmkERR_BREAK(gckVIDMEM_HANDLE_Lookup(record->kernel,
ProcessID,
handle,
&nodeObject));
/* Unlock CPU. */
gcmkVERIFY_OK(gckVIDMEM_NODE_UnlockCPU(
record->kernel, nodeObject, ProcessID, gcvTRUE, gcvFALSE));
/* Unlock what we still locked */
status = gckVIDMEM_NODE_Unlock(record->kernel,
nodeObject,
ProcessID,
&asynchronous);
{
/* Deref handle. */
gcmkVERIFY_OK(gckVIDMEM_HANDLE_Dereference(record->kernel,
ProcessID,
handle));
if (gcmIS_SUCCESS(status) && (gcvTRUE == asynchronous))
{
/* Schedule unlock: will unlock and deref node later. */
status = gckEVENT_Unlock(record->kernel->eventObj,
gcvKERNEL_PIXEL,
nodeObject);
}
else
{
/* Deref node */
gcmkVERIFY_OK(gckVIDMEM_NODE_Dereference(record->kernel,
nodeObject));
}
}
gcmkTRACE_ZONE(gcvLEVEL_WARNING, gcvZONE_DATABASE,
"DB: VIDEO_MEMORY_LOCKED 0x%x (status=%d)",
record->data, status);
break;
case gcvDB_CONTEXT:
status = gckCOMMAND_Detach(record->kernel->command, gcmNAME_TO_PTR(record->data));
gcmRELEASE_NAME(record->data);
gcmkTRACE_ZONE(gcvLEVEL_WARNING, gcvZONE_DATABASE,
"DB: CONTEXT 0x%x (status=%d)",
record->data, status);
break;
case gcvDB_MAP_MEMORY:
/* Unmap memory. */
status = gckKERNEL_UnmapMemory(record->kernel,
record->physical,
record->bytes,
record->data,
ProcessID);
gcmkTRACE_ZONE(gcvLEVEL_WARNING, gcvZONE_DATABASE,
"DB: MAP MEMORY %d (status=%d)",
gcmPTR2INT32(record->data), status);
break;
case gcvDB_SHBUF:
/* Free shared buffer. */
status = gckKERNEL_DestroyShBuffer(record->kernel,
(gctSHBUF) record->data);
gcmkTRACE_ZONE(gcvLEVEL_WARNING, gcvZONE_DATABASE,
"DB: SHBUF %u (status=%d)",
(gctUINT32)(gctUINTPTR_T) record->data, status);
break;
default:
gcmkTRACE_ZONE(gcvLEVEL_ERROR, gcvZONE_DATABASE,
"DB: Correcupted record=0x%08x type=%d",
record, record->type);
break;
}
/* Delete the record. */
gcmkONERROR(gckKERNEL_DeleteRecord(Kernel,
database,
record->type,
record->data,
gcvNULL));
}
}
gcmkONERROR(gckKERNEL_DestroyProcessReservedUserMap(Kernel, ProcessID));
/* Acquire the database mutex. */
gcmkONERROR(gckOS_AcquireMutex(Kernel->os, Kernel->db->dbMutex, gcvINFINITE));
acquired = gcvTRUE;
/* Walk the hash list. */
for (db = Kernel->db->db[slot];
db != gcvNULL;
db = db->next)
{
if (db->processID == ProcessID)
{
break;
}
previous = db;
}
if (db != database || !db->deleted)
{
gcmkFATAL("%s(%d): DB of Process=0x%x corrupted after found in deletion\n",
__FUNCTION__, __LINE__, ProcessID);
gcmkONERROR(gcvSTATUS_NOT_FOUND);
}
/* Remove the database from the hash list. */
if (previous)
{
previous->next = database->next;
}
else
{
Kernel->db->db[slot] = database->next;
}
/* Deinit current database. */
gcmkVERIFY_OK(gckKERNEL_DeinitDatabase(Kernel, database));
if (Kernel->db->lastDatabase)
{
/* Insert last database to the free list. */
Kernel->db->lastDatabase->next = Kernel->db->freeDatabase;
Kernel->db->freeDatabase = Kernel->db->lastDatabase;
}
/* Update last database to current one. */
Kernel->db->lastDatabase = database;
OnError:
OnExit:
if (acquired)
{
/* Release the database mutex. */
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
}
/* Return the status. */
gcmkFOOTER();
return status;
}
/*******************************************************************************
** gckKERNEL_QueryProcessDB
**
** Query a process database for the current usage of a particular record type.
**
** INPUT:
**
** gckKERNEL Kernel
** Pointer to a gckKERNEL object.
**
** gctUINT32 ProcessID
** Process ID used to identify the database.
**
** gctBOOL LastProcessID
** gcvTRUE if searching for the last known process ID. gcvFALSE if
** we need to search for the process ID specified by the ProcessID
** argument.
**
** gceDATABASE_TYPE Type
** Type of the record to query.
**
** OUTPUT:
**
** gcuDATABASE_INFO * Info
** Pointer to a variable that receives the requested information.
*/
gceSTATUS
gckKERNEL_QueryProcessDB(
IN gckKERNEL Kernel,
IN gctUINT32 ProcessID,
IN gctBOOL LastProcessID,
IN gceDATABASE_TYPE Type,
OUT gcuDATABASE_INFO * Info
)
{
gceSTATUS status;
gcsDATABASE_PTR database;
gcePOOL vidMemPool;
gcmkHEADER_ARG("Kernel=%p ProcessID=%d Type=%d Info=%p",
Kernel, ProcessID, Type, Info);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
gcmkVERIFY_ARGUMENT(Info != gcvNULL);
/* Deocde pool. */
vidMemPool = (Type & gcdDB_VIDEO_MEMORY_POOL_MASK) >> gcdDB_VIDEO_MEMORY_POOL_SHIFT;
Type &= gcdDATABASE_TYPE_MASK;
/* Find the database. */
gcmkONERROR(gckKERNEL_FindDatabase(Kernel, ProcessID, LastProcessID, &database));
gcmkVERIFY_OK(gckOS_AcquireMutex(Kernel->os, database->counterMutex, gcvINFINITE));
/* Get pointer to counters. */
switch (Type)
{
case gcvDB_VIDEO_MEMORY:
if (vidMemPool != gcvPOOL_UNKNOWN)
{
gckOS_MemCopy(&Info->counters,
&database->vidMemPool[vidMemPool],
gcmSIZEOF(database->vidMemPool[vidMemPool]));
}
else
{
gckOS_MemCopy(&Info->counters,
&database->vidMem,
gcmSIZEOF(database->vidMem));
}
break;
case gcvDB_NON_PAGED:
gckOS_MemCopy(&Info->counters,
&database->nonPaged,
gcmSIZEOF(database->vidMem));
break;
case gcvDB_CONTIGUOUS:
gckOS_MemCopy(&Info->counters,
&database->contiguous,
gcmSIZEOF(database->vidMem));
break;
case gcvDB_IDLE:
Info->time = Kernel->db->idleTime;
Kernel->db->idleTime = 0;
break;
case gcvDB_MAP_MEMORY:
gckOS_MemCopy(&Info->counters,
&database->mapMemory,
gcmSIZEOF(database->mapMemory));
break;
default:
break;
}
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, database->counterMutex));
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
OnError:
/* Return the status. */
gcmkFOOTER();
return status;
}
gceSTATUS
gckKERNEL_FindHandleDatbase(
IN gckKERNEL Kernel,
IN gctUINT32 ProcessID,
OUT gctPOINTER * HandleDatabase,
OUT gctPOINTER * HandleDatabaseMutex
)
{
gceSTATUS status;
gcsDATABASE_PTR database;
gcmkHEADER_ARG("Kernel=%p ProcessID=%d",
Kernel, ProcessID);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
/* Find the database. */
gcmkONERROR(gckKERNEL_FindDatabase(Kernel, ProcessID, gcvFALSE, &database));
*HandleDatabase = database->handleDatabase;
*HandleDatabaseMutex = database->handleDatabaseMutex;
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
OnError:
/* Return the status. */
gcmkFOOTER();
return status;
}
gceSTATUS
gckKERNEL_DumpProcessDB(
IN gckKERNEL Kernel
)
{
gcsDATABASE_PTR database;
gctINT i, pid;
gctUINT8 name[24];
gcmkHEADER_ARG("Kernel=%p", Kernel);
/* Acquire the database mutex. */
gcmkVERIFY_OK(
gckOS_AcquireMutex(Kernel->os, Kernel->db->dbMutex, gcvINFINITE));
gcmkPRINT("**************************\n");
gcmkPRINT("*** PROCESS DB DUMP ***\n");
gcmkPRINT("**************************\n");
gcmkPRINT_N(8, "%-8s%s\n", "PID", "NAME");
/* Walk the databases. */
for (i = 0; i < gcmCOUNTOF(Kernel->db->db); ++i)
{
for (database = Kernel->db->db[i];
database != gcvNULL;
database = database->next)
{
pid = database->processID;
gcmkVERIFY_OK(gckOS_ZeroMemory(name, gcmSIZEOF(name)));
gcmkVERIFY_OK(gckOS_GetProcessNameByPid(pid, gcmSIZEOF(name), name));
gcmkPRINT_N(8, "%-8d%s\n", pid, name);
}
}
/* Release the database mutex. */
gcmkVERIFY_OK(gckOS_ReleaseMutex(Kernel->os, Kernel->db->dbMutex));
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
}
void
_DumpCounter(
IN gcsDATABASE_COUNTERS * Counter,
IN gctCONST_STRING Name
)
{
gcmkPRINT("%s:", Name);
gcmkPRINT(" Currently allocated : %10lld", Counter->bytes);
gcmkPRINT(" Maximum allocated : %10lld", Counter->maxBytes);
gcmkPRINT(" Total allocated : %10lld", Counter->totalBytes);
}
gceSTATUS
gckKERNEL_DumpVidMemUsage(
IN gckKERNEL Kernel,
IN gctINT32 ProcessID
)
{
gceSTATUS status;
gcsDATABASE_PTR database;
gcsDATABASE_COUNTERS * counter;
gctUINT32 i = 0;
static gctCONST_STRING vidmemTypes[] = {
"GENERIC",
"INDEX",
"VERTEX",
"TEXTURE",
"RENDER_TARGET",
"DEPTH",
"BITMAP",
"TILE_STATUS",
"IMAGE",
"MASK",
"SCISSOR",
"HIERARCHICAL_DEPTH",
"ICACHE",
"TXDESC",
"FENCE",
"TFBHEADER",
"COMMAND",
};
gcmkHEADER_ARG("Kernel=%p ProcessID=%d",
Kernel, ProcessID);
gcmSTATIC_ASSERT(gcmCOUNTOF(vidmemTypes) == gcvVIDMEM_TYPE_COUNT,
"Video memory type mismatch");
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
/* Find the database. */
gcmkONERROR(
gckKERNEL_FindDatabase(Kernel, ProcessID, gcvFALSE, &database));
gcmkPRINT("VidMem Usage (Process %d):", ProcessID);
/* Get pointer to counters. */
counter = &database->vidMem;
_DumpCounter(counter, "Total Video Memory");
for (i = 0; i < gcvVIDMEM_TYPE_COUNT; i++)
{
counter = &database->vidMemType[i];
_DumpCounter(counter, vidmemTypes[i]);
}
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
OnError:
/* Return the status. */
gcmkFOOTER();
return status;
}