////////////////////////////////////////////////////////////////////////////////
// CPU16x4k.cpp -- this file is part of the Emulator Developers Kit
// available at http://ourworld.compuserve.com/homepages/pc64/develop.htm

RegisterPersistentClass(CPU16x4k);


////////////////////////////////////////////////////////////////////////////////
// bit masks for 1 to 16 chunks

static const int gaiMask[16] = {
  0x0001, 0x0003, 0x0007, 0x000F,
  0x001F, 0x003F, 0x007F, 0x00FF,
  0x01FF, 0x03FF, 0x07FF, 0x0FFF,
  0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
};


////////////////////////////////////////////////////////////////////////////////
// helper function to initialize memory

static byte* InitMem(byte* pbMem, int iSize) {
  memset(pbMem, 0, iSize);
  memset(pbMem + 65536, 0, iSize);
  for (int i = 0; i < iSize; i += 4096) {
    pbMem[65536 + i] = 1;
  }
  return pbMem;
}


////////////////////////////////////////////////////////////////////////////////
// allocate emulator memory in 4k chunks, maximum 64k

global byte* CPU16x4k::AllocMem(int iSize) {

  // verify parameters
  assert(iSize > 0);
  assert(iSize <= 65536);
  assert((iSize & 4095) == 0);

  // search for an existing block with enough free space
  int iIndex = iSize / 4096 - 1;
  MCB* p = pMCBRoot;
  while (p != NULL) {
    int iMask = gaiMask[iIndex];
    for (int i = iIndex; i < 16; i++) {
      if ((p->iUsedMask & iMask) == 0) {
        p->iUsedMask |= iMask;
        return InitMem(p->pbMem + (i - iIndex) * 4096, iSize);
      }
      iMask <<= 1;
    }
    p = p->pNext;
  }

  // no space, allocate a new block
  p = (MCB*)MemAlloc(sizeof MCB);
  p->pbMem = MemAlloc(65536 * 2 + 1);
  p->pbMem[65536 * 2] = 1;
  p->iUsedMask = gaiMask[iIndex];
  p->pNext = pMCBRoot;
  pMCBRoot = p;
  return InitMem(p->pbMem, iSize);
}


////////////////////////////////////////////////////////////////////////////////
// free emulator memory in 4k chunks, maximum 64k at once

global void CPU16x4k::FreeMem(byte* pbMem, int iSize) {

  // verify parameters
  assert(pbMem != NULL);
  assert(iSize > 0);
  assert(iSize <= 65536);
  assert((iSize & 4095) == 0);

  // find MCB and free requested chunks
  for (MCB* p = pMCBRoot; p != NULL; p = p->pNext) {
    int iIndex = pbMem - p->pbMem;
    if (iIndex >= 0 && iIndex < 65536) {
      assert((iIndex & 4095) == 0);
      assert(iIndex + iSize <= 65536);
      int iMask = gaiMask[(iSize / 4096) - 1] << (iIndex / 4096);
      assert((p->iUsedMask & iMask) == iMask);
      p->iUsedMask &= ~iMask;
      return;
    }
  }

  // pbMem parameter was invalid
  assert(0);
}


////////////////////////////////////////////////////////////////////////////////
// dummies for unused registers

global byte CPU16x4k::ReadNoReg() {
  return 0xFF;
}
global void CPU16x4k::WriteNoReg(byte) {
}


////////////////////////////////////////////////////////////////////////////////
// allocate traps for registers and breakpoints

global byte CPU16x4k::AllocTrap(Object* pObject, bpfn bpfnOnRead, pfnb pfnbOnWrite, int iStart, int iEnd) {

  // verify parameters
  assert(pObject != NULL);
  assert(iStart >= 2);
  assert(iEnd <= 256);
  assert(iStart <= iEnd);

  // dummies for no register
  if (bpfnOnRead == NULL && pfnbOnWrite == NULL) {
    pObject = this;
  }
  if (bpfnOnRead == NULL) {
    bpfnOnRead = (bpfn)ReadNoReg;
  }
  if (pfnbOnWrite == NULL) {
    pfnbOnWrite = (pfnb)WriteNoReg;
  }

  // use the old trap if it has been allocated before
  for (int i = iStart; i < iEnd; i++) {
    if (pObject == apObject[i] && abpfnOnRead[i] == bpfnOnRead && apfnbOnWrite[i] == pfnbOnWrite) {
      return (byte)i;
    }
  }

  // search for a free trap and allocate it
  for (i = iStart; i < iEnd; i++) {
    if (apObject[i] == NULL) {
      apObject[i] = pObject;
      abpfnOnRead[i] = bpfnOnRead;
      apfnbOnWrite[i] = pfnbOnWrite;
      return (byte)i;
    }
  }
  error("no more traps available");
  return 0; // calm compiler
}


////////////////////////////////////////////////////////////////////////////////
// free a trap

global void CPU16x4k::FreeTrap(byte bTrap) {

  // verify parameters
  assert(bTrap >= 2);
  assert(apObject[bTrap] != NULL);

  // free the trap
  apObject[bTrap] = NULL;
  abpfnOnRead[bTrap] = NULL;
  apfnbOnWrite[bTrap] = NULL;
}


////////////////////////////////////////////////////////////////////////////////
// map a chip into memory including shadow registers

global void CPU16x4k::MapChip(Chip* pChip, byte* pbMem, int iSize) {

  // verify parameters
  assert(pChip != NULL);
  assert(((CPU16x4k*)pChip)->iRegisterCount > 0);
  assert(((CPU16x4k*)pChip)->pRegisters != NULL);
  assert(pbMem != NULL);
  assert(iSize > 0);
  assert(iSize <= 65536);

  // map all registers into memory
  for (int iReg = 0; iReg < ((CPU16x4k*)pChip)->iRegisterCount; iReg++) {

    // allocate the trap
    byte bTrap;
    if ((((CPU16x4k*)pChip)->pRegisters[iReg].iFlags & DummyReads) != 0) {
      bTrap = AllocIOTrapWithDummyReads(pChip, ((CPU16x4k*)pChip)->pRegisters[iReg].bpfnOnRead, ((CPU16x4k*)pChip)->pRegisters[iReg].pfnbOnWrite);
    } else {
      bTrap = AllocIOTrap(pChip, ((CPU16x4k*)pChip)->pRegisters[iReg].bpfnOnRead, ((CPU16x4k*)pChip)->pRegisters[iReg].pfnbOnWrite);
    }

    // map the register including shadow registers
    for (int i = iReg; i < iSize; i += ((CPU16x4k*)pChip)->iRegisterCount) {
      SetTrap(pbMem + i, bTrap);
    }
  }
}


////////////////////////////////////////////////////////////////////////////////
// ummap a chip from memory

global void CPU16x4k::UnmapChip(Chip* pChip, byte* pbMem, int iSize) {

  // verify parameters
  assert(pChip != NULL);
  assert(((CPU16x4k*)pChip)->iRegisterCount > 0);
  assert(((CPU16x4k*)pChip)->pRegisters != NULL);
  assert(pbMem != NULL);
  assert(iSize > 0);
  assert(iSize <= 65536);

  // unmap all registers from memory
  for (int iReg = 0; iReg < ((CPU16x4k*)pChip)->iRegisterCount; iReg++) {
    byte bTrap = GetTrap(pbMem + iReg);

    // unmap the register including shadow registers
    for (int i = iReg; i < iSize; i += ((CPU16x4k*)pChip)->iRegisterCount) {
      SetTrap(pbMem + i, 0);
    }

    // free the trap
    FreeTrap(bTrap);
  }
}
