/*
    (C) 1995 AROS - The Amiga Replacement OS
    $Id: allocmem.c 1.1 1995/11/14 22:31:07 digulla Exp digulla $
    $Log: allocmem.c $
 * Revision 1.1  1995/11/14  22:31:07  digulla
 * Initial revision
 *
    Desc:
    Lang: english
*/
#include "exec_intern.h"
#include "memory.h"
#include <aros/abscall.h>
#include <exec/alerts.h>

typedef LONG (*LowMemHandlerPtr)(struct MemHandlerData *mhd,APTR data);

/*****************************************************************************

    NAME */
	#include <exec/memory.h>
	#include <clib/exec_protos.h>

	__AROS_LH2(APTR, AllocMem,

/*  SYNOPSIS */
	__AROS_LA(unsigned long, byteSize, D0),
	__AROS_LA(unsigned long, requirements, D1),

/*  LOCATION */
	struct ExecBase *, SysBase, 33, Exec)

/*  FUNCTION
	Allocate some memory from the sytem memory pool with the given
	requirements.

    INPUTS
	byteSize     - Number of bytes you want to get
	requirements - Type of memory

    RESULT
	A pointer to the number of bytes you wanted or NULL if the memory
	couldn't be allocated

    NOTES
	The memory is aligned to sizeof(struct MemChunk). All requests
	are rounded up to a multiple of that size.

    EXAMPLE
	mytask=(struct Task *)AllocMem(sizeof(struct Task),MEMF_PUBLIC|MEMF_CLEAR);

    BUGS

    SEE ALSO
	FreeMem()

    INTERNALS

    HISTORY
	08-10-95    created by m. fleischer
	16-10-95    increased portability
	20-10-95    digulla lmhd has been auto-initialized with non-constant
		    values -> initialized by hand.
	26-10-95    digulla adjusted to new calling scheme

******************************************************************************/
{
    __AROS_FUNC_INIT
    struct Interrupt *lmh;
    struct MemHandlerData lmhd;

    /* Zero bytes requested? May return everything ;-). */
    if(!byteSize)
	return NULL;

    /* Initialize lmhd */
    lmhd.memh_RequestSize  = byteSize;
    lmhd.memh_RequestFlags = requirements;
    lmhd.memh_Flags	   = 0;

    /* First round byteSize to a multiple of MEMCHUNK_TOTAL. */
    byteSize=(byteSize+MEMCHUNK_TOTAL-1)&~(MEMCHUNK_TOTAL-1);

    /* Protect memory list against other tasks */
    Forbid();

    /* Loop over low memory handlers */
    lmh=(struct Interrupt *)SysBase->ex_MemHandlers.mlh_Head;
    for(;;)
    {
	struct MemHeader *mh;
	ULONG lmhr;

	/* Loop over MemHeader structures */
	mh=(struct MemHeader *)SysBase->MemList.lh_Head;
	while(mh->mh_Node.ln_Succ!=NULL)
	{
	    struct MemChunk *p1,*p2;

	    /*
		Check for the right requirements and enough free memory.
		The requirements are OK if there's no bit in the
		'attributes' that isn't set in the 'mh->mh_Attributes'.
		MEMF_CLEAR, MEMF_REVERSE and MEMF_NO_EXPUNGE are treated
		as if they were always set in the memheader.
	    */
	    if(!(requirements&~(MEMF_CLEAR|MEMF_REVERSE|
				MEMF_NO_EXPUNGE|mh->mh_Attributes))
	       &&mh->mh_Free>=byteSize)
	    {
		struct MemChunk *mc=NULL;

		/*
		    The free memory list is only single linked, i.e. to remove
		    elements from the list I need node's predessor. For the
		    first element I can use mh->mh_First instead of a real predessor.
		*/
		p1=(struct MemChunk *)&mh->mh_First;
		p2=p1->mc_Next;

		/* Is there anything in the list? */
		if(p2!=NULL)
		{
		    /* Then follow it */
		    for(;;)
		    {
#if !defined(NO_CONSISTENCY_CHECKS)
			/* Consistency check: Check alignment restrictions */
			if( ((ULONG)p2|(ULONG)p2->mc_Bytes)
			   & (MEMCHUNK_TOTAL-1) )
			    Alert(AN_MemCorrupt|AT_DeadEnd);
#endif
			/* Check if the current block is large enough */
			if(p2->mc_Bytes>=byteSize)
			{
			    /* It is. */
			    mc=p1;
			    /* Use this one if MEMF_REVERSE is not set.*/
			    if(!(requirements&MEMF_REVERSE))
				break;
			    /* Else continue - there may be more to come. */
			}

			/* Go to next block */
			p1=p2;
			p2=p1->mc_Next;

			/* Check if this was the end */
			if(p2==NULL)
			    break;
#if !defined(NO_CONSISTENCY_CHECKS)
			/*
			    Consistency check:
			    If the end of the last block+1 is bigger or equal to
			    the start of the current block something must be wrong.
			*/
			if((UBYTE *)p2<=(UBYTE *)p1+p1->mc_Bytes)
			    Alert(AN_MemCorrupt|AT_DeadEnd);
#endif
		    }
		    /* Something found? */
		    if(mc!=NULL)
		    {
			/*
			    Remember: if MEMF_REVERSE is set
			    p1 and p2 are now invalid.
			*/
			p1=mc;
			p2=p1->mc_Next;

			/* Remove the block from the list and return it. */
			if(p2->mc_Bytes==byteSize)
			{
			    /* Fits exactly. Just relink the list. */
			    p1->mc_Next=p2->mc_Next;
			    mc=p2;
			}else
			{
			    if(requirements&MEMF_REVERSE)
			    {
				/* Return the last bytes. */
				p1->mc_Next=p2;
				mc=(struct MemChunk *)((UBYTE *)p2+byteSize);
			    }else
			    {
				/* Return the first bytes. */
				p1->mc_Next=(struct MemChunk *)((UBYTE *)p2+byteSize);
				mc=p2;
			    }
			    p1=p1->mc_Next;
			    p1->mc_Next=p2->mc_Next;
			    p1->mc_Bytes=p2->mc_Bytes-byteSize;
			}
			mh->mh_Free-=byteSize;

			/* No need to forbid dispatching any longer. */
			Permit();
			if(requirements&MEMF_CLEAR)
			{
			    /* Clear memory. */
			    ULONG cnt,*p;

			    p=(ULONG *)mc;
			    cnt=byteSize/sizeof(ULONG);

			    while(cnt--)
				*p++=0;
			}
			return mc;
		    }
		}
	    }
	    /* Go to next memory header */
	    mh=(struct MemHeader *)mh->mh_Node.ln_Succ;
	}

	/* Is it forbidden to call low-memory handlers? */
	if(requirements&MEMF_NO_EXPUNGE)
	{
	    Permit();
	    return NULL;
	}

	/* All memory headers done. Check low memory handlers. */
	do
	{
	    /* Is there another one? */
	    if(lmh->is_Node.ln_Succ==NULL)
	    {
		/* No. return 'Not enough memory'. */
		Permit();
		return NULL;
	    }
	    /* Yes. Execute it. */
	    lmhr=__AROS_AC3(LONG,lmh->is_Code,
		__AROS_ACA(struct MemHandlerData *, &lmhd,        A0),
		__AROS_ACA(APTR,                    lmh->is_Data, A1),
		__AROS_ACA(struct ExecBase *,       SysBase,      A6));

	    /* Check returncode. */
	    if(lmhr==MEM_TRY_AGAIN)
	    {
		/* MemHandler said he did something. Try again. */
		/* Is there any program that depends on this flag??? */
		lmhd.memh_Flags|=MEMHF_RECYCLE;
		break;
	    }
	    /* Nothing more to expect from this handler. */
	    lmh=(struct Interrupt *)lmh->is_Node.ln_Succ;
	    lmhd.memh_Flags&=~MEMHF_RECYCLE;

	/*
	    If this handler did nothing at all there's no need
	    to try the allocation. Try the next handler immediately.
	*/
	}while(lmhr==MEM_DID_NOTHING);
    }
    __AROS_FUNC_EXIT
} /* AllocMem */

