/* m68k_mem.c: memory handling for m68k_cpu
  
   Copyright (c) 2009, Holger Veit

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included in
   all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   Holger Veit BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
   IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

   Except as contained in this notice, the name of Holger Veit et al shall not be
   used in advertising or otherwise to promote the sale, use or other dealings
   in this Software without prior written authorization from Holger Veit et al.

   04-Oct-09    HV      Initial version
   20-Dec-09    HV      Rewrite memory handler for MMU and noncontiguous memory
*/

#include "m68k_cpu.h"

/* io hash */
#define IOHASHSIZE 97 /* must be prime */
#define MAKEIOHASH(p) (p % IOHASHSIZE)
static IOHANDLER** iohash = NULL;

/*
 * memory
 */
uint8*  M = 0;
t_addr  addrmask = 0xffffffff;
int     m68k_fcode = 0;
int     m68k_dma = 0;

#if 0
/* TODO */
t_stat m68k_set_mmu(UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
    uptr->flags |= value;
    
    /* TODO initialize the MMU */
    TranslateAddr = &m68k_translateaddr;
    return SCPE_OK;
}

t_stat m68k_set_nommu(UNIT *uptr, int32 value, CONST char *cptr, void *desc)
{
    uptr->flags &= ~value;

    /* initialize NO MMU */
    TranslateAddr = &m68k_translateaddr;
    return SCPE_OK;
}
#endif

/* I/O dispatcher
 * 
 * I/O devices are implemented this way:
 * a unit will register its own I/O addresses together with its handler 
 * in a hash which allows simple translation of physical addresses 
 * into units in the ReadPx/WritePx routines.
 * These routines call the iohandler entry on memory mapped read/write.
 * The handler has the option to enqueue an event for its unit for
 * asynchronous callback, e.g. interrupt processing
 */
t_stat m68k_ioinit() 
{
    if (iohash == NULL) {
        iohash = (IOHANDLER**)calloc(IOHASHSIZE,sizeof(IOHANDLER*));
        if (iohash == NULL) return SCPE_MEM;
    }
    return SCPE_OK;
}

t_stat add_iohandler(UNIT* u,void* ctxt,
    t_stat (*io)(struct _iohandler* ioh,uint32* value,uint32 rw,uint32 mask))
{
    PNP_INFO* pnp = (PNP_INFO*)ctxt;
    IOHANDLER* ioh;
    uint32 i,k;
    
    if (!pnp) return SCPE_IERR;
    for (k=i=0; i<pnp->io_size; i+=pnp->io_incr,k++) {
        t_addr p = (pnp->io_base+i) & addrmask;
        t_addr idx = MAKEIOHASH(p);
        ioh = iohash[idx];
        while (ioh != NULL && ioh->port != p) ioh = ioh->next;
        if (ioh) continue; /* already registered */
        
//      printf("Register IO for address %x offset=%d\n",p,k);
        ioh = (IOHANDLER*)malloc(sizeof(IOHANDLER));
        if (ioh == NULL) return SCPE_MEM;
        ioh->ctxt = ctxt;
        ioh->port = p;
        ioh->offset = k;
        ioh->u = u;
        ioh->io = io;
        ioh->next = iohash[idx];
        iohash[idx] = ioh;
    }
    return SCPE_OK;
}
t_stat del_iohandler(void* ctxt)
{
    uint32 i;
    PNP_INFO* pnp = (PNP_INFO*)ctxt;
    
    if (!pnp) return SCPE_IERR;
    for (i=0; i<pnp->io_size; i += pnp->io_incr) {
        t_addr p = (pnp->io_base+i) & addrmask;
        t_addr idx = MAKEIOHASH(p);
        IOHANDLER **ioh = &iohash[idx];
        while (*ioh != NULL && (*ioh)->port != p) ioh = &((*ioh)->next);
        if (*ioh) {
            IOHANDLER *e = *ioh;
            *ioh = (*ioh)->next;
            free((void*)e);
        }
    }
    return SCPE_OK;
}

/***********************************************************************************************
 * Memory handling
 * ReadP{B|W|L} and WriteP{B|W|L} simply access physical memory (addrmask applies)
 * ReadV{B|W|L} and WriteV{B|W|L} access virtual memory, i.e. after a "decoder" or mmu has processed
 *                                the address/rwmode/fcode
 * TranslateAddr is a user-supplied function, to be set into the function pointer,
 * which converts an address and other data (e.g. rw, fcode) provided by the CPU
 * into the real physical address. This is basically the MMU.
 * 
 * TranslateAddr returns SCPE_OK for valid translation
 *                       SIM_ISIO if I/O dispatch is required; ioh contains pointer to iohandler
 *                       STOP_ERRADDR if address is invalid
 * Mem accesses memory, selected by a (translated) address. Override in own code for non-contiguous memory
 * Mem returns SCPE_OK and a pointer to the selected byte, if okay, STOP_ERRADR for invalid accesses
 */

/* default handler */
t_stat m68k_translateaddr(t_addr in,t_addr* out, IOHANDLER** ioh,int rw,int fc,int dma)
{
    t_addr ma = in & addrmask;
    t_addr idx = MAKEIOHASH(ma);
    IOHANDLER* i = iohash[idx];

    *out = ma;
    *ioh = 0;
    while (i != NULL && i->port != ma) i = i->next;
    if (i) {
        *ioh = i;
        return SIM_ISIO;
    } else
        return SCPE_OK;
}

/* default memory pointer */
t_stat m68k_mem(t_addr addr,uint8** mem)
{
    if (addr > MEMORYSIZE) return STOP_ERRADR;
    *mem = M+addr;
    return SCPE_OK;
}

t_stat (*TranslateAddr)(t_addr in,t_addr* out,IOHANDLER** ioh,int rw,int fc,int dma) = &m68k_translateaddr;
t_stat (*Mem)(t_addr addr,uint8** mem) = &m68k_mem;

/* memory access routines 
 * The Motorola CPU is big endian, whereas others like the i386 is
 * little endian. The memory uses the natural order of the emulating CPU.
 *
 * addressing uses all bits but LSB to access the memory cell
 * 
 * Memorybits 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00
 *            ------68K Byte0-(MSB)-- ---68K Byte1-----------
 *            ------68K Byte2-------- ---68K Byte3-(LSB)-----
 */
t_stat ReadPB(t_addr a, uint32* val)
{
    uint8* mem;

    t_stat rc = Mem(a & addrmask,&mem);
    switch (rc) {
    default:
        return rc;
    case SIM_NOMEM:
        *val = 0xff;
        return SCPE_OK;
    case SCPE_OK:
        *val = *mem & BMASK;
        return SCPE_OK;
    }
}

t_stat ReadPW(t_addr a, uint32* val)
{
    uint8* mem;
    uint32 dat1,dat2;

    t_stat rc = Mem((a+1)&addrmask,&mem);
    switch (rc) {
    default:
        return rc;
    case SIM_NOMEM:
        *val = 0xffff;
        return SCPE_OK;
    case SCPE_OK:
        /* 68000/08/10 do not like unaligned access */
        if (cputype < 3 && (a & 1)) return STOP_ERRADR;
        dat1 = (*(mem-1) & BMASK) << 8;
        dat2 = *mem & BMASK;
        *val = (dat1 | dat2) & WMASK;
        return SCPE_OK;
    }
}

t_stat ReadPL(t_addr a, uint32* val)
{
    uint8* mem;
    uint32 dat1,dat2,dat3,dat4;

    t_stat rc = Mem((a+3)&addrmask,&mem);
    switch (rc) {
    default:
        return rc;
    case SIM_NOMEM:
        *val = 0xffffffff;
        return SCPE_OK;
    case SCPE_OK:
        /* 68000/08/10 do not like unaligned access */
        if (cputype < 3 && (a & 1)) return STOP_ERRADR;
        dat1 = *(mem-3) & BMASK;
        dat2 = *(mem-2) & BMASK;
        dat3 = *(mem-1) & BMASK;
        dat4 = *mem & BMASK;
        *val = (((((dat1 << 8) | dat2) << 8) | dat3) << 8) | dat4;
        return SCPE_OK;
    }
}

t_stat WritePB(t_addr a, uint32 val)
{
    uint8* mem;
    
    t_stat rc = Mem(a&addrmask,&mem);
    switch (rc) {
    default:
        return rc;
    case SCPE_OK:
        *mem = val & BMASK;
        /*fallthru*/
    case SIM_NOMEM:
        return SCPE_OK;
    }
}

t_stat WritePW(t_addr a, uint32 val)
{
    uint8* mem;
    t_stat rc = Mem((a+1)&addrmask,&mem);
    switch (rc) {
    default:
        return rc;
    case SCPE_OK:
        /* 68000/08/10 do not like unaligned access */
        if (cputype < 3 && (a & 1)) return STOP_ERRADR;
        *(mem-1) = (val >> 8) & BMASK;
        *mem     =  val       & BMASK;
        /*fallthru*/
    case SIM_NOMEM:
        return SCPE_OK;
    }
}

t_stat WritePL(t_addr a, uint32 val)
{
    uint8* mem;

    t_stat rc = Mem((a+3)&addrmask,&mem);
    switch (rc) {
    default:
        return rc;
    case SCPE_OK:
        /* 68000/08/10 do not like unaligned access */
        if (cputype < 3 && (a & 1)) return STOP_ERRADR;
        *(mem-3) = (val >> 24) & BMASK;
        *(mem-2) = (val >> 16) & BMASK;
        *(mem-1) = (val >>  8) & BMASK;
        *mem     =  val        & BMASK;
        /*fallthru*/
    case SIM_NOMEM:
        return SCPE_OK;
    }
}

t_stat ReadVB(t_addr a, uint32* val)
{
    t_addr addr;
    IOHANDLER* ioh;
    t_stat rc = TranslateAddr(a,&addr,&ioh,MEM_READ,m68k_fcode,m68k_dma);
    switch (rc) {
    case SIM_NOMEM:
        /* note this is a hack to persuade memory testing code that there is no memory:
         * writing to such an address is a bit bucket,
         * and reading from it will return some arbitrary value.
         * 
         * SIM_NOMEM has to be defined for systems without a strict memory handling that will
         * result in reading out anything without trapping a memory fault
         */
        *val = 0xff;
        return SCPE_OK;
    case SIM_ISIO:
        return ioh->io(ioh,val,IO_READ,BMASK);
    case SCPE_OK:
        return ReadPB(addr,val);
    default:
        return rc;
    }
}

t_stat ReadVW(t_addr a, uint32* val)
{
    t_addr addr;
    IOHANDLER* ioh;
    t_stat rc = TranslateAddr(a,&addr,&ioh,MEM_READ,m68k_fcode,m68k_dma);
    switch (rc) {
    case SIM_NOMEM:
        *val = 0xffff;
        return SCPE_OK;
    case SIM_ISIO:
        return ioh->io(ioh,val,IO_READ,WMASK);
    case SCPE_OK:
        return ReadPW(addr,val);
    default:
        return rc;
    }
}

t_stat ReadVL(t_addr a, uint32* val)
{
    t_addr addr;
    IOHANDLER* ioh;
    t_stat rc = TranslateAddr(a,&addr,&ioh,MEM_READ,m68k_fcode,m68k_dma);
    switch (rc) {
    case SIM_NOMEM:
        *val = 0xffffffff;
        return SCPE_OK;
    case SIM_ISIO:
        return ioh->io(ioh,val,IO_READ,LMASK);
    case SCPE_OK:
        return ReadPL(addr,val);
    default:
        return rc;
    }
}

t_stat WriteVB(t_addr a, uint32 val)
{
    t_addr addr;
    IOHANDLER* ioh;
    t_stat rc = TranslateAddr(a,&addr,&ioh,MEM_WRITE,m68k_fcode,m68k_dma);
    switch (rc) {
    case SIM_NOMEM:
        /* part 2 of hack for less strict memory handling: ignore anything written
         * to a nonexisting address
         */
        return SCPE_OK;
    case SIM_ISIO:
        return ioh->io(ioh,&val,IO_WRITE,BMASK);
    case SCPE_OK:
        return WritePB(addr,val);
    default:
        return rc;
    }
}

t_stat WriteVW(t_addr a, uint32 val)
{
    t_addr addr;
    IOHANDLER* ioh;
    t_stat rc = TranslateAddr(a,&addr,&ioh,MEM_WRITE,m68k_fcode,m68k_dma);
    switch (rc) {
    case SIM_NOMEM:
        return SCPE_OK;
    case SIM_ISIO:
        return ioh->io(ioh,&val,IO_WRITE,WMASK);
    case SCPE_OK:
        return WritePW(addr,val);
    default:
        return rc;
    }
}

t_stat WriteVL(t_addr a, uint32 val)
{
    t_addr addr;
    IOHANDLER* ioh;
    t_stat rc = TranslateAddr(a,&addr,&ioh,MEM_WRITE,m68k_fcode,m68k_dma);
    switch (rc) {
    case SIM_NOMEM:
        return SCPE_OK;
    case SIM_ISIO:
        return ioh->io(ioh,&val,IO_WRITE,LMASK);
    case SCPE_OK:
        return WritePL(addr,val);
    default:
        return rc;
    }
}
