/* BEGIN_ICS_COPYRIGHT5 ****************************************

Copyright (c) 2015, Intel Corporation

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
    * Neither the name of Intel Corporation nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

** END_ICS_COPYRIGHT5   ****************************************/

#include "datatypes.h"
#include "imemory.h"
#include "imath.h"
#include "idebug.h"
#include "iquickmap.h"
#include "ispinlock.h"
#include "errno.h"
#include "string.h"
#include <sys/mman.h>

#ifdef IB_STACK_IBACCESS
static uint32 s_lock_strategy = 0xffffffff;

/* 
 * Certain redhat kernels use these defines without providing them. 
 * This allows us to support these features with such kernels.
 *
 * Note that this has no impact on kernels that really don't support
 * this feature.
 */
#if !defined(MADV_DONTFORK)
#define MADV_DONTFORK 10
#endif
#if !defined(MADV_DOFORK)
#define MADV_DOFORK 11
#endif

static FSTATUS
MemoryLockPrepareMlock(uintn start, uintn length);
static FSTATUS
MemoryLockUnprepareMlock(uintn start, uintn length);

#endif /* IB_STACK_IBACCESS */

void*
MemoryAllocatePriv( IN uint32 Bytes, IN uint32 flags _UNUSED_, IN uint32 Tag _UNUSED_ )
{
    return malloc( Bytes );
}

void
MemoryDeallocatePriv( IN void *pMemory )
{
    free( pMemory );
}

void
MemoryFill( IN void *pMemory, IN uchar Fill, IN uint32 Bytes )
{
    memset( pMemory, Fill, Bytes );
} 

void*
MemoryCopy( IN void *pDest, IN const void *pSrc, IN uint32 Bytes )
{
    return memcpy( pDest, pSrc, Bytes );
}

int32
MemoryCompare( IN const void *pMemory1, IN const void *pMemory2, IN uint32 Bytes )
{
    return memcmp( pMemory1, pMemory2, Bytes );
} 

#ifdef IB_STACK_IBACCESS
FSTATUS MemoryLockPrepare(uintn buf_org, uintn Length)
{
	uintn buf_aligned = 0;
	uintn size = Length;
	uint32 page_size = getpagesize();
	uintn tos_aligned;

	buf_aligned = ROUNDDOWNP2(buf_org, page_size);
	size = size + buf_org - buf_aligned;
	size = ROUNDUPP2(size, page_size);
	// This is an odd case, if buf_aligned is on the stack, we need to make sure
	// the stack itself is at least 1 page beyond the buf itself
	// we use &tos_aligned as an rough indication of top of stack
	// if we didn't do this, the VMA created by madvise could actually grow
	// after we locked the memory, which would confuse the memory locking code

	// for stacks which grow down:
	tos_aligned = ROUNDDOWNP2(&tos_aligned, page_size);
	if (tos_aligned == buf_aligned)
	{
		// force stack to grow by 1 page so TOS is on a different page than buf
		volatile uint8 *temp = (uint8*)alloca(page_size);
		if (temp == NULL)
			return FINSUFFICIENT_MEMORY;
		*temp = 1;	// touch new page so stack grows
	}
#if 0
	// for stacks which grow up:
	tos_aligned = ROUNDUPP2(&tos_aligned, page_size);
	if ( buf_aligned + size == tos_aligned)
	{
		// force stack to grow by 1 page so TOS is on a different page than buf
		volatile uint8 *temp = (uint8*)alloca(page_size);
		if (temp == NULL)
			return FINSUFFICIENT_MEMORY;;		
		*(temp+page_size) = 1;	// touch new page so stack grows
	}
#endif

	switch (s_lock_strategy) {
	case MEMORY_LOCK_STRAT_MADVISE:
#if MLOCK_DBG > 1
		MsgOut("%s: madvise addr:0x%"PRIxN" size:0x%"PRIxN"\n",__func__,buf_aligned,size);
#endif
		if(madvise((void *)buf_aligned, (size_t)size, MADV_SEQUENTIAL ))
		{
			MsgOut("%s: madvise failed with buf:0x%"PRIxN" size:%"PRIdN"\n",__func__,buf_aligned,size);
			return FINSUFFICIENT_RESOURCES;;		
		}
		break;

	case MEMORY_LOCK_STRAT_MLOCK:
		return MemoryLockPrepareMlock(buf_aligned, size);
		break;

	default:
		/* strategy not supported or MemoryLockSetStrategy not called first */
		return FINVALID_OPERATION;
	}
	return FSUCCESS;
}

FSTATUS MemoryLockUnprepare(uintn buf_org, uintn Length)
{
	switch (s_lock_strategy) {
	case MEMORY_LOCK_STRAT_MADVISE:
		/* no action needed */
		return FSUCCESS;
		break;

	case MEMORY_LOCK_STRAT_MLOCK:
		{
		uint32 page_size = getpagesize();
		uintn buf_aligned = ROUNDDOWNP2(buf_org,page_size);
		uintn size = Length + buf_org - buf_aligned;
		size = ROUNDUPP2(size, page_size);
		return MemoryLockUnprepareMlock(buf_aligned, size);
		break;
		}

	default:
		/* strategy not supported or MemoryLockSetStrategy not called first */
		return FINVALID_OPERATION;
	}
}

/* This provides to the User Memory module the memory lock strategy which Uvca
 * obtained via Vka from the kernel Memory module
 */
FSTATUS MemorySetLockStrategy(uint32 strategy)
{
	switch (strategy) {
	case MEMORY_LOCK_STRAT_MADVISE:
		s_lock_strategy = strategy;
		return FSUCCESS;
		break;

	case MEMORY_LOCK_STRAT_MLOCK:
		s_lock_strategy = strategy;
		return FSUCCESS;
		break;

	default:
		return FINVALID_PARAMETER;
		break;
	}
}

/* Tell UVca if MemoryLockUnprepare is necessary for the configured locking
 * strategy.  If not, UVCA has the option of optimizing its operation to
 * avoid the MemoryLockUnprepare.
 * However, if unnecessary, MemoryLockUnprepare should be a noop and should
 * not fail (hence allowing UVCA to choose not to optimize it out).
 */
boolean MemoryNeedLockUnprepare(void)
{
	switch (s_lock_strategy) {
	case MEMORY_LOCK_STRAT_MADVISE:
		return FALSE;
		break;
	case MEMORY_LOCK_STRAT_MLOCK:
		return TRUE;
		break;
	default:	/* unexpected case, play it safe */
		return TRUE;
		break;
	}
}


/* ********************************************************************** */
/* This implements the MEMORY_LOCK_STRAT_MLOCK core functionality
 * in this strategy the user space uses mlock and madvise prior to
 * the kernel MemoryLock being called.  Since mlock and madvise do not "stack"
 * we must track the presently locked regions and only call mlock/madvise for
 * the "delta" caused by the given lock/unlock of memory.
 * note that MemoryLockPrepare is called before MemoryLock
 * and MemoryLockUnprepare is called after MemoryUnlock
 *
 * This implementation of MemoryLockPrepare/MemoryLockUnprepare "stacks"
 * This assumes every MemoryLockUnprepare exactly matches a previous
 * call to MemoryLockPrepare.  It will assert otherwise.
 * UVCA follows this requirement.
 */

#define MLOCK_TAG	MAKE_MEM_TAG(k,c,l,m)

/* initial testing indicates most applications have relatively few
 * locked areas at a time, hence the cost of compressing then uncompressing
 * them (on unlock) is more expensive that having more areas to track and store
 * especially since the locked_area structure is relatively small and a
 * cl_qmap is used to search them
 */
#define COMPRESS_AREAS 0	/* conserve memory by merging areas when possible */

typedef struct _locked_area {
	cl_map_item_t MapItem;
	uintn		start;	// virtual address
	uintn		end;	// last address included in area
	uint32		lock_cnt;// number of times locked
} LOCKED_AREA;

typedef struct _addr_range {
	uintn		start;	// virtual address
	uintn		end;	// last address included in range
} ADDR_RANGE;

static cl_qmap_t	LockedArea_Map;
static SPIN_LOCK 	LockedAreaMap_Lock;
#if MLOCK_DBG
static unsigned s_num_locks = 0;	/* count of MemoryLockPrepare calls */
static unsigned s_num_split = 0;	/* count of LOCKED_AREAs split */
static unsigned s_num_merge = 0;	/* count of LOCKED_AREAs merged */
static unsigned s_max_areas = 0;	/* maximum number of areas */
#endif

#if MLOCK_DBG
/* dump all the locked areas, must be called with LockedAreaMap_Lock held */
static void LockedAreaDump(void)
{
	cl_map_item_t *pMapItem;

	MsgOut("      start              end               cnt\n");
	for (pMapItem = cl_qmap_head(&LockedArea_Map);
		 pMapItem != cl_qmap_end(&LockedArea_Map);
		 pMapItem = cl_qmap_next(pMapItem))
	{
		LOCKED_AREA *pArea = PARENT_STRUCT(pMapItem, LOCKED_AREA, MapItem);

		MsgOut("0x%16.16"PRIxN" 0x%16.16"PRIxN"  %10u\n", pArea->start, pArea->end, pArea->lock_cnt);
	}
}
#endif

/* compare start addresses of 2 entries in map
 * used as primary sort for LockedArea_Map.  The map should not have any
 * overlapping areas, hence sort by start address is sufficient
 * Return:
 *	-1: key1 < key 2
 *	 0: key1 = key 2
 *	 1: key1 > key 2
 */
static int LockedAreaCompare(uint64 key1, uint64 key2)
{
	LOCKED_AREA *pArea1 = (LOCKED_AREA*)(uintn)key1;
	LOCKED_AREA *pArea2 = (LOCKED_AREA*)(uintn)key2;

	if (pArea1->start < pArea2->start)
		return -1;
	else if (pArea1->start == pArea2->start)
		return 0;
	else
		return 1;
}

/* compare address range in key2 against address range in key1
 * key1 is a LOCKED_AREA*, key2 is a ADDR_RANGE*
 * Return:
 *	-1: key1 < key 2 (eg. key 2 is above range of key 1)
 *	 0: key1 = key 2 (eg. key 2 overlaps with range of key 1)
 *	 1: key1 > key 2 (eg. key 2 is below range of key 1)
 * Note that more than 1 range in the LockedArea_Map could overlap
 * a given value for key2
 */
static int LockedAreaRangeCompare(uint64 key1, uint64 key2)
{
	LOCKED_AREA *pArea1 = (LOCKED_AREA*)(uintn)key1;
	ADDR_RANGE *pRange2 = (ADDR_RANGE*)(uintn)key2;

	if (pArea1->start > pRange2->end)
		return 1;
	else if (pArea1->end < pRange2->start)
		return -1;
	else 
		return 0;
}

/* convert a pMapItem into a pArea, returns NULL if "end marker" */
static LOCKED_AREA *LockedAreaFromMapItem(cl_map_item_t *pMapItem)
{
	if (pMapItem != cl_qmap_end(&LockedArea_Map)) {
		return PARENT_STRUCT(pMapItem, LOCKED_AREA, MapItem);
	} else {
		return NULL;
	}
}

/* next area after pArea, returns NULL if no areas after pArea */
static _inline LOCKED_AREA *LockedAreaNext(LOCKED_AREA *pArea)
{
	return (LockedAreaFromMapItem(cl_qmap_next(&pArea->MapItem)));
}

/* prev area before pArea, returns NULL if no areas before pArea */
static _inline LOCKED_AREA *LockedAreaPrev(LOCKED_AREA *pArea)
{
	return (LockedAreaFromMapItem(cl_qmap_prev(&pArea->MapItem)));
}

/* must call with Map locked
 * merges pArea1 and pArea2 (which must be adjacent areas)
 * returns resulting merged area (Area2 is freed)
 * cannot fail
 */
static LOCKED_AREA *LockedAreaMerge(LOCKED_AREA *pArea1, LOCKED_AREA *pArea2)
{
	ASSERT(pArea1->end+1 == pArea2->start);
	ASSERT(pArea1->lock_cnt == pArea2->lock_cnt);
	pArea1->end = pArea2->end;
	cl_qmap_remove_item(&LockedArea_Map, &pArea2->MapItem);
#if MLOCK_DBG
	s_num_merge++;
#endif
	return pArea1;
}

/* create a new LOCKED_AREA and insert it into LockedArea_Map
 * returns NULL on memory allocation errors, otherwise pointer to new area
 */
static LOCKED_AREA *LockedAreaAlloc(uintn start, uintn end, uint32 lock_cnt)
{
	LOCKED_AREA *pNewArea;
	cl_map_item_t *pMapItem;

	pNewArea = (LOCKED_AREA*)MemoryAllocate2(
			sizeof(LOCKED_AREA), IBA_MEM_FLAG_NONE, MLOCK_TAG);
	if (! pNewArea)
		return NULL;
	pNewArea->start = start;
	pNewArea->end = end;
	pNewArea->lock_cnt = lock_cnt;
	pMapItem = cl_qmap_insert(&LockedArea_Map, (uintn)pNewArea, &pNewArea->MapItem);
	// assert new area is unique
	ASSERT(pMapItem == &pNewArea->MapItem);
#if MLOCK_DBG
	if (cl_qmap_count(&LockedArea_Map) > s_max_areas)
		s_max_areas = cl_qmap_count(&LockedArea_Map);
#endif
	return pNewArea;
}

/* must call with Map locked
 * splits pArea into 2 areas at addr
 * 1st area will be pArea->start to addr-1
 * 2nd area will be addr to pArea->end
 * returns 2nd area (pArea becomes 1st area)
 * returns NULL on failure to allocate memory
 */
static LOCKED_AREA *LockedAreaSplit(LOCKED_AREA *pArea, uintn addr)
{
	LOCKED_AREA *pNewArea;
	cl_map_item_t *pMapItem;

	ASSERT(pArea->start < addr);
	ASSERT(pArea->end >= addr);
	pNewArea = (LOCKED_AREA*)MemoryAllocate2(
			sizeof(LOCKED_AREA), IBA_MEM_FLAG_NONE, MLOCK_TAG);
	if (! pNewArea)
		return NULL;
	pNewArea->start = addr;
	pNewArea->end = pArea->end;
	pNewArea->lock_cnt = pArea->lock_cnt;
	pArea->end = addr-1;	// must fixup before insert pNewArea
	pMapItem = cl_qmap_insert(&LockedArea_Map, (uintn)pNewArea, &pNewArea->MapItem);
	ASSERT(pMapItem == &pNewArea->MapItem); /* assert new area is unique */
#if MLOCK_DBG
	s_num_split++;
	if (cl_qmap_count(&LockedArea_Map) > s_max_areas)
		s_max_areas = cl_qmap_count(&LockedArea_Map);
#endif
	return pNewArea;
}

/* must call with Map locked
 * find the 1st area in LockedArea_Map which overlaps start-end
 * returns NULL if no overlapping area(s)
 */
static LOCKED_AREA *FindFirstOverlap(uintn start, uintn end)
{
	ADDR_RANGE Range;
	cl_map_item_t *pMapItem;
	LOCKED_AREA *pArea;

	Range.start = start;
	Range.end = end;

	/* first find if any locked areas overlap the Range */
	pMapItem = cl_qmap_get_compare(&LockedArea_Map, (uint64)(uintn)&Range, LockedAreaRangeCompare);
	if (pMapItem == cl_qmap_end(&LockedArea_Map)) {
		/* no overlap with existing locks */
		return NULL;
	}
	pArea = PARENT_STRUCT(pMapItem, LOCKED_AREA, MapItem);
	/* because RangeCompare is not unique, we could get any area which
	 * overlaps, so we must back up to find first area which overlaps
	 */
	while ((pMapItem = cl_qmap_prev(&pArea->MapItem)) != cl_qmap_end(&LockedArea_Map)) {
		if (LockedAreaRangeCompare(cl_qmap_key(pMapItem), (uint64)(uintn)&Range) != 0) {
			// no overlap, pArea is 1st to overlap Range
			break;
		}
		pArea = PARENT_STRUCT(pMapItem, LOCKED_AREA, MapItem);
	}
	/* now pArea is 1st area to overlap Range */
	return pArea;
}

/* account for the locking/unlocking of start-end in pArea
 * as needed we could split pArea into up to 2 pieces
 * the returned value is the area which overlaps desired start/end
 * returns NULL on error
 */
static LOCKED_AREA *LockedAreaAddLock(LOCKED_AREA *pArea, uintn start, uintn end, uint32 add)
{
	LOCKED_AREA *p = NULL;

	if (pArea->start < start) {
		ASSERT(pArea->end >= start);
		p = pArea;	// save for recovery
		pArea = LockedAreaSplit(pArea, start);	// 2nd half
		if (! pArea)
			return NULL;	// failed
	}
	if (pArea->end > end) {
		ASSERT(pArea->start <= end);
		if (! LockedAreaSplit(pArea, end+1)) {
			// error, undo split above
			if (p)
				(void)LockedAreaMerge(p, pArea);
			return NULL;	// failed
		}
		// pArea is still 1st half
	}
	pArea->lock_cnt += add;
	return pArea;
}

static _inline LOCKED_AREA *LockedAreaIncLock(LOCKED_AREA *pArea, uintn start, uintn end)
{
	return LockedAreaAddLock(pArea, start, end, 1);
}

static _inline LOCKED_AREA *LockedAreaDecLock(LOCKED_AREA *pArea, uintn start, uintn end)
{
	return LockedAreaAddLock(pArea, start, end, (uint32)-1);
}

/* create a new locked area covering start-end */
static LOCKED_AREA *LockedAreaCreate(uintn start, uintn end)
{
	LOCKED_AREA *pArea;
	uintn length = (end-start)+1;

#if MLOCK_DBG > 1
	MsgOut("%s: mlock addr:0x%"PRIxN" size:0x%"PRIxN"\n",__func__,start,length);
#endif
	if (mlock((void*)start, (size_t)length) < 0) {
		MsgOut("mlock failed: start=0x%p, len=%u, errno=%s (%d)\n",
			(void*)start, (unsigned)length, strerror(errno), errno);
		return NULL;
	}
#if MLOCK_DBG > 1
	MsgOut("%s: madvise DONTFORK addr:0x%"PRIxN" size:0x%"PRIxN"\n",__func__,start,length);
#endif
	if (madvise((void*)start, (size_t)length, MADV_DONTFORK) < 0) {
		MsgOut("madvise DONTFORK failed: start=0x%p, len=%u, errno=%s (%d)\n",
			(void*)start, (unsigned)length, strerror(errno), errno);
		(void)munlock((void*)start, (size_t)length);
		return NULL;
	}
	pArea = LockedAreaAlloc(start, end, 1);
	if (! pArea) {
		(void)madvise((void*)start, (size_t)length, MADV_DOFORK);
		(void)munlock((void*)start, (size_t)length);
	}
	return pArea;
}

/* destroy a locked area */
static void LockedAreaDestroy(LOCKED_AREA *pArea)
{
	uintn length = (pArea->end - pArea->start)+1;
#if MLOCK_DBG > 1
	MsgOut("%s: madvise DOFORK addr:0x%"PRIxN" size:0x%"PRIxN"\n",__func__,pArea->start,length);
#endif
	(void)madvise((void*)pArea->start, (size_t)length, MADV_DOFORK);
#if MLOCK_DBG > 1
	MsgOut("%s: munlock addr:0x%"PRIxN" size:0x%"PRIxN"\n",__func__,pArea->start,length);
#endif
	(void)munlock((void*)pArea->start, (size_t)length);
	cl_qmap_remove_item(&LockedArea_Map, &pArea->MapItem);
	MemoryDeallocate(pArea);
}

#if COMPRESS_AREAS
/* merge Areas with same lock count.  This saves space but
 * may make for more work in unlock
 */
static void CompressAreas(LOCKED_AREA *pFirstArea, uintn end)
{
	LOCKED_AREA *pArea;

	ASSERT(pFirstArea);
	/* start merge process with 1st area before this lock (if any) */
	pArea = LockedAreaPrev(pFirstArea);
	if (pArea)
		pFirstArea = pArea;
	while (pFirstArea->start <= end) {
		pArea = LockedAreaNext(pFirstArea);
		if (! pArea)
			break;
		if (pArea->lock_cnt == pFirstArea->lock_cnt
			&& pFirstArea->end+1 == pArea->start)
			pFirstArea = LockedAreaMerge(pFirstArea, pArea);
		else
			pFirstArea = pArea;
	}
}
#endif /* COMPRESS_AREAS */


/* heart of managing mlock/madvise locked areas.
 * start and length must be pagesize aligned
 */
static FSTATUS
MemoryLockPrepareMlock(uintn start, uintn length)
{
	LOCKED_AREA *pArea;
#if COMPRESS_AREAS
	LOCKED_AREA *pFirstArea;
#endif
	uintn end = start + length-1;
	uintn addr;

#if MLOCK_DBG > 1
	MsgOut("%s: addr:0x%"PRIxN" size:0x%"PRIxN"\n",__func__,start,length);
#endif
	if (! length)
		return FSUCCESS;
// TBD - should we just mlock and madvise whole area 1st outside spin lock?
// however error unwind would be impossible if madvise failed?
// does kernel properly count mlock if done twice to same page?
	SpinLockAcquire(&LockedAreaMap_Lock);
#if MLOCK_DBG
	s_num_locks++;
#endif
	pArea = FindFirstOverlap(start, end);
#if COMPRESS_AREAS
	pFirstArea = NULL;
#endif
	for (addr=start; addr<=end; ) {
		if (! pArea) {
			pArea = LockedAreaCreate(addr, end);
		} else if (pArea->start > addr) {
			// fill gap
			pArea = LockedAreaCreate(addr, MIN(end, pArea->start-1));
		} else {
			pArea = LockedAreaIncLock(pArea, addr, end);
		}
		if (! pArea) {
			// error, unwind what we did so far
			SpinLockRelease(&LockedAreaMap_Lock);
#if MLOCK_DBG
			MsgOut("failure for MemoryLockPrepare(0x%"PRIxN", 0x%"PRIxN") for 0x%"PRIxN"-0x%"PRIxN"\n", start, length, addr, end);
			LockedAreaDump();
			MsgOut("MemoryLockUnprepare(0x%"PRIxN", 0x%"PRIxN") for 0x%"PRIxN"-0x%"PRIxN"\n", start, length, addr, end);
#endif
			if (addr != start)
				(void)MemoryLockUnprepareMlock(start, addr-start);	
			return FINSUFFICIENT_RESOURCES;
		}
		// adjust to reflect what is left to do
		addr = pArea->end+1;
#if COMPRESS_AREAS
		if (! pFirstArea)
			pFirstArea = pArea;
#endif
		pArea = LockedAreaNext(pArea);
	}
#if COMPRESS_AREAS
	/* merge Areas with same lock count.  This saves space but
	 * may make for more work in unlock
	 */
	ASSERT(pFirstArea);
	CompressAreas(pFirstArea, end);
#endif
#if MLOCK_DBG > 1
	LockedAreaDump();
#endif
	SpinLockRelease(&LockedAreaMap_Lock);
	return FSUCCESS;
}

/* start and length must be pagesize aligned
 */
static FSTATUS
MemoryLockUnprepareMlock(uintn start, uintn length)
{
	LOCKED_AREA *pArea;
	LOCKED_AREA *p;
#if COMPRESS_AREAS
	LOCKED_AREA *pFirstArea;
#endif
	uintn end = start + length-1;
	uintn addr;

#if MLOCK_DBG > 1
	MsgOut("%s: addr:0x%"PRIxN" size:0x%"PRIxN"\n",__func__,start,length);
#endif
	if (! length)
		return FSUCCESS;
	SpinLockAcquire(&LockedAreaMap_Lock);
	pArea = FindFirstOverlap(start, end);
#if COMPRESS_AREAS
	pFirstArea = NULL;
#endif
	for (addr=start; addr<=end; ) {
		ASSERT(pArea);
		ASSERT(pArea->start <= addr);
		pArea = LockedAreaDecLock(pArea, addr, end);
#if COMPRESS_AREAS
		if (! pArea) {
			// stuck, can't really re-lock it, so leave it
			// partially unlocked and give up
			DbgOut("Unable to unlock: addr=0x%p, end=0x%p\n",
				(void*)addr, (void*)end);
			break;
		}
#else
		// since never merge, should be no need to split, so should not fail
		ASSERT(pArea);
#endif
		// adjust to reflect what is left to do
		addr = pArea->end+1;
		p = LockedAreaNext(pArea);
		if (! pArea->lock_cnt) {
			LockedAreaDestroy(pArea);
#if COMPRESS_AREAS
		} else {
			if (! pFirstArea)
				pFirstArea = pArea;
#endif
		}
		pArea = p;
	}
#if COMPRESS_AREAS
	/* merge Areas with same lock count */
	if (pFirstArea)
		CompressAreas(pFirstArea, end);
#endif
#if MLOCK_DBG > 1
	LockedAreaDump();
#endif
	SpinLockRelease(&LockedAreaMap_Lock);
	return FSUCCESS;
}
/* end of MEMORY_LOCK_STRAT_MLOCK specific code */
/* ********************************************************************** */


FSTATUS
MemoryLockPrepareInit(void)
{

	cl_qmap_init(&LockedArea_Map, LockedAreaCompare);
	SpinLockInitState( &LockedAreaMap_Lock );

	if ( !SpinLockInit ( &LockedAreaMap_Lock ) )
	{
		MsgOut ("MemoryLockPrepareInit: Locked Area SpinLock init failed\n");
		return FERROR;
	}


	return FSUCCESS;
}

void
MemoryLockPrepareCleanup(void)
{

#if MLOCK_DBG
	MsgOut("MLock Stats: numLocks: %u, maxAreas: %u, numSplit: %u, numMerge: %u\n",
			s_num_locks, s_max_areas, s_num_split, s_num_merge);
#endif
	if (! cl_is_qmap_empty(&LockedArea_Map)) {
		DbgOut("MemoryLockPrepareCleanup: Locked Area Map not empty\n");
	}
	SpinLockDestroy( &LockedAreaMap_Lock );

} 
#endif /* IB_STACK_IBACCESS */
