blob: fb0a74e8363b7ef7e7f88e79e0735dcf7173e132 [file] [log] [blame]
/*************************************************************************/ /*!
@File
@Title Resource Allocator
@Copyright Copyright (c) Imagination Technologies Ltd. All Rights Reserved
@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.
@Description
Implements generic resource allocation. The resource
allocator was originally intended to manage address spaces. In
practice the resource allocator is generic and can manage arbitrary
sets of integers.
Resources are allocated from arenas. Arena's can be created with an
initial span of resources. Further resources spans can be added to
arenas. A call back mechanism allows an arena to request further
resource spans on demand.
Each arena maintains an ordered list of resource segments each
described by a boundary tag. Each boundary tag describes a segment
of resources which are either 'free', available for allocation, or
'busy' currently allocated. Adjacent 'free' segments are always
coallesced to avoid fragmentation.
For allocation, all 'free' segments are kept on lists of 'free'
segments in a table index by pvr_log2(segment size). ie Each table index
n holds 'free' segments in the size range 2^n -> 2^(n+1) - 1.
Allocation policy is based on an *almost* good fit strategy.
Allocated segments are inserted into a self scaling hash table which
maps the base resource of the span to the relevant boundary
tag. This allows the code to get back to the bounary tag without
exporting explicit boundary tag references through the API.
Each arena has an associated quantum size, all allocations from the
arena are made in multiples of the basic quantum.
On resource exhaustion in an arena, a callback if provided will be
used to request further resources. Resouces spans allocated by the
callback mechanism will be returned when freed (through one of the
two callbacks).
*/ /**************************************************************************/
/* Issues:
* - flags, flags are passed into the resource allocator but are not currently used.
* - determination, of import size, is currently braindead.
* - debug code should be moved out to own module and #ifdef'd
*/
#include "img_types.h"
#include "pvr_debug.h"
#include "pvrsrv_error.h"
#include "uniq_key_splay_tree.h"
#include "hash.h"
#include "ra.h"
#include "pvrsrv_memallocflags.h"
#include "osfunc.h"
#include "allocmem.h"
#include "lock.h"
#include "pvr_intrinsics.h"
/* The initial, and minimum size of the live address -> boundary tag
structure hash table. The value 64 is a fairly arbitrary
choice. The hash table resizes on demand so the value chosen is
not critical. */
#define MINIMUM_HASH_SIZE (64)
/* #define RA_VALIDATE */
#if defined(__KLOCWORK__)
/* make sure Klocworks analyse all the code (including the debug one) */
#if !defined(RA_VALIDATE)
#define RA_VALIDATE
#endif
#endif
#if (!defined(PVRSRV_NEED_PVR_ASSERT)) || (!defined(RA_VALIDATE))
/* Disable the asserts unless explicitly told otherwise. They slow the driver
too much for other people */
#undef PVR_ASSERT
/* let's use a macro that really do not do anything when compiling in release
mode! */
#define PVR_ASSERT(x)
#endif
/* boundary tags, used to describe a resource segment */
struct _BT_
{
enum bt_type
{
btt_free, /* free resource segment */
btt_live /* allocated resource segment */
} type;
unsigned int is_leftmost;
unsigned int is_rightmost;
unsigned int free_import;
/* The base resource and extent of this segment */
RA_BASE_T base;
RA_LENGTH_T uSize;
/* doubly linked ordered list of all segments within the arena */
struct _BT_ *pNextSegment;
struct _BT_ *pPrevSegment;
/* doubly linked un-ordered list of free segments with the same flags. */
struct _BT_ * next_free;
struct _BT_ * prev_free;
/* a user reference associated with this span, user references are
* currently only provided in the callback mechanism */
IMG_HANDLE hPriv;
/* Flags to match on this span */
IMG_UINT32 uFlags;
};
typedef struct _BT_ BT;
/* resource allocation arena */
struct _RA_ARENA_
{
/* arena name for diagnostics output */
IMG_CHAR *name;
/* allocations within this arena are quantum sized */
RA_LENGTH_T uQuantum;
/* import interface, if provided */
PVRSRV_ERROR (*pImportAlloc)(RA_PERARENA_HANDLE h,
RA_LENGTH_T uSize,
IMG_UINT32 uFlags,
const IMG_CHAR *pszAnnotation,
RA_BASE_T *pBase,
RA_LENGTH_T *pActualSize,
RA_PERISPAN_HANDLE *phPriv);
void (*pImportFree) (RA_PERARENA_HANDLE,
RA_BASE_T,
RA_PERISPAN_HANDLE hPriv);
/* arbitrary handle provided by arena owner to be passed into the
* import alloc and free hooks */
void *pImportHandle;
IMG_PSPLAY_TREE per_flags_buckets;
/* resource segment list */
BT *pHeadSegment;
/* segment address to boundary tag hash table */
HASH_TABLE *pSegmentHash;
/* Lock for this arena */
POS_LOCK hLock;
/* LockClass of this arena. This is used within lockdep to decide if a
* recursive call sequence with the same lock class is allowed or not. */
IMG_UINT32 ui32LockClass;
/* If TRUE, imports will not be split up. Allocations will always get their
* own import
*/
IMG_BOOL bNoSplit;
};
/*************************************************************************/ /*!
@Function _RequestAllocFail
@Description Default callback allocator used if no callback is
specified, always fails to allocate further resources to the
arena.
@Input _h - callback handle
@Input _uSize - requested allocation size
@Output _pActualSize - actual allocation size
@Input _pRef - user reference
@Input _uflags - allocation flags
@Input _pBase - receives allocated base
@Return PVRSRV_ERROR_RA_REQUEST_ALLOC_FAIL, this function always fails to allocate.
*/ /**************************************************************************/
static PVRSRV_ERROR
_RequestAllocFail (RA_PERARENA_HANDLE _h,
RA_LENGTH_T _uSize,
IMG_UINT32 _uFlags,
const IMG_CHAR *_pszAnnotation,
RA_BASE_T *_pBase,
RA_LENGTH_T *_pActualSize,
RA_PERISPAN_HANDLE *_phPriv)
{
PVR_UNREFERENCED_PARAMETER (_h);
PVR_UNREFERENCED_PARAMETER (_uSize);
PVR_UNREFERENCED_PARAMETER (_pActualSize);
PVR_UNREFERENCED_PARAMETER (_phPriv);
PVR_UNREFERENCED_PARAMETER (_uFlags);
PVR_UNREFERENCED_PARAMETER (_pBase);
PVR_UNREFERENCED_PARAMETER (_pszAnnotation);
return PVRSRV_ERROR_RA_REQUEST_ALLOC_FAIL;
}
#if defined (PVR_CTZLL)
/* make sure to trigger an error if someone change the buckets or the bHasEltsMapping size
the bHasEltsMapping is used to quickly determine the smallest bucket containing elements.
therefore it must have at least as many bits has the buckets array have buckets. The RA
implementation actually uses one more bit. */
static_assert((sizeof(((IMG_PSPLAY_TREE) 0)->buckets) / sizeof(((IMG_PSPLAY_TREE) 0)->buckets[0]))
< 8 * sizeof(((IMG_PSPLAY_TREE) 0)->bHasEltsMapping),
"Too many buckets for bHasEltsMapping bitmap");
#endif
/*************************************************************************/ /*!
@Function pvr_log2
@Description Computes the floor of the log base 2 of a unsigned integer
@Input n Unsigned integer
@Return Floor(Log2(n))
*/ /**************************************************************************/
#if defined(PVR_CLZLL)
/* make sure to trigger a problem if someone changes the RA_LENGTH_T type
indeed the __builtin_clzll is for unsigned long long variables.
if someone changes RA_LENGTH to unsigned long, then use __builtin_clzl
if it changes to unsigned int, use __builtin_clz
if it changes for something bigger than unsigned long long,
then revert the pvr_log2 to the classic implementation */
static_assert(sizeof(RA_LENGTH_T) == sizeof(unsigned long long),
"RA log routines not tuned for sizeof(RA_LENGTH_T)");
static inline IMG_UINT32 pvr_log2(RA_LENGTH_T n)
{
PVR_ASSERT( n != 0 ); /* Log2 is not defined on 0 */
return (8 * sizeof(RA_LENGTH_T)) - 1 - PVR_CLZLL(n);
}
#else
static IMG_UINT32
pvr_log2 (RA_LENGTH_T n)
{
IMG_UINT32 l = 0;
PVR_ASSERT( n != 0 ); /* Log2 is not defined on 0 */
n>>=1;
while (n>0)
{
n>>=1;
l++;
}
return l;
}
#endif
#if defined(RA_VALIDATE)
/*************************************************************************/ /*!
@Function _IsInSegmentList
@Description Tests if a BT is in the segment list.
@Input pArena The arena.
@Input pBT The boundary tag to look for.
@Return IMG_FALSE BT was not in the arena's segment list.
IMG_TRUE BT was in the arena's segment list.
*/ /**************************************************************************/
static IMG_BOOL
_IsInSegmentList (RA_ARENA *pArena,
BT *pBT)
{
BT* pBTScan;
PVR_ASSERT (pArena != NULL);
PVR_ASSERT (pBT != NULL);
/* Walk the segment list until we see the BT pointer... */
pBTScan = pArena->pHeadSegment;
while (pBTScan != NULL && pBTScan != pBT)
{
pBTScan = pBTScan->pNextSegment;
}
/* Test if we found it and then return */
return (pBTScan == pBT);
}
/*************************************************************************/ /*!
@Function _IsInFreeList
@Description Tests if a BT is in the free list.
@Input pArena The arena.
@Input pBT The boundary tag to look for.
@Return IMG_FALSE BT was not in the arena's free list.
IMG_TRUE BT was in the arena's free list.
*/ /**************************************************************************/
static IMG_BOOL
_IsInFreeList (RA_ARENA *pArena,
BT *pBT)
{
BT* pBTScan;
IMG_UINT32 uIndex;
PVR_ASSERT (pArena != NULL);
PVR_ASSERT (pBT != NULL);
/* Look for the free list that holds BTs of this size... */
uIndex = pvr_log2 (pBT->uSize);
PVR_ASSERT (uIndex < FREE_TABLE_LIMIT);
pArena->per_flags_buckets = PVRSRVSplay(pBT->uFlags, pArena->per_flags_buckets);
if ((pArena->per_flags_buckets == NULL) || (pArena->per_flags_buckets->flags != pBT->uFlags))
{
return 0;
}
else
{
pBTScan = pArena->per_flags_buckets->buckets[uIndex];
while (pBTScan != NULL && pBTScan != pBT)
{
pBTScan = pBTScan->next_free;
}
/* Test if we found it and then return */
return (pBTScan == pBT);
}
}
/* is_arena_valid should only be used in debug mode.
it checks that some properties an arena must have are verified */
static int is_arena_valid(struct _RA_ARENA_ * arena)
{
struct _BT_ * chunk;
#if defined(PVR_CTZLL)
unsigned int i;
#endif
for (chunk = arena->pHeadSegment; chunk != NULL; chunk = chunk->pNextSegment)
{
/* if next segment is NULL, then it must be a rightmost */
PVR_ASSERT((chunk->pNextSegment != NULL) || (chunk->is_rightmost));
/* if prev segment is NULL, then it must be a leftmost */
PVR_ASSERT((chunk->pPrevSegment != NULL) || (chunk->is_leftmost));
if (chunk->type == btt_free)
{
/* checks the correctness of the type field */
PVR_ASSERT(_IsInFreeList(arena, chunk));
/* check that there can't be two consecutive free chunks.
Indeed, instead of having two consecutive free chunks,
there should be only one that span the size of the two. */
PVR_ASSERT((chunk->is_leftmost) || (chunk->pPrevSegment->type != btt_free));
PVR_ASSERT((chunk->is_rightmost) || (chunk->pNextSegment->type != btt_free));
}
else
{
/* checks the correctness of the type field */
PVR_ASSERT(!_IsInFreeList(arena, chunk));
}
PVR_ASSERT((chunk->is_leftmost) || (chunk->pPrevSegment->base + chunk->pPrevSegment->uSize == chunk->base));
PVR_ASSERT((chunk->is_rightmost) || (chunk->base + chunk->uSize == chunk->pNextSegment->base));
/* all segments of the same imports must have the same flags ... */
PVR_ASSERT((chunk->is_rightmost) || (chunk->uFlags == chunk->pNextSegment->uFlags));
/* ... and the same import handle */
PVR_ASSERT((chunk->is_rightmost) || (chunk->hPriv == chunk->pNextSegment->hPriv));
/* if a free chunk spans a whole import, then it must be an 'not to free import'.
Otherwise it should have been freed. */
PVR_ASSERT((!chunk->is_leftmost) || (!chunk->is_rightmost) || (chunk->type == btt_live) || (!chunk->free_import));
}
#if defined(PVR_CTZLL)
if (arena->per_flags_buckets != NULL)
{
for (i = 0; i < FREE_TABLE_LIMIT; ++i)
{
/* verify that the bHasEltsMapping is correct for this flags bucket */
PVR_ASSERT(
((arena->per_flags_buckets->buckets[i] == NULL) &&
(( (arena->per_flags_buckets->bHasEltsMapping & ((IMG_ELTS_MAPPINGS) 1 << i)) == 0)))
||
((arena->per_flags_buckets->buckets[i] != NULL) &&
(( (arena->per_flags_buckets->bHasEltsMapping & ((IMG_ELTS_MAPPINGS) 1 << i)) != 0)))
);
}
}
#endif
/* if arena was not valid, one of the assert before should have triggered */
return 1;
}
#endif
/*************************************************************************/ /*!
@Function _SegmentListInsertAfter
@Description Insert a boundary tag into an arena segment list after a
specified boundary tag.
@Input pInsertionPoint The insertion point.
@Input pBT The boundary tag to insert.
@Return PVRSRV_OK (doesn't fail)
*/ /**************************************************************************/
static INLINE PVRSRV_ERROR
_SegmentListInsertAfter (BT *pInsertionPoint,
BT *pBT)
{
PVR_ASSERT (pBT != NULL);
PVR_ASSERT (pInsertionPoint != NULL);
pBT->pNextSegment = pInsertionPoint->pNextSegment;
pBT->pPrevSegment = pInsertionPoint;
if (pInsertionPoint->pNextSegment != NULL)
{
pInsertionPoint->pNextSegment->pPrevSegment = pBT;
}
pInsertionPoint->pNextSegment = pBT;
return PVRSRV_OK;
}
/*************************************************************************/ /*!
@Function _SegmentListInsert
@Description Insert a boundary tag into an arena segment list
@Input pArena The arena.
@Input pBT The boundary tag to insert.
@Return PVRSRV_OK (doesn't fail)
*/ /**************************************************************************/
static INLINE PVRSRV_ERROR
_SegmentListInsert (RA_ARENA *pArena, BT *pBT)
{
PVRSRV_ERROR eError = PVRSRV_OK;
PVR_ASSERT (!_IsInSegmentList(pArena, pBT));
/* insert into the segment chain */
pBT->pNextSegment = pArena->pHeadSegment;
pArena->pHeadSegment = pBT;
if (pBT->pNextSegment != NULL)
{
pBT->pNextSegment->pPrevSegment = pBT;
}
pBT->pPrevSegment = NULL;
return eError;
}
/*************************************************************************/ /*!
@Function _SegmentListRemove
@Description Remove a boundary tag from an arena segment list.
@Input pArena The arena.
@Input pBT The boundary tag to remove.
*/ /**************************************************************************/
static void
_SegmentListRemove (RA_ARENA *pArena, BT *pBT)
{
PVR_ASSERT (_IsInSegmentList(pArena, pBT));
if (pBT->pPrevSegment == NULL)
pArena->pHeadSegment = pBT->pNextSegment;
else
pBT->pPrevSegment->pNextSegment = pBT->pNextSegment;
if (pBT->pNextSegment != NULL)
pBT->pNextSegment->pPrevSegment = pBT->pPrevSegment;
}
/*************************************************************************/ /*!
@Function _BuildBT
@Description Construct a boundary tag for a free segment.
@Input base The base of the resource segment.
@Input uSize The extent of the resouce segment.
@Input uFlags The flags to give to the boundary tag
@Return Boundary tag or NULL
*/ /**************************************************************************/
static BT *
_BuildBT (RA_BASE_T base,
RA_LENGTH_T uSize,
RA_FLAGS_T uFlags
)
{
BT *pBT;
pBT = OSAllocMem(sizeof(BT));
if (pBT == NULL)
{
return NULL;
}
OSCachedMemSet(pBT, 0, sizeof(BT));
pBT->is_leftmost = 1;
pBT->is_rightmost = 1;
pBT->type = btt_live;
pBT->base = base;
pBT->uSize = uSize;
pBT->uFlags = uFlags;
pBT->free_import = 0;
return pBT;
}
/*************************************************************************/ /*!
@Function _SegmentSplit
@Description Split a segment into two, maintain the arena segment list. The
boundary tag should not be in the free table. Neither the
original or the new neighbour bounary tag will be in the free
table.
@Input pBT The boundary tag to split.
@Input uSize The required segment size of boundary tag after
splitting.
@Return New neighbour boundary tag or NULL.
*/ /**************************************************************************/
static BT *
_SegmentSplit (BT *pBT, RA_LENGTH_T uSize)
{
BT *pNeighbour;
pNeighbour = _BuildBT(pBT->base + uSize, pBT->uSize - uSize, pBT->uFlags);
if (pNeighbour == NULL)
{
return NULL;
}
_SegmentListInsertAfter(pBT, pNeighbour);
pNeighbour->is_leftmost = 0;
pNeighbour->is_rightmost = pBT->is_rightmost;
pNeighbour->free_import = pBT->free_import;
pBT->is_rightmost = 0;
pNeighbour->hPriv = pBT->hPriv;
pBT->uSize = uSize;
pNeighbour->uFlags = pBT->uFlags;
return pNeighbour;
}
/*************************************************************************/ /*!
@Function _FreeListInsert
@Description Insert a boundary tag into an arena free table.
@Input pArena The arena.
@Input pBT The boundary tag.
*/ /**************************************************************************/
static void
_FreeListInsert (RA_ARENA *pArena, BT *pBT)
{
IMG_UINT32 uIndex;
uIndex = pvr_log2 (pBT->uSize);
PVR_ASSERT (uIndex < FREE_TABLE_LIMIT);
PVR_ASSERT (!_IsInFreeList(pArena, pBT));
pBT->type = btt_free;
pArena->per_flags_buckets = PVRSRVSplay(pBT->uFlags, pArena->per_flags_buckets);
/* the flags item in the splay tree must have been created before-hand by
_InsertResource */
PVR_ASSERT(pArena->per_flags_buckets != NULL);
PVR_ASSERT(pArena->per_flags_buckets->buckets != NULL);
pBT->next_free = pArena->per_flags_buckets->buckets[uIndex];
if (pBT->next_free != NULL)
{
pBT->next_free->prev_free = pBT;
}
pBT->prev_free = NULL;
pArena->per_flags_buckets->buckets[uIndex] = pBT;
#if defined(PVR_CTZLL)
/* tells that bucket[index] now contains elements */
pArena->per_flags_buckets->bHasEltsMapping |= ((IMG_ELTS_MAPPINGS) 1 << uIndex);
#endif
}
/*************************************************************************/ /*!
@Function _FreeListRemove
@Description Remove a boundary tag from an arena free table.
@Input pArena The arena.
@Input pBT The boundary tag.
*/ /**************************************************************************/
static void
_FreeListRemove (RA_ARENA *pArena, BT *pBT)
{
IMG_UINT32 uIndex;
uIndex = pvr_log2 (pBT->uSize);
PVR_ASSERT (uIndex < FREE_TABLE_LIMIT);
PVR_ASSERT (_IsInFreeList(pArena, pBT));
if (pBT->next_free != NULL)
{
pBT->next_free->prev_free = pBT->prev_free;
}
if (pBT->prev_free != NULL)
{
pBT->prev_free->next_free = pBT->next_free;
}
else
{
pArena->per_flags_buckets = PVRSRVSplay(pBT->uFlags, pArena->per_flags_buckets);
/* the flags item in the splay tree must have already been created
(otherwise how could there be a segment with these flags */
PVR_ASSERT(pArena->per_flags_buckets != NULL);
PVR_ASSERT(pArena->per_flags_buckets->buckets != NULL);
pArena->per_flags_buckets->buckets[uIndex] = pBT->next_free;
#if defined(PVR_CTZLL)
if (pArena->per_flags_buckets->buckets[uIndex] == NULL)
{
/* there is no more elements in this bucket. Update the mapping. */
pArena->per_flags_buckets->bHasEltsMapping &= ~((IMG_ELTS_MAPPINGS) 1 << uIndex);
}
#endif
}
PVR_ASSERT (!_IsInFreeList(pArena, pBT));
pBT->type = btt_live;
}
/*************************************************************************/ /*!
@Function _InsertResource
@Description Add a free resource segment to an arena.
@Input pArena The arena.
@Input base The base of the resource segment.
@Input uSize The extent of the resource segment.
@Input uFlags The flags of the new resources.
@Return New bucket pointer
NULL on failure
*/ /**************************************************************************/
static BT *
_InsertResource (RA_ARENA *pArena,
RA_BASE_T base,
RA_LENGTH_T uSize,
RA_FLAGS_T uFlags
)
{
BT *pBT;
PVR_ASSERT (pArena!=NULL);
pBT = _BuildBT (base, uSize, uFlags);
if (pBT != NULL)
{
IMG_PSPLAY_TREE tmp = PVRSRVInsert(pBT->uFlags, pArena->per_flags_buckets);
if (tmp == NULL)
{
OSFreeMem(pBT);
return NULL;
}
pArena->per_flags_buckets = tmp;
_SegmentListInsert (pArena, pBT);
_FreeListInsert (pArena, pBT);
}
return pBT;
}
/*************************************************************************/ /*!
@Function _InsertResourceSpan
@Description Add a free resource span to an arena, marked for free_import.
@Input pArena The arena.
@Input base The base of the resource segment.
@Input uSize The extent of the resource segment.
@Return The boundary tag representing the free resource segment,
or NULL on failure.
*/ /**************************************************************************/
static INLINE BT *
_InsertResourceSpan (RA_ARENA *pArena,
RA_BASE_T base,
RA_LENGTH_T uSize,
RA_FLAGS_T uFlags)
{
BT *pBT = _InsertResource(pArena, base, uSize, uFlags);
if (pBT != NULL)
{
pBT->free_import = 1;
}
return pBT;
}
/*************************************************************************/ /*!
@Function _RemoveResourceSpan
@Description Frees a resource span from an arena, returning the imported
span via the callback.
@Input pArena The arena.
@Input pBT The boundary tag to free.
@Return IMG_FALSE failure - span was still in use
IMG_TRUE success - span was removed and returned
*/ /**************************************************************************/
static INLINE IMG_BOOL
_RemoveResourceSpan (RA_ARENA *pArena, BT *pBT)
{
PVR_ASSERT (pArena!=NULL);
PVR_ASSERT (pBT!=NULL);
if (pBT->free_import &&
pBT->is_leftmost &&
pBT->is_rightmost)
{
_SegmentListRemove (pArena, pBT);
pArena->pImportFree (pArena->pImportHandle, pBT->base, pBT->hPriv);
OSFreeMem(pBT);
return IMG_TRUE;
}
return IMG_FALSE;
}
/*************************************************************************/ /*!
@Function _FreeBT
@Description Free a boundary tag taking care of the segment list and the
boundary tag free table.
@Input pArena The arena.
@Input pBT The boundary tag to free.
*/ /**************************************************************************/
static void
_FreeBT (RA_ARENA *pArena, BT *pBT)
{
BT *pNeighbour;
PVR_ASSERT (pArena!=NULL);
PVR_ASSERT (pBT!=NULL);
PVR_ASSERT (!_IsInFreeList(pArena, pBT));
/* try and coalesce with left neighbour */
pNeighbour = pBT->pPrevSegment;
if ((!pBT->is_leftmost) && (pNeighbour->type == btt_free))
{
/* Sanity check. */
PVR_ASSERT(pNeighbour->base + pNeighbour->uSize == pBT->base);
_FreeListRemove (pArena, pNeighbour);
_SegmentListRemove (pArena, pNeighbour);
pBT->base = pNeighbour->base;
pBT->uSize += pNeighbour->uSize;
pBT->is_leftmost = pNeighbour->is_leftmost;
OSFreeMem(pNeighbour);
}
/* try to coalesce with right neighbour */
pNeighbour = pBT->pNextSegment;
if ((!pBT->is_rightmost) && (pNeighbour->type == btt_free))
{
/* sanity check */
PVR_ASSERT(pBT->base + pBT->uSize == pNeighbour->base);
_FreeListRemove (pArena, pNeighbour);
_SegmentListRemove (pArena, pNeighbour);
pBT->uSize += pNeighbour->uSize;
pBT->is_rightmost = pNeighbour->is_rightmost;
OSFreeMem(pNeighbour);
}
if (_RemoveResourceSpan(pArena, pBT) == IMG_FALSE)
{
_FreeListInsert (pArena, pBT);
PVR_ASSERT( (!pBT->is_rightmost) || (!pBT->is_leftmost) || (!pBT->free_import) );
}
PVR_ASSERT(is_arena_valid(pArena));
}
/*
This function returns the first element in a bucket that can be split
in a way that one of the subsegment can meet the size and alignment
criteria.
The first_elt is the bucket to look into. Remember that a bucket is
implemented as a pointer to the first element of the linked list.
nb_max_try is used to limit the number of elements considered.
This is used to only consider the first nb_max_try elements in the
free-list. The special value ~0 is used to say unlimited i.e. consider
all elements in the free list
*/
static INLINE
struct _BT_ * find_chunk_in_bucket(struct _BT_ * first_elt,
RA_LENGTH_T uSize,
RA_LENGTH_T uAlignment,
unsigned int nb_max_try)
{
struct _BT_ * walker;
for (walker = first_elt; (walker != NULL) && (nb_max_try != 0); walker = walker->next_free)
{
const RA_BASE_T aligned_base = (uAlignment > 1) ?
(walker->base + uAlignment - 1) & ~(uAlignment - 1)
: walker->base;
if (walker->base + walker->uSize >= aligned_base + uSize)
{
return walker;
}
/* 0xFFFF...FFFF is used has nb_max_try = infinity. */
if (nb_max_try != (unsigned int) ~0)
{
nb_max_try--;
}
}
return NULL;
}
/*************************************************************************/ /*!
@Function _AttemptAllocAligned
@Description Attempt an allocation from an arena.
@Input pArena The arena.
@Input uSize The requested allocation size.
@Output phPriv The user references associated with
the imported segment. (optional)
@Input flags Allocation flags
@Input uAlignment Required uAlignment, or 0.
Must be a power of 2 if not 0
@Output base Allocated resource base (non optional, must not be NULL)
@Return IMG_FALSE failure
IMG_TRUE success
*/ /**************************************************************************/
static IMG_BOOL
_AttemptAllocAligned (RA_ARENA *pArena,
RA_LENGTH_T uSize,
IMG_UINT32 uFlags,
RA_LENGTH_T uAlignment,
RA_BASE_T *base,
RA_PERISPAN_HANDLE *phPriv) /* this is the "per-import" private data */
{
IMG_UINT32 index_low;
IMG_UINT32 index_high;
IMG_UINT32 i;
struct _BT_ * pBT = NULL;
RA_BASE_T aligned_base;
PVR_ASSERT (pArena!=NULL);
PVR_ASSERT (base != NULL);
pArena->per_flags_buckets = PVRSRVSplay(uFlags, pArena->per_flags_buckets);
if ((pArena->per_flags_buckets == NULL) || (pArena->per_flags_buckets->ui32Flags != uFlags))
{
/* no chunks with these flags. */
return IMG_FALSE;
}
index_low = pvr_log2(uSize);
index_high = pvr_log2(uSize + uAlignment - 1);
PVR_ASSERT(index_low < FREE_TABLE_LIMIT);
PVR_ASSERT(index_high < FREE_TABLE_LIMIT);
PVR_ASSERT(index_low <= index_high);
#if defined(PVR_CTZLL)
i = PVR_CTZLL((IMG_ELTS_MAPPINGS) (~((1 << (index_high + 1)) - 1)) & pArena->per_flags_buckets->bHasEltsMapping);
#else
for (i = index_high + 1; (i < FREE_TABLE_LIMIT) && (pArena->per_flags_buckets->buckets[i] == NULL); ++i)
{
}
#endif
PVR_ASSERT(i <= FREE_TABLE_LIMIT);
if (i != FREE_TABLE_LIMIT)
{
/* since we start at index_high + 1, we are guarantee to exit */
pBT = find_chunk_in_bucket(pArena->per_flags_buckets->buckets[i], uSize, uAlignment, 1);
}
else
{
for (i = index_high; (i != index_low - 1) && (pBT == NULL); --i)
{
pBT = find_chunk_in_bucket(pArena->per_flags_buckets->buckets[i], uSize, uAlignment, (unsigned int) ~0);
}
}
if (pBT == NULL)
{
return IMG_FALSE;
}
aligned_base = (uAlignment > 1) ? (pBT->base + uAlignment - 1) & ~(uAlignment - 1) : pBT->base;
_FreeListRemove (pArena, pBT);
if(pArena->bNoSplit)
{
goto nosplit;
}
/* with uAlignment we might need to discard the front of this segment */
if (aligned_base > pBT->base)
{
BT *pNeighbour;
pNeighbour = _SegmentSplit (pBT, (RA_LENGTH_T)(aligned_base - pBT->base));
/* partition the buffer, create a new boundary tag */
if (pNeighbour == NULL)
{
PVR_DPF ((PVR_DBG_ERROR, "%s: Front split failed", __FUNCTION__));
/* Put pBT back in the list */
_FreeListInsert (pArena, pBT);
return IMG_FALSE;
}
_FreeListInsert(pArena, pBT);
pBT = pNeighbour;
}
/* the segment might be too big, if so, discard the back of the segment */
if (pBT->uSize > uSize)
{
BT *pNeighbour;
pNeighbour = _SegmentSplit(pBT, uSize);
/* partition the buffer, create a new boundary tag */
if (pNeighbour == NULL)
{
PVR_DPF ((PVR_DBG_ERROR, "%s: Back split failed", __FUNCTION__));
/* Put pBT back in the list */
_FreeListInsert (pArena, pBT);
return IMG_FALSE;
}
_FreeListInsert (pArena, pNeighbour);
}
nosplit:
pBT->type = btt_live;
if (!HASH_Insert_Extended (pArena->pSegmentHash, &pBT->base, (uintptr_t)pBT))
{
_FreeBT (pArena, pBT);
return IMG_FALSE;
}
if (phPriv != NULL)
*phPriv = pBT->hPriv;
*base = pBT->base;
return IMG_TRUE;
}
/*************************************************************************/ /*!
@Function RA_Create
@Description To create a resource arena.
@Input name The name of the arena for diagnostic purposes.
@Input base The base of an initial resource span or 0.
@Input uSize The size of an initial resource span or 0.
@Input uFlags The flags of an initial resource span or 0.
@Input ulog2Quantum The arena allocation quantum.
@Input imp_alloc A resource allocation callback or 0.
@Input imp_free A resource de-allocation callback or 0.
@Input pImportHandle Handle passed to alloc and free or 0.
@Input bNoSplit Disable splitting up imports.
@Return arena handle, or NULL.
*/ /**************************************************************************/
IMG_INTERNAL RA_ARENA *
RA_Create (IMG_CHAR *name,
RA_LOG2QUANTUM_T uLog2Quantum,
IMG_UINT32 ui32LockClass,
PVRSRV_ERROR (*imp_alloc)(RA_PERARENA_HANDLE h,
RA_LENGTH_T uSize,
RA_FLAGS_T _flags,
const IMG_CHAR *pszAnnotation,
/* returned data */
RA_BASE_T *pBase,
RA_LENGTH_T *pActualSize,
RA_PERISPAN_HANDLE *phPriv),
void (*imp_free) (RA_PERARENA_HANDLE,
RA_BASE_T,
RA_PERISPAN_HANDLE),
RA_PERARENA_HANDLE arena_handle,
IMG_BOOL bNoSplit)
{
RA_ARENA *pArena;
PVRSRV_ERROR eError;
if (name == NULL)
{
PVR_DPF ((PVR_DBG_ERROR, "RA_Create: invalid parameter 'name' (NULL not accepted)"));
return NULL;
}
PVR_DPF ((PVR_DBG_MESSAGE, "RA_Create: name='%s'", name));
pArena = OSAllocMem(sizeof (*pArena));
if (pArena == NULL)
{
goto arena_fail;
}
eError = OSLockCreate(&pArena->hLock, LOCK_TYPE_PASSIVE);
if (eError != PVRSRV_OK)
{
goto lock_fail;
}
pArena->pSegmentHash = HASH_Create_Extended(MINIMUM_HASH_SIZE, sizeof(RA_BASE_T), HASH_Func_Default, HASH_Key_Comp_Default);
if (pArena->pSegmentHash==NULL)
{
goto hash_fail;
}
pArena->name = name;
pArena->pImportAlloc = (imp_alloc!=NULL) ? imp_alloc : &_RequestAllocFail;
pArena->pImportFree = imp_free;
pArena->pImportHandle = arena_handle;
pArena->pHeadSegment = NULL;
pArena->uQuantum = (IMG_UINT64) (1 << uLog2Quantum);
pArena->per_flags_buckets = NULL;
pArena->ui32LockClass = ui32LockClass;
pArena->bNoSplit = bNoSplit;
PVR_ASSERT(is_arena_valid(pArena));
return pArena;
hash_fail:
OSLockDestroy(pArena->hLock);
lock_fail:
OSFreeMem(pArena);
/*not nulling pointer, out of scope*/
arena_fail:
return NULL;
}
/*************************************************************************/ /*!
@Function RA_Delete
@Description To delete a resource arena. All resources allocated from
the arena must be freed before deleting the arena.
@Input pArena The arena to delete.
*/ /**************************************************************************/
IMG_INTERNAL void
RA_Delete (RA_ARENA *pArena)
{
IMG_UINT32 uIndex;
IMG_BOOL bWarn = IMG_TRUE;
PVR_ASSERT(pArena != NULL);
if (pArena == NULL)
{
PVR_DPF ((PVR_DBG_ERROR,"RA_Delete: invalid parameter - pArena"));
return;
}
PVR_ASSERT(is_arena_valid(pArena));
PVR_DPF ((PVR_DBG_MESSAGE,
"RA_Delete: name='%s'", pArena->name));
while (pArena->pHeadSegment != NULL)
{
BT *pBT = pArena->pHeadSegment;
if (pBT->type != btt_free)
{
if (bWarn)
{
PVR_DPF ((PVR_DBG_ERROR, "%s: Allocations still exist in the arena that is being destroyed", __func__));
PVR_DPF ((PVR_DBG_ERROR, "%s: Likely Cause: client drivers not freeing allocations before destroying devmem context", __func__));
PVR_DPF ((PVR_DBG_ERROR, "%s: base = 0x%llx size=0x%llx", __func__,
(unsigned long long)pBT->base, (unsigned long long)pBT->uSize));
PVR_DPF ((PVR_DBG_ERROR, "%s: This warning will be issued only once for the first allocation found!", __func__));
bWarn = IMG_FALSE;
}
}
else
{
_FreeListRemove(pArena, pBT);
}
_SegmentListRemove (pArena, pBT);
OSFreeMem(pBT);
/*not nulling original pointer, it has changed*/
}
while (pArena->per_flags_buckets != NULL)
{
for (uIndex=0; uIndex<FREE_TABLE_LIMIT; uIndex++)
{
PVR_ASSERT(pArena->per_flags_buckets->buckets[uIndex] == NULL);
}
pArena->per_flags_buckets = PVRSRVDelete(pArena->per_flags_buckets->ui32Flags, pArena->per_flags_buckets);
}
HASH_Delete (pArena->pSegmentHash);
OSLockDestroy(pArena->hLock);
OSFreeMem(pArena);
/*not nulling pointer, copy on stack*/
}
/*************************************************************************/ /*!
@Function RA_Add
@Description To add a resource span to an arena. The span must not
overlapp with any span previously added to the arena.
@Input pArena The arena to add a span into.
@Input base The base of the span.
@Input uSize The extent of the span.
@Input uFlags the flags of the new import
@Input hPriv a private handle associate to the span. (reserved for user)
@Return IMG_TRUE - Success
IMG_FALSE - failure
*/ /**************************************************************************/
IMG_INTERNAL IMG_BOOL
RA_Add (RA_ARENA *pArena,
RA_BASE_T base,
RA_LENGTH_T uSize,
RA_FLAGS_T uFlags,
RA_PERISPAN_HANDLE hPriv)
{
struct _BT_* bt;
PVR_ASSERT (pArena != NULL);
PVR_ASSERT (uSize != 0);
if (pArena == NULL)
{
PVR_DPF ((PVR_DBG_ERROR,"RA_Add: invalid parameter - pArena"));
return IMG_FALSE;
}
if(uSize == 0)
{
PVR_DPF((PVR_DBG_ERROR, "RA_Add: invalid size 0 added to arena %s", pArena->name));
return IMG_FALSE;
}
OSLockAcquireNested(pArena->hLock, pArena->ui32LockClass);
PVR_ASSERT(is_arena_valid(pArena));
PVR_DPF ((PVR_DBG_MESSAGE, "RA_Add: name='%s', "
"base=0x%llx, size=0x%llx", pArena->name,
(unsigned long long)base, (unsigned long long)uSize));
uSize = (uSize + pArena->uQuantum - 1) & ~(pArena->uQuantum - 1);
bt = _InsertResource(pArena, base, uSize, uFlags);
if (bt != NULL)
{
bt->hPriv = hPriv;
}
PVR_ASSERT(is_arena_valid(pArena));
OSLockRelease(pArena->hLock);
return bt != NULL;
}
/*************************************************************************/ /*!
@Function RA_Alloc
@Description To allocate resource from an arena.
@Input pArena The arena
@Input uRequestSize The size of resource segment requested.
@Input uImportMultiplier Import x-times more for future requests if
we have to import new memory.
@Output pActualSize The actual size of resource segment
allocated, typcially rounded up by quantum.
@Output phPriv The user reference associated with allocated resource span.
@Input uImportFlags Flags influencing allocation policy.
@Input uAlignment The uAlignment constraint required for the
allocated segment, use 0 if uAlignment not required, otherwise
must be a power of 2.
@Output base Allocated base resource
@Return PVRSRV_OK - success
*/ /**************************************************************************/
IMG_INTERNAL PVRSRV_ERROR
RA_Alloc (RA_ARENA *pArena,
RA_LENGTH_T uRequestSize,
IMG_UINT8 uImportMultiplier,
RA_FLAGS_T uImportFlags,
RA_LENGTH_T uAlignment,
const IMG_CHAR *pszAnnotation,
RA_BASE_T *base,
RA_LENGTH_T *pActualSize,
RA_PERISPAN_HANDLE *phPriv)
{
PVRSRV_ERROR eError;
IMG_BOOL bResult;
RA_LENGTH_T uSize = uRequestSize;
RA_FLAGS_T uFlags = (uImportFlags & PVRSRV_MEMALLOCFLAGS_RA_DIFFERENTIATION_MASK);
if (pArena == NULL || uImportMultiplier == 0 || uSize == 0)
{
PVR_DPF ((PVR_DBG_ERROR,
"RA_Alloc: One of the necessary parameters is 0"));
return PVRSRV_ERROR_INVALID_PARAMS;
}
OSLockAcquireNested(pArena->hLock, pArena->ui32LockClass);
PVR_ASSERT(is_arena_valid(pArena));
if (pActualSize != NULL)
{
*pActualSize = uSize;
}
/* Must be a power of 2 or 0 */
PVR_ASSERT((uAlignment == 0) || (uAlignment & (uAlignment - 1)) == 0);
PVR_DPF ((PVR_DBG_MESSAGE,
"RA_Alloc: arena='%s', size=0x%llx(0x%llx), "
"alignment=0x%llx", pArena->name,
(unsigned long long)uSize, (unsigned long long)uRequestSize,
(unsigned long long)uAlignment));
/* if allocation failed then we might have an import source which
can provide more resource, else we will have to fail the
allocation to the caller. */
bResult = _AttemptAllocAligned (pArena, uSize, uFlags, uAlignment, base, phPriv);
if (!bResult)
{
IMG_HANDLE hPriv;
RA_BASE_T import_base;
RA_LENGTH_T uImportSize = uSize;
/*
Ensure that we allocate sufficient space to meet the uAlignment
constraint
*/
if (uAlignment > pArena->uQuantum)
{
uImportSize += (uAlignment - pArena->uQuantum);
}
/* apply over-allocation multiplier after all alignment adjustments */
uImportSize *= uImportMultiplier;
/* ensure that we import according to the quanta of this arena */
uImportSize = (uImportSize + pArena->uQuantum - 1) & ~(pArena->uQuantum - 1);
eError = pArena->pImportAlloc (pArena->pImportHandle,
uImportSize, uImportFlags,
pszAnnotation,
&import_base, &uImportSize,
&hPriv);
if (PVRSRV_OK != eError)
{
OSLockRelease(pArena->hLock);
return eError;
}
else
{
BT *pBT;
pBT = _InsertResourceSpan (pArena, import_base, uImportSize, uFlags);
/* successfully import more resource, create a span to
represent it and retry the allocation attempt */
if (pBT == NULL)
{
/* insufficient resources to insert the newly acquired span,
so free it back again */
pArena->pImportFree(pArena->pImportHandle, import_base, hPriv);
PVR_DPF ((PVR_DBG_MESSAGE, "RA_Alloc: name='%s', "
"size=0x%llx failed!", pArena->name,
(unsigned long long)uSize));
/* RA_Dump (arena); */
OSLockRelease(pArena->hLock);
return PVRSRV_ERROR_RA_INSERT_RESOURCE_SPAN_FAILED;
}
pBT->hPriv = hPriv;
bResult = _AttemptAllocAligned(pArena, uSize, uFlags, uAlignment, base, phPriv);
if (!bResult)
{
PVR_DPF ((PVR_DBG_ERROR,
"RA_Alloc: name='%s' second alloc failed!",
pArena->name));
/*
On failure of _AttemptAllocAligned() depending on the exact point
of failure, the imported segment may have been used and freed, or
left untouched. If the later, we need to return it.
*/
_FreeBT(pArena, pBT);
OSLockRelease(pArena->hLock);
return PVRSRV_ERROR_RA_ATTEMPT_ALLOC_ALIGNED_FAILED;
}
else
{
/* Check if the new allocation was in the span we just added... */
if (*base < import_base || *base > (import_base + uImportSize))
{
PVR_DPF ((PVR_DBG_ERROR,
"RA_Alloc: name='%s' alloc did not occur in the imported span!",
pArena->name));
/*
Remove the imported span which should not be in use (if it is then
that is okay, but essentially no span should exist that is not used).
*/
_FreeBT(pArena, pBT);
}
}
}
}
PVR_DPF ((PVR_DBG_MESSAGE, "RA_Alloc: name='%s', size=0x%llx, "
"*base=0x%llx = %d",pArena->name, (unsigned long long)uSize,
(unsigned long long)*base, bResult));
PVR_ASSERT(is_arena_valid(pArena));
OSLockRelease(pArena->hLock);
return PVRSRV_OK;
}
/*************************************************************************/ /*!
@Function RA_Free
@Description To free a resource segment.
@Input pArena The arena the segment was originally allocated from.
@Input base The base of the resource span to free.
*/ /**************************************************************************/
IMG_INTERNAL void
RA_Free (RA_ARENA *pArena, RA_BASE_T base)
{
BT *pBT;
PVR_ASSERT (pArena != NULL);
if (pArena == NULL)
{
PVR_DPF ((PVR_DBG_ERROR,"RA_Free: invalid parameter - pArena"));
return;
}
OSLockAcquireNested(pArena->hLock, pArena->ui32LockClass);
PVR_ASSERT(is_arena_valid(pArena));
PVR_DPF ((PVR_DBG_MESSAGE, "RA_Free: name='%s', base=0x%llx", pArena->name,
(unsigned long long)base));
pBT = (BT *) HASH_Remove_Extended (pArena->pSegmentHash, &base);
PVR_ASSERT (pBT != NULL);
if (pBT)
{
PVR_ASSERT (pBT->base == base);
_FreeBT (pArena, pBT);
}
else
{
PVR_DPF((PVR_DBG_ERROR, "RA_Free: no resource span found for given base (0x%llX) in arena %s",
(unsigned long long) base,
pArena->name));
}
PVR_ASSERT(is_arena_valid(pArena));
OSLockRelease(pArena->hLock);
}