418 lines
12 KiB
C
418 lines
12 KiB
C
/* 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;
|
|
}
|
|
}
|