| /* 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; | |
| } | |
| } |