blob: 4a85b00dbcaed824d592fd3924a8cb140d640ead [file] [log] [blame]
/****************************************************************************
*
* The MIT License (MIT)
*
* Copyright (c) 2014 - 2016 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 - 2016 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"
#if gcdENABLE_VG
#define _GC_OBJ_ZONE gcvZONE_MMU
/*******************************************************************************
**
** 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 gctUINT32 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 = (gctUINT32)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 = (gctUINT32)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;
}
/*******************************************************************************
**
** gckVGMMU_Destroy
**
** Destroy a nAQMMU object.
**
** INPUT:
**
** gckVGMMU Mmu
** Pointer to an gckVGMMU object.
**
** OUTPUT:
**
** Nothing.
*/
gceSTATUS gckVGMMU_Destroy(
IN gckVGMMU Mmu
)
{
gcmkHEADER_ARG("Mmu=0x%x", Mmu);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Mmu, gcvOBJ_MMU);
/* Free the page table. */
gcmkVERIFY_OK(gckOS_FreeContiguous(Mmu->os,
Mmu->pageTablePhysical,
Mmu->pageTableLogical,
Mmu->pageTableSize));
/* Roll back. */
gcmkVERIFY_OK(gckOS_DeleteMutex(Mmu->os, Mmu->mutex));
/* Mark the gckVGMMU object as unknown. */
Mmu->object.type = gcvOBJ_UNKNOWN;
/* Free the gckVGMMU object. */
gcmkVERIFY_OK(gckOS_Free(Mmu->os, Mmu));
gcmkFOOTER_NO();
/* Success. */
return gcvSTATUS_OK;
}
/*******************************************************************************
**
** gckVGMMU_AllocatePages
**
** Allocate pages inside the page table.
**
** INPUT:
**
** gckVGMMU Mmu
** Pointer to an gckVGMMU object.
**
** gctSIZE_T PageCount
** Number of pages to allocate.
**
** OUTPUT:
**
** gctPOINTER * PageTable
** Pointer to a variable that receives the base address of the page
** table.
**
** gctUINT32 * Address
** Pointer to a variable that receives the hardware specific address.
*/
gceSTATUS gckVGMMU_AllocatePages(
IN gckVGMMU Mmu,
IN gctSIZE_T PageCount,
OUT gctPOINTER * PageTable,
OUT gctUINT32 * Address
)
{
gceSTATUS status;
gctUINT32 tail, index, i;
gctUINT32 * table;
gctBOOL allocated = gcvFALSE;
gcmkHEADER_ARG("Mmu=0x%x PageCount=0x%x PageTable=0x%x Address=0x%x",
Mmu, PageCount, PageTable, Address);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Mmu, gcvOBJ_MMU);
gcmkVERIFY_ARGUMENT(PageCount > 0);
gcmkVERIFY_ARGUMENT(PageTable != gcvNULL);
gcmkVERIFY_ARGUMENT(Address != gcvNULL);
gcmkTRACE_ZONE(
gcvLEVEL_INFO, gcvZONE_MMU,
"%s(%d): %u pages.\n",
__FUNCTION__, __LINE__,
PageCount
);
if (PageCount > Mmu->entryCount)
{
gcmkTRACE_ZONE(
gcvLEVEL_ERROR, gcvZONE_MMU,
"%s(%d): page table too small for %u pages.\n",
__FUNCTION__, __LINE__,
PageCount
);
gcmkFOOTER_NO();
/* Not enough pages avaiable. */
return gcvSTATUS_OUT_OF_RESOURCES;
}
/* Grab the mutex. */
status = gckOS_AcquireMutex(Mmu->os, Mmu->mutex, gcvINFINITE);
if (status < 0)
{
gcmkTRACE_ZONE(
gcvLEVEL_ERROR, gcvZONE_MMU,
"%s(%d): could not acquire mutex.\n"
,__FUNCTION__, __LINE__
);
gcmkFOOTER();
/* Error. */
return status;
}
/* Compute the tail for this allocation. */
tail = Mmu->entryCount - (gctUINT32)PageCount;
/* Walk all entries until we find enough slots. */
for (index = Mmu->entry; index <= tail;)
{
/* Access page table. */
table = (gctUINT32 *) Mmu->pageTableLogical + index;
/* See if all slots are available. */
for (i = 0; i < PageCount; i++, table++)
{
if (*table != ~0)
{
/* Start from next slot. */
index += i + 1;
break;
}
}
if (i == PageCount)
{
/* Bail out if we have enough page entries. */
allocated = gcvTRUE;
break;
}
}
if (!allocated)
{
if (status >= 0)
{
/* Walk all entries until we find enough slots. */
for (index = 0; index <= tail;)
{
/* Access page table. */
table = (gctUINT32 *) Mmu->pageTableLogical + index;
/* See if all slots are available. */
for (i = 0; i < PageCount; i++, table++)
{
if (*table != ~0)
{
/* Start from next slot. */
index += i + 1;
break;
}
}
if (i == PageCount)
{
/* Bail out if we have enough page entries. */
allocated = gcvTRUE;
break;
}
}
}
}
if (!allocated && (status >= 0))
{
gcmkTRACE_ZONE(
gcvLEVEL_ERROR, gcvZONE_MMU,
"%s(%d): not enough free pages for %u pages.\n",
__FUNCTION__, __LINE__,
PageCount
);
/* Not enough empty slots available. */
status = gcvSTATUS_OUT_OF_RESOURCES;
}
if (status >= 0)
{
/* Build virtual address. */
status = gckVGHARDWARE_BuildVirtualAddress(Mmu->hardware,
index,
0,
Address);
if (status >= 0)
{
/* Update current entry into page table. */
Mmu->entry = index + (gctUINT32)PageCount;
/* Return pointer to page table. */
*PageTable = (gctUINT32 *) Mmu->pageTableLogical + index;
gcmkTRACE_ZONE(
gcvLEVEL_INFO, gcvZONE_MMU,
"%s(%d): allocated %u pages at index %u (0x%08X) @ %p.\n",
__FUNCTION__, __LINE__,
PageCount,
index,
*Address,
*PageTable
);
}
}
/* Release the mutex. */
gcmkVERIFY_OK(gckOS_ReleaseMutex(Mmu->os, Mmu->mutex));
gcmkFOOTER();
/* Return status. */
return status;
}
/*******************************************************************************
**
** gckVGMMU_FreePages
**
** Free pages inside the page table.
**
** INPUT:
**
** gckVGMMU Mmu
** Pointer to an gckVGMMU object.
**
** gctPOINTER PageTable
** Base address of the page table to free.
**
** gctSIZE_T PageCount
** Number of pages to free.
**
** OUTPUT:
**
** Nothing.
*/
gceSTATUS gckVGMMU_FreePages(
IN gckVGMMU Mmu,
IN gctPOINTER PageTable,
IN gctSIZE_T PageCount
)
{
gctUINT32 * table;
gcmkHEADER_ARG("Mmu=0x%x PageTable=0x%x PageCount=0x%x",
Mmu, PageTable, PageCount);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Mmu, gcvOBJ_MMU);
gcmkVERIFY_ARGUMENT(PageTable != gcvNULL);
gcmkVERIFY_ARGUMENT(PageCount > 0);
gcmkTRACE_ZONE(
gcvLEVEL_INFO, gcvZONE_MMU,
"%s(%d): freeing %u pages at index %u @ %p.\n",
__FUNCTION__, __LINE__,
PageCount,
((gctUINT32 *) PageTable - (gctUINT32 *) Mmu->pageTableLogical),
PageTable
);
/* Convert pointer. */
table = (gctUINT32 *) PageTable;
/* Mark the page table entries as available. */
while (PageCount-- > 0)
{
*table++ = (gctUINT32)~0;
}
gcmkFOOTER_NO();
/* Success. */
return gcvSTATUS_OK;
}
gceSTATUS
gckVGMMU_SetPage(
IN gckVGMMU Mmu,
IN gctUINT32 PageAddress,
IN gctUINT32 *PageEntry
)
{
gcmkHEADER_ARG("Mmu=0x%x", Mmu);
/* Verify the arguments. */
gcmkVERIFY_OBJECT(Mmu, gcvOBJ_MMU);
gcmkVERIFY_ARGUMENT(PageEntry != gcvNULL);
gcmkVERIFY_ARGUMENT(!(PageAddress & 0xFFF));
*PageEntry = PageAddress;
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
}
gceSTATUS
gckVGMMU_Flush(
IN gckVGMMU Mmu
)
{
gckVGHARDWARE hardware;
gcmkHEADER_ARG("Mmu=0x%x", Mmu);
hardware = Mmu->hardware;
gcmkVERIFY_OK(
gckOS_AtomSet(hardware->os, hardware->pageTableDirty, 1));
/* Success. */
gcmkFOOTER_NO();
return gcvSTATUS_OK;
}
#endif /* gcdENABLE_VG */