/*******************************************************************************
**
**	gckKERNEL_MapVideoMemory
**
**	Get the logical address for a hardware specific memory address for the
**	current process.
**
**	INPUT:
**
**		gckKERNEL Kernel
**			Pointer to an gckKERNEL object.
**
**      gctBOOL InUserSpace
**          gcvTRUE to map the memory into the user space.
**
**		gctUINT32 Address
**			Hardware specific memory address.
**
**	OUTPUT:
**
**		gctPOINTER * Logical
**			Pointer to a variable that will hold the logical address of the
**			specified memory address.
*/
gceSTATUS gckKERNEL_MapVideoMemoryEx(
	IN gckKERNEL Kernel,
    IN gceCORE   Core,
	IN gctBOOL InUserSpace,
	IN gctUINT32 Address,
	OUT gctPOINTER * Logical
	)
{
	GCHAL * gchal;
	gcePOOL pool;
	gctUINT32 offset, base;
	gceSTATUS status;
	gctPOINTER logical;

    gcmkHEADER_ARG("Kernel=%p InUserSpace=%d Address=%08x",
                   Kernel, InUserSpace, Address);

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

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

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

	do
	{
#if gcdENABLE_VG
		if (Core == gcvCORE_VG)
		{
			/* Split the memory address into a pool type and offset. */
			gcmkERR_BREAK(gckVGHARDWARE_SplitMemory(Kernel->vg->hardware,
											 Address,
											 &pool,
											 &offset));
		}
		else
#endif
		{
			/* Split the memory address into a pool type and offset. */
			gcmkERR_BREAK(gckHARDWARE_SplitMemory(Kernel->hardware,
											 Address,
											 &pool,
											 &offset));
		}
		/* Dispatch on pool. */
		switch (pool)
		{
		case gcvPOOL_LOCAL_INTERNAL:
			/* Internal memory. */
			logical = gchal->GetInternalLogical();
			break;

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

		case gcvPOOL_SYSTEM:
			/* System memory. */
#if UNDER_CE >= 600
			if (InUserSpace)
			{
				logical = gchal->GetProcessContiguousLogical();
			}
			else
			{
				logical = gchal->GetContiguousLogical();
			}
#else
			logical = gchal->GetContiguousLogical();
#endif

#if gcdENABLE_VG
			if (Core == gcvCORE_VG)
			{
				gcmkVERIFY_OK(gckVGHARDWARE_SplitMemory(Kernel->vg->hardware,
												 gchal->GetContiguousHeap()->baseAddress,
												 &pool,
												 &base));
			}
			else
#endif
			{
				gcmkVERIFY_OK(gckHARDWARE_SplitMemory(Kernel->hardware,
												 gchal->GetContiguousHeap()->baseAddress,
												 &pool,
												 &base));
			}
			offset -= base;
			break;

		default:
			/* Invalid memory pool. */
			gcmkFATAL("Unknown memory pool: %u", pool);
			return gcvSTATUS_INVALID_ARGUMENT;
		}

		/* Build logical address of specified address. */
		*Logical = reinterpret_cast<gctPOINTER>
			(static_cast<gctUINT8 *>(logical) + offset);
	}
	while (gcvFALSE);

	if (gcmIS_SUCCESS(status))
	{
		gcmkTRACE_ZONE(gcvLEVEL_INFO, gcvZONE_KERNEL,
				   "gckKERNEL_MapVideoMemory: Address 0x%08X maps to %p",
				   Address, *Logical);
	}

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

	/* Return the status. */
    gcmkFOOTER();
	return status;
}
/*******************************************************************************
**
**  gckVGMMU_Construct
**
**  Construct a new gckVGMMU object.
**
**  INPUT:
**
**      gckVGKERNEL Kernel
**          Pointer to an gckVGKERNEL object.
**
**      gctSIZE_T MmuSize
**          Number of bytes for the page table.
**
**  OUTPUT:
**
**      gckVGMMU * Mmu
**          Pointer to a variable that receives the gckVGMMU object pointer.
*/
gceSTATUS gckVGMMU_Construct(
    IN gckVGKERNEL Kernel,
    IN gctSIZE_T MmuSize,
    OUT gckVGMMU * Mmu
    )
{
    gckOS os;
    gckVGHARDWARE hardware;
    gceSTATUS status;
    gckVGMMU mmu;
    gctUINT32 * pageTable;
    gctUINT32 i;

    gcmkHEADER_ARG("Kernel=0x%x MmuSize=0x%x Mmu=0x%x", Kernel, MmuSize, Mmu);

    /* Verify the arguments. */
    gcmkVERIFY_OBJECT(Kernel, gcvOBJ_KERNEL);
    gcmkVERIFY_ARGUMENT(MmuSize > 0);
    gcmkVERIFY_ARGUMENT(Mmu != gcvNULL);

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

    /* Extract the gckVGHARDWARE object pointer. */
    hardware = Kernel->hardware;
    gcmkVERIFY_OBJECT(hardware, gcvOBJ_HARDWARE);

    /* Allocate memory for the gckVGMMU object. */
    status = gckOS_Allocate(os, sizeof(struct _gckVGMMU), (gctPOINTER *) &mmu);

    if (status < 0)
    {
        /* Error. */
        gcmkFATAL(
            "%s(%d): could not allocate gckVGMMU object.",
            __FUNCTION__, __LINE__
            );

        gcmkFOOTER();
        return status;
    }

    /* Initialize the gckVGMMU object. */
    mmu->object.type = gcvOBJ_MMU;
    mmu->os = os;
    mmu->hardware = hardware;

    /* Create the mutex. */
    status = gckOS_CreateMutex(os, &mmu->mutex);

    if (status < 0)
    {
        /* Roll back. */
        mmu->object.type = gcvOBJ_UNKNOWN;
        gcmkVERIFY_OK(gckOS_Free(os, mmu));

        gcmkFOOTER();
        /* Error. */
        return status;
    }

    /* Allocate the page table. */
    mmu->pageTableSize = MmuSize;
    status = gckOS_AllocateContiguous(os,
                                      gcvFALSE,
                                      &mmu->pageTableSize,
                                      &mmu->pageTablePhysical,
                                      &mmu->pageTableLogical);

    if (status < 0)
    {
        /* Roll back. */
        gcmkVERIFY_OK(gckOS_DeleteMutex(os, mmu->mutex));

        mmu->object.type = gcvOBJ_UNKNOWN;
        gcmkVERIFY_OK(gckOS_Free(os, mmu));

        /* Error. */
        gcmkFATAL(
            "%s(%d): could not allocate page table.",
            __FUNCTION__, __LINE__
            );

        gcmkFOOTER();
        return status;
    }

    /* Compute number of entries in page table. */
    mmu->entryCount = mmu->pageTableSize / sizeof(gctUINT32);
    mmu->entry = 0;

    /* Mark the entire page table as available. */
    pageTable = (gctUINT32 *) mmu->pageTableLogical;
    for (i = 0; i < mmu->entryCount; i++)
    {
        pageTable[i] = (gctUINT32)~0;
    }

    /* Set page table address. */
    status = gckVGHARDWARE_SetMMU(hardware, mmu->pageTableLogical);

    if (status < 0)
    {
        /* Free the page table. */
        gcmkVERIFY_OK(gckOS_FreeContiguous(mmu->os,
                                      mmu->pageTablePhysical,
                                      mmu->pageTableLogical,
                                      mmu->pageTableSize));

        /* Roll back. */
        gcmkVERIFY_OK(gckOS_DeleteMutex(os, mmu->mutex));

        mmu->object.type = gcvOBJ_UNKNOWN;
        gcmkVERIFY_OK(gckOS_Free(os, mmu));

        /* Error. */
        gcmkFATAL(
            "%s(%d): could not program page table.",
            __FUNCTION__, __LINE__
            );

        gcmkFOOTER();
        return status;
    }

    /* Return the gckVGMMU object pointer. */
    *Mmu = mmu;

    gcmkTRACE_ZONE(
        gcvLEVEL_INFO, gcvZONE_MMU,
        "%s(%d): %u entries at %p.(0x%08X)\n",
        __FUNCTION__, __LINE__,
        mmu->entryCount,
        mmu->pageTableLogical,
        mmu->pageTablePhysical
        );

    gcmkFOOTER_NO();
    /* Success. */
    return gcvSTATUS_OK;
}
/*******************************************************************************
**
**	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;
}