/*
	SetCPU V1.60
	by Dave Haynie, April 13, 1990
	Released to the Public Domain

	MEMORY.C MODULE

	This module is responsible for ROM image allocation, MMU table
	allocation and creation, and other functions based on memory.
*/

#include "setcpu.h"

/* ====================================================================== */

/* Local data types */

struct range {
   ULONG first;
   ULONG last;
};

/* ====================================================================== */

/* This function copies from the source to the destination, by longword, with
   BYTE length "length". */

void MemCopy(src,des,len)
ULONG *src, *des;
ULONG len;
{
   len = (len + 3)>>2;
   while (len--) *des++ = *src++;
}

/* ====================================================================== */

/* This section contains the memory allocation code.  There are two
   problems here.  First of all, we'd like to use 32 bit FAST memory if 
   at all possible.  Next, block translation and page tables must be on at
   least a page sized boundary, if not a block boundary.  */
   
/* This routine finds the memory block for me to use in AllocAligned().
   It takes into account either A2620 or A2630 systems, where I can snoop 
   out the memory for that particular board, knowing it's the fastest.  I
   can adjust for 1.4's automatic memory merging in this case too, to be
   sure I have 32 bit RAM.  If I don't have one of my boards, I'll return 
   a pointer to the first non-$C00000 memory list marked as FAST. */

static struct range SRange = { 0L, 0L };
static ULONG MaxMem = 0;

LONG SmartlyGetRange() {
   struct ExecBase *eb = *((struct ExecBase **)4L);
   register struct MemHeader *mem;

   /* First try for either A2620 or A2630 */
   
   if (A26x0.Addr && A26x0.Size) {
      SRange.first = A26x0.Addr;
      SRange.last = A26x0.Size + SRange.first;
   } 

   /* For another critters, but we go here to find MaxMem, even
      if we know it's an A26x0. */

   for (mem = (struct MemHeader *)eb->MemList.lh_Head; mem->mh_Node.ln_Succ;
        mem = (struct MemHeader *)mem->mh_Node.ln_Succ) {
      if ((ULONG)(mem->mh_Upper) > MaxMem) MaxMem = (ULONG)mem->mh_Upper;
      if (mem->mh_Attributes & MEMF_CHIP) continue;
      if (((ULONG)mem >= 0xc00000 && (ULONG)mem <= 0xc80000)) continue;
      if (!SRange.first) {
         SRange.first = (ULONG)mem->mh_Lower;
         SRange.last = (ULONG)mem->mh_Upper;
      }
   }
   if (SRange.first) 
      return TRUE;
   return FALSE;
}

/* This routine allocates such an aligned block of memory from a specified 
   memory list. */
   
void *AllocAligned(size,bound)
register ULONG size;
register ULONG bound;
{
   register ULONG target;
   void *mem = NULL;

   Forbid();
   if (!allochead) {
      target = (SRange.last-size) & ~(bound-1);
      while (target > SRange.first && !(mem = AllocAbs(size,target)))
         target -= bound;
      SRange.last = (ULONG)mem;
   } else {
      target = (SRange.first+size+bound-1) & ~(bound-1);
      while (target < SRange.last && !(mem = AllocAbs(size,target)))
         target += bound;
      SRange.first = (ULONG)mem+size;
   }
   Permit();
   return mem;
}

/* This routine finds the memory wrap and appropriate MMU table size for
   the given configuration. It requires the value of MaxMem to have been
   already calculated. */
   
void FindWrap(tag)
struct systag *tag;
{
   ULONG test;
     
   if (forcewrap == -1) {
      tag->wrapdown = 0;
      for (test = MaxMem; !(test & 0x80000000) && tag->wrapdown < 8; test <<= 1)
         tag->wrapdown++;
   } else
      tag->wrapdown = forcewrap;
   tag->tablesize = (128 << (8 - tag->wrapdown)) * sizeof(ULONG);
}

/* This routine computes the ROM size from the magic tag values. */

ULONG CalcROMSize(tagval)
ULONG tagval;
{
   if (tagval == MAGIC_256) return SMALLROMSIZE;
   if (tagval == MAGIC_512) return BIGROMSIZE;
   return 0L;
}

/* This function sizes the ROM based on its base value and correctly splits 
 a base address between possible ROM halves. This routine uses the Commodore
   ROM file format, which is like this:

	0:	00000000
	4:	size
	8:	start of ROM

   The ROM, in any case, begins with either "$11114ef9", for 256L ROMs, or
   "$11144ef9", for 512K ROMs.  The next longword can be used to figure out
   where the ROM actually goes, in memory. */

ULONG *SizeROM(tag,base,getram)
struct systag *tag;
ULONG *base, getram;
{
   ULONG *rom = NULL;

   if (!(tag->romsize = CalcROMSize(base[0]))) return NULL;

   if (getram) 
      if (!(rom = (ULONG *)AllocAligned(tag->romsize,ROMROUND))) return NULL;

   if (tag->romsize == SMALLROMSIZE) {
      tag->romstart = base[1];
      tag->romloc = base[1] & 0xfffc0000;
      tag->romlo = 0L;
      tag->romhi = rom;
   } else {
      tag->romstart = base[1];
      tag->romloc = base[1] & 0xfff80000;
      tag->romlo = rom;
      tag->romhi = (ULONG *)((ULONG)rom + SMALLROMSIZE);
   }
   return rom;
}

/* ====================================================================== */

/* This section contains routines that manage different ROM image types. */

/* This function gets an aligned ROM image copied from system ROM. */

struct systag *AllocROMImage(type)
UWORD type;
{
   struct systag *tag = NULL, *oldtag = NULL;
   ULONG *rom = NULL, *table = NULL, *base;
   
  /* Let's make the allocations.  I allocate the ROM first, then the table,
     then the tag; since we're coming from the end of memory, this should
     result in the least fragging. */

   SmartlyGetRange();
   if (!(tag = AllocAligned(SizeOf(struct systag),8L))) goto fail;
   if (oldtag = GetSysTag())
      base = (ULONG *)oldtag->romloc;
   else {
      if (*(base = (ULONG *)0x00f80000) != MAGIC_512)
         base = (ULONG *)0x00fc0000;
   }
   rom = SizeROM(tag,base,TRUE);
   FindWrap(tag);
   if (!(table = AllocAligned(tag->tablesize+4,TABROUND))) goto fail;
   tag->maintable = table;
   tag->romtype = type;
   MemCopy(tag->romloc,rom,tag->romsize);
   FillBasicTable(tag,FALSE);
   return tag;

fail:
   if (rom)   FreeMem(rom,tag->romsize);
   if (table) FreeMem(table,tag->tablesize+4);
   if (tag)   FreeMem(tag,SizeOf(struct systag));
   return NULL;
}

/* This function gets an aligned, reset-safe image in either $00C00000 or 
   CHIP memory, copies the ROM code from the passed temporary image, and 
   then sets up it's MMU table such that the memory used for the safe image
   will be missed by the Amiga's memory-sizing logic on reboot. */

struct systag *AllocSAFEImage(temp)
struct systag *temp;
{
   struct ExecBase *eb = *((struct ExecBase **)4L);
   struct MemHeader *safe, *safeC000 = NULL, *safeCHIP = NULL, *tmem;
   struct systag *tag;
   ULONG upper, base, *table;

   for (safe = (struct MemHeader *)eb->MemList.lh_Head; safe->mh_Node.ln_Succ; 
        safe = (struct MemHeader *)safe->mh_Node.ln_Succ) {
      tmem = (struct MemHeader *)safe;
      if ((ULONG)(tmem->mh_Upper) > MaxMem) MaxMem = (ULONG)tmem->mh_Upper;      
      if (tmem->mh_Attributes & MEMF_CHIP) {
         if (!safeCHIP || safeCHIP->mh_Upper < tmem->mh_Upper)
            safeCHIP = tmem;
      } else if ((ULONG)safe >= 0xc00000 && (ULONG)safe <= 0xc80000) {
         if (!safeC000 || safeC000->mh_Upper < tmem->mh_Upper)
            safeC000 = tmem;
      }
   }

  /* Will it fit?  You need at least 1 meg of memory. */

   if (!safeC000 && safeCHIP->mh_Upper < 0x080000L) return NULL;

  /* Now, where should it go. */

   if (safeC000)
      upper = ((ULONG)safeC000->mh_Upper+ROMROUND-1L) & ~(ROMROUND-1L);
   else if (safeCHIP)
      upper = ((ULONG)safeCHIP->mh_Upper+ROMROUND-1L) & ~(ROMROUND-1L);

   FindWrap(temp);
   table = (ULONG *)(base = upper-ROMROUND);
   tag = (struct systag *)(base = (base + temp->tablesize + 36L) & ~7L);
   *tag = *temp;
   tag->maintable = table;
   
   if (temp->romlo) {
      tag->romlo = (ULONG *)(upper - SMALLROMSIZE - ROMROUND);
      if (safeC000) {
         upper = ((ULONG)safeCHIP->mh_Upper+ROMROUND-1L) & ~(ROMROUND-1L);
         tag->romhi = (ULONG *)(upper - SMALLROMSIZE);
      } else
         tag->romhi = (ULONG *)(upper - SMALLROMSIZE*2 - ROMROUND);
   } else {
      tag->romhi = (ULONG *)(upper - temp->romsize - ROMROUND);
      tag->romlo = 0L;
   }

  /* Other tag initializations. */

   tag->romtype = ROM_KICK;
   FillBasicTable(tag,TRUE);

   base = (base + SizeOf(struct systag) + 32L) & ~7L;

   tag->BerrHandler = (char *)(base = (base + SizeOf(struct systag)+32L) & ~7L);
   tag->BerrSize = (BerrCodeSize + 4) & ~3L;

   if (tag->romlo) MemCopy(temp->romlo,tag->romlo,SMALLROMSIZE);
   MemCopy(temp->romhi,tag->romhi,SMALLROMSIZE);

   tag->ResetCode = (char *)(base = (base + BerrCodeSize + 32L) & ~7L);
   tag->ResetSize = BerrCodeSize;
   MemCopy(BootCode,tag->ResetCode,BootCodeSize);

   return tag;
}

/* This function returns memory to the system, automatically setting the
   appropriate memory flags. */

void ReturnMem(size,mem)
ULONG size;
ULONG mem;
{
   if (mem < 0x00200000)
      AddMemList(size,MEMF_CHIP|MEMF_PUBLIC,-15L,(char *)mem,NULL);
   else if (mem >= 0x00c00000 && mem < 0x00c80000)
      AddMemList(size,MEMF_FAST|MEMF_PUBLIC,-15L,(char *)mem,NULL);
   else
      AddMemList(size,MEMF_FAST|MEMF_PUBLIC,0L,(char *)mem,NULL);
}

/* This function can be used after rebooting to remove the specially allocated
   SAFE image.  This is normally used after copying the safe image into a 
   standard FASTROM image and adjusting the MMU accordingly.  The SAFE RAM
   at this point isn't in any memory list, so we aren't really freeing it,
   just linking it into a list. */

void FreeSAFEImage(kick)
struct systag *kick;
{
   if (kick->romlo == 0 || (ULONG)kick->romhi+SMALLROMSIZE == (ULONG)kick->romlo)
      ReturnMem(ROMROUND+kick->romsize,kick->romhi);
   else {
      ReturnMem(ROMROUND+SMALLROMSIZE,kick->romlo);
      ReturnMem(SMALLROMSIZE,kick->romhi);
   }
}

/* ====================================================================== */

/* This routine gets the system tag from the patched system, if it's
   there.  This tag is stored in an invalid table entry that's within the
   physical ROM image.  If the MMU isn't on, there's no system tag, by
   definition.  This has been enhanced to perform a sanity check on the
   tag encountered -- the systag contains a pointer to the table, this
   can be checked. */
   
struct systag *GetSysTag() {
   struct systag *maybetag;
   ULONG i, myCRP[2], *table = NULL,size;
   APTR oldTrap;
   struct Task *task;

   if (cpu == 68040 || !(GetTC() & TC_ENB) || aliens) return NULL;

   GetCRP(myCRP);
   table = (ULONG *)myCRP[1];

  /* In case the MMU is alien and has some of this memory read protected... */
   task = FindTask(0L);
   oldTrap = task->tc_TrapCode;
   task->tc_TrapCode = (APTR)BerrCode;

  /* The tag is now in an easier-to-locate place. This isn't SetCPU V1.5
     compatible; SetCPU V1.5 FASTROM will appear alien to SetCPU V1.6. But
     this is much better for modern systems and modern software. */
   size = (myCRP[0]>>16)+1;
   maybetag = (struct systag *)IV_ADDR(table[size]);
   if (!maybetag || maybetag->maintable != table || maybetag->tablesize != size<<2) 
      maybetag = NULL;

  /* Restore the trap vector */
   task->tc_TrapCode = oldTrap;
   return maybetag;
}

