/* 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" | |
#include <ctype.h> | |
#if defined(_WIN32) | |
#include <windows.h> | |
#else | |
#include <unistd.h> | |
#endif | |
/* 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, 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, 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; | |
} | |
} |