blob: d9554d50cee41950f2ffa670d9b257cb94389282 [file] [log] [blame]
/*************************************************************************/ /*!
@File
@Title Implementation of PMR functions for OS managed memory
@Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved
@Description Part of the memory management. This module is responsible for
implementing the function callbacks for physical memory borrowed
from that normally managed by the operating system.
@License Dual MIT/GPLv2
The contents of this file are subject to the MIT license as set out below.
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.
Alternatively, the contents of this file may be used under the terms of
the GNU General Public License Version 2 ("GPL") in which case the provisions
of GPL are applicable instead of those above.
If you wish to allow use of your version of this file only under the terms of
GPL, and not to allow others to use your version of this file under the terms
of the MIT license, indicate your decision by deleting the provisions above
and replace them with the notice and other provisions required by GPL as set
out in the file called "GPL-COPYING" included in this distribution. If you do
not delete the provisions above, a recipient may use your version of this file
under the terms of either the MIT license or GPL.
This License is also included in this distribution in the file called
"MIT-COPYING".
EXCEPT AS OTHERWISE STATED IN A NEGOTIATED AGREEMENT: (A) 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; AND (B) 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.
*/ /**************************************************************************/
#include <linux/version.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/highmem.h>
#include <linux/mm_types.h>
#include <linux/vmalloc.h>
#include <linux/gfp.h>
#include <linux/sched.h>
#include <linux/atomic.h>
#if defined(CONFIG_X86)
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0))
#include <asm/set_memory.h>
#else
#include <asm/cacheflush.h>
#endif
#endif
/* include/ */
#include "rgx_heaps.h"
#include "img_types.h"
#include "img_defs.h"
#include "pvr_debug.h"
#include "pvrsrv_error.h"
#include "pvrsrv_memallocflags.h"
#include "rgx_pdump_panics.h"
/* services/server/include/ */
#include "allocmem.h"
#include "osfunc.h"
#include "pdump_km.h"
#include "pmr.h"
#include "pmr_impl.h"
#include "cache_km.h"
#include "devicemem_server_utils.h"
/* ourselves */
#include "physmem_osmem.h"
#include "physmem_osmem_linux.h"
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
#include "process_stats.h"
#endif
#include "kernel_compatibility.h"
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0))
static IMG_UINT32 g_uiMaxOrder = PVR_LINUX_PHYSMEM_MAX_ALLOC_ORDER_NUM;
#else
/* split_page not available on older kernels */
#undef PVR_LINUX_PHYSMEM_MAX_ALLOC_ORDER_NUM
#define PVR_LINUX_PHYSMEM_MAX_ALLOC_ORDER_NUM 0
static IMG_UINT32 g_uiMaxOrder;
#endif
/*
These corresponds to the MMU min/max page sizes and associated PTE
alignment that can be used on the device for an allocation. It is
4KB (min) and 2MB (max) respectively.
*/
#define PVR_MIN_PHYSMEM_CONTIG_ALLOC_LOG2PGSZ RGX_HEAP_4KB_PAGE_SHIFT
#define PVR_MAX_PHYSMEM_CONTIG_ALLOC_LOG2PGSZ RGX_HEAP_2MB_PAGE_SHIFT
/* Defines how many pages should be mapped at once to the kernel */
#define PVR_LINUX_PHYSMEM_MAX_KMAP_PAGES 1024 /* 4 MB */
/*
These are used to get/set/mask lower-order bits in a dma_addr_t
to provide side-band information associated with that address.
These includes whether the address was obtained via alloc_page
or dma_alloc and if address came allocated pre-aligned or an
adjustment was made manually to aligned it.
*/
#define DMA_SET_ADJUSTED_ADDR(x) ((x) | ((dma_addr_t)0x02))
#define DMA_IS_ADDR_ADJUSTED(x) ((x) & ((dma_addr_t)0x02))
#define DMA_SET_ALLOCPG_ADDR(x) ((x) | ((dma_addr_t)0x01))
#define DMA_IS_ALLOCPG_ADDR(x) ((x) & ((dma_addr_t)0x01))
#define DMA_GET_ALIGN_ADJUSTMENT(x) ((x>>2) & ((dma_addr_t)0x3ff))
#define DMA_SET_ALIGN_ADJUSTMENT(x,y) ((x) | (((dma_addr_t)y)<<0x02))
#define DMA_GET_ADDR(x) (((dma_addr_t)x) & ((dma_addr_t)~0xfff))
#define DMA_VADDR_NOT_IN_USE 0xCAFEF00DDEADBEEFULL
typedef struct _PMR_OSPAGEARRAY_DATA_ {
/* Device for which this allocation has been made */
PVRSRV_DEVICE_NODE *psDevNode;
/* The pid that made this allocation */
IMG_PID uiPid;
/*
* iNumOSPagesAllocated:
* Number of pages allocated in this PMR so far.
* This allows for up to (2^31 - 1) pages. With 4KB pages, that's 8TB of memory for each PMR.
*/
IMG_INT32 iNumOSPagesAllocated;
/*
* uiTotalNumOSPages:
* Total number of pages supported by this PMR. (Fixed as of now due the fixed Page table array size)
* number of "pages" (a.k.a. macro pages, compound pages, higher order pages, etc...)
*/
IMG_UINT32 uiTotalNumOSPages;
/*
uiLog2AllocPageSize;
size of each "page" -- this would normally be the same as
PAGE_SHIFT, but we support the idea that we may allocate pages
in larger chunks for better contiguity, using order>0 in the
call to alloc_pages()
*/
IMG_UINT32 uiLog2AllocPageSize;
/*
ui64DmaMask;
*/
IMG_UINT64 ui64DmaMask;
/*
For non DMA/CMA allocation, pagearray references the pages
thus allocated; one entry per compound page when compound
pages are used. In addition, for DMA/CMA allocations, we
track the returned cpu virtual and device bus address.
*/
struct page **pagearray;
dma_addr_t *dmaphysarray;
void **dmavirtarray;
/*
Record at alloc time whether poisoning will be required when the
PMR is freed.
*/
IMG_BOOL bZero;
IMG_BOOL bPoisonOnFree;
IMG_BOOL bPoisonOnAlloc;
IMG_BOOL bOnDemand;
IMG_BOOL bUnpinned; /* Should be protected by page pool lock */
IMG_BOOL bIsCMA; /* Is CMA memory allocated via DMA framework */
/*
The cache mode of the PMR. Additionally carrying the CPU-Cache-Clean
flag, advising us to do cache maintenance on behalf of the caller.
Boolean used to track if we need to revert the cache attributes
of the pages used in this allocation. Depends on OS/architecture.
*/
IMG_UINT32 ui32CPUCacheFlags;
IMG_BOOL bUnsetMemoryType;
} PMR_OSPAGEARRAY_DATA;
/***********************************
* Page pooling for uncached pages *
***********************************/
static INLINE void
_FreeOSPage_CMA(struct device *dev,
size_t alloc_size,
IMG_UINT32 uiOrder,
void *virt_addr,
dma_addr_t dev_addr,
struct page *psPage);
static void
_FreeOSPage(IMG_UINT32 uiOrder,
IMG_BOOL bUnsetMemoryType,
struct page *psPage);
static PVRSRV_ERROR
_FreeOSPages(PMR_OSPAGEARRAY_DATA *psPageArrayData,
IMG_UINT32 *pai32FreeIndices,
IMG_UINT32 ui32FreePageCount);
static PVRSRV_ERROR
_FreePagesFromPoolUnlocked(IMG_UINT32 uiMaxPagesToFree,
IMG_UINT32 *puiPagesFreed);
/* A struct for our page pool holding an array of zeroed (!) pages.
* We always put units of page arrays to the pool but are
* able to take individual pages */
typedef struct
{
/* Linkage for page pool LRU list */
struct list_head sPagePoolItem;
/* How many items are still in the page array */
IMG_UINT32 uiItemsRemaining;
/* Array of the actual pages */
struct page **ppsPageArray;
} LinuxPagePoolEntry;
/* CleanupThread structure to put allocation in page pool */
typedef struct
{
PVRSRV_CLEANUP_THREAD_WORK sCleanupWork;
IMG_UINT32 ui32CPUCacheMode;
LinuxPagePoolEntry *psPoolEntry;
} LinuxCleanupData;
/* A struct for the unpinned items */
typedef struct
{
struct list_head sUnpinPoolItem;
PMR_OSPAGEARRAY_DATA *psPageArrayDataPtr;
} LinuxUnpinEntry;
/* Caches to hold page pool and page array structures */
static struct kmem_cache *g_psLinuxPagePoolCache;
static struct kmem_cache *g_psLinuxPageArray;
/* Track what is live, all protected by pool lock.
* x86 needs two page pools because we have to change the memory attributes
* of the pages which is expensive due to an implicit flush.
* See set_pages_array_uc/wc/wb. */
static IMG_UINT32 g_ui32UnpinPageCount;
static IMG_UINT32 g_ui32PagePoolUCCount;
#if defined(CONFIG_X86)
static IMG_UINT32 g_ui32PagePoolWCCount;
#endif
/* Tracks asynchronous tasks currently accessing the page pool.
* It is incremented if a defer free task
* is created. Both will decrement the value when they finished the work.
* The atomic prevents piling up of deferred work in case the deferred thread
* cannot keep up with the application.*/
static ATOMIC_T g_iPoolCleanTasks;
/* We don't want too many asynchronous threads trying to access the page pool
* at the same time */
#define PVR_LINUX_PHYSMEM_MAX_ASYNC_CLEAN_TASKS 128
/* Defines how many pages the page cache should hold. */
#if defined(PVR_LINUX_PHYSMEM_MAX_POOL_PAGES)
static const IMG_UINT32 g_ui32PagePoolMaxEntries = PVR_LINUX_PHYSMEM_MAX_POOL_PAGES;
#else
static const IMG_UINT32 g_ui32PagePoolMaxEntries;
#endif
/* We double check if we would exceed this limit if we are below MAX_POOL_PAGES
and want to add an allocation to the pool.
This prevents big allocations being given back to the OS just because they
exceed the MAX_POOL_PAGES limit even though the pool is currently empty. */
#if defined(PVR_LINUX_PHYSMEM_MAX_EXCESS_POOL_PAGES)
static const IMG_UINT32 g_ui32PagePoolMaxExcessEntries = PVR_LINUX_PHYSMEM_MAX_EXCESS_POOL_PAGES;
#else
static const IMG_UINT32 g_ui32PagePoolMaxExcessEntries;
#endif
#if defined(CONFIG_X86)
#define PHYSMEM_OSMEM_NUM_OF_POOLS 2
static const IMG_UINT32 g_aui32CPUCacheFlags[PHYSMEM_OSMEM_NUM_OF_POOLS] = {
PVRSRV_MEMALLOCFLAG_CPU_UNCACHED,
PVRSRV_MEMALLOCFLAG_CPU_WRITE_COMBINE
};
#else
#define PHYSMEM_OSMEM_NUM_OF_POOLS 1
static const IMG_UINT32 g_aui32CPUCacheFlags[PHYSMEM_OSMEM_NUM_OF_POOLS] = {
PVRSRV_MEMALLOCFLAG_CPU_UNCACHED
};
#endif
/* Global structures we use to manage the page pool */
static DEFINE_MUTEX(g_sPagePoolMutex);
/* List holding the page array pointers: */
static LIST_HEAD(g_sPagePoolList_WC);
static LIST_HEAD(g_sPagePoolList_UC);
static LIST_HEAD(g_sUnpinList);
static inline IMG_UINT32
_PagesInPoolUnlocked(void)
{
IMG_UINT32 uiCnt = g_ui32PagePoolUCCount;
#if defined(CONFIG_X86)
uiCnt += g_ui32PagePoolWCCount;
#endif
return uiCnt;
}
static inline void
_PagePoolLock(void)
{
mutex_lock(&g_sPagePoolMutex);
}
static inline int
_PagePoolTrylock(void)
{
return mutex_trylock(&g_sPagePoolMutex);
}
static inline void
_PagePoolUnlock(void)
{
mutex_unlock(&g_sPagePoolMutex);
}
static PVRSRV_ERROR
_AddUnpinListEntryUnlocked(PMR_OSPAGEARRAY_DATA *psOSPageArrayData)
{
LinuxUnpinEntry *psUnpinEntry;
psUnpinEntry = OSAllocMem(sizeof(*psUnpinEntry));
if (!psUnpinEntry)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: OSAllocMem failed. Cannot add entry to unpin list.",
__func__));
return PVRSRV_ERROR_OUT_OF_MEMORY;
}
psUnpinEntry->psPageArrayDataPtr = psOSPageArrayData;
/* Add into pool that the shrinker can access easily*/
list_add_tail(&psUnpinEntry->sUnpinPoolItem, &g_sUnpinList);
g_ui32UnpinPageCount += psOSPageArrayData->iNumOSPagesAllocated;
return PVRSRV_OK;
}
static void
_RemoveUnpinListEntryUnlocked(PMR_OSPAGEARRAY_DATA *psOSPageArrayData)
{
LinuxUnpinEntry *psUnpinEntry, *psTempUnpinEntry;
/* Remove from pool */
list_for_each_entry_safe(psUnpinEntry,
psTempUnpinEntry,
&g_sUnpinList,
sUnpinPoolItem)
{
if (psUnpinEntry->psPageArrayDataPtr == psOSPageArrayData)
{
list_del(&psUnpinEntry->sUnpinPoolItem);
break;
}
}
OSFreeMem(psUnpinEntry);
g_ui32UnpinPageCount -= psOSPageArrayData->iNumOSPagesAllocated;
}
static inline IMG_BOOL
_GetPoolListHead(IMG_UINT32 ui32CPUCacheFlags,
struct list_head **ppsPoolHead,
IMG_UINT32 **ppuiCounter)
{
switch (PVRSRV_CPU_CACHE_MODE(ui32CPUCacheFlags))
{
case PVRSRV_MEMALLOCFLAG_CPU_WRITE_COMBINE:
#if defined(CONFIG_X86)
/*
For x86 we need to keep different lists for uncached
and write-combined as we must always honour the PAT
setting which cares about this difference.
*/
*ppsPoolHead = &g_sPagePoolList_WC;
*ppuiCounter = &g_ui32PagePoolWCCount;
break;
#endif
case PVRSRV_MEMALLOCFLAG_CPU_UNCACHED:
*ppsPoolHead = &g_sPagePoolList_UC;
*ppuiCounter = &g_ui32PagePoolUCCount;
break;
default:
PVR_DPF((PVR_DBG_ERROR,
"%s: Unknown CPU caching mode. "
"Using default UC pool.",
__func__));
*ppsPoolHead = &g_sPagePoolList_UC;
*ppuiCounter = &g_ui32PagePoolUCCount;
PVR_ASSERT(0);
return IMG_FALSE;
}
return IMG_TRUE;
}
static struct shrinker g_sShrinker;
/* Returning the number of pages that still reside in the page pool. */
static unsigned long
_GetNumberOfPagesInPoolUnlocked(void)
{
return _PagesInPoolUnlocked() + g_ui32UnpinPageCount;
}
/* Linux shrinker function that informs the OS about how many pages we are caching and
* it is able to reclaim. */
static unsigned long
_CountObjectsInPagePool(struct shrinker *psShrinker, struct shrink_control *psShrinkControl)
{
int remain;
PVR_ASSERT(psShrinker == &g_sShrinker);
(void)psShrinker;
(void)psShrinkControl;
/* In order to avoid possible deadlock use mutex_trylock in place of mutex_lock */
if (_PagePoolTrylock() == 0)
return 0;
remain = _GetNumberOfPagesInPoolUnlocked();
_PagePoolUnlock();
return remain;
}
/* Linux shrinker function to reclaim the pages from our page pool */
static unsigned long
_ScanObjectsInPagePool(struct shrinker *psShrinker, struct shrink_control *psShrinkControl)
{
unsigned long uNumToScan = psShrinkControl->nr_to_scan;
unsigned long uSurplus = 0;
LinuxUnpinEntry *psUnpinEntry, *psTempUnpinEntry;
IMG_UINT32 uiPagesFreed;
PVR_ASSERT(psShrinker == &g_sShrinker);
(void)psShrinker;
/* In order to avoid possible deadlock use mutex_trylock in place of mutex_lock */
if (_PagePoolTrylock() == 0)
return SHRINK_STOP;
_FreePagesFromPoolUnlocked(uNumToScan,
&uiPagesFreed);
uNumToScan -= uiPagesFreed;
if (uNumToScan == 0)
{
goto e_exit;
}
/* Free unpinned memory, starting with LRU entries */
list_for_each_entry_safe(psUnpinEntry,
psTempUnpinEntry,
&g_sUnpinList,
sUnpinPoolItem)
{
PMR_OSPAGEARRAY_DATA *psPageArrayDataPtr = psUnpinEntry->psPageArrayDataPtr;
IMG_UINT32 uiNumPages = (psPageArrayDataPtr->uiTotalNumOSPages > psPageArrayDataPtr->iNumOSPagesAllocated)?
psPageArrayDataPtr->iNumOSPagesAllocated:psPageArrayDataPtr->uiTotalNumOSPages;
PVRSRV_ERROR eError;
/* Free associated pages */
eError = _FreeOSPages(psPageArrayDataPtr,
NULL,
0);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Shrinker is unable to free unpinned pages. Error: %s (%d)",
__func__,
PVRSRVGetErrorString(eError),
eError));
goto e_exit;
}
/* Remove item from pool */
list_del(&psUnpinEntry->sUnpinPoolItem);
g_ui32UnpinPageCount -= uiNumPages;
/* Check if there is more to free or if we already surpassed the limit */
if (uiNumPages < uNumToScan)
{
uNumToScan -= uiNumPages;
}
else if (uiNumPages > uNumToScan)
{
uSurplus += uiNumPages - uNumToScan;
uNumToScan = 0;
goto e_exit;
}
else
{
uNumToScan -= uiNumPages;
goto e_exit;
}
}
e_exit:
if (list_empty(&g_sUnpinList))
{
PVR_ASSERT(g_ui32UnpinPageCount == 0);
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0))
{
int remain;
remain = _GetNumberOfPagesInPoolUnlocked();
_PagePoolUnlock();
return remain;
}
#else
/* Returning the number of pages freed during the scan */
_PagePoolUnlock();
return psShrinkControl->nr_to_scan - uNumToScan + uSurplus;
#endif
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0))
static int
_ShrinkPagePool(struct shrinker *psShrinker, struct shrink_control *psShrinkControl)
{
if (psShrinkControl->nr_to_scan != 0)
{
return _ScanObjectsInPagePool(psShrinker, psShrinkControl);
}
else
{
/* No pages are being reclaimed so just return the page count */
return _CountObjectsInPagePool(psShrinker, psShrinkControl);
}
}
static struct shrinker g_sShrinker =
{
.shrink = _ShrinkPagePool,
.seeks = DEFAULT_SEEKS
};
#else
static struct shrinker g_sShrinker =
{
.count_objects = _CountObjectsInPagePool,
.scan_objects = _ScanObjectsInPagePool,
.seeks = DEFAULT_SEEKS
};
#endif
/* Register the shrinker so Linux can reclaim cached pages */
void LinuxInitPhysmem(void)
{
g_psLinuxPageArray = kmem_cache_create("pvr-pa", sizeof(PMR_OSPAGEARRAY_DATA), 0, 0, NULL);
_PagePoolLock();
g_psLinuxPagePoolCache = kmem_cache_create("pvr-pp", sizeof(LinuxPagePoolEntry), 0, 0, NULL);
if (g_psLinuxPagePoolCache)
{
/* Only create the shrinker if we created the cache OK */
register_shrinker(&g_sShrinker);
}
_PagePoolUnlock();
OSAtomicWrite(&g_iPoolCleanTasks, 0);
}
/* Unregister the shrinker and remove all pages from the pool that are still left */
void LinuxDeinitPhysmem(void)
{
IMG_UINT32 uiPagesFreed;
if (OSAtomicRead(&g_iPoolCleanTasks) > 0)
{
PVR_DPF((PVR_DBG_WARNING, "Still deferred cleanup tasks running "
"while deinitialising memory subsystem."));
}
_PagePoolLock();
if (_FreePagesFromPoolUnlocked(IMG_UINT32_MAX, &uiPagesFreed) != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "Unable to free all pages from page pool when "
"deinitialising memory subsystem."));
PVR_ASSERT(0);
}
PVR_ASSERT(_PagesInPoolUnlocked() == 0);
/* Free the page cache */
kmem_cache_destroy(g_psLinuxPagePoolCache);
unregister_shrinker(&g_sShrinker);
_PagePoolUnlock();
kmem_cache_destroy(g_psLinuxPageArray);
}
static void EnableOOMKiller(void)
{
current->flags &= ~PF_DUMPCORE;
}
static void DisableOOMKiller(void)
{
/* PF_DUMPCORE is treated by the VM as if the OOM killer was disabled.
*
* As oom_killer_disable() is an inline, non-exported function, we
* can't use it from a modular driver. Furthermore, the OOM killer
* API doesn't look thread safe, which 'current' is.
*/
WARN_ON(current->flags & PF_DUMPCORE);
current->flags |= PF_DUMPCORE;
}
/* Prints out the addresses in a page array for debugging purposes
* Define PHYSMEM_OSMEM_DEBUG_DUMP_PAGE_ARRAY locally to activate: */
/* #define PHYSMEM_OSMEM_DEBUG_DUMP_PAGE_ARRAY 1 */
static inline void
_DumpPageArray(struct page **pagearray, IMG_UINT32 uiPagesToPrint)
{
#if defined(PHYSMEM_OSMEM_DEBUG_DUMP_PAGE_ARRAY)
IMG_UINT32 i;
if (pagearray)
{
printk("Array %p:\n", pagearray);
for (i = 0; i < uiPagesToPrint; i++)
{
printk("%p | ", (pagearray)[i]);
}
printk("\n");
}
else
{
printk("Array is NULL:\n");
}
#else
PVR_UNREFERENCED_PARAMETER(pagearray);
PVR_UNREFERENCED_PARAMETER(uiPagesToPrint);
#endif
}
/* Debugging function that dumps out the number of pages for every
* page array that is currently in the page pool.
* Not defined by default. Define locally to activate feature: */
/* #define PHYSMEM_OSMEM_DEBUG_DUMP_PAGE_POOL 1 */
static void
_DumpPoolStructure(void)
{
#if defined(PHYSMEM_OSMEM_DEBUG_DUMP_PAGE_POOL)
LinuxPagePoolEntry *psPagePoolEntry, *psTempPoolEntry;
struct list_head *psPoolHead = NULL;
IMG_UINT32 j;
IMG_UINT32 *puiCounter;
printk("\n");
/* Empty all pools */
for (j = 0; j < PHYSMEM_OSMEM_NUM_OF_POOLS; j++)
{
printk("pool = %u\n", j);
/* Get the correct list for this caching mode */
if (!_GetPoolListHead(g_aui32CPUCacheFlags[j], &psPoolHead, &puiCounter))
{
break;
}
list_for_each_entry_safe(psPagePoolEntry,
psTempPoolEntry,
psPoolHead,
sPagePoolItem)
{
printk("%u | ", psPagePoolEntry->uiItemsRemaining);
}
printk("\n");
}
#endif
}
/* Free a certain number of pages from the page pool.
* Mainly used in error paths or at deinitialisation to
* empty the whole pool. */
static PVRSRV_ERROR
_FreePagesFromPoolUnlocked(IMG_UINT32 uiMaxPagesToFree,
IMG_UINT32 *puiPagesFreed)
{
PVRSRV_ERROR eError = PVRSRV_OK;
LinuxPagePoolEntry *psPagePoolEntry, *psTempPoolEntry;
struct list_head *psPoolHead = NULL;
IMG_UINT32 i, j;
IMG_UINT32 *puiCounter;
*puiPagesFreed = uiMaxPagesToFree;
/* Empty all pools */
for (j = 0; j < PHYSMEM_OSMEM_NUM_OF_POOLS; j++)
{
/* Get the correct list for this caching mode */
if (!_GetPoolListHead(g_aui32CPUCacheFlags[j], &psPoolHead, &puiCounter))
{
break;
}
/* Free the pages and remove page arrays from the pool if they are exhausted */
list_for_each_entry_safe(psPagePoolEntry,
psTempPoolEntry,
psPoolHead,
sPagePoolItem)
{
IMG_UINT32 uiItemsToFree;
struct page **ppsPageArray;
/* Check if we are going to free the whole page array or just parts */
if (psPagePoolEntry->uiItemsRemaining <= uiMaxPagesToFree)
{
uiItemsToFree = psPagePoolEntry->uiItemsRemaining;
ppsPageArray = psPagePoolEntry->ppsPageArray;
}
else
{
uiItemsToFree = uiMaxPagesToFree;
ppsPageArray = &(psPagePoolEntry->ppsPageArray[psPagePoolEntry->uiItemsRemaining - uiItemsToFree]);
}
#if defined(CONFIG_X86)
/* Set the correct page caching attributes on x86 */
if (!PVRSRV_CHECK_CPU_CACHED(g_aui32CPUCacheFlags[j]))
{
int ret;
ret = set_pages_array_wb(ppsPageArray, uiItemsToFree);
if (ret)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Failed to reset page attributes",
__func__));
eError = PVRSRV_ERROR_FAILED_TO_FREE_PAGES;
goto e_exit;
}
}
#endif
/* Free the actual pages */
for (i = 0; i < uiItemsToFree; i++)
{
__free_pages(ppsPageArray[i], 0);
ppsPageArray[i] = NULL;
}
/* Reduce counters */
uiMaxPagesToFree -= uiItemsToFree;
*puiCounter -= uiItemsToFree;
psPagePoolEntry->uiItemsRemaining -= uiItemsToFree;
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
/*
* MemStats usually relies on having the bridge lock held, however
* the page pool code may call PVRSRVStatsIncrMemAllocPoolStat and
* PVRSRVStatsDecrMemAllocPoolStat without the bridge lock held, so
* the page pool lock is used to ensure these calls are mutually
* exclusive
*/
PVRSRVStatsDecrMemAllocPoolStat(PAGE_SIZE * uiItemsToFree);
#endif
/* Is this pool entry exhausted, delete it */
if (psPagePoolEntry->uiItemsRemaining == 0)
{
OSFreeMemNoStats(psPagePoolEntry->ppsPageArray);
list_del(&psPagePoolEntry->sPagePoolItem);
kmem_cache_free(g_psLinuxPagePoolCache, psPagePoolEntry);
}
/* Return if we have all our pages */
if (uiMaxPagesToFree == 0)
{
goto e_exit;
}
}
}
e_exit:
*puiPagesFreed -= uiMaxPagesToFree;
_DumpPoolStructure();
return eError;
}
/* Get a certain number of pages from the page pool and
* copy them directly into a given page array. */
static void
_GetPagesFromPoolUnlocked(IMG_UINT32 ui32CPUCacheFlags,
IMG_UINT32 uiMaxNumPages,
struct page **ppsPageArray,
IMG_UINT32 *puiNumReceivedPages)
{
LinuxPagePoolEntry *psPagePoolEntry, *psTempPoolEntry;
struct list_head *psPoolHead = NULL;
IMG_UINT32 i;
IMG_UINT32 *puiCounter;
*puiNumReceivedPages = 0;
/* Get the correct list for this caching mode */
if (!_GetPoolListHead(ui32CPUCacheFlags, &psPoolHead, &puiCounter))
{
return;
}
/* Check if there are actually items in the list */
if (list_empty(psPoolHead))
{
return;
}
PVR_ASSERT(*puiCounter > 0);
/* Receive pages from the pool */
list_for_each_entry_safe(psPagePoolEntry,
psTempPoolEntry,
psPoolHead,
sPagePoolItem)
{
/* Get the pages from this pool entry */
for (i = psPagePoolEntry->uiItemsRemaining; i != 0 && *puiNumReceivedPages < uiMaxNumPages; i--)
{
ppsPageArray[*puiNumReceivedPages] = psPagePoolEntry->ppsPageArray[i-1];
(*puiNumReceivedPages)++;
psPagePoolEntry->uiItemsRemaining--;
}
/* Is this pool entry exhausted, delete it */
if (psPagePoolEntry->uiItemsRemaining == 0)
{
OSFreeMemNoStats(psPagePoolEntry->ppsPageArray);
list_del(&psPagePoolEntry->sPagePoolItem);
kmem_cache_free(g_psLinuxPagePoolCache, psPagePoolEntry);
}
/* Return if we have all our pages */
if (*puiNumReceivedPages == uiMaxNumPages)
{
goto exit_ok;
}
}
exit_ok:
/* Update counters */
*puiCounter -= *puiNumReceivedPages;
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
/* MemStats usually relies on having the bridge lock held, however
* the page pool code may call PVRSRVStatsIncrMemAllocPoolStat and
* PVRSRVStatsDecrMemAllocPoolStat without the bridge lock held, so
* the page pool lock is used to ensure these calls are mutually
* exclusive
*/
PVRSRVStatsDecrMemAllocPoolStat(PAGE_SIZE * (*puiNumReceivedPages));
#endif
_DumpPoolStructure();
return;
}
/* Same as _GetPagesFromPoolUnlocked but handles locking and
* checks first whether pages from the pool are a valid option. */
static inline void
_GetPagesFromPoolLocked(PVRSRV_DEVICE_NODE *psDevNode,
IMG_UINT32 ui32CPUCacheFlags,
IMG_UINT32 uiPagesToAlloc,
IMG_UINT32 uiOrder,
IMG_BOOL bZero,
struct page **ppsPageArray,
IMG_UINT32 *puiPagesFromPool)
{
#if defined(PVR_LINUX_PHYSMEM_ZERO_ALL_PAGES)
PVR_UNREFERENCED_PARAMETER(bZero);
#else
/* Don't get pages from pool if it doesn't provide zeroed pages */
if (bZero)
{
return;
}
#endif
/* The page pool stores only order 0 pages. If we need zeroed memory we
* directly allocate from the OS because it is faster than
* doing it within the driver. */
if (uiOrder == 0 &&
!PVRSRV_CHECK_CPU_CACHED(ui32CPUCacheFlags))
{
_PagePoolLock();
_GetPagesFromPoolUnlocked(ui32CPUCacheFlags,
uiPagesToAlloc,
ppsPageArray,
puiPagesFromPool);
_PagePoolUnlock();
}
return;
}
/* Takes a page array and maps it into the kernel to write zeros */
static PVRSRV_ERROR
_ZeroPageArray(IMG_UINT32 uiNumToClean,
struct page **ppsCleanArray,
pgprot_t pgprot)
{
IMG_CPU_VIRTADDR pvAddr;
IMG_UINT32 uiMaxPagesToMap = MIN(PVR_LINUX_PHYSMEM_MAX_KMAP_PAGES,
uiNumToClean);
/* Map and fill the pages with zeros.
* For large page arrays do it PVR_LINUX_PHYSMEM_MAX_KMAP_SIZE
* at a time. */
while (uiNumToClean != 0)
{
IMG_UINT32 uiToClean = (uiNumToClean >= uiMaxPagesToMap) ?
uiMaxPagesToMap :
uiNumToClean;
#if !defined(CONFIG_64BIT) || defined(PVRSRV_FORCE_SLOWER_VMAP_ON_64BIT_BUILDS)
pvAddr = vmap(ppsCleanArray, uiToClean, VM_WRITE, pgprot);
#else
pvAddr = vm_map_ram(ppsCleanArray, uiToClean, -1, pgprot);
#endif
if (!pvAddr)
{
if (uiMaxPagesToMap <= 1)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Out of vmalloc memory, "
"unable to map pages for zeroing.",
__func__));
return PVRSRV_ERROR_OUT_OF_MEMORY;
}
else
{
/* Halve the pages to map at once and try again. */
uiMaxPagesToMap = uiMaxPagesToMap >> 1;
continue;
}
}
OSDeviceMemSet(pvAddr, 0, PAGE_SIZE * uiToClean);
#if !defined(CONFIG_64BIT) || defined(PVRSRV_FORCE_SLOWER_VMAP_ON_64BIT_BUILDS)
vunmap(pvAddr);
#else
vm_unmap_ram(pvAddr, uiToClean);
#endif
ppsCleanArray = &(ppsCleanArray[uiToClean]);
uiNumToClean -= uiToClean;
}
return PVRSRV_OK;
}
static PVRSRV_ERROR
_CleanupThread_CleanPages(void *pvData)
{
LinuxCleanupData *psCleanupData = (LinuxCleanupData*) pvData;
LinuxPagePoolEntry *psPagePoolEntry = psCleanupData->psPoolEntry;
struct list_head *psPoolHead = NULL;
IMG_UINT32 *puiCounter = NULL;
#if defined(PVR_LINUX_PHYSMEM_ZERO_ALL_PAGES)
PVRSRV_ERROR eError;
pgprot_t pgprot;
IMG_UINT32 i;
#endif /* defined(PVR_LINUX_PHYSMEM_ZERO_ALL_PAGES) */
/* Get the correct pool for this caching mode. */
_GetPoolListHead(psCleanupData->ui32CPUCacheMode , &psPoolHead, &puiCounter);
#if defined(PVR_LINUX_PHYSMEM_ZERO_ALL_PAGES)
switch (PVRSRV_CPU_CACHE_MODE(psCleanupData->ui32CPUCacheMode))
{
case PVRSRV_MEMALLOCFLAG_CPU_UNCACHED:
#if defined(CONFIG_X86)
/* For x86 we can only map with the same attributes
* as in the PAT settings*/
pgprot = pgprot_noncached(PAGE_KERNEL);
break;
#endif
case PVRSRV_MEMALLOCFLAG_CPU_WRITE_COMBINE:
pgprot = pgprot_writecombine(PAGE_KERNEL);
break;
default:
PVR_DPF((PVR_DBG_ERROR,
"%s: Unknown caching mode to set page protection flags.",
__func__));
eError = PVRSRV_ERROR_INVALID_PARAMS;
goto eExit;
}
/* Map and fill the pages with zeros.
* For large page arrays do it PVR_LINUX_PHYSMEM_MAX_KMAP_SIZE
* at a time. */
eError = _ZeroPageArray(psPagePoolEntry->uiItemsRemaining,
psPagePoolEntry->ppsPageArray,
pgprot);
if (eError != PVRSRV_OK)
{
goto eExit;
}
#endif /* defined(PVR_LINUX_PHYSMEM_ZERO_ALL_PAGES) */
/* Lock down pool and add item */
_PagePoolLock();
/* Pool counters were already updated so don't do it here again*/
/* The pages are all zeroed so return them to the pool. */
list_add_tail(&psPagePoolEntry->sPagePoolItem, psPoolHead);
_DumpPoolStructure();
_PagePoolUnlock();
OSFreeMem(pvData);
OSAtomicDecrement(&g_iPoolCleanTasks);
return PVRSRV_OK;
#if defined(PVR_LINUX_PHYSMEM_ZERO_ALL_PAGES)
eExit:
/* we failed to zero the pages so return the error so we can
* retry during the next spin */
if ((psCleanupData->sCleanupWork.ui32RetryCount - 1) > 0)
{
return eError;
}
/* this was the last retry, give up and free pages to OS */
PVR_DPF((PVR_DBG_ERROR,
"%s: Deferred task error, freeing pages to OS.",
__func__));
_PagePoolLock();
*puiCounter -= psPagePoolEntry->uiItemsRemaining;
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
/* MemStats usually relies on having the bridge lock held, however
* the page pool code may call PVRSRVStatsIncrMemAllocPoolStat and
* PVRSRVStatsDecrMemAllocPoolStat without the bridge lock held, so
* the page pool lock is used to ensure these calls are mutually
* exclusive
*/
PVRSRVStatsDecrMemAllocPoolStat(PAGE_SIZE * psCleanupData->psPoolEntry->uiItemsRemaining);
#endif
_PagePoolUnlock();
for (i = 0; i < psCleanupData->psPoolEntry->uiItemsRemaining; i++)
{
_FreeOSPage(0, IMG_TRUE, psPagePoolEntry->ppsPageArray[i]);
}
OSFreeMemNoStats(psPagePoolEntry->ppsPageArray);
kmem_cache_free(g_psLinuxPagePoolCache, psPagePoolEntry);
OSFreeMem(psCleanupData);
OSAtomicDecrement(&g_iPoolCleanTasks);
return PVRSRV_OK;
#endif /* defined(PVR_LINUX_PHYSMEM_ZERO_ALL_PAGES) */
}
/* Put page array to the page pool.
* Handles locking and checks whether the pages are
* suitable to be stored in the pool. */
static inline IMG_BOOL
_PutPagesToPoolLocked(IMG_UINT32 ui32CPUCacheFlags,
struct page **ppsPageArray,
IMG_BOOL bUnpinned,
IMG_UINT32 uiOrder,
IMG_UINT32 uiNumPages)
{
LinuxCleanupData *psCleanupData;
PVRSRV_CLEANUP_THREAD_WORK *psCleanupThreadFn;
#if defined(SUPPORT_PHYSMEM_TEST)
PVRSRV_DATA *psPVRSRVData = PVRSRVGetPVRSRVData();
#endif
if (uiOrder == 0 &&
!bUnpinned &&
!PVRSRV_CHECK_CPU_CACHED(ui32CPUCacheFlags))
{
IMG_UINT32 uiEntries;
IMG_UINT32 *puiCounter;
struct list_head *psPoolHead;
_PagePoolLock();
uiEntries = _PagesInPoolUnlocked();
/* Check for number of current page pool entries and whether
* we have other asynchronous tasks in-flight */
if ( (uiEntries < g_ui32PagePoolMaxEntries) &&
((uiEntries + uiNumPages) <
(g_ui32PagePoolMaxEntries + g_ui32PagePoolMaxExcessEntries) ))
{
if (OSAtomicIncrement(&g_iPoolCleanTasks) <=
PVR_LINUX_PHYSMEM_MAX_ASYNC_CLEAN_TASKS)
{
#if defined(SUPPORT_PHYSMEM_TEST)
if (!psPVRSRVData->hCleanupThread)
{
goto eDecrement;
}
#endif
psCleanupData = OSAllocMem(sizeof(*psCleanupData));
if (!psCleanupData)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Failed to get memory for deferred page pool cleanup. "
"Trying to free pages immediately",
__func__));
goto eDecrement;
}
psCleanupThreadFn = &psCleanupData->sCleanupWork;
psCleanupData->ui32CPUCacheMode = ui32CPUCacheFlags;
psCleanupData->psPoolEntry = kmem_cache_alloc(g_psLinuxPagePoolCache, GFP_KERNEL);
if (!psCleanupData->psPoolEntry)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Failed to get memory for deferred page pool cleanup. "
"Trying to free pages immediately",
__func__));
goto eFreeCleanupData;
}
if (!_GetPoolListHead(ui32CPUCacheFlags, &psPoolHead, &puiCounter))
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Failed to get correct page pool",
__func__));
goto eFreePoolEntry;
}
/* Increase counter here to avoid deferred cleanup tasks piling up */
*puiCounter = *puiCounter + uiNumPages;
psCleanupData->psPoolEntry->ppsPageArray = ppsPageArray;
psCleanupData->psPoolEntry->uiItemsRemaining = uiNumPages;
psCleanupThreadFn->pfnFree = _CleanupThread_CleanPages;
psCleanupThreadFn->pvData = psCleanupData;
psCleanupThreadFn->bDependsOnHW = IMG_FALSE;
CLEANUP_THREAD_SET_RETRY_COUNT(psCleanupThreadFn,
CLEANUP_THREAD_RETRY_COUNT_DEFAULT);
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
/* MemStats usually relies on having the bridge lock held, however
* the page pool code may call PVRSRVStatsIncrMemAllocPoolStat and
* PVRSRVStatsDecrMemAllocPoolStat without the bridge lock held, so
* the page pool lock is used to ensure these calls are mutually
* exclusive
*/
PVRSRVStatsIncrMemAllocPoolStat(PAGE_SIZE * uiNumPages);
#endif
/* We must not hold the pool lock when calling AddWork because it might call us back to
* free pooled pages directly when unloading the driver */
_PagePoolUnlock();
PVRSRVCleanupThreadAddWork(psCleanupThreadFn);
}
else
{
goto eDecrement;
}
}
else
{
goto eUnlock;
}
}
else
{
goto eExitFalse;
}
return IMG_TRUE;
eFreePoolEntry:
OSFreeMem(psCleanupData->psPoolEntry);
eFreeCleanupData:
OSFreeMem(psCleanupData);
eDecrement:
OSAtomicDecrement(&g_iPoolCleanTasks);
eUnlock:
_PagePoolUnlock();
eExitFalse:
return IMG_FALSE;
}
/* Get the GFP flags that we pass to the page allocator */
static inline gfp_t
_GetGFPFlags(IMG_BOOL bZero,
PVRSRV_DEVICE_NODE *psDevNode)
{
struct device *psDev = psDevNode->psDevConfig->pvOSDevice;
gfp_t gfp_flags = GFP_USER | __GFP_NOWARN | __GFP_NOMEMALLOC;
#if defined(PVR_LINUX_PHYSMEM_USE_HIGHMEM_ONLY)
/* Force use of HIGHMEM */
gfp_flags |= __GFP_HIGHMEM;
PVR_UNREFERENCED_PARAMETER(psDev);
#else
if (psDev)
{
#if defined(CONFIG_64BIT) || defined(CONFIG_ARM_LPAE) || defined(CONFIG_X86_PAE)
if (*psDev->dma_mask > DMA_BIT_MASK(32))
{
/* If our system is able to handle large addresses use highmem */
gfp_flags |= __GFP_HIGHMEM;
}
else if (*psDev->dma_mask == DMA_BIT_MASK(32))
{
/* Limit to 32 bit.
* Achieved by setting __GFP_DMA32 for 64 bit systems */
gfp_flags |= __GFP_DMA32;
}
else
{
/* Limit to size of DMA zone. */
gfp_flags |= __GFP_DMA;
}
#else
if (*psDev->dma_mask < DMA_BIT_MASK(32))
{
gfp_flags |= __GFP_DMA;
}
else
{
gfp_flags |= __GFP_HIGHMEM;
}
#endif /* if defined(CONFIG_64BIT) || defined(CONFIG_ARM_LPAE) || defined(CONFIG_X86_PAE) */
}
#endif /* if defined(PVR_LINUX_PHYSMEM_USE_HIGHMEM_ONLY) */
if (bZero)
{
gfp_flags |= __GFP_ZERO;
}
return gfp_flags;
}
/*
* @Function _PoisonDevicePage
*
* @Description Poisons a device page. In normal case the device page has the
* same size as the OS page and so the ui32DevPageOrder will be
* equal to 0 and page argument will point to one OS page
* structure. In case of Non4K pages the order will be greater
* than 0 and page argument will point to an array of OS
* allocated pages.
*
* @Input psDevNode pointer to the device object
* @Input page array of the pages allocated by from the OS
* @Input ui32DevPageOrder order of the page (same as the one used to allocate
* the page array by alloc_pages())
* @Input ui32CPUCacheFlags CPU cache flags applied to the page
* @Input ui8PoisonValue value used to poison the page
*/
static void
_PoisonDevicePage(PVRSRV_DEVICE_NODE *psDevNode,
struct page *page,
IMG_UINT32 ui32DevPageOrder,
IMG_UINT32 ui32CPUCacheFlags,
IMG_BYTE ui8PoisonValue)
{
IMG_CPU_PHYADDR sCPUPhysAddrStart, sCPUPhysAddrEnd;
IMG_UINT32 ui32OsPageIdx;
for (ui32OsPageIdx = 0;
ui32OsPageIdx < (1U << ui32DevPageOrder);
ui32OsPageIdx++)
{
struct page *current_page = page + ui32OsPageIdx;
void *kvaddr = kmap_atomic(current_page);
if (PVRSRV_CHECK_CPU_UNCACHED(ui32CPUCacheFlags) ||
PVRSRV_CHECK_CPU_WRITE_COMBINE(ui32CPUCacheFlags))
{
OSDeviceMemSet(kvaddr, ui8PoisonValue, PAGE_SIZE);
}
else
{
OSCachedMemSet(kvaddr, ui8PoisonValue, PAGE_SIZE);
}
sCPUPhysAddrStart.uiAddr = page_to_phys(current_page);
sCPUPhysAddrEnd.uiAddr = sCPUPhysAddrStart.uiAddr + PAGE_SIZE;
OSCPUCacheFlushRangeKM(psDevNode,
kvaddr, kvaddr + PAGE_SIZE,
sCPUPhysAddrStart, sCPUPhysAddrEnd);
kunmap_atomic(kvaddr);
}
}
/* Allocate and initialise the structure to hold the metadata of the allocation */
static PVRSRV_ERROR
_AllocOSPageArray(PVRSRV_DEVICE_NODE *psDevNode,
PMR_SIZE_T uiChunkSize,
IMG_UINT32 ui32NumPhysChunks,
IMG_UINT32 ui32NumVirtChunks,
IMG_UINT32 uiLog2AllocPageSize,
IMG_BOOL bZero,
IMG_BOOL bIsCMA,
IMG_BOOL bPoisonOnAlloc,
IMG_BOOL bPoisonOnFree,
IMG_BOOL bOnDemand,
IMG_UINT32 ui32CPUCacheFlags,
IMG_PID uiPid,
PMR_OSPAGEARRAY_DATA **ppsPageArrayDataPtr)
{
PVRSRV_ERROR eError;
PMR_SIZE_T uiSize = uiChunkSize * ui32NumVirtChunks;
IMG_UINT32 uiNumOSPageSizeVirtPages;
IMG_UINT32 uiNumDevPageSizeVirtPages;
PMR_OSPAGEARRAY_DATA *psPageArrayData;
IMG_UINT64 ui64DmaMask = 0;
PVR_UNREFERENCED_PARAMETER(ui32NumPhysChunks);
/* Use of cast below is justified by the assertion that follows to
* prove that no significant bits have been truncated */
uiNumOSPageSizeVirtPages = (IMG_UINT32) (((uiSize - 1) >> PAGE_SHIFT) + 1);
PVR_ASSERT(((PMR_SIZE_T) uiNumOSPageSizeVirtPages << PAGE_SHIFT) == uiSize);
uiNumDevPageSizeVirtPages = uiNumOSPageSizeVirtPages >> (uiLog2AllocPageSize - PAGE_SHIFT);
/* Allocate the struct to hold the metadata */
psPageArrayData = kmem_cache_alloc(g_psLinuxPageArray, GFP_KERNEL);
if (psPageArrayData == NULL)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: OS refused the memory allocation for the private data.",
__func__));
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e_freed_none;
}
/*
* Allocate the page array
*
* We avoid tracking this memory because this structure might go into the page pool.
* The OS can drain the pool asynchronously and when doing that we have to avoid
* any potential deadlocks.
*
* In one scenario the process stats vmalloc hash table lock is held and then
* the oom-killer softirq is trying to call _ScanObjectsInPagePool(), it must not
* try to acquire the vmalloc hash table lock again.
*/
psPageArrayData->pagearray = OSAllocZMemNoStats(sizeof(struct page *) * uiNumDevPageSizeVirtPages);
if (psPageArrayData->pagearray == NULL)
{
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e_free_kmem_cache;
}
else
{
if (bIsCMA)
{
/* Allocate additional DMA/CMA cpu kernel virtual address & device bus address array state */
psPageArrayData->dmavirtarray = OSAllocZMemNoStats(sizeof(void*) * uiNumDevPageSizeVirtPages);
if (psPageArrayData->dmavirtarray == NULL)
{
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e_free_pagearray;
}
psPageArrayData->dmaphysarray = OSAllocZMemNoStats(sizeof(dma_addr_t) * uiNumDevPageSizeVirtPages);
if (psPageArrayData->dmaphysarray == NULL)
{
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e_free_cpuvirtaddrarray;
}
}
}
if (psDevNode->psDevConfig && psDevNode->psDevConfig->pvOSDevice)
{
struct device *psDev = psDevNode->psDevConfig->pvOSDevice;
ui64DmaMask = *psDev->dma_mask;
}
/* Init metadata */
psPageArrayData->psDevNode = psDevNode;
psPageArrayData->uiPid = uiPid;
psPageArrayData->iNumOSPagesAllocated = 0;
psPageArrayData->uiTotalNumOSPages = uiNumOSPageSizeVirtPages;
psPageArrayData->uiLog2AllocPageSize = uiLog2AllocPageSize;
psPageArrayData->ui64DmaMask = ui64DmaMask;
psPageArrayData->bZero = bZero;
psPageArrayData->bIsCMA = bIsCMA;
psPageArrayData->bOnDemand = bOnDemand;
psPageArrayData->bUnpinned = IMG_FALSE;
psPageArrayData->bPoisonOnFree = bPoisonOnFree;
psPageArrayData->bPoisonOnAlloc = bPoisonOnAlloc;
psPageArrayData->ui32CPUCacheFlags = ui32CPUCacheFlags;
/* Indicate whether this is an allocation with default caching attribute (i.e cached) or not */
if (PVRSRV_CHECK_CPU_UNCACHED(ui32CPUCacheFlags) ||
PVRSRV_CHECK_CPU_WRITE_COMBINE(ui32CPUCacheFlags))
{
psPageArrayData->bUnsetMemoryType = IMG_TRUE;
}
else
{
psPageArrayData->bUnsetMemoryType = IMG_FALSE;
}
*ppsPageArrayDataPtr = psPageArrayData;
return PVRSRV_OK;
/* Error path */
e_free_cpuvirtaddrarray:
OSFreeMemNoStats(psPageArrayData->dmavirtarray);
e_free_pagearray:
OSFreeMemNoStats(psPageArrayData->pagearray);
e_free_kmem_cache:
kmem_cache_free(g_psLinuxPageArray, psPageArrayData);
PVR_DPF((PVR_DBG_ERROR,
"%s: OS refused the memory allocation for the page pointer table. "
"Did you ask for too much?",
__func__));
e_freed_none:
PVR_ASSERT(eError != PVRSRV_OK);
return eError;
}
static inline void
_ApplyCacheMaintenance(PVRSRV_DEVICE_NODE *psDevNode,
struct page **ppsPage,
IMG_UINT32 uiNumPages,
IMG_BOOL bFlush)
{
PVRSRV_ERROR eError = PVRSRV_ERROR_RETRY;
void * pvAddr;
#if !defined(__arm__) && !defined(__arm64__) && !defined(__aarch64__)
if ((uiNumPages << PAGE_SHIFT) >= PVR_DIRTY_BYTES_FLUSH_THRESHOLD)
{
/* May fail so fallback to range-based flush */
eError = OSCPUOperation(PVRSRV_CACHE_OP_FLUSH);
}
#endif
if (eError != PVRSRV_OK)
{
if (OSCPUCacheOpAddressType() == PVRSRV_CACHE_OP_ADDR_TYPE_VIRTUAL)
{
pgprot_t pgprot = PAGE_KERNEL;
IMG_UINT32 uiNumToClean = uiNumPages;
struct page **ppsCleanArray = ppsPage;
/* Map and flush page.
* For large page arrays do it PVR_LINUX_PHYSMEM_MAX_KMAP_SIZE
* at a time. */
while (uiNumToClean != 0)
{
IMG_UINT32 uiToClean = MIN(PVR_LINUX_PHYSMEM_MAX_KMAP_PAGES,
uiNumToClean);
IMG_CPU_PHYADDR sUnused =
{ IMG_CAST_TO_CPUPHYADDR_UINT(0xCAFEF00DDEADBEEFULL) };
pvAddr = vm_map_ram(ppsCleanArray, uiToClean, -1, pgprot);
if (!pvAddr)
{
PVR_DPF((PVR_DBG_ERROR,
"Unable to flush page cache for new allocation, skipping flush."));
return;
}
CacheOpExec(psDevNode,
pvAddr,
pvAddr + PAGE_SIZE,
sUnused,
sUnused,
PVRSRV_CACHE_OP_FLUSH);
vm_unmap_ram(pvAddr, uiToClean);
ppsCleanArray = &(ppsCleanArray[uiToClean]);
uiNumToClean -= uiToClean;
}
}
else
{
IMG_UINT32 ui32Idx;
for (ui32Idx = 0; ui32Idx < uiNumPages; ++ui32Idx)
{
IMG_CPU_PHYADDR sCPUPhysAddrStart, sCPUPhysAddrEnd;
pvAddr = kmap(ppsPage[ui32Idx]);
sCPUPhysAddrStart.uiAddr = page_to_phys(ppsPage[ui32Idx]);
sCPUPhysAddrEnd.uiAddr = sCPUPhysAddrStart.uiAddr + PAGE_SIZE;
/* If we're zeroing, we need to make sure the cleared memory is pushed out
* of the cache before the cache lines are invalidated */
CacheOpExec(psDevNode,
pvAddr,
pvAddr + PAGE_SIZE,
sCPUPhysAddrStart,
sCPUPhysAddrEnd,
PVRSRV_CACHE_OP_FLUSH);
kunmap(ppsPage[ui32Idx]);
}
}
}
}
/* Change the caching attribute of pages on x86 systems and takes care of
* cache maintenance. This function is supposed to be called once for pages that
* came from alloc_pages(). It expects an array of OS page sized pages!
*
* Flush/Invalidate pages in case the allocation is not cached. Necessary to
* remove pages from the cache that might be flushed later and corrupt memory. */
static inline PVRSRV_ERROR
_ApplyOSPagesAttribute(PVRSRV_DEVICE_NODE *psDevNode,
struct page **ppsPage,
IMG_UINT32 uiNumPages,
IMG_BOOL bFlush,
IMG_UINT32 ui32CPUCacheFlags)
{
PVRSRV_ERROR eError = PVRSRV_OK;
IMG_BOOL bCPUCached = PVRSRV_CHECK_CPU_CACHED(ui32CPUCacheFlags);
IMG_BOOL bCPUUncached = PVRSRV_CHECK_CPU_UNCACHED(ui32CPUCacheFlags);
IMG_BOOL bCPUWriteCombine = PVRSRV_CHECK_CPU_WRITE_COMBINE(ui32CPUCacheFlags);
if (ppsPage != NULL && uiNumPages != 0)
{
#if defined (CONFIG_X86)
/* On x86 we have to set page cache attributes for non-cached pages.
* The call is implicitly taking care of all flushing/invalidating
* and therefore we can skip the usual cache maintenance after this. */
if (bCPUUncached || bCPUWriteCombine)
{
/* On x86 if we already have a mapping (e.g. low memory) we need to change the mode of
current mapping before we map it ourselves */
int ret = IMG_FALSE;
PVR_UNREFERENCED_PARAMETER(bFlush);
switch (PVRSRV_CPU_CACHE_MODE(ui32CPUCacheFlags))
{
case PVRSRV_MEMALLOCFLAG_CPU_UNCACHED:
ret = set_pages_array_uc(ppsPage, uiNumPages);
if (ret)
{
eError = PVRSRV_ERROR_UNABLE_TO_SET_CACHE_MODE;
PVR_DPF((PVR_DBG_ERROR, "Setting Linux page caching mode to UC failed, returned %d", ret));
}
break;
case PVRSRV_MEMALLOCFLAG_CPU_WRITE_COMBINE:
ret = set_pages_array_wc(ppsPage, uiNumPages);
if (ret)
{
eError = PVRSRV_ERROR_UNABLE_TO_SET_CACHE_MODE;
PVR_DPF((PVR_DBG_ERROR, "Setting Linux page caching mode to WC failed, returned %d", ret));
}
break;
case PVRSRV_MEMALLOCFLAG_CPU_CACHED:
break;
default:
break;
}
}
else
#endif
{
if ( bFlush ||
bCPUUncached || bCPUWriteCombine ||
(bCPUCached && PVRSRV_CHECK_CPU_CACHE_CLEAN(ui32CPUCacheFlags)) )
{
/* We can be given pages which still remain in the cache.
In order to make sure that the data we write through our mappings
doesn't get overwritten by later cache evictions we invalidate the
pages that are given to us.
Note:
This still seems to be true if we request cold pages, it's just less
likely to be in the cache. */
_ApplyCacheMaintenance(psDevNode,
ppsPage,
uiNumPages,
bFlush);
}
}
}
return eError;
}
/* Same as _AllocOSPage except it uses DMA framework to perform allocation.
* uiPageIndex is expected to be the pagearray index where to store the higher order page. */
static PVRSRV_ERROR
_AllocOSPage_CMA(PMR_OSPAGEARRAY_DATA *psPageArrayData,
gfp_t gfp_flags,
IMG_UINT32 ui32AllocOrder,
IMG_UINT32 ui32MinOrder,
IMG_UINT32 uiPageIndex)
{
void *virt_addr;
struct page *page;
dma_addr_t bus_addr;
IMG_UINT32 uiAllocIsMisaligned;
size_t alloc_size = PAGE_SIZE << ui32AllocOrder;
struct device *dev = psPageArrayData->psDevNode->psDevConfig->pvOSDevice;
PVR_ASSERT(ui32AllocOrder == ui32MinOrder);
do
{
DisableOOMKiller();
#if defined(CONFIG_L4) || defined(PVR_LINUX_PHYSMEM_SUPPRESS_DMA_AC)
virt_addr = NULL;
#else
virt_addr = dma_alloc_coherent(dev, alloc_size, &bus_addr, gfp_flags);
#endif
if (virt_addr == NULL)
{
/* The idea here is primarily to support some older kernels with
broken or non-functioning DMA/CMA implementations (< Linux-3.4)
and to also handle DMA/CMA allocation failures by attempting a
normal page allocation though we expect dma_alloc_coherent()
already attempts this internally also before failing but
nonetheless it does no harm to retry the allocation ourselves */
page = alloc_pages(gfp_flags, ui32AllocOrder);
if (page)
{
/* Taint bus_addr as alloc_page, needed when freeing;
also acquire the low memory page address only, this
prevents mapping possible high memory pages into
kernel virtual address space which might exhaust
the VMALLOC address space */
bus_addr = DMA_SET_ALLOCPG_ADDR(page_to_phys(page));
virt_addr = (void*)(uintptr_t) DMA_VADDR_NOT_IN_USE;
}
else
{
EnableOOMKiller();
return PVRSRV_ERROR_OUT_OF_MEMORY;
}
}
else
{
#if !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
page = pfn_to_page(bus_addr >> PAGE_SHIFT);
#else
/* Assumes bus address space is identical to physical address space */
page = phys_to_page(bus_addr);
#endif
}
EnableOOMKiller();
/* Physical allocation alignment works/hidden behind the scene transparently,
we do this here if the allocated buffer address does not meet its alignment
requirement by over-allocating using the next power-2 order and reporting
aligned-adjusted values back to meet the requested alignment constraint.
Evidently we waste memory by doing this so should only do so if we do not
initially meet the alignment constraint. */
uiAllocIsMisaligned = DMA_GET_ADDR(bus_addr) & ((PAGE_SIZE<<ui32MinOrder)-1);
if (uiAllocIsMisaligned || ui32AllocOrder > ui32MinOrder)
{
IMG_BOOL bUsedAllocPages = DMA_IS_ALLOCPG_ADDR(bus_addr);
if (ui32AllocOrder == ui32MinOrder)
{
if (bUsedAllocPages)
{
__free_pages(page, ui32AllocOrder);
}
else
{
dma_free_coherent(dev, alloc_size, virt_addr, bus_addr);
}
ui32AllocOrder = ui32AllocOrder + 1;
alloc_size = PAGE_SIZE << ui32AllocOrder;
PVR_ASSERT(uiAllocIsMisaligned != 0);
}
else
{
size_t align_adjust = PAGE_SIZE << ui32MinOrder;
/* Adjust virtual/bus addresses to meet alignment */
bus_addr = bUsedAllocPages ? page_to_phys(page) : bus_addr;
align_adjust = PVR_ALIGN((size_t)bus_addr, align_adjust);
align_adjust -= (size_t)bus_addr;
if (align_adjust)
{
if (bUsedAllocPages)
{
page += align_adjust >> PAGE_SHIFT;
bus_addr = DMA_SET_ALLOCPG_ADDR(page_to_phys(page));
virt_addr = (void*)(uintptr_t) DMA_VADDR_NOT_IN_USE;
}
else
{
bus_addr += align_adjust;
virt_addr += align_adjust;
#if !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
page = pfn_to_page(bus_addr >> PAGE_SHIFT);
#else
/* Assumes bus address space is identical to physical address space */
page = phys_to_page(bus_addr);
#endif
}
/* Store adjustments in PAGE_SIZE counts */
align_adjust = align_adjust >> PAGE_SHIFT;
bus_addr = DMA_SET_ALIGN_ADJUSTMENT(bus_addr, align_adjust);
}
/* Taint bus_addr due to over-allocation, allows us to free
* memory correctly */
bus_addr = DMA_SET_ADJUSTED_ADDR(bus_addr);
uiAllocIsMisaligned = 0;
}
}
} while (uiAllocIsMisaligned);
/* Convert OSPageSize-based index into DevicePageSize-based index */
psPageArrayData->dmavirtarray[uiPageIndex] = virt_addr;
psPageArrayData->dmaphysarray[uiPageIndex] = bus_addr;
psPageArrayData->pagearray[uiPageIndex] = page;
return PVRSRV_OK;
}
/* Allocate a page of order uiAllocOrder and stores it in the page array ppsPage at
* position uiPageIndex.
*
* If the order is higher than 0, it splits the page into multiples and
* stores them at position uiPageIndex to uiPageIndex+(1<<uiAllocOrder).
*
* This function is supposed to be used for uiMinOrder == 0 only! */
static PVRSRV_ERROR
_AllocOSPage(PMR_OSPAGEARRAY_DATA *psPageArrayData,
gfp_t gfp_flags,
IMG_UINT32 uiAllocOrder,
IMG_UINT32 uiMinOrder,
IMG_UINT32 uiPageIndex)
{
struct page *psPage;
IMG_UINT32 ui32Count;
/* Sanity check. If it fails we write into the wrong places in the array. */
PVR_ASSERT(uiMinOrder == 0);
/* Allocate the page */
DisableOOMKiller();
psPage = alloc_pages(gfp_flags, uiAllocOrder);
EnableOOMKiller();
if (psPage == NULL)
{
return PVRSRV_ERROR_OUT_OF_MEMORY;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0))
/* In case we need to, split the higher order page;
this should only be used for order-0 allocations
as higher order allocations should use DMA/CMA */
if (uiAllocOrder != 0)
{
split_page(psPage, uiAllocOrder);
}
#endif
/* Store the page (or multiple split pages) in the page array */
for (ui32Count = 0; ui32Count < (1 << uiAllocOrder); ui32Count++)
{
psPageArrayData->pagearray[uiPageIndex + ui32Count] = &(psPage[ui32Count]);
}
return PVRSRV_OK;
}
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
#if defined(PVRSRV_ENABLE_MEMORY_STATS)
static inline void _AddMemAllocRecord_UmaPages(PMR_OSPAGEARRAY_DATA *psPageArrayData,
struct page *psPage)
{
IMG_CPU_PHYADDR sCPUPhysAddr = { page_to_phys(psPage) };
PVRSRVStatsAddMemAllocRecord(PVRSRV_MEM_ALLOC_TYPE_ALLOC_UMA_PAGES,
NULL, sCPUPhysAddr,
1 << psPageArrayData->uiLog2AllocPageSize,
NULL, psPageArrayData->uiPid);
}
static inline void _RemoveMemAllocRecord_UmaPages(PMR_OSPAGEARRAY_DATA *psPageArrayData,
struct page *psPage)
{
PVRSRVStatsRemoveMemAllocRecord(PVRSRV_MEM_ALLOC_TYPE_ALLOC_UMA_PAGES,
(IMG_UINT64) page_to_phys(psPage),
psPageArrayData->uiPid);
}
#else /* defined(PVRSRV_ENABLE_MEMORY_STATS) */
static inline void _IncrMemAllocStat_UmaPages(size_t uiSize, IMG_PID uiPid)
{
PVRSRVStatsIncrMemAllocStat(PVRSRV_MEM_ALLOC_TYPE_ALLOC_UMA_PAGES,
uiSize, uiPid);
}
static inline void _DecrMemAllocStat_UmaPages(size_t uiSize, IMG_PID uiPid)
{
PVRSRVStatsDecrMemAllocStat(PVRSRV_MEM_ALLOC_TYPE_ALLOC_UMA_PAGES,
uiSize, uiPid);
}
#endif /* defined(PVRSRV_ENABLE_MEMORY_STATS) */
#endif /* defined(PVRSRV_ENABLE_PROCESS_STATS) */
/* Allocation of OS pages: We may allocate 2^N order pages at a time for two reasons.
*
* Firstly to support device pages which are larger than OS. By asking the OS for 2^N
* order OS pages at a time we guarantee the device page is contiguous.
*
* Secondly for performance where we may ask for 2^N order pages to reduce the number
* of calls to alloc_pages, and thus reduce time for huge allocations.
*
* Regardless of page order requested, we need to break them down to track _OS pages.
* The maximum order requested is increased if all max order allocations were successful.
* If any request fails we reduce the max order.
*/
static PVRSRV_ERROR
_AllocOSPages_Fast(PMR_OSPAGEARRAY_DATA *psPageArrayData)
{
PVRSRV_ERROR eError;
IMG_UINT32 uiArrayIndex = 0;
IMG_UINT32 ui32Order;
IMG_UINT32 ui32MinOrder = psPageArrayData->uiLog2AllocPageSize - PAGE_SHIFT;
IMG_BOOL bIncreaseMaxOrder = IMG_TRUE;
IMG_UINT32 ui32NumPageReq;
IMG_UINT32 uiPagesToAlloc;
IMG_UINT32 uiPagesFromPool = 0;
gfp_t gfp_flags = _GetGFPFlags(ui32MinOrder ? psPageArrayData->bZero : IMG_FALSE, /* Zero all pages later as batch */
psPageArrayData->psDevNode);
gfp_t ui32GfpFlags;
gfp_t ui32HighOrderGfpFlags = ((gfp_flags & ~__GFP_RECLAIM) | __GFP_NORETRY);
struct page **ppsPageArray = psPageArrayData->pagearray;
struct page **ppsPageAttributeArray = NULL;
uiPagesToAlloc = psPageArrayData->uiTotalNumOSPages;
/* Try to get pages from the pool since it is faster;
the page pool currently only supports zero-order pages
thus currently excludes all DMA/CMA allocated memory */
_GetPagesFromPoolLocked(psPageArrayData->psDevNode,
psPageArrayData->ui32CPUCacheFlags,
uiPagesToAlloc,
ui32MinOrder,
psPageArrayData->bZero,
ppsPageArray,
&uiPagesFromPool);
uiArrayIndex = uiPagesFromPool;
if ((uiPagesToAlloc - uiPagesFromPool) < PVR_LINUX_HIGHORDER_ALLOCATION_THRESHOLD)
{ /* Small allocations: ask for one device page at a time */
ui32Order = ui32MinOrder;
bIncreaseMaxOrder = IMG_FALSE;
}
else
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0))
/* Large zero-order or none zero-order allocations, ask for
MAX(max-order,min-order) order pages at a time; alloc
failures throttles this down to ZeroOrder allocations */
ui32Order = MAX(g_uiMaxOrder, ui32MinOrder);
#else
/* Because split_pages() is not available on older kernels
we cannot mix-and-match any-order pages in the PMR;
only same-order pages must be present in page array.
So we unconditionally force it to use ui32MinOrder on
these older kernels */
ui32Order = ui32MinOrder;
#if defined(DEBUG)
if (! psPageArrayData->bIsCMA)
{
/* Sanity check that this is zero */
PVR_ASSERT(! ui32Order);
}
#endif
#endif
}
/* Only if asking for more contiguity than we actually need, let it fail */
ui32GfpFlags = (ui32Order > ui32MinOrder) ? ui32HighOrderGfpFlags : gfp_flags;
ui32NumPageReq = (1 << ui32Order);
while (uiArrayIndex < uiPagesToAlloc)
{
IMG_UINT32 ui32PageRemain = uiPagesToAlloc - uiArrayIndex;
while (ui32NumPageReq > ui32PageRemain)
{
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0))
/* Pages to request is larger than that remaining
so ask for less so never over allocate */
ui32Order = MAX(ui32Order >> 1,ui32MinOrder);
#else
/* Pages to request is larger than that remaining so
do nothing thus over allocate as we do not support
mix/match of any-order pages in PMR page-array in
older kernels (simplifies page free logic) */
PVR_ASSERT(ui32Order == ui32MinOrder);
#endif
ui32NumPageReq = (1 << ui32Order);
ui32GfpFlags = (ui32Order > ui32MinOrder) ? ui32HighOrderGfpFlags : gfp_flags;
}
if (psPageArrayData->bIsCMA)
{
/* As the DMA/CMA framework rounds-up request to the
next power-of-two, we request multiple uiMinOrder
pages to satisfy allocation request in order to
minimise wasting memory */
eError = _AllocOSPage_CMA(psPageArrayData,
ui32GfpFlags,
ui32Order,
ui32MinOrder,
uiArrayIndex >> ui32MinOrder);
}
else
{
/* Allocate uiOrder pages at uiArrayIndex */
eError = _AllocOSPage(psPageArrayData,
ui32GfpFlags,
ui32Order,
ui32MinOrder,
uiArrayIndex);
}
if (eError == PVRSRV_OK)
{
/* Successful request. Move onto next. */
uiArrayIndex += ui32NumPageReq;
}
else
{
if (ui32Order > ui32MinOrder)
{
/* Last request failed. Let's ask for less next time */
ui32Order = MAX(ui32Order >> 1,ui32MinOrder);
bIncreaseMaxOrder = IMG_FALSE;
ui32NumPageReq = (1 << ui32Order);
ui32GfpFlags = (ui32Order > ui32MinOrder) ? ui32HighOrderGfpFlags : gfp_flags;
g_uiMaxOrder = ui32Order;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0))
/* We should not trigger this code path in older kernels,
this is enforced by ensuring ui32Order == ui32MinOrder */
PVR_ASSERT(ui32Order == ui32MinOrder);
#endif
}
else
{
/* Failed to alloc pages at required contiguity. Failed allocation */
PVR_DPF((PVR_DBG_ERROR, "%s: %s failed to honour request at %u of %u, flags = %x, order = %u (%s)",
__func__,
psPageArrayData->bIsCMA ? "dma_alloc_coherent" : "alloc_pages",
uiArrayIndex,
uiPagesToAlloc,
ui32GfpFlags,
ui32Order,
PVRSRVGetErrorString(eError)));
eError = PVRSRV_ERROR_PMR_FAILED_TO_ALLOC_PAGES;
goto e_free_pages;
}
}
}
if (bIncreaseMaxOrder && (g_uiMaxOrder < PVR_LINUX_PHYSMEM_MAX_ALLOC_ORDER_NUM))
{ /* All successful allocations on max order. Let's ask for more next time */
g_uiMaxOrder++;
}
/* Construct table of page pointers to apply attributes */
ppsPageAttributeArray = &ppsPageArray[uiPagesFromPool];
if (psPageArrayData->bIsCMA)
{
IMG_UINT32 uiIdx, uiIdy, uiIdz;
ppsPageAttributeArray = OSAllocMem(sizeof(struct page *) * uiPagesToAlloc);
if (ppsPageAttributeArray == NULL)
{
PVR_DPF((PVR_DBG_ERROR, "Failed OSAllocMem() for page attributes table"));
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e_free_pages;
}
for (uiIdx = 0; uiIdx < uiPagesToAlloc; uiIdx += ui32NumPageReq)
{
uiIdy = uiIdx >> ui32Order;
for (uiIdz = 0; uiIdz < ui32NumPageReq; uiIdz++)
{
ppsPageAttributeArray[uiIdx+uiIdz] = psPageArrayData->pagearray[uiIdy];
ppsPageAttributeArray[uiIdx+uiIdz] += uiIdz;
}
}
}
if (psPageArrayData->bZero && ui32MinOrder == 0)
{
eError = _ZeroPageArray(uiPagesToAlloc - uiPagesFromPool,
ppsPageAttributeArray,
PAGE_KERNEL);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "Failed to zero pages (fast)"));
goto e_free_pages;
}
}
/* Do the cache management as required */
eError = _ApplyOSPagesAttribute(psPageArrayData->psDevNode,
ppsPageAttributeArray,
uiPagesToAlloc - uiPagesFromPool,
psPageArrayData->bZero,
psPageArrayData->ui32CPUCacheFlags);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "Failed to set page attributes"));
goto e_free_pages;
}
else
{
if (psPageArrayData->bIsCMA)
{
OSFreeMem(ppsPageAttributeArray);
}
}
/* Update metadata */
psPageArrayData->iNumOSPagesAllocated = psPageArrayData->uiTotalNumOSPages;
{
IMG_UINT32 ui32NumPages =
psPageArrayData->iNumOSPagesAllocated >> ui32MinOrder;
IMG_UINT32 i;
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
#if defined(PVRSRV_ENABLE_MEMORY_STATS)
for (i = 0; i < ui32NumPages; i++)
{
_AddMemAllocRecord_UmaPages(psPageArrayData, ppsPageArray[i]);
}
#else
_IncrMemAllocStat_UmaPages(uiPagesToAlloc * PAGE_SIZE,
psPageArrayData->uiPid);
#endif
#endif
if (psPageArrayData->bPoisonOnAlloc)
{
for (i = 0; i < ui32NumPages; i++)
{
_PoisonDevicePage(psPageArrayData->psDevNode,
ppsPageArray[i],
ui32MinOrder,
psPageArrayData->ui32CPUCacheFlags,
PVRSRV_POISON_ON_ALLOC_VALUE);
}
}
}
return PVRSRV_OK;
/* Error path */
e_free_pages:
{
IMG_UINT32 ui32PageToFree;
if (psPageArrayData->bIsCMA)
{
IMG_UINT32 uiDevArrayIndex = uiArrayIndex >> ui32Order;
IMG_UINT32 uiDevPageSize = PAGE_SIZE << ui32Order;
PVR_ASSERT(ui32Order == ui32MinOrder);
if (ppsPageAttributeArray)
{
OSFreeMem(ppsPageAttributeArray);
}
for (ui32PageToFree = 0; ui32PageToFree < uiDevArrayIndex; ui32PageToFree++)
{
_FreeOSPage_CMA(psPageArrayData->psDevNode->psDevConfig->pvOSDevice,
uiDevPageSize,
ui32MinOrder,
psPageArrayData->dmavirtarray[ui32PageToFree],
psPageArrayData->dmaphysarray[ui32PageToFree],
ppsPageArray[ui32PageToFree]);
psPageArrayData->dmaphysarray[ui32PageToFree]= (dma_addr_t)0;
psPageArrayData->dmavirtarray[ui32PageToFree] = NULL;
ppsPageArray[ui32PageToFree] = NULL;
}
}
else
{
/* Free the pages we got from the pool */
for (ui32PageToFree = 0; ui32PageToFree < uiPagesFromPool; ui32PageToFree++)
{
_FreeOSPage(ui32MinOrder,
psPageArrayData->bUnsetMemoryType,
ppsPageArray[ui32PageToFree]);
ppsPageArray[ui32PageToFree] = NULL;
}
for (ui32PageToFree = uiPagesFromPool; ui32PageToFree < uiArrayIndex; ui32PageToFree++)
{
_FreeOSPage(ui32MinOrder, IMG_FALSE, ppsPageArray[ui32PageToFree]);
ppsPageArray[ui32PageToFree] = NULL;
}
}
return eError;
}
}
/* Allocation of OS pages: This function is used for sparse allocations.
*
* Sparse allocations provide only a proportion of sparse physical backing within the total
* virtual range. */
static PVRSRV_ERROR
_AllocOSPages_Sparse(PMR_OSPAGEARRAY_DATA *psPageArrayData,
IMG_UINT32 *puiAllocIndices,
IMG_UINT32 uiPagesToAlloc)
{
PVRSRV_ERROR eError;
IMG_UINT32 i;
struct page **ppsPageArray = psPageArrayData->pagearray;
IMG_UINT32 uiOrder = psPageArrayData->uiLog2AllocPageSize - PAGE_SHIFT;
IMG_UINT32 uiPagesFromPool = 0;
IMG_UINT32 uiNumOSPagesToAlloc = uiPagesToAlloc * (1 << uiOrder);
IMG_UINT32 uiTotalNumAllocPages = psPageArrayData->uiTotalNumOSPages >> uiOrder;
gfp_t ui32GfpFlags = _GetGFPFlags(uiOrder ? psPageArrayData->bZero :
IMG_FALSE, /* Zero pages later as batch */
psPageArrayData->psDevNode);
/* We use this page array to receive pages from the pool and then reuse it afterwards to
* store pages that need their cache attribute changed on x86*/
struct page **ppsTempPageArray;
IMG_UINT32 uiTempPageArrayIndex = 0;
/* Allocate the temporary page array that we need here to receive pages
* from the pool and to store pages that need their caching attributes changed.
* Allocate number of OS pages to be able to use the attribute function later. */
ppsTempPageArray = OSAllocMem(sizeof(struct page*) * uiNumOSPagesToAlloc);
if (ppsTempPageArray == NULL)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed metadata allocation", __func__));
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e_exit;
}
/* Check the requested number of pages if they fit in the page array */
if (uiTotalNumAllocPages <
((psPageArrayData->iNumOSPagesAllocated >> uiOrder) + uiPagesToAlloc) )
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Trying to allocate more pages (Order %u) than this buffer can handle, "
"Request + Allocated < Max! Request %u, Allocated %u, Max %u.",
__func__,
uiOrder,
uiPagesToAlloc,
psPageArrayData->iNumOSPagesAllocated >> uiOrder,
uiTotalNumAllocPages));
eError = PVRSRV_ERROR_PMR_BAD_MAPPINGTABLE_SIZE;
goto e_free_temp_array;
}
/* Try to get pages from the pool since it is faster */
_GetPagesFromPoolLocked(psPageArrayData->psDevNode,
psPageArrayData->ui32CPUCacheFlags,
uiPagesToAlloc,
uiOrder,
psPageArrayData->bZero,
ppsTempPageArray,
&uiPagesFromPool);
/* Allocate pages from the OS or move the pages that we got from the pool
* to the page array */
for (i = 0; i < uiPagesToAlloc; i++)
{
/* Check if the indices we are allocating are in range */
if (puiAllocIndices[i] >= uiTotalNumAllocPages)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Given alloc index %u at %u is larger than page array %u.",
__func__,
i,
puiAllocIndices[i],
uiTotalNumAllocPages));
eError = PVRSRV_ERROR_DEVICEMEM_OUT_OF_RANGE;
goto e_free_pages;
}
/* Check if there is not already a page allocated at this position */
if (NULL != ppsPageArray[puiAllocIndices[i]])
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Mapping number %u at page array index %u already exists. "
"Page struct %p",
__func__,
i,
puiAllocIndices[i],
ppsPageArray[puiAllocIndices[i]]));
eError = PVRSRV_ERROR_PMR_MAPPING_ALREADY_EXISTS;
goto e_free_pages;
}
/* Finally assign a page to the array.
* Either from the pool or allocate a new one. */
if (uiPagesFromPool != 0)
{
uiPagesFromPool--;
ppsPageArray[puiAllocIndices[i]] = ppsTempPageArray[uiPagesFromPool];
}
else
{
if (psPageArrayData->bIsCMA)
{
/* As the DMA/CMA framework rounds-up request to the
next power-of-two, we request multiple uiMinOrder
pages to satisfy allocation request in order to
minimise wasting memory */
eError = _AllocOSPage_CMA(psPageArrayData,
ui32GfpFlags,
uiOrder,
uiOrder,
puiAllocIndices[i]);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "Failed to alloc CMA pages"));
goto e_free_pages;
}
}
else
{
DisableOOMKiller();
ppsPageArray[puiAllocIndices[i]] = alloc_pages(ui32GfpFlags, uiOrder);
EnableOOMKiller();
}
if (ppsPageArray[puiAllocIndices[i]] != NULL)
{
/* Reusing the temp page array if it has no pool pages anymore */
if (psPageArrayData->bIsCMA)
{
IMG_UINT32 idx;
struct page* psPageAddr;
psPageAddr = ppsPageArray[puiAllocIndices[i]];
for (idx = 0; idx < (1 << uiOrder); idx++)
{
ppsTempPageArray[uiTempPageArrayIndex + idx] = psPageAddr;
psPageAddr++;
}
uiTempPageArrayIndex += (1 << uiOrder);
}
else
{
ppsTempPageArray[uiTempPageArrayIndex] = ppsPageArray[puiAllocIndices[i]];
uiTempPageArrayIndex++;
}
}
else
{
/* Failed to alloc pages at required contiguity. Failed allocation */
PVR_DPF((PVR_DBG_ERROR,
"%s: alloc_pages failed to honour request at %u of %u, flags = %x, order = %u",
__func__,
i,
uiPagesToAlloc,
ui32GfpFlags,
uiOrder));
eError = PVRSRV_ERROR_PMR_FAILED_TO_ALLOC_PAGES;
goto e_free_pages;
}
}
}
if (psPageArrayData->bZero && uiOrder == 0)
{
eError = _ZeroPageArray(uiTempPageArrayIndex,
ppsTempPageArray,
PAGE_KERNEL);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "Failed to zero pages (sparse)"));
goto e_free_pages;
}
}
/* Do the cache management as required */
eError = _ApplyOSPagesAttribute(psPageArrayData->psDevNode,
ppsTempPageArray,
uiTempPageArrayIndex,
psPageArrayData->bZero,
psPageArrayData->ui32CPUCacheFlags);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "Failed to set page attributes"));
goto e_free_pages;
}
/* Update metadata */
psPageArrayData->iNumOSPagesAllocated += uiNumOSPagesToAlloc;
/* Free temporary page array */
OSFreeMem(ppsTempPageArray);
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
#if defined(PVRSRV_ENABLE_MEMORY_STATS)
for (i = 0; i < uiPagesToAlloc; i++)
{
_AddMemAllocRecord_UmaPages(psPageArrayData,
ppsPageArray[puiAllocIndices[i]]);
}
#else
_IncrMemAllocStat_UmaPages(uiNumOSPagesToAlloc * PAGE_SIZE,
psPageArrayData->uiPid);
#endif
#endif
if (psPageArrayData->bPoisonOnAlloc)
{
for (i = 0; i < uiPagesToAlloc; i++)
{
_PoisonDevicePage(psPageArrayData->psDevNode,
ppsPageArray[puiAllocIndices[i]],
uiOrder,
psPageArrayData->ui32CPUCacheFlags,
PVRSRV_POISON_ON_ALLOC_VALUE);
}
}
return PVRSRV_OK;
/* Error path */
e_free_pages:
{
IMG_UINT32 ui32PageToFree;
if (psPageArrayData->bIsCMA)
{
IMG_UINT32 uiDevPageSize = PAGE_SIZE << uiOrder;
for (ui32PageToFree = 0; ui32PageToFree < i; ui32PageToFree++)
{
_FreeOSPage_CMA(psPageArrayData->psDevNode->psDevConfig->pvOSDevice,
uiDevPageSize,
uiOrder,
psPageArrayData->dmavirtarray[puiAllocIndices[ui32PageToFree]],
psPageArrayData->dmaphysarray[puiAllocIndices[ui32PageToFree]],
ppsPageArray[puiAllocIndices[ui32PageToFree]]);
psPageArrayData->dmaphysarray[puiAllocIndices[ui32PageToFree]]= (dma_addr_t)0;
psPageArrayData->dmavirtarray[puiAllocIndices[ui32PageToFree]] = NULL;
ppsPageArray[puiAllocIndices[ui32PageToFree]] = NULL;
}
}
else
{
/* Free the pages we got from the pool */
for (ui32PageToFree = 0; ui32PageToFree < uiPagesFromPool; ui32PageToFree++)
{
_FreeOSPage(0,
psPageArrayData->bUnsetMemoryType,
ppsTempPageArray[ui32PageToFree]);
}
/* Free the pages we just allocated from the OS */
for (ui32PageToFree = uiPagesFromPool; ui32PageToFree < i; ui32PageToFree++)
{
_FreeOSPage(0,
IMG_FALSE,
ppsPageArray[puiAllocIndices[ui32PageToFree]]);
}
/* Reset all page array entries that have been set so far*/
for (ui32PageToFree = 0; ui32PageToFree < i; ui32PageToFree++)
{
ppsPageArray[puiAllocIndices[ui32PageToFree]] = NULL;
}
}
}
e_free_temp_array:
OSFreeMem(ppsTempPageArray);
e_exit:
return eError;
}
/* Allocate pages for a given page array.
*
* The executed allocation path depends whether an array with allocation
* indices has been passed or not */
static PVRSRV_ERROR
_AllocOSPages(PMR_OSPAGEARRAY_DATA *psPageArrayData,
IMG_UINT32 *puiAllocIndices,
IMG_UINT32 uiPagesToAlloc)
{
PVRSRV_ERROR eError;
struct page **ppsPageArray;
/* Sanity checks */
PVR_ASSERT(NULL != psPageArrayData);
if (psPageArrayData->bIsCMA)
{
PVR_ASSERT(psPageArrayData->dmaphysarray != NULL);
PVR_ASSERT(psPageArrayData->dmavirtarray != NULL);
}
PVR_ASSERT(psPageArrayData->pagearray != NULL);
PVR_ASSERT(0 <= psPageArrayData->iNumOSPagesAllocated);
ppsPageArray = psPageArrayData->pagearray;
/* Go the sparse alloc path if we have an array with alloc indices.*/
if (puiAllocIndices != NULL)
{
eError = _AllocOSPages_Sparse(psPageArrayData,
puiAllocIndices,
uiPagesToAlloc);
}
else
{
eError = _AllocOSPages_Fast(psPageArrayData);
}
if (eError != PVRSRV_OK)
{
goto e_exit;
}
_DumpPageArray(ppsPageArray,
psPageArrayData->uiTotalNumOSPages >>
(psPageArrayData->uiLog2AllocPageSize - PAGE_SHIFT) );
PVR_DPF((PVR_DBG_MESSAGE, "physmem_osmem_linux.c: allocated OS memory for PMR @0x%p", psPageArrayData));
return PVRSRV_OK;
e_exit:
return eError;
}
/* Same as _FreeOSPage except free memory using DMA framework */
static INLINE void
_FreeOSPage_CMA(struct device *dev,
size_t alloc_size,
IMG_UINT32 uiOrder,
void *virt_addr,
dma_addr_t dev_addr,
struct page *psPage)
{
if (DMA_IS_ALLOCPG_ADDR(dev_addr))
{
#if defined(CONFIG_X86)
void *pvPageVAddr = page_address(psPage);
if (pvPageVAddr)
{
int ret = set_memory_wb((unsigned long)pvPageVAddr, 1);
if (ret)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Failed to reset page attribute",
__func__));
}
}
#endif
if (DMA_IS_ADDR_ADJUSTED(dev_addr))
{
psPage -= DMA_GET_ALIGN_ADJUSTMENT(dev_addr);
uiOrder += 1;
}
__free_pages(psPage, uiOrder);
}
else
{
if (DMA_IS_ADDR_ADJUSTED(dev_addr))
{
size_t align_adjust;
align_adjust = DMA_GET_ALIGN_ADJUSTMENT(dev_addr);
alloc_size = alloc_size << 1;
dev_addr = DMA_GET_ADDR(dev_addr);
dev_addr -= align_adjust << PAGE_SHIFT;
virt_addr -= align_adjust << PAGE_SHIFT;
}
dma_free_coherent(dev, alloc_size, virt_addr, DMA_GET_ADDR(dev_addr));
}
}
/* Free a single page back to the OS.
* Make sure the cache type is set back to the default value.
*
* Note:
* We must _only_ check bUnsetMemoryType in the case where we need to free
* the page back to the OS since we may have to revert the cache properties
* of the page to the default as given by the OS when it was allocated. */
static void
_FreeOSPage(IMG_UINT32 uiOrder,
IMG_BOOL bUnsetMemoryType,
struct page *psPage)
{
#if defined(CONFIG_X86)
void *pvPageVAddr;
pvPageVAddr = page_address(psPage);
if (pvPageVAddr && bUnsetMemoryType)
{
int ret;
ret = set_memory_wb((unsigned long)pvPageVAddr, 1);
if (ret)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed to reset page attribute",
__func__));
}
}
#else
PVR_UNREFERENCED_PARAMETER(bUnsetMemoryType);
#endif
__free_pages(psPage, uiOrder);
}
/* Free the struct holding the metadata */
static PVRSRV_ERROR
_FreeOSPagesArray(PMR_OSPAGEARRAY_DATA *psPageArrayData)
{
PVR_DPF((PVR_DBG_MESSAGE, "physmem_osmem_linux.c: freed OS memory for PMR @0x%p", psPageArrayData));
/* Check if the page array actually still exists.
* It might be the case that has been moved to the page pool */
if (psPageArrayData->pagearray != NULL)
{
OSFreeMemNoStats(psPageArrayData->pagearray);
}
kmem_cache_free(g_psLinuxPageArray, psPageArrayData);
return PVRSRV_OK;
}
/* Free all or some pages from a sparse page array */
static PVRSRV_ERROR
_FreeOSPages_Sparse(PMR_OSPAGEARRAY_DATA *psPageArrayData,
IMG_UINT32 *pai32FreeIndices,
IMG_UINT32 ui32FreePageCount)
{
IMG_BOOL bSuccess;
IMG_UINT32 uiOrder = psPageArrayData->uiLog2AllocPageSize - PAGE_SHIFT;
IMG_UINT32 uiPageIndex, i, j, uiTempIdx = 0;
struct page **ppsPageArray = psPageArrayData->pagearray;
IMG_UINT32 uiNumPages;
struct page **ppsTempPageArray;
IMG_UINT32 uiTempArraySize;
/* We really should have something to free before we call this */
PVR_ASSERT(psPageArrayData->iNumOSPagesAllocated != 0);
if (pai32FreeIndices == NULL)
{
uiNumPages = psPageArrayData->uiTotalNumOSPages >> uiOrder;
uiTempArraySize = psPageArrayData->iNumOSPagesAllocated;
}
else
{
uiNumPages = ui32FreePageCount;
uiTempArraySize = ui32FreePageCount << uiOrder;
}
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
#if defined(PVRSRV_ENABLE_MEMORY_STATS)
for (i = 0; i < uiNumPages; i++)
{
IMG_UINT32 idx = pai32FreeIndices ? pai32FreeIndices[i] : i;
if (NULL != ppsPageArray[idx])
{
_RemoveMemAllocRecord_UmaPages(psPageArrayData, ppsPageArray[idx]);
}
}
#else
_DecrMemAllocStat_UmaPages(uiTempArraySize * PAGE_SIZE,
psPageArrayData->uiPid);
#endif
#endif
if (psPageArrayData->bPoisonOnFree)
{
for (i = 0; i < uiNumPages; i++)
{
IMG_UINT32 idx = pai32FreeIndices ? pai32FreeIndices[i] : i;
if (NULL != ppsPageArray[idx])
{
_PoisonDevicePage(psPageArrayData->psDevNode,
ppsPageArray[idx],
uiOrder,
psPageArrayData->ui32CPUCacheFlags,
PVRSRV_POISON_ON_FREE_VALUE);
}
}
}
if (psPageArrayData->bIsCMA)
{
IMG_UINT32 uiDevNumPages = uiNumPages;
IMG_UINT32 uiDevPageSize = 1<<psPageArrayData->uiLog2AllocPageSize;
for (i = 0; i < uiDevNumPages; i++)
{
IMG_UINT32 idx = pai32FreeIndices ? pai32FreeIndices[i] : i;
if (NULL != ppsPageArray[idx])
{
_FreeOSPage_CMA(psPageArrayData->psDevNode->psDevConfig->pvOSDevice,
uiDevPageSize,
uiOrder,
psPageArrayData->dmavirtarray[idx],
psPageArrayData->dmaphysarray[idx],
ppsPageArray[idx]);
psPageArrayData->dmaphysarray[idx] = (dma_addr_t)0;
psPageArrayData->dmavirtarray[idx] = NULL;
ppsPageArray[idx] = NULL;
uiTempIdx++;
}
}
uiTempIdx <<= uiOrder;
}
else
{
/* OSAllocMemNoStats required because this code may be run without the bridge lock held */
ppsTempPageArray = OSAllocMemNoStats(sizeof(struct page*) * uiTempArraySize);
if (ppsTempPageArray == NULL)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed free_pages metadata allocation", __func__));
return PVRSRV_ERROR_OUT_OF_MEMORY;
}
/* Put pages in a contiguous array so further processing is easier */
for (i = 0; i < uiNumPages; i++)
{
uiPageIndex = pai32FreeIndices ? pai32FreeIndices[i] : i;
if (NULL != ppsPageArray[uiPageIndex])
{
struct page *psPage = ppsPageArray[uiPageIndex];
for (j = 0; j < (1<<uiOrder); j++)
{
ppsTempPageArray[uiTempIdx] = psPage;
uiTempIdx++;
psPage++;
}
ppsPageArray[uiPageIndex] = NULL;
}
}
/* Try to move the temp page array to the pool */
bSuccess = _PutPagesToPoolLocked(psPageArrayData->ui32CPUCacheFlags,
ppsTempPageArray,
psPageArrayData->bUnpinned,
0,
uiTempIdx);
if (bSuccess)
{
goto exit_ok;
}
/* Free pages and reset page caching attributes on x86 */
#if defined(CONFIG_X86)
if (uiTempIdx != 0 && psPageArrayData->bUnsetMemoryType)
{
int iError;
iError = set_pages_array_wb(ppsTempPageArray, uiTempIdx);
if (iError)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed to reset page attributes", __func__));
}
}
#endif
/* Free the pages */
for (i = 0; i < uiTempIdx; i++)
{
__free_pages(ppsTempPageArray[i], 0);
}
/* Free the temp page array here if it did not move to the pool */
OSFreeMemNoStats(ppsTempPageArray);
}
exit_ok:
/* Update metadata */
psPageArrayData->iNumOSPagesAllocated -= uiTempIdx;
PVR_ASSERT(0 <= psPageArrayData->iNumOSPagesAllocated);
return PVRSRV_OK;
}
/* Free all the pages in a page array */
static PVRSRV_ERROR
_FreeOSPages_Fast(PMR_OSPAGEARRAY_DATA *psPageArrayData)
{
IMG_BOOL bSuccess;
IMG_UINT32 i;
IMG_UINT32 uiNumPages = psPageArrayData->uiTotalNumOSPages;
IMG_UINT32 uiOrder = psPageArrayData->uiLog2AllocPageSize - PAGE_SHIFT;
IMG_UINT32 uiDevNumPages = uiNumPages >> uiOrder;
IMG_UINT32 uiDevPageSize = PAGE_SIZE << uiOrder;
struct page **ppsPageArray = psPageArrayData->pagearray;
/* We really should have something to free before we call this */
PVR_ASSERT(psPageArrayData->iNumOSPagesAllocated != 0);
#if defined(PVRSRV_ENABLE_PROCESS_STATS)
#if defined(PVRSRV_ENABLE_MEMORY_STATS)
for (i = 0; i < uiDevNumPages; i++)
{
_RemoveMemAllocRecord_UmaPages(psPageArrayData, ppsPageArray[i]);
}
#else
_DecrMemAllocStat_UmaPages(uiNumPages * PAGE_SIZE,
psPageArrayData->uiPid);
#endif
#endif
if (psPageArrayData->bPoisonOnFree)
{
for (i = 0; i < uiDevNumPages; i++)
{
_PoisonDevicePage(psPageArrayData->psDevNode,
ppsPageArray[i],
uiOrder,
psPageArrayData->ui32CPUCacheFlags,
PVRSRV_POISON_ON_FREE_VALUE);
}
}
/* Try to move the page array to the pool */
bSuccess = _PutPagesToPoolLocked(psPageArrayData->ui32CPUCacheFlags,
ppsPageArray,
psPageArrayData->bUnpinned,
uiOrder,
uiNumPages);
if (bSuccess)
{
psPageArrayData->pagearray = NULL;
goto exit_ok;
}
if (psPageArrayData->bIsCMA)
{
for (i = 0; i < uiDevNumPages; i++)
{
_FreeOSPage_CMA(psPageArrayData->psDevNode->psDevConfig->pvOSDevice,
uiDevPageSize,
uiOrder,
psPageArrayData->dmavirtarray[i],
psPageArrayData->dmaphysarray[i],
ppsPageArray[i]);
psPageArrayData->dmaphysarray[i] = (dma_addr_t)0;
psPageArrayData->dmavirtarray[i] = NULL;
ppsPageArray[i] = NULL;
}
}
else
{
#if defined(CONFIG_X86)
if (psPageArrayData->bUnsetMemoryType)
{
int ret;
ret = set_pages_array_wb(ppsPageArray, uiNumPages);
if (ret)
{
PVR_DPF((PVR_DBG_ERROR, "%s: Failed to reset page attributes",
__func__));
}
}
#endif
for (i = 0; i < uiNumPages; i++)
{
_FreeOSPage(uiOrder, IMG_FALSE, ppsPageArray[i]);
ppsPageArray[i] = NULL;
}
}
exit_ok:
/* Update metadata */
psPageArrayData->iNumOSPagesAllocated = 0;
return PVRSRV_OK;
}
/* Free pages from a page array.
* Takes care of mem stats and chooses correct free path depending on parameters. */
static PVRSRV_ERROR
_FreeOSPages(PMR_OSPAGEARRAY_DATA *psPageArrayData,
IMG_UINT32 *pai32FreeIndices,
IMG_UINT32 ui32FreePageCount)
{
PVRSRV_ERROR eError;
/* Go the sparse or non-sparse path */
if (psPageArrayData->iNumOSPagesAllocated != psPageArrayData->uiTotalNumOSPages
|| pai32FreeIndices != NULL)
{
eError = _FreeOSPages_Sparse(psPageArrayData,
pai32FreeIndices,
ui32FreePageCount);
}
else
{
eError = _FreeOSPages_Fast(psPageArrayData);
}
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR, "_FreeOSPages_FreePages failed"));
}
_DumpPageArray(psPageArrayData->pagearray,
psPageArrayData->uiTotalNumOSPages >>
(psPageArrayData->uiLog2AllocPageSize - PAGE_SHIFT) );
return eError;
}
/*
*
* Implementation of callback functions
*
*/
/* Destructor func is called after last reference disappears, but
* before PMR itself is freed. */
static PVRSRV_ERROR
PMRFinalizeOSMem(PMR_IMPL_PRIVDATA pvPriv)
{
PVRSRV_ERROR eError;
PMR_OSPAGEARRAY_DATA *psOSPageArrayData = pvPriv;
/* We can't free pages until now. */
if (psOSPageArrayData->iNumOSPagesAllocated != 0)
{
_PagePoolLock();
if (psOSPageArrayData->bUnpinned)
{
_RemoveUnpinListEntryUnlocked(psOSPageArrayData);
}
_PagePoolUnlock();
eError = _FreeOSPages(psOSPageArrayData,
NULL,
0);
PVR_ASSERT (eError == PVRSRV_OK); /* can we do better? */
}
eError = _FreeOSPagesArray(psOSPageArrayData);
PVR_ASSERT (eError == PVRSRV_OK); /* can we do better? */
return PVRSRV_OK;
}
/* Callback function for locking the system physical page addresses.
* This function must be called before the lookup address func. */
static PVRSRV_ERROR
PMRLockSysPhysAddressesOSMem(PMR_IMPL_PRIVDATA pvPriv)
{
PVRSRV_ERROR eError;
PMR_OSPAGEARRAY_DATA *psOSPageArrayData = pvPriv;
if (psOSPageArrayData->bOnDemand)
{
/* Allocate Memory for deferred allocation */
eError = _AllocOSPages(psOSPageArrayData, NULL, psOSPageArrayData->uiTotalNumOSPages);
if (eError != PVRSRV_OK)
{
return eError;
}
}
eError = PVRSRV_OK;
return eError;
}
static PVRSRV_ERROR
PMRUnlockSysPhysAddressesOSMem(PMR_IMPL_PRIVDATA pvPriv)
{
/* Just drops the refcount. */
PVRSRV_ERROR eError = PVRSRV_OK;
PMR_OSPAGEARRAY_DATA *psOSPageArrayData = pvPriv;
if (psOSPageArrayData->bOnDemand)
{
/* Free Memory for deferred allocation */
eError = _FreeOSPages(psOSPageArrayData,
NULL,
0);
if (eError != PVRSRV_OK)
{
return eError;
}
}
PVR_ASSERT (eError == PVRSRV_OK);
return eError;
}
/* N.B. It is assumed that PMRLockSysPhysAddressesOSMem() is called _before_ this function! */
static PVRSRV_ERROR
PMRSysPhysAddrOSMem(PMR_IMPL_PRIVDATA pvPriv,
IMG_UINT32 ui32Log2PageSize,
IMG_UINT32 ui32NumOfPages,
IMG_DEVMEM_OFFSET_T *puiOffset,
IMG_BOOL *pbValid,
IMG_DEV_PHYADDR *psDevPAddr)
{
const PMR_OSPAGEARRAY_DATA *psOSPageArrayData = pvPriv;
IMG_UINT32 uiPageSize = 1U << psOSPageArrayData->uiLog2AllocPageSize;
IMG_UINT32 uiInPageOffset;
IMG_UINT32 uiPageIndex;
IMG_UINT32 uiIdx;
if (psOSPageArrayData->uiLog2AllocPageSize < ui32Log2PageSize)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Requested physical addresses from PMR "
"for incompatible contiguity %u!",
__func__,
ui32Log2PageSize));
return PVRSRV_ERROR_PMR_INCOMPATIBLE_CONTIGUITY;
}
for (uiIdx=0; uiIdx < ui32NumOfPages; uiIdx++)
{
if (pbValid[uiIdx])
{
uiPageIndex = puiOffset[uiIdx] >> psOSPageArrayData->uiLog2AllocPageSize;
uiInPageOffset = puiOffset[uiIdx] - ((IMG_DEVMEM_OFFSET_T)uiPageIndex << psOSPageArrayData->uiLog2AllocPageSize);
PVR_ASSERT(uiPageIndex < psOSPageArrayData->uiTotalNumOSPages);
PVR_ASSERT(uiInPageOffset < uiPageSize);
psDevPAddr[uiIdx].uiAddr = page_to_phys(psOSPageArrayData->pagearray[uiPageIndex]);
psDevPAddr[uiIdx].uiAddr += uiInPageOffset;
#if !defined(PVR_LINUX_PHYSMEM_USE_HIGHMEM_ONLY)
/* this is just a precaution, normally this should be always
* available */
if (psOSPageArrayData->ui64DmaMask)
{
if (psDevPAddr[uiIdx].uiAddr > psOSPageArrayData->ui64DmaMask)
{
PVR_DPF((PVR_DBG_ERROR, "%s: physical address"
" (%" IMG_UINT64_FMTSPECX ") out of allowable range"
" [0; %" IMG_UINT64_FMTSPECX "]", __func__,
psDevPAddr[uiIdx].uiAddr,
psOSPageArrayData->ui64DmaMask));
BUG();
}
}
#endif
}
}
return PVRSRV_OK;
}
typedef struct _PMR_OSPAGEARRAY_KERNMAP_DATA_ {
void *pvBase;
IMG_UINT32 ui32PageCount;
} PMR_OSPAGEARRAY_KERNMAP_DATA;
static PVRSRV_ERROR
PMRAcquireKernelMappingDataOSMem(PMR_IMPL_PRIVDATA pvPriv,
size_t uiOffset,
size_t uiSize,
void **ppvKernelAddressOut,
IMG_HANDLE *phHandleOut,
PMR_FLAGS_T ulFlags)
{
PVRSRV_ERROR eError;
PMR_OSPAGEARRAY_DATA *psOSPageArrayData = pvPriv;
void *pvAddress;
pgprot_t prot = PAGE_KERNEL;
IMG_UINT32 ui32PageOffset=0;
size_t uiMapOffset=0;
IMG_UINT32 ui32PageCount = 0;
IMG_UINT32 uiLog2AllocPageSize = psOSPageArrayData->uiLog2AllocPageSize;
IMG_UINT32 uiOSPageShift = OSGetPageShift();
IMG_UINT32 uiPageSizeDiff = 0;
struct page **pagearray;
PMR_OSPAGEARRAY_KERNMAP_DATA *psData;
/* For cases device page size greater than the OS page size,
* multiple physically contiguous OS pages constitute one device page.
* However only the first page address of such an ensemble is stored
* as part of the mapping table in the driver. Hence when mapping the PMR
* in part/full, all OS pages that constitute the device page
* must also be mapped to kernel.
*
* For the case where device page size less than OS page size,
* treat it the same way as the page sizes are equal */
if (uiLog2AllocPageSize > uiOSPageShift)
{
uiPageSizeDiff = uiLog2AllocPageSize - uiOSPageShift;
}
/*
Zero offset and size as a special meaning which means map in the
whole of the PMR, this is due to fact that the places that call
this callback might not have access to be able to determine the
physical size
*/
if ((uiOffset == 0) && (uiSize == 0))
{
ui32PageOffset = 0;
uiMapOffset = 0;
/* Page count = amount of OS pages */
ui32PageCount = psOSPageArrayData->iNumOSPagesAllocated;
}
else
{
size_t uiEndoffset;
ui32PageOffset = uiOffset >> uiLog2AllocPageSize;
uiMapOffset = uiOffset - (ui32PageOffset << uiLog2AllocPageSize);
uiEndoffset = uiOffset + uiSize - 1;
/* Add one as we want the count, not the offset */
/* Page count = amount of device pages (note uiLog2AllocPageSize being used) */
ui32PageCount = (uiEndoffset >> uiLog2AllocPageSize) + 1;
ui32PageCount -= ui32PageOffset;
/* The OS page count to be mapped might be different if the
* OS page size is lesser than the device page size */
ui32PageCount <<= uiPageSizeDiff;
}
switch (PVRSRV_CPU_CACHE_MODE(psOSPageArrayData->ui32CPUCacheFlags))
{
case PVRSRV_MEMALLOCFLAG_CPU_UNCACHED:
prot = pgprot_noncached(prot);
break;
case PVRSRV_MEMALLOCFLAG_CPU_WRITE_COMBINE:
prot = pgprot_writecombine(prot);
break;
case PVRSRV_MEMALLOCFLAG_CPU_CACHED:
break;
default:
eError = PVRSRV_ERROR_INVALID_PARAMS;
goto e0;
}
if (uiPageSizeDiff)
{
/* Each device page can be broken down into ui32SubPageCount OS pages */
IMG_UINT32 ui32SubPageCount = 1 << uiPageSizeDiff;
IMG_UINT32 i;
struct page **psPage = &psOSPageArrayData->pagearray[ui32PageOffset];
/* Allocate enough memory for the OS page pointers for this mapping */
pagearray = OSAllocMem(ui32PageCount * sizeof(pagearray[0]));
if (pagearray == NULL)
{
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e0;
}
/* construct array that holds the page pointers that constitute the requested
* mapping */
for (i = 0; i < ui32PageCount; i++)
{
IMG_UINT32 ui32OSPageArrayIndex = i / ui32SubPageCount;
IMG_UINT32 ui32OSPageArrayOffset = i % ui32SubPageCount;
/*
* The driver only stores OS page pointers for the first OS page
* within each device page (psPage[ui32OSPageArrayIndex]).
* Get the next OS page structure at device page granularity,
* then calculate OS page pointers for all the other pages.
*/
pagearray[i] = psPage[ui32OSPageArrayIndex] + ui32OSPageArrayOffset;
}
}
else
{
pagearray = &psOSPageArrayData->pagearray[ui32PageOffset];
}
psData = OSAllocMem(sizeof(*psData));
if (psData == NULL)
{
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e1;
}
#if !defined(CONFIG_64BIT) || defined(PVRSRV_FORCE_SLOWER_VMAP_ON_64BIT_BUILDS)
pvAddress = vmap(pagearray, ui32PageCount, VM_READ | VM_WRITE, prot);
#else
pvAddress = vm_map_ram(pagearray, ui32PageCount, -1, prot);
#endif
if (pvAddress == NULL)
{
eError = PVRSRV_ERROR_OUT_OF_MEMORY;
goto e2;
}
*ppvKernelAddressOut = pvAddress + uiMapOffset;
psData->pvBase = pvAddress;
psData->ui32PageCount = ui32PageCount;
*phHandleOut = psData;
if (uiPageSizeDiff)
{
OSFreeMem(pagearray);
}
return PVRSRV_OK;
/*
error exit paths follow
*/
e2:
OSFreeMem(psData);
e1:
if (uiPageSizeDiff)
{
OSFreeMem(pagearray);
}
e0:
PVR_ASSERT(eError != PVRSRV_OK);
return eError;
}
static void PMRReleaseKernelMappingDataOSMem(PMR_IMPL_PRIVDATA pvPriv,
IMG_HANDLE hHandle)
{
PMR_OSPAGEARRAY_KERNMAP_DATA *psData = hHandle;
PVR_UNREFERENCED_PARAMETER(pvPriv);
#if !defined(CONFIG_64BIT) || defined(PVRSRV_FORCE_SLOWER_VMAP_ON_64BIT_BUILDS)
vunmap(psData->pvBase);
#else
vm_unmap_ram(psData->pvBase, psData->ui32PageCount);
#endif
OSFreeMem(psData);
}
static
PVRSRV_ERROR PMRUnpinOSMem(PMR_IMPL_PRIVDATA pPriv)
{
PMR_OSPAGEARRAY_DATA *psOSPageArrayData = pPriv;
PVRSRV_ERROR eError = PVRSRV_OK;
/* Lock down the pool and add the array to the unpin list */
_PagePoolLock();
/* Sanity check */
PVR_ASSERT(psOSPageArrayData->bUnpinned == IMG_FALSE);
PVR_ASSERT(psOSPageArrayData->bOnDemand == IMG_FALSE);
eError = _AddUnpinListEntryUnlocked(psOSPageArrayData);
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Unable to add allocation to unpinned list (%d).",
__func__,
eError));
goto e_exit;
}
psOSPageArrayData->bUnpinned = IMG_TRUE;
e_exit:
_PagePoolUnlock();
return eError;
}
static
PVRSRV_ERROR PMRPinOSMem(PMR_IMPL_PRIVDATA pPriv,
PMR_MAPPING_TABLE *psMappingTable)
{
PVRSRV_ERROR eError;
PMR_OSPAGEARRAY_DATA *psOSPageArrayData = pPriv;
IMG_UINT32 *pui32MapTable = NULL;
IMG_UINT32 i,j=0, ui32Temp=0;
_PagePoolLock();
/* Sanity check */
PVR_ASSERT(psOSPageArrayData->bUnpinned);
psOSPageArrayData->bUnpinned = IMG_FALSE;
/* If there are still pages in the array remove entries from the pool */
if (psOSPageArrayData->iNumOSPagesAllocated != 0)
{
_RemoveUnpinListEntryUnlocked(psOSPageArrayData);
_PagePoolUnlock();
eError = PVRSRV_OK;
goto e_exit_mapalloc_failure;
}
_PagePoolUnlock();
/* If pages were reclaimed we allocate new ones and
* return PVRSRV_ERROR_PMR_NEW_MEMORY */
if (psMappingTable->ui32NumVirtChunks == 1)
{
eError = _AllocOSPages(psOSPageArrayData, NULL, psOSPageArrayData->uiTotalNumOSPages);
}
else
{
pui32MapTable = (IMG_UINT32 *)OSAllocMem(sizeof(*pui32MapTable) * psMappingTable->ui32NumPhysChunks);
if (NULL == pui32MapTable)
{
eError = PVRSRV_ERROR_PMR_FAILED_TO_ALLOC_PAGES;
PVR_DPF((PVR_DBG_ERROR,
"%s: Unable to Alloc Map Table.",
__func__));
goto e_exit_mapalloc_failure;
}
for (i = 0,j=0; i < psMappingTable->ui32NumVirtChunks; i++)
{
ui32Temp = psMappingTable->aui32Translation[i];
if (TRANSLATION_INVALID != ui32Temp)
{
pui32MapTable[j++] = ui32Temp;
}
}
eError = _AllocOSPages(psOSPageArrayData, pui32MapTable, psMappingTable->ui32NumPhysChunks);
}
if (eError != PVRSRV_OK)
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Unable to get new pages for unpinned allocation.",
__func__));
eError = PVRSRV_ERROR_PMR_FAILED_TO_ALLOC_PAGES;
goto e_exit;
}
PVR_DPF((PVR_DBG_MESSAGE,
"%s: Allocating new pages for unpinned allocation. "
"Old content is lost!",
__func__));
eError = PVRSRV_ERROR_PMR_NEW_MEMORY;
e_exit:
OSFreeMem(pui32MapTable);
e_exit_mapalloc_failure:
return eError;
}
/*************************************************************************/ /*!
@Function PMRChangeSparseMemOSMem
@Description This function Changes the sparse mapping by allocating & freeing
of pages. It does also change the GPU and CPU maps accordingly
@Return PVRSRV_ERROR failure code
*/ /**************************************************************************/
static PVRSRV_ERROR
PMRChangeSparseMemOSMem(PMR_IMPL_PRIVDATA pPriv,
const PMR *psPMR,
IMG_UINT32 ui32AllocPageCount,
IMG_UINT32 *pai32AllocIndices,
IMG_UINT32 ui32FreePageCount,
IMG_UINT32 *pai32FreeIndices,
IMG_UINT32 uiFlags)
{
PVRSRV_ERROR eError;
PMR_MAPPING_TABLE *psPMRMapTable = PMR_GetMappigTable(psPMR);
PMR_OSPAGEARRAY_DATA *psPMRPageArrayData = (PMR_OSPAGEARRAY_DATA *)pPriv;
struct page **psPageArray = psPMRPageArrayData->pagearray;
void **psDMAVirtArray = psPMRPageArrayData->dmavirtarray;
dma_addr_t *psDMAPhysArray = psPMRPageArrayData->dmaphysarray;
struct page *psPage;
dma_addr_t psDMAPAddr;
void *pvDMAVAddr;
IMG_UINT32 ui32AdtnlAllocPages = 0; /*<! Number of pages to alloc from the OS */
IMG_UINT32 ui32AdtnlFreePages = 0; /*<! Number of pages to free back to the OS */
IMG_UINT32 ui32CommonRequestCount = 0; /*<! Number of pages to move position in the page array */
IMG_UINT32 ui32Loop = 0;
IMG_UINT32 ui32Index = 0;
IMG_UINT32 uiAllocpgidx;
IMG_UINT32 uiFreepgidx;
IMG_UINT32 uiOrder = psPMRPageArrayData->uiLog2AllocPageSize - PAGE_SHIFT;
IMG_BOOL bCMA = psPMRPageArrayData->bIsCMA;
/* Check SPARSE flags and calculate pages to allocate and free */
if (SPARSE_RESIZE_BOTH == (uiFlags & SPARSE_RESIZE_BOTH))
{
ui32CommonRequestCount = (ui32AllocPageCount > ui32FreePageCount) ?
ui32FreePageCount : ui32AllocPageCount;
PDUMP_PANIC(SPARSEMEM_SWAP, "Request to swap alloc & free pages not supported");
}
if (SPARSE_RESIZE_ALLOC == (uiFlags & SPARSE_RESIZE_ALLOC))
{
ui32AdtnlAllocPages = ui32AllocPageCount - ui32CommonRequestCount;
}
else
{
ui32AllocPageCount = 0;
}
if (SPARSE_RESIZE_FREE == (uiFlags & SPARSE_RESIZE_FREE))
{
ui32AdtnlFreePages = ui32FreePageCount - ui32CommonRequestCount;
}
else
{
ui32FreePageCount = 0;
}
if (0 == (ui32CommonRequestCount || ui32AdtnlAllocPages || ui32AdtnlFreePages))
{
eError = PVRSRV_ERROR_INVALID_PARAMS;
PVR_DPF((PVR_DBG_ERROR,
"%s: Missing parameters for number of pages to alloc/free",
__func__));
return eError;
}
/* The incoming request is classified into two operations independent of
* each other: alloc & free pages.
* These operations can be combined with two mapping operations as well
* which are GPU & CPU space mappings.
*
* From the alloc and free page requests, the net amount of pages to be
* allocated or freed is computed. Pages that were requested to be freed
* will be reused to fulfil alloc requests.
*
* The order of operations is:
* 1. Allocate new pages from the OS
* 2. Move the free pages from free request to alloc positions.
* 3. Free the rest of the pages not used for alloc
*
* Alloc parameters are validated at the time of allocation
* and any error will be handled then. */
/* Validate the free indices */
if (ui32FreePageCount)
{
if (NULL != pai32FreeIndices){
for (ui32Loop = 0; ui32Loop < ui32FreePageCount; ui32Loop++)
{
uiFreepgidx = pai32FreeIndices[ui32Loop];
if (uiFreepgidx > (psPMRPageArrayData->uiTotalNumOSPages >> uiOrder))
{
eError = PVRSRV_ERROR_DEVICEMEM_OUT_OF_RANGE;
goto e0;
}
if (NULL == psPageArray[uiFreepgidx])
{
eError = PVRSRV_ERROR_INVALID_PARAMS;
PVR_DPF((PVR_DBG_ERROR,
"%s: Trying to free non-allocated page",
__func__));
goto e0;
}
}
}
else
{
eError = PVRSRV_ERROR_INVALID_PARAMS;
PVR_DPF((PVR_DBG_ERROR,
"%s: Given non-zero free count but missing indices array",
__func__));
return eError;
}
}
/* Validate the alloc indices */
for (ui32Loop = ui32AdtnlAllocPages; ui32Loop < ui32AllocPageCount; ui32Loop++)
{
uiAllocpgidx = pai32AllocIndices[ui32Loop];
if (uiAllocpgidx > (psPMRPageArrayData->uiTotalNumOSPages >> uiOrder))
{
eError = PVRSRV_ERROR_DEVICEMEM_OUT_OF_RANGE;
goto e0;
}
if (SPARSE_REMAP_MEM != (uiFlags & SPARSE_REMAP_MEM))
{
if ((NULL != psPageArray[uiAllocpgidx]) ||
(TRANSLATION_INVALID != psPMRMapTable->aui32Translation[uiAllocpgidx]))
{
eError = PVRSRV_ERROR_INVALID_PARAMS;
PVR_DPF((PVR_DBG_ERROR,
"%s: Trying to allocate already allocated page again",
__func__));
goto e0;
}
}
else
{
if ((NULL == psPageArray[uiAllocpgidx]) ||
(TRANSLATION_INVALID == psPMRMapTable->aui32Translation[uiAllocpgidx]) )
{
eError = PVRSRV_ERROR_INVALID_PARAMS;
PVR_DPF((PVR_DBG_ERROR,
"%s: Unable to remap memory due to missing page",
__func__));
goto e0;
}
}
}
ui32Loop = 0;
/* Allocate new pages from the OS */
if (0 != ui32AdtnlAllocPages)
{
eError = _AllocOSPages(psPMRPageArrayData, pai32AllocIndices, ui32AdtnlAllocPages);
if (PVRSRV_OK != eError)
{
PVR_DPF((PVR_DBG_MESSAGE,
"%s: New Addtl Allocation of pages failed",
__func__));
goto e0;
}
psPMRMapTable->ui32NumPhysChunks += ui32AdtnlAllocPages;
/*Mark the corresponding pages of translation table as valid */
for (ui32Loop = 0; ui32Loop < ui32AdtnlAllocPages; ui32Loop++)
{
psPMRMapTable->aui32Translation[pai32AllocIndices[ui32Loop]] = pai32AllocIndices[ui32Loop];
}
}
ui32Index = ui32Loop;
/* Move the corresponding free pages to alloc request */
for (ui32Loop = 0; ui32Loop < ui32CommonRequestCount; ui32Loop++, ui32Index++)
{
uiAllocpgidx = pai32AllocIndices[ui32Index];
uiFreepgidx = pai32FreeIndices[ui32Loop];
psPage = psPageArray[uiAllocpgidx];
psPageArray[uiAllocpgidx] = psPageArray[uiFreepgidx];
if (bCMA)
{
pvDMAVAddr = psDMAVirtArray[uiAllocpgidx];
psDMAPAddr = psDMAPhysArray[uiAllocpgidx];
psDMAVirtArray[uiAllocpgidx] = psDMAVirtArray[uiFreepgidx];
psDMAPhysArray[uiAllocpgidx] = psDMAPhysArray[uiFreepgidx];
}
/* Is remap mem used in real world scenario? Should it be turned to a
* debug feature? The condition check needs to be out of loop, will be
* done at later point though after some analysis */
if (SPARSE_REMAP_MEM != (uiFlags & SPARSE_REMAP_MEM))
{
psPMRMapTable->aui32Translation[uiFreepgidx] = TRANSLATION_INVALID;
psPMRMapTable->aui32Translation[uiAllocpgidx] = uiAllocpgidx;
psPageArray[uiFreepgidx] = NULL;
if (bCMA)
{
psDMAVirtArray[uiFreepgidx] = NULL;
psDMAPhysArray[uiFreepgidx] = (dma_addr_t)0;
}
}
else
{
psPMRMapTable->aui32Translation[uiFreepgidx] = uiFreepgidx;
psPMRMapTable->aui32Translation[uiAllocpgidx] = uiAllocpgidx;
psPageArray[uiFreepgidx] = psPage;
if (bCMA)
{
psDMAVirtArray[uiFreepgidx] = pvDMAVAddr;
psDMAPhysArray[uiFreepgidx] = psDMAPAddr;
}
}
}
/* Free the additional free pages */
if (0 != ui32AdtnlFreePages)
{
eError = _FreeOSPages(psPMRPageArrayData,
&pai32FreeIndices[ui32Loop],
ui32AdtnlFreePages);
if (eError != PVRSRV_OK)
{
goto e0;
}
psPMRMapTable->ui32NumPhysChunks -= ui32AdtnlFreePages;
while (ui32Loop < ui32FreePageCount)
{
psPMRMapTable->aui32Translation[pai32FreeIndices[ui32Loop]] = TRANSLATION_INVALID;
ui32Loop++;
}
}
eError = PVRSRV_OK;
e0:
return eError;
}
/*************************************************************************/ /*!
@Function PMRChangeSparseMemCPUMapOSMem
@Description This function Changes CPU maps accordingly
@Return PVRSRV_ERROR failure code
*/ /**************************************************************************/
static
PVRSRV_ERROR PMRChangeSparseMemCPUMapOSMem(PMR_IMPL_PRIVDATA pPriv,
const PMR *psPMR,
IMG_UINT64 sCpuVAddrBase,
IMG_UINT32 ui32AllocPageCount,
IMG_UINT32 *pai32AllocIndices,
IMG_UINT32 ui32FreePageCount,
IMG_UINT32 *pai32FreeIndices)
{
struct page **psPageArray;
PMR_OSPAGEARRAY_DATA *psPMRPageArrayData = (PMR_OSPAGEARRAY_DATA *)pPriv;
IMG_CPU_PHYADDR sCPUPAddr;
sCPUPAddr.uiAddr = 0;
psPageArray = psPMRPageArrayData->pagearray;
return OSChangeSparseMemCPUAddrMap((void **)psPageArray,
sCpuVAddrBase,
sCPUPAddr,
ui32AllocPageCount,
pai32AllocIndices,
ui32FreePageCount,
pai32FreeIndices,
IMG_FALSE);
}
static PMR_IMPL_FUNCTAB _sPMROSPFuncTab = {
.pfnLockPhysAddresses = &PMRLockSysPhysAddressesOSMem,
.pfnUnlockPhysAddresses = &PMRUnlockSysPhysAddressesOSMem,
.pfnDevPhysAddr = &PMRSysPhysAddrOSMem,
.pfnAcquireKernelMappingData = &PMRAcquireKernelMappingDataOSMem,
.pfnReleaseKernelMappingData = &PMRReleaseKernelMappingDataOSMem,
.pfnReadBytes = NULL,
.pfnWriteBytes = NULL,
.pfnUnpinMem = &PMRUnpinOSMem,
.pfnPinMem = &PMRPinOSMem,
.pfnChangeSparseMem = &PMRChangeSparseMemOSMem,
.pfnChangeSparseMemCPUMap = &PMRChangeSparseMemCPUMapOSMem,
.pfnFinalize = &PMRFinalizeOSMem,
};
PVRSRV_ERROR
PhysmemNewOSRamBackedPMR(PVRSRV_DEVICE_NODE *psDevNode,
IMG_DEVMEM_SIZE_T uiSize,
IMG_DEVMEM_SIZE_T uiChunkSize,
IMG_UINT32 ui32NumPhysChunks,
IMG_UINT32 ui32NumVirtChunks,
IMG_UINT32 *puiAllocIndices,
IMG_UINT32 uiLog2AllocPageSize,
PVRSRV_MEMALLOCFLAGS_T uiFlags,
const IMG_CHAR *pszAnnotation,
IMG_PID uiPid,
PMR **ppsPMRPtr)
{
PVRSRV_ERROR eError;
PVRSRV_ERROR eError2;
PMR *psPMR;
struct _PMR_OSPAGEARRAY_DATA_ *psPrivData;
PMR_FLAGS_T uiPMRFlags;
PHYS_HEAP *psPhysHeap;
IMG_UINT32 ui32CPUCacheFlags;
IMG_BOOL bZero;
IMG_BOOL bIsCMA;
IMG_BOOL bPoisonOnAlloc;
IMG_BOOL bPoisonOnFree;
IMG_BOOL bOnDemand;
IMG_BOOL bCpuLocal;
IMG_BOOL bFwLocal;
/*
* The host driver (but not guest) can still use this factory for firmware
* allocations
*/
if (PVRSRV_VZ_MODE_IS(DRIVER_MODE_GUEST) && PVRSRV_CHECK_FW_LOCAL(uiFlags))
{
PVR_ASSERT(0);
eError = PVRSRV_ERROR_INVALID_PARAMS;
goto errorOnParam;
}
/* Select correct caching mode */
eError = DevmemCPUCacheMode(psDevNode, uiFlags, &ui32CPUCacheFlags);
if (eError != PVRSRV_OK)
{
goto errorOnParam;
}
if (PVRSRV_CHECK_CPU_CACHE_CLEAN(uiFlags))
{
ui32CPUCacheFlags |= PVRSRV_MEMALLOCFLAG_CPU_CACHE_CLEAN;
}
/*
* Use CMA framework if order is greater than OS page size; please note
* that OSMMapPMRGeneric() has the same expectation as well.
*/
bIsCMA = uiLog2AllocPageSize > PAGE_SHIFT ? IMG_TRUE : IMG_FALSE;
bOnDemand = PVRSRV_CHECK_ON_DEMAND(uiFlags) ? IMG_TRUE : IMG_FALSE;
bCpuLocal = PVRSRV_CHECK_CPU_LOCAL(uiFlags) ? IMG_TRUE : IMG_FALSE;
bFwLocal = PVRSRV_CHECK_FW_LOCAL(uiFlags) ? IMG_TRUE : IMG_FALSE;
bZero = PVRSRV_CHECK_ZERO_ON_ALLOC(uiFlags) ? IMG_TRUE : IMG_FALSE;
bPoisonOnAlloc = PVRSRV_CHECK_POISON_ON_ALLOC(uiFlags) ? IMG_TRUE : IMG_FALSE;
bPoisonOnFree = PVRSRV_CHECK_POISON_ON_FREE(uiFlags) ? IMG_TRUE : IMG_FALSE;
#if defined(PVR_LINUX_PHYSMEM_ZERO_ALL_PAGES)
/* Overwrite flags and always zero pages that could go back to UM */
bZero = IMG_TRUE;
bPoisonOnAlloc = IMG_FALSE;
#endif
/* Physical allocation alignment is generally not supported except under
very restrictive conditions, also there is a maximum alignment value
which must not exceed the largest device page-size. If these are not
met then fail the aligned-requested allocation */
if (bIsCMA)
{
IMG_UINT32 uiAlign = 1 << uiLog2AllocPageSize;
if (uiAlign > uiSize || uiAlign > (1 << PVR_MAX_PHYSMEM_CONTIG_ALLOC_LOG2PGSZ))
{
PVR_DPF((PVR_DBG_ERROR,
"%s: Invalid PA alignment: size 0x%llx, align 0x%x",
__func__, uiSize, uiAlign));
eError = PVRSRV_ERROR_INVALID_ALIGNMENT;
goto errorOnParam;
}
PVR_ASSERT(uiLog2AllocPageSize > PVR_MIN_PHYSMEM_CONTIG_ALLOC_LOG2PGSZ);
}
/* Create Array structure that hold the physical pages */
eError = _AllocOSPageArray(psDevNode,
uiChunkSize,
ui32NumPhysChunks,
ui32NumVirtChunks,
uiLog2AllocPageSize,
bZero,
bIsCMA,
bPoisonOnAlloc,
bPoisonOnFree,
bOnDemand,
ui32CPUCacheFlags,
uiPid,
&psPrivData);
if (eError != PVRSRV_OK)
{
goto errorOnAllocPageArray;
}
if (!bOnDemand)
{
/* Do we fill the whole page array or just parts (sparse)? */
if (ui32NumPhysChunks == ui32NumVirtChunks)
{
/* Allocate the physical pages */
eError = _AllocOSPages(psPrivData,
NULL,
psPrivData->uiTotalNumOSPages >> (uiLog2AllocPageSize - PAGE_SHIFT));
}
else if (ui32NumPhysChunks != 0)
{
/* Calculate the number of pages we want to allocate */
IMG_UINT32 uiPagesToAlloc =
(IMG_UINT32)((((ui32NumPhysChunks * uiChunkSize) - 1) >> uiLog2AllocPageSize) + 1);
/* Make sure calculation is correct */
PVR_ASSERT(((PMR_SIZE_T) uiPagesToAlloc << uiLog2AllocPageSize) ==
(ui32NumPhysChunks * uiChunkSize));
/* Allocate the physical pages */
eError = _AllocOSPages(psPrivData, puiAllocIndices,
uiPagesToAlloc);
}
if (eError != PVRSRV_OK)
{
goto errorOnAllocPages;
}
}
/*
* In this instance, we simply pass flags straight through.
*
* Generically, uiFlags can include things that control the PMR factory, but
* we don't need any such thing (at the time of writing!), and our caller
* specifies all PMR flags so we don't need to meddle with what was given to
* us.
*/
uiPMRFlags = (PMR_FLAGS_T)(uiFlags & PVRSRV_MEMALLOCFLAGS_PMRFLAGSMASK);
/*
* Check no significant bits were lost in cast due to different bit widths
* for flags
*/
PVR_ASSERT(uiPMRFlags == (uiFlags & PVRSRV_MEMALLOCFLAGS_PMRFLAGSMASK));
if (bOnDemand)
{
PDUMPCOMMENT("Deferred Allocation PMR (UMA)");
}
if (bFwLocal)
{
PDUMPCOMMENT("FW_LOCAL allocation requested");
psPhysHeap = psDevNode->apsPhysHeap[PVRSRV_DEVICE_PHYS_HEAP_FW_LOCAL];
}
else if (bCpuLocal)
{
PDUMPCOMMENT("CPU_LOCAL allocation requested");
psPhysHeap = psDevNode->apsPhysHeap[PVRSRV_DEVICE_PHYS_HEAP_CPU_LOCAL];
}
else
{
psPhysHeap = psDevNode->apsPhysHeap[PVRSRV_DEVICE_PHYS_HEAP_GPU_LOCAL];
}
eError = PMRCreatePMR(psDevNode,
psPhysHeap,
uiSize,
uiChunkSize,
ui32NumPhysChunks,
ui32NumVirtChunks,
puiAllocIndices,
uiLog2AllocPageSize,
uiPMRFlags,
pszAnnotation,
&_sPMROSPFuncTab,
psPrivData,
PMR_TYPE_OSMEM,
&psPMR,
PDUMP_NONE);
if (eError != PVRSRV_OK)
{
goto errorOnCreate;
}
*ppsPMRPtr = psPMR;
return PVRSRV_OK;
errorOnCreate:
if (!bOnDemand)
{
eError2 = _FreeOSPages(psPrivData, NULL, 0);
PVR_ASSERT(eError2 == PVRSRV_OK);
}
errorOnAllocPages:
eError2 = _FreeOSPagesArray(psPrivData);
PVR_ASSERT(eError2 == PVRSRV_OK);
errorOnAllocPageArray:
errorOnParam:
PVR_ASSERT(eError != PVRSRV_OK);
return eError;
}