/*******************************************************************************
**
**	gckKERNEL_GetVideoMemoryPool
**
**	Get the gckVIDMEM object belonging to the specified pool.
**
**	INPUT:
**
**		gckKERNEL Kernel
**			Pointer to an gckKERNEL object.
**
**		gcePOOL Pool
**			Pool to query gckVIDMEM object for.
**
**	OUTPUT:
**
**		gckVIDMEM * VideoMemory
**			Pointer to a variable that will hold the pointer to the gckVIDMEM
**			object belonging to the requested pool.
*/
gceSTATUS gckKERNEL_GetVideoMemoryPool(
	IN gckKERNEL Kernel,
	IN gcePOOL Pool,
	OUT gckVIDMEM * VideoMemory
	)
{
	GCHAL * gchal;
	gckVIDMEM videoMemory;
	gceSTATUS status;

    gcmkHEADER_ARG("Kernel=%p Pool=%d", Kernel, Pool);

	gcmkTRACE_ZONE(gcvLEVEL_VERBOSE, gcvZONE_KERNEL,
			   "[ENTER] gckHARDWARE_GetVideoMemoryPool");

	/* Verify the arguments. */
	gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
	gcmkVERIFY_ARGUMENT(VideoMemory != gcvNULL);

    /* Extract the pointer to the GCHAL class. */
    gchal = (GCHAL *) Kernel->context;

	/* Dispatch on pool. */
	switch (Pool)
	{
	case gcvPOOL_LOCAL_INTERNAL:
		/* Internal memory. */
		videoMemory = gchal->GetInternalHeap();
		break;

	case gcvPOOL_LOCAL_EXTERNAL:
		/* External memory. */
		videoMemory = gchal->GetExternalHeap();
		break;

	case gcvPOOL_SYSTEM:
		/* System memory. */
		videoMemory = gchal->GetContiguousHeap();
		break;

	default:
		/* Unknown pool. */
		videoMemory = gcvNULL;
		gcmkFATAL("Unknown memory pool: %u", Pool);
	}

	/* Return pointer to the gckVIDMEM object. */
	*VideoMemory = videoMemory;

	/* Determine the status. */
	status = (videoMemory == gcvNULL) ? gcvSTATUS_OUT_OF_MEMORY : gcvSTATUS_OK;

	if (gcmIS_SUCCESS(status))
	{
		gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_KERNEL,
				   "gckHARDWARE_GetVideoMemoryPool: Pool %u starts at %p",
				   Pool, videoMemory);
	}

	gcmkTRACE_ZONE(gcvLEVEL_VERBOSE, gcvZONE_KERNEL,
			   "[LEAVE] gckHARDWARE_GetVideoMemoryPool(%u)",
			   status);

	/* Return the status. */
    gcmkFOOTER_ARG("status=%d, *VideoMemory=%p", status, gcmOPT_VALUE(VideoMemory));
	return status;
}
/*******************************************************************************
**
**  gckVIDMEM_Unlock
**
**  Unlock a video memory node.
**
**  INPUT:
**
**      gcuVIDMEM_NODE_PTR Node
**          Pointer to a locked gcuVIDMEM_NODE union.
**
**      gceSURF_TYPE Type
**          Type of surface to unlock.
**
**      gctSIZE_T * CommandSize
**          Pointer to a variable specifying the number of bytes in the command
**          buffer specified by 'Commands'.  If gcvNULL, there is no command
**          buffer and the video memory shoud be unlocked synchronously.
**
**      gctBOOL * Asynchroneous
**          Pointer to a variable specifying whether the surface should be
**          unlocked asynchroneously or not.
**
**  OUTPUT:
**
**      gctBOOL * Asynchroneous
**          Pointer to a variable receiving the number of bytes used in the
**          command buffer specified by 'Commands'.  If gcvNULL, there is no
**          command buffer.
*/
gceSTATUS
gckVIDMEM_Unlock(
    IN gcuVIDMEM_NODE_PTR Node,
    IN gceSURF_TYPE Type,
    IN OUT gctBOOL * Asynchroneous
    )
{
    gceSTATUS status;
    gckKERNEL kernel;
    gckHARDWARE hardware;
    gctPOINTER buffer;
    gctSIZE_T requested, bufferSize;
    gckCOMMAND command = gcvNULL;
    gceKERNEL_FLUSH flush;
    gckOS os = gcvNULL;
    gctBOOL acquired = gcvFALSE;
    gctBOOL needRelease = gcvFALSE;
    gctBOOL pendingUnlock = gcvFALSE;

    gcmkHEADER_ARG("Node=0x%x Type=%d *Asynchroneous=%d",
                   Node, Type, gcmOPT_VALUE(Asynchroneous));

    /* Verify the arguments. */
    if ((Node == gcvNULL)
    ||  (Node->VidMem.memory == gcvNULL)
    )
    {
        /* Invalid object. */
        gcmkONERROR(gcvSTATUS_INVALID_OBJECT);
    }

    /**************************** Video Memory ********************************/

    if (Node->VidMem.memory->object.type == gcvOBJ_VIDMEM)
    {
        if (Node->VidMem.locked <= 0)
        {
            /* The surface was not locked. */
            gcmkONERROR(gcvSTATUS_MEMORY_UNLOCKED);
        }

        /* Decrement the lock count. */
        Node->VidMem.locked --;

        if (Asynchroneous != gcvNULL)
        {
            /* No need for any events. */
            *Asynchroneous = gcvFALSE;
        }
    }

    /*************************** Virtual Memory *******************************/

    else
    {
        /* Verify the gckKERNEL object pointer. */
        kernel = Node->Virtual.kernel;
        gcmkVERIFY_OBJECT(kernel, gcvOBJ_KERNEL);

        /* Verify the gckHARDWARE object pointer. */
        hardware = Node->Virtual.kernel->hardware;
        gcmkVERIFY_OBJECT(hardware, gcvOBJ_HARDWARE);

        /* Verify the gckCOMMAND object pointer. */
        command = Node->Virtual.kernel->command;
        gcmkVERIFY_OBJECT(command, gcvOBJ_COMMAND);

        if (Asynchroneous == gcvNULL)
        {
            gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_VIDMEM,
                           "gckVIDMEM_Unlock: Unlocking virtual node 0x%x (%d)",
                           Node,
                           Node->Virtual.locked);

            /* Get the gckOS object pointer. */
            os = kernel->os;
            gcmkVERIFY_OBJECT(os, gcvOBJ_OS);

            /* Grab the mutex. */
            gcmkONERROR(
                gckOS_AcquireMutex(os, Node->Virtual.mutex, gcvINFINITE));

            /* If we need to unlock a node from virtual memory we have to be
            ** very carefull.  If the node is still inside the caches we
            ** might get a bus error later if the cache line needs to be
            ** replaced.  So - we have to flush the caches before we do
            ** anything.  We also need to stall to make sure the flush has
            ** happened.  However - when we get to this point we are inside
            ** the interrupt handler and we cannot just gckCOMMAND_Wait
            ** because it will wait forever.  So - what we do here is we
            ** verify the type of the surface, flush the appropriate cache,
            ** mark the node as flushed, and issue another unlock to unmap
            ** the MMU. */
            if (!Node->Virtual.contiguous
            &&  (Node->Virtual.locked == 1)
#ifdef __QNXTO__
            &&  !Node->Virtual.unlockPending
#else
            &&  !Node->Virtual.pending
#endif
            )
            {
                if (Type == gcvSURF_BITMAP)
                {
                    /* Flush 2D cache. */
                    flush = gcvFLUSH_2D;
                }
                else if (Type == gcvSURF_RENDER_TARGET)
                {
                    /* Flush color cache. */
                    flush = gcvFLUSH_COLOR;
                }
                else if (Type == gcvSURF_DEPTH)
                {
                    /* Flush depth cache. */
                    flush = gcvFLUSH_DEPTH;
                }
                else
                {
                    /* No flush required. */
                    flush = (gceKERNEL_FLUSH) 0;
                }

                gcmkONERROR(
                    gckHARDWARE_Flush(hardware, flush, gcvNULL, &requested));

                if (requested != 0)
                {
                    gcmkONERROR(
                        gckCOMMAND_Reserve(command,
                                           requested,
                                           &buffer,
                                           &bufferSize));

                    needRelease = gcvTRUE;

                    gcmkONERROR(gckHARDWARE_Flush(hardware,
                                                  flush,
                                                  buffer,
                                                  &bufferSize));

                    gcmkONERROR(
                        gckEVENT_Unlock(Node->Virtual.kernel->event,
                                        gcvKERNEL_PIXEL,
                                        Node,
                                        Type));

                    /* Mark node as pending. */
#ifdef __QNXNTO__
                    Node->Virtual.unlockPending = gcvTRUE;
#else
                    Node->Virtual.pending = gcvTRUE;
#endif

                    needRelease = gcvFALSE;

                    gcmkONERROR(gckCOMMAND_Execute(command, requested));

                    pendingUnlock = gcvTRUE;
                }
            }

            if (!pendingUnlock)
            {
                if (Node->Virtual.locked == 0)
                {
                    status = gcvSTATUS_MEMORY_UNLOCKED;
                    goto OnError;
                }

                /* Decrement lock count. */
                -- Node->Virtual.locked;

                /* See if we can unlock the resources. */
                if (Node->Virtual.locked == 0)
                {
                    /* Unlock the pages. */
#ifdef __QNXNTO__
                    gcmkONERROR(
                        gckOS_UnlockPages(os,
                                          Node->Virtual.physical,
                                          Node->Virtual.userPID,
                                          Node->Virtual.bytes,
                                          Node->Virtual.logical));
#else
                    gcmkONERROR(
                        gckOS_UnlockPages(os,
                                          Node->Virtual.physical,
                                          Node->Virtual.bytes,
                                          Node->Virtual.logical));
#endif

                    /* Free the page table. */
                    if (Node->Virtual.pageTable != gcvNULL)
                    {
                        gcmkONERROR(
                            gckMMU_FreePages(Node->Virtual.kernel->mmu,
                                             Node->Virtual.pageTable,
                                             Node->Virtual.pageCount));

                        /* Mark page table as freed. */
                        Node->Virtual.pageTable = gcvNULL;
                    }

                    /* Mark node as unlocked. */
#ifdef __QNXTO
                    Node->Virtual.unlockPending = gcvFALSE;
#else
                    Node->Virtual.pending = gcvFALSE;
#endif
                }

                gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_VIDMEM,
                               "Unmapped virtual node 0x%x from 0x%08X",
                               Node, Node->Virtual.address);
            }

            /* Release the mutex. */
            gcmkVERIFY_OK(gckOS_ReleaseMutex(os, Node->Virtual.mutex));
        }

        else
        {
            gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_VIDMEM,
                           "Scheduled unlock for virtual node 0x%x",
                           Node);

            /* Schedule the surface to be unlocked. */
            *Asynchroneous = gcvTRUE;
        }
    }

    /* Success. */
    gcmkFOOTER_ARG("*Asynchroneous=%d", gcmOPT_VALUE(Asynchroneous));
    return gcvSTATUS_OK;

OnError:
    if (needRelease)
    {
        gcmkVERIFY_OK(gckCOMMAND_Release(command));
    }

    if (acquired)
    {
        /* Release the mutex. */
        gcmkVERIFY_OK(gckOS_ReleaseMutex(os, Node->Virtual.mutex));
    }

    /* 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=0x%x Database=0x%x Type=%d Data=0x%x",
                   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;
}