/* BlizKick EXTRES allocation API functions.

   Written by Harry "Piru" Sintonen, Jan 2000.
   Public Domain.

*/

#include "bkapi.h"

#define __USE_SYSBASE 1

#include <proto/exec.h>
#include <proto/dos.h>


/****** bkapi.o/--background-- ***********************************************
*
*   PURPOSE
*	If you don't know what BlizKick is get util/boot/BlizKick.lha
*	from aminet and read the documentation of it.
*
*	The BlizKick EXTRES buffer is a finite space where BlizKick stores
*	resident tags. This area is magically added to Kickstart ROM's
*	internal list of memory areas to scan for resident tags. As a
*	result BlizKick does not need to use unreliable exec.library
*	KickMem & KickPtr vectors.
*
*	This package provides easy (as easy as it can get) access to this
*	BlizKick EXTRES buffer.
*
*	First application to think of, would be ROM Update software that
*	could easily kick in new resident module by just calling
*	er_allocmem and copying resident tag to memory area returned. On
*	next reboot this new resident tag would be activated.
*
*	To get things even easier, one can use InternalLoadSeg with
*	er_allocmem as allocfunc, er_free as freefunc and dos Read() as
*	readfunc. Then just LoadSeg any library / device to make it
*	resident. Relocs and such get handled autogically! (however most
*	disk based libraries would not survive, namely residenttag ln_Pri
*	is wrong.)
*
*	Don't expect this API to stay unchanged. There will be some sort
*	of MMU protection for EXTRES buffer some day... I think. However
*	programs respecting ERH_API_V1 will probably recompile out of the
*	box in the future too.
*
*   HISTORY
*	1.0.1 - 17th Jan 2000, second release. fixed a typo.
*	1.0.0 - 15th Jan 2000, first release.
*
*   LEGAL
*	Written by Harry "Piru" Sintonen, Jan 2000.
*	bkapi package is public domain.
*
******************************************************************************
*
*/

struct bkerhss *glob_ss = NULL;


/*#define BKDEBUG*/

#ifdef BKDEBUG
#  define D(a) (a);
#else
#  define D(a) ;
#endif


/****** bkapi.o/er_init ******************************************************
*
*   NAME
*	er_init -- initialize EXTRES buffer use
*
*   SYNOPSIS
*	version = er_init()
*	D0
*
*	ULONG er_init();
*
*   FUNCTION
*	Initializes use of EXTRES buffer memory pool of BlizKick. This
*	routine must be called before using any of the other functions.
*
*   RESULT
*	version - version number of EXTRES buf API available or zero
*	    if EXTRES buf could not be found (ie. BlizKick not run).
*	    Currently ERH_API_V1.
*
*   SEE ALSO
*
******************************************************************************
*
*/

ULONG er_init(void) {
  struct bkerhss *ss;

  if (!glob_ss) {

    Forbid();
    ss = (struct bkerhss *) FindSemaphore("EXTRES Handler");
    Permit();

    if (ss) {

      switch (ss->version) {

        case ERH_API_V1:
          glob_ss = ss;
          break;

        /*
        case ERH_API_V2:
          glob_ss = ss;
          ... could do some version dependant check / init here ...
          break;
        */
      }

      /* force sanity check */
      er_availmem(MEMF_LARGEST);

    }
  }
  return glob_ss ? glob_ss->version : 0;
}


/****** bkapi.o/er_alloc *****************************************************
*
*   NAME
*	er_alloc -- allocate bytesize bytes from EXTRES memory pool
*
*   SYNOPSIS
*	memoryblock = er_alloc(bytesize)
*	D0		       D0
*
*	void *er_alloc(ULONG);
*
*   FUNCTION
*	Allocates memory from EXTRES buffer memory pool of BlizKick. This
*	memory area is specific in a way that resident tags will be scanned
*	from this area on system boot up.
*
*	This means easy addition of system transparent resident tags.
*	System transparent means no kickmem or kicktag pointers will be
*	used.
*
*   INPUTS
*	bytesize - the size of the desired block in bytes.  (will
*	    automatically round this number to a multiple of the
*	    system memory chunk size)
*
*   RESULT
*	memoryblock - a pointer to the newly allocated memory block.
*	    If there are no free memory regions large enough to satisfy
*	    the request, zero will be returned.  The pointer must be
*	    The memory block returned is quad word aligned.
*
*   WARNING
*	The result of any memory allocation MUST be checked, and a viable
*	error handling path taken. ANY allocation may fail if memory has
*	been filled.
*
*   NOTE
*	Allocation *will* fail if BlizKick is not run.
*
*   SEE ALSO
*	er_allocmem, er_free, exec.library/Allocate
*
******************************************************************************
*
*/

void * ASM er_alloc(REG(d0,ULONG bytesize)) {
  void *ret = NULL;

  if (glob_ss) {

    ObtainSemaphore(&glob_ss->ss);
    ret = Allocate(glob_ss->mh, bytesize);
    ReleaseSemaphore(&glob_ss->ss);
  }

  if (!ret) SetIoErr(ERROR_NO_FREE_STORE);
  return ret;
}


/****** bkapi.o/er_allocmem **************************************************
*
*   NAME
*	er_allocmem -- AllocMem wrapper for er_alloc
*
*   SYNOPSIS
*	memoryblock = er_allocmem(bytesize, attributes)
*	D0			  D0	    D1
*
*	void *er_allocmem(ULONG, ULONG);
*
*   FUNCTION
*	Allocates memory from EXTRES buffer memory pool of BlizKick. This
*	memory area is specific in a way that resident tags will be scanned
*	from this area on system boot up.
*
*	This means easy addition of system transparent resident tags.
*	System transparent means no kickmem or kicktag pointers will be
*	used.
*
*	This function is quite similar to er_alloc, the only difference
*	is that er_allocmem supports MEMF_CLEAR.
*
*	If MEMF_CHIP is specified this function will fail.
*
*   INPUTS
*	bytesize - the size of the desired block in bytes.  (will
*	    automatically round this number to a multiple of the
*	    system memory chunk size)
*
*	attributes -
*	    requirements
*
*		MEMF_CHIP:	Will cause the allocation to fail. EXTRES
*				buffer memory is in fastmem.
*
*	    options
*
*		MEMF_CLEAR:	The memory will be initialized to all
*				zeros.
*
*   RESULT
*	memoryBlock - a pointer to the newly allocated memory block.
*	    If there are no free memory regions large enough to satisfy
*	    the request, zero will be returned. The pointer must be
*	    checked for zero before the memory block may be used!
*	    The memory block returned is quad word aligned.
*
*   WARNING
*	The result of any memory allocation MUST be checked, and a viable
*	error handling path taken. ANY allocation may fail if memory has
*	been filled.
*
*   NOTE
*	Allocation *will* fail if BlizKick is not run.
*
*	MEMF_CLEAR is the only flag supported! Other flags will be
*	silentry ignored, except MEMF_CHIP that will cause allocation to
*	fail.
*
*	This function is provided for completeness and also for use as
*	dos.library/InternalLoadSeg() allocfunc.
*
*   SEE ALSO
*	er_alloc, er_free, exec.library/AllocMem, exec/memory.h
*
******************************************************************************
*
*/

void * __saveds ASM er_allocmem(REG(d0,ULONG bytesize), REG(d1,ULONG attributes)) {
  void *ret = NULL;
  ULONG *clearptr;
  
  if ( (bytesize==0) || (attributes & MEMF_CHIP) ) return NULL;

  if (glob_ss) {

    ObtainSemaphore(&glob_ss->ss);

    if ( (ret = Allocate(glob_ss->mh, bytesize)) ) {

      D(Printf("er_allocmem(%ld) = $%lx\n",
               bytesize,(ULONG) ret));

      /* actually the following could clear a bit too much, but it
         doesn't really matter. should clear only bytesize bytes. */

      if (attributes & MEMF_CLEAR) {
        clearptr = (ULONG *) ret;
        bytesize = (bytesize + MEM_BLOCKMASK) & -MEM_BLOCKSIZE;

        D(Printf("er_allocmem: clearing %ld bytes from $%lx\n",
                 bytesize,(ULONG) clearptr));

        for ( ; bytesize; bytesize -= 8) {
          *clearptr++ = 0;
          *clearptr++ = 0;
        }
      }
    }

    ReleaseSemaphore(&glob_ss->ss);
  }

  if (!ret) SetIoErr(ERROR_NO_FREE_STORE);

  return ret;
}


/****** bkapi.o/er_free ******************************************************
*
*   NAME
*	er_free -- deallocate memory from EXTRES memory pool
*
*   SYNOPSIS
*	er_free(memoryblock, bytesize)
*		A1	     D0
*
*	void er_free(void *, ULONG);
*
*   FUNCTION
*	Erase and free a region of memory, returning it to the EXTRES
*	memory pool.
*
*   INPUTS
*	memoryblock - pointer to the memory block to free
*	bytesize - the size of the desired block in bytes. (will
*	    automatically round this number to a multiple of the
*	    system memory chunk size)
*
*   NOTE
*	If a block of memory is freed twice, the system will Guru. The
*	Alert is AN_FreeTwice ($01000009). If you pass the wrong pointer,
*	you will probably see AN_MemCorrupt $01000005.
*
*	Will also fill the memory area to be released with all ones
*	before releasing. This ensures no partial resident tag will
*	remain in unallocated memory.
*
*	Both memory allocated by er_alloc and er_allocmem must be released
*	with this function!
*
*   SEE ALSO
*	er_alloc, er_allocmem, exec.library/Allocate
*
******************************************************************************
*
*/

void __saveds ASM er_free(REG(a1,void *memblock), REG(d0,ULONG bytesize)) {
  ULONG *fillptr; ULONG fillsize;

  if ( (bytesize==NULL) || (memblock==NULL) ) return;

  if (glob_ss) {

    /* actually the following could fill a bit too much, but it
       doesn't really matter. should fill only bytesize bytes. */

    fillptr = (ULONG *) ((ULONG)memblock & -MEM_BLOCKSIZE);
    fillsize = (bytesize + MEM_BLOCKMASK +
               ((ULONG) fillptr - (ULONG) memblock)) &
               -MEM_BLOCKSIZE;

    /* some sanity checking - note that doesn't require ObtainSemaphore
       because only mh_Lower and mh_Upper are read (both are static) */

    if ( ((ULONG) fillptr < (ULONG) glob_ss->mh->mh_Lower) ||
         (((ULONG)fillptr + fillsize) > (ULONG) glob_ss->mh->mh_Upper) ) {

       D(Printf("er_free: *** illegal arguments ***!\n"));
       return;
    }

    D(Printf("er_free, memblock: $%lx bytesize: %ld fillptr: $%lx fillsize: %ld\n",
             (ULONG) memblock, bytesize, (ULONG) fillptr, fillsize));

    ObtainSemaphore(&glob_ss->ss);

    for ( ; fillsize; fillsize -= 8) {
      *fillptr++ = 0xffffffff;
      *fillptr++ = 0xffffffff;
    }

    Deallocate(glob_ss->mh, memblock, bytesize);
    ReleaseSemaphore(&glob_ss->ss);

  }
}


/****** bkapi.o/er_allocvec **************************************************
*
*   NAME
*	er_allocvec -- allocate EXTRES memory and keep track of the size
*
*   SYNOPSIS
*	memoryblock = er_allocvec(bytesize, attributes)
*	D0			  D0	    D1
*
*	void *er_allocvec(ULONG, ULONG);
*
*   FUNCTION
*	This function works similar to er_allocmem(), but tracks the size
*	of the allocation.
*
*	See the er_allocmem() documentation for details.
*
*   RESULT
*	memoryBlock - a pointer to the newly allocated memory block.
*	    If there are no free memory regions large enough to satisfy
*	    the request, zero will be returned. The pointer must be
*	    checked for zero before the memory block may be used!
*	    The memory block returned is *long* word aligned.
*
*   WARNING
*	The result of any memory allocation MUST be checked, and a viable
*	error handling path taken. ANY allocation may fail if memory has
*	been filled.
*
*   SEE ALSO
*	er_allocmem, er_freevec, exec.library/AllocVec
*
******************************************************************************
*
*/

void * ASM er_allocvec(REG(d0,ULONG bytesize), REG(d1,ULONG attributes)) {
  void *ret;

  bytesize += 4;
  if ( (ret = er_allocmem(bytesize, attributes)) ) {
    *(ULONG *)ret = bytesize;
    return (void *) ((ULONG) ret + 4);
  }

  return NULL;
}


/****** bkapi.o/er_freevec ***************************************************
*
*   NAME
*	er_freevec -- free er_allocvec() EXTRES memory
*
*   SYNOPSIS
*	er_freevec(memoryblock)
*		   A1
*
*	void er_freevec(void *);
*
*   FUNCTION
*	Free an allocation made by the er_allocvec() call.
*
*   NOTE
*	If a block of memory is freed twice, the system will Guru. The
*	Alert is AN_FreeTwice ($01000009). If you pass the wrong pointer,
*	you will probably see AN_MemCorrupt $01000005.
*
*   INPUTS
*	memoryblock - pointer to the memory block to free, or NULL.
*
*   SEE ALSO
*	er_allocvec, exec.library/FreeVec
*
******************************************************************************
*
*/

void ASM er_freevec(REG(a1,void *memoryblock)) {
  ULONG size;
  if (memoryblock) {
    memoryblock = (void *) ((ULONG) memoryblock - 4);
    size = *((ULONG *) memoryblock);
    er_free( memoryblock, size);
  }
}


/****** bkapi.o/er_lock ******************************************************
*
*   NAME
*	er_lock -- lock EXTRES buffer memory
*
*   SYNOPSIS
*	er_lock()
*
*	void er_lock(void);
*
*   FUNCTION
*	Locks access to EXTRES buffer memory for this task only.
*
*   WARNING
*	Be *very* careful not to keep the lock if another process you
*	depend/wait will try to er_lock() simultanously!
*
*   NOTE
*	You *must* er_lock() before you read/write EXTRES buffer memory
*	area. Call er_unlock() when done tempering with it.
*
*	er_lock() and er_unlock() nest.
*
*   SEE ALSO
*	er_unlock, er_getarea
*
******************************************************************************
*
*/

void ASM er_lock(void) {
  if (glob_ss) {

    ObtainSemaphore(&glob_ss->ss);

  }
}


/****** bkapi.o/er_unlock ****************************************************
*
*   NAME
*	er_unlock -- unlock EXTRES buffer memory
*
*   SYNOPSIS
*	er_unlock()
*
*	void er_unlock(void);
*
*   FUNCTION
*	Unlock access to EXTRES buffer memory. Other tasks may bid for
*	access now.
*
*   WARNING
*	Be *very* careful not to keep the lock if another process you
*	depend/wait will try to er_lock() simultanously!
*
*   NOTE
*	You *must* er_lock() before you read/write EXTRES buffer memory
*	area. Call er_unlock() when done tempering with it.
*
*	er_lock() and er_unlock() nest.
*
*   SEE ALSO
*	er_lock, er_getarea
*
******************************************************************************
*
*/

void ASM er_unlock(void) {
  if (glob_ss) {

    ReleaseSemaphore(&glob_ss->ss);

  }
}


/****** bkapi.o/er_getarea ***************************************************
*
*   NAME
*	er_getarea -- get EXTRES resident module area
*
*   SYNOPSIS
*	start = er_getarea(len_ptr)
*	D0		   A0
*
*	void *er_getarea(ULONG *);
*
*   FUNCTION
*	Return lower and upper bound of memory area covered by EXTRES
*	buffer.
*
*   INPUTS
*	len_ptr - pointer to ULONG to put area lenght to.
*
*   RESULT
*	start - pointer to start of resident module area. If there's no
*	    BlizKick EXTRES buffer available will be zero.
*
*   NOTE
*	You *must* er_lock() before you read/write EXTRES buffer memory
*	area. Call er_unlock() when done tempering with it.
*
*	Will return zero if BlizKick is not run.
*
*   SEE ALSO
*	er_lock, er_unlock
*
******************************************************************************
*
*/

ULONG ASM er_getarea(REG(a0,ULONG *len_ptr)) {
  ULONG ret = NULL;

  if (glob_ss) {
    ObtainSemaphore(&glob_ss->ss);

    ret = (ULONG) glob_ss->mh->mh_Lower;
    *len_ptr = (ULONG) glob_ss->mh->mh_Upper - ret;

    ReleaseSemaphore(&glob_ss->ss);
  }
  return ret;
}


/****** bkapi.o/er_availmem **************************************************
*
*   NAME
*	er_availmem -- return EXTRES buffer memory available
*
*   SYNOPSIS
*	size = er_availmem(requirements)
*	D0		   D1
*
*	ULONG er_availmem(ULONG);
*
*   FUNCTION
*	This function returns the amount of free EXTRES buffer memory.
*
*	To find out what the largest block is, specify MEMF_LARGEST in
*	attributes argument.
*
*   WARNING
*	Due to the effect of multitasking, the value returned may not
*	actually be the amount of free memory available at that instant.
*	However if you have locked memory with er_lock() before, the
*	result is exact.
*
*   INPUTS
*	requirements - MEMF_LARGEST results calculation of the size of
*	    the largest block.
*
*   RESULT
*	size - total free space remaining (or the largest free block).
*
*   NOTE
*	er_availmem(MEMF_LARGEST) does a consistency check on the
*	memory list. In case of an error 0xFFFFFFFF is returned, and
*	further allocations are impossible. If memory header itself is
*	bad will alert with BKA_MemoryInsane.
*
*   SEE ALSO
*	exec.library/AvailMem, exec/memory.h
*
******************************************************************************
*
*/

ULONG ASM er_availmem(REG(d1,ULONG attributes)) {
  struct MemHeader *mh;
  struct MemChunk *mc, *predmc;
  ULONG ret = NULL, free = 0, failed = 0;

  if (glob_ss) {
    ObtainSemaphore(&glob_ss->ss);

    if (attributes & MEMF_LARGEST) {

      mh = glob_ss->mh;

      if ( (mh->mh_Node.ln_Succ == NULL) &&
           (mh->mh_Node.ln_Pred == NULL) &&
           (mh->mh_Node.ln_Type == NT_MEMORY) &&
           (((ULONG) mh->mh_Lower & MEM_BLOCKMASK) == 0) &&
           (((ULONG) mh->mh_Upper & MEM_BLOCKMASK) == 0) &&
           (mh->mh_Lower < mh->mh_Upper)
         ) {

        mc = mh->mh_First;
        predmc = (struct MemChunk *) ((ULONG) mc - 1);

        while (mc && (!failed) ) {
          if ( ( ((ULONG) mc & MEM_BLOCKMASK) == 0) &&
               ((ULONG) mc > (ULONG) predmc) &&
               ((ULONG) mc <= ((ULONG) mh->mh_Upper - MEM_BLOCKSIZE))
             ) {

            if ( (mc->mc_Bytes & MEM_BLOCKMASK) == 0) {

              free += mc->mc_Bytes;
              if (mc->mc_Bytes > ret) ret = mc->mc_Bytes;

            } else failed = 1;

            predmc = mc;
            mc = mc->mc_Next;

          } else failed = 1;

        }
      } else {
        failed = 2;
      }

      if ( (!failed) && (free != mh->mh_Free) ) failed = 1;

      switch (failed) {
        case 2:
          /* memheader screwed up!! */
          D(Printf("er_availmem(largest): *** memheader fucked ***!!\n"));
          Alert(BKA_MemoryInsane);
          /* fall thru */
        case 1:
          D(Printf("er_availmem(largest): freelist fucked!\n"));
          mh->mh_First = NULL;
          mh->mh_Free = 0;
          ret = 0xFFFFFFFF;
          break;
      }

    } else {
      ret = glob_ss->mh->mh_Free;
    }

    ReleaseSemaphore(&glob_ss->ss);
  }
  return ret;
}

/****** bkapi.o/er_nextresident **********************************************
*
*   NAME
*	er_nextresident -- find first/next resident tag from EXTRES buffer
*
*   SYNOPSIS
*	nextresident = er_nextresident(oldresident)
*	D0			       A0
*
*	struct Resident *er_nextresident(struct Resident *);
*
*   FUNCTION
*	Find first or next Resident tag from EXTRES buffer memory.
*
*   WARNING
*	You *must* have er_lock() on memory before calling this routine!
*	Keep the lock until you're done accessing the resident tag(s).
*
*   INPUTS
*	oldresident - startpoint for search, will not find this
*	    particular resident but next. Pass NULL to find first
*	    resident.
*
*   RESULT
*	nextresident - pointer to resident tag structure or zero if no
*	    more resident tags could be found.
*
*   EXAMPLE
*
*	struct Resident *res = NULL;
*
*	er_lock();
*	while ( (res = er_nextresident(res)) ) {
*	  \* do something with this Resident tag *\
*	}
*	er_unlock();
*
*   SEE ALSO
*	er_findresident
*
******************************************************************************
*
*/

struct Resident * ASM er_nextresident(REG(a0,struct Resident *oldresident)) {
  UWORD *wpt;
  LONG numw;

  if (glob_ss) {

    if (oldresident) {

      /* sanitycheck input */

      if ( ((ULONG) oldresident < (ULONG) glob_ss->mh->mh_Lower) ||
           (((ULONG) oldresident + sizeof(struct Resident)) >=
           (ULONG) glob_ss->mh->mh_Upper) ) {
         return NULL;
      }

      if ( (oldresident->rt_MatchWord == RTC_MATCHWORD) &&
           (oldresident->rt_MatchTag == oldresident) ) {

        wpt = (UWORD *) oldresident->rt_EndSkip;

      } else {

        wpt = (UWORD *) (((ULONG) oldresident + 3) & -2);

      }

    } else {
      wpt = (UWORD *) glob_ss->mh->mh_Lower;
    }

    /* more sanity checking */

    if ( ((ULONG) wpt < (ULONG) glob_ss->mh->mh_Lower) ||
         (((ULONG) wpt + sizeof(struct Resident)) >=
         (ULONG) glob_ss->mh->mh_Upper) ) {
       return NULL;
    }

    numw = (((ULONG) glob_ss->mh->mh_Upper
             - sizeof(struct Resident)
             - (ULONG) wpt) ) >> 1;

    if (numw > 0) {
      for ( ; ; ) {
        while (--numw && (*wpt++ != RTC_MATCHWORD));
        if (!numw) return NULL;
        if ( *(ULONG *) wpt == ((ULONG) wpt - 2) ) {
          return (struct Resident *) ((ULONG) wpt - 2);
        }
      }
    }
  }

  return NULL;
}


/****** bkapi.o/er_findresident **********************************************
*
*   NAME
*	er_findresident -- find resident tag from EXTRES buffer by name
*
*   SYNOPSIS
*	resident = er_findresident(name)
*	D0			   A1
*
*	struct Resident *er_findresident(STRPTR);
*
*   FUNCTION
*	Scan EXTRES buffer memory for Resident tag with given name.
*
*   WARNING
*	You must have er_lock() on memory before calling this routine
*	if you intend to access the resident tag found! Keep the lock
*	until you're done accessing the Resident tag.
*
*   INPUTS
*	name - pointer to name string
*
*   RESULT
*	resident - pointer to the resident tag structure or
*	    zero if none found.
*
*   EXAMPLE
*
*	struct Resident *res = NULL;
*
*	er_lock();
*	if (res = er_findresident("EXTRES Handler")) {
*	  \* do something with the resident *\
*
*	  \* keep the lock until done! *\
*	}
*	er_unlock();
*
*   NOTE
*	exec.library/FindResident() will only find the currently *active*
*	resident tags in EXTRES buffer memory. Also if resident tag with
*	same name is found from both the ROM and EXTRES memory, the one
*	having newer rt_Version will be activated on boot, and thus
*	exec.library/FindResident() will find that particular resident.
*
*   SEE ALSO
*	er_nextresident, exec.library/FindResident, exec/resident.h
*
******************************************************************************
*
*/

struct Resident * ASM er_findresident(REG(a1,STRPTR name)) {
  struct Resident *ret = NULL;
  STRPTR s1, s2;

  if (glob_ss) {
    er_lock();
    while ( (ret = er_nextresident(ret)) ) {
      for ( s1 = name, s2 = ret->rt_Name; *s1 && (*s1 == *s2) ; s1++, s2++ );
      if (*s1 == *s2) {
        er_unlock();
        return ret;
      }
    }
    er_unlock();
  }

  return NULL;
}
