 /*
  * UAE - The Un*x Amiga Emulator
  *
  * Memory management
  *
  * (c) 1995 Bernd Schmidt
  */

#include "sysconfig.h"
#include "sysdeps.h"

#include "config.h"
#include "options.h"
#include "memory.h"
#include "custom.h"
#include "cia.h"
#include "ersatz.h"
#include "zfile.h"
#include "events.h"
#include "newcpu.h"
#include "compiler.h"

#ifdef USE_COMPILER
#include <sys/mman.h>
#endif

int ersatzkickfile = 0;

int buserr;
addrbank membanks[65536];

/* Default memory access functions */

int default_check(CPTR a, ULONG b)
{
    return 0;
}

UBYTE *default_xlate(CPTR a)
{
    fprintf(stderr, "Your Amiga program just did something terribly stupid\n");
    return 0;
}

UWORD default_awget(CPTR addr)
{
    default_xlate(addr);
    return 0;
}

ULONG default_alget(CPTR addr)
{
    default_xlate(addr);
    return 0;
}

/* A dummy bank that only contains zeros */

static ULONG dummy_lget(CPTR) REGPARAM;
static UWORD dummy_wget(CPTR) REGPARAM;
static UBYTE dummy_bget(CPTR) REGPARAM;
static void  dummy_lput(CPTR, ULONG) REGPARAM;
static void  dummy_wput(CPTR, UWORD) REGPARAM;
static void  dummy_bput(CPTR, UBYTE) REGPARAM;
static int   dummy_check(CPTR addr, ULONG size) REGPARAM;

ULONG dummy_lget(CPTR addr)
{
    if (illegal_mem) printf("Illegal lget at %08lx pc=%08lx\n",addr,m68k_getpc());
    return 0;
}

UWORD dummy_wget(CPTR addr)
{
    if (illegal_mem) printf("Illegal wget at %08lx pc=%08lx\n",addr,m68k_getpc());
    return 0;
}

UBYTE dummy_bget(CPTR addr)
{
    if (illegal_mem) printf("Illegal bget at %08lx pc=%08lx\n",addr,m68k_getpc());
    return 0;
}

void dummy_lput(CPTR addr, ULONG l)
{
    if (illegal_mem) printf("Illegal lput at %08lx pc=%08lx\n",addr,m68k_getpc());
}
void dummy_wput(CPTR addr, UWORD w)
{
    if (illegal_mem) printf("Illegal wput at %08lx pc=%08lx\n",addr,m68k_getpc());
}
void dummy_bput(CPTR addr, UBYTE b)
{
    if (illegal_mem) printf("Illegal bput at %08lx pc=%08lx\n",addr,m68k_getpc());
}

int dummy_check(CPTR addr, ULONG size)
{
    if (illegal_mem) printf("Illegal check at %08lx pc=%08lx\n",addr,m68k_getpc());
    return 0;
}

/* Chip memory */

ULONG chipmem_mask,kickmem_mask,bogomem_mask;

UBYTE *chipmemory;

static ULONG chipmem_alget(CPTR) REGPARAM;
static UWORD chipmem_awget(CPTR) REGPARAM;

static ULONG chipmem_lget(CPTR) REGPARAM;
static UWORD chipmem_wget(CPTR) REGPARAM;
static UBYTE chipmem_bget(CPTR) REGPARAM;
static void  chipmem_lput(CPTR, ULONG) REGPARAM;
static void  chipmem_wput(CPTR, UWORD) REGPARAM;
static void  chipmem_bput(CPTR, UBYTE) REGPARAM;

static int   chipmem_check(CPTR addr, ULONG size) REGPARAM;
static UBYTE *chipmem_xlate(CPTR addr) REGPARAM;

ULONG chipmem_alget(CPTR addr)
{
    UBYTE *m;
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    m = chipmemory + addr;
    return (((ULONG)m[0] << 24) + ((ULONG)m[1] << 16) 
	    + ((ULONG)m[2] << 8) + ((ULONG)m[3]));
}

UWORD chipmem_awget(CPTR addr)
{
    UBYTE *m;
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    m = chipmemory + addr;
    return ((UWORD)m[0] << 8) + m[1];
}

ULONG chipmem_lget(CPTR addr)
{
    UBYTE *m;
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    m = chipmemory + addr;
    return (((ULONG)m[0] << 24) + ((ULONG)m[1] << 16) 
	    + ((ULONG)m[2] << 8) + ((ULONG)m[3]));
}

UWORD chipmem_wget(CPTR addr)
{
    UBYTE *m;
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    m = chipmemory + addr;
    return ((UWORD)m[0] << 8) + m[1];
}

UBYTE chipmem_bget(CPTR addr)
{
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    return chipmemory[addr];
}

void chipmem_lput(CPTR addr, ULONG l)
{
    UBYTE *m;
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    m = chipmemory + addr;
    m[0] = l >> 24;
    m[1] = l >> 16;
    m[2] = l >> 8;
    m[3] = l;
}

void chipmem_wput(CPTR addr, UWORD w)
{
    UBYTE *m;
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    m = chipmemory + addr;
    m[0] = w >> 8;
    m[1] = w;
}

void chipmem_bput(CPTR addr, UBYTE b)
{
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    chipmemory[addr] = b;
}

int chipmem_check(CPTR addr, ULONG size)
{
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    return (addr + size) < chipmem_size;
}

UBYTE *chipmem_xlate(CPTR addr)
{
    addr -= chipmem_start & chipmem_mask;
    addr &= chipmem_size-1;
    return chipmemory + addr;
}

/* Slow memory */

static UBYTE *bogomemory;

static ULONG bogomem_alget(CPTR) REGPARAM;
static UWORD bogomem_awget(CPTR) REGPARAM;
static ULONG bogomem_lget(CPTR) REGPARAM;
static UWORD bogomem_wget(CPTR) REGPARAM;
static UBYTE bogomem_bget(CPTR) REGPARAM;
static void  bogomem_lput(CPTR, ULONG) REGPARAM;
static void  bogomem_wput(CPTR, UWORD) REGPARAM;
static void  bogomem_bput(CPTR, UBYTE) REGPARAM;
static int  bogomem_check(CPTR addr, ULONG size) REGPARAM;
static UBYTE *bogomem_xlate(CPTR addr) REGPARAM;

ULONG bogomem_alget(CPTR addr)
{
    UBYTE *m;
    addr -= bogomem_start & bogomem_mask;
    addr &= bogomem_size-1;
    m = bogomemory + addr;
    return (((ULONG)m[0] << 24) + ((ULONG)m[1] << 16) 
	    + ((ULONG)m[2] << 8) + ((ULONG)m[3]));
}

UWORD bogomem_awget(CPTR addr)
{
    UBYTE *m;
    addr -= bogomem_start & bogomem_mask;
    addr &= bogomem_size-1;
    m = bogomemory + addr;
    return ((UWORD)m[0] << 8) + m[1];
}

ULONG bogomem_lget(CPTR addr)
{
    UBYTE *m;
    addr -= bogomem_start & bogomem_mask;
    addr &= bogomem_size-1;
    m = bogomemory + addr;
    return (((ULONG)m[0] << 24) + ((ULONG)m[1] << 16) 
	    + ((ULONG)m[2] << 8) + ((ULONG)m[3]));
}

UWORD bogomem_wget(CPTR addr)
{
    UBYTE *m;
    addr -= bogomem_start & bogomem_mask;
    addr &= bogomem_size-1;
    m = bogomemory + addr;
    return ((UWORD)m[0] << 8) + m[1];
}

UBYTE bogomem_bget(CPTR addr)
{
    addr -= bogomem_start & bogomem_mask;
    addr &= bogomem_size-1;
    return bogomemory[addr];
}

void bogomem_lput(CPTR addr, ULONG l)
{
    UBYTE *m;
    addr -= bogomem_start & bogomem_mask;
    addr &= bogomem_size-1;
    m = bogomemory + addr;
    m[0] = l >> 24;
    m[1] = l >> 16;
    m[2] = l >> 8;
    m[3] = l;
}

void bogomem_wput(CPTR addr, UWORD w)
{
    UBYTE *m;
    addr -= bogomem_start & bogomem_mask;
    addr &= bogomem_size-1;
    m = bogomemory + addr;
    m[0] = w >> 8;
    m[1] = w;
}

void bogomem_bput(CPTR addr, UBYTE b)
{
    addr -= bogomem_start & bogomem_mask;
    addr &= bogomem_size-1;
    bogomemory[addr] = b;
}

int bogomem_check(CPTR addr, ULONG size)
{
    addr -= bogomem_start & (bogomem_size-1);
    addr &= bogomem_size-1;
    return (addr + size) < bogomem_size;
}

UBYTE *bogomem_xlate(CPTR addr)
{
    addr -= bogomem_start & (bogomem_size-1);
    addr &= bogomem_size-1;
    return bogomemory + addr;
}

/* Kick memory */

static int zkickfile = 0;
UBYTE *kickmemory;

static ULONG kickmem_alget(CPTR) REGPARAM;
static UWORD kickmem_awget(CPTR) REGPARAM;
static ULONG kickmem_lget(CPTR) REGPARAM;
static UWORD kickmem_wget(CPTR) REGPARAM;
static UBYTE kickmem_bget(CPTR) REGPARAM;
static void  kickmem_lput(CPTR, ULONG) REGPARAM;
static void  kickmem_wput(CPTR, UWORD) REGPARAM;
static void  kickmem_bput(CPTR, UBYTE) REGPARAM;
static int  kickmem_check(CPTR addr, ULONG size) REGPARAM;
static UBYTE *kickmem_xlate(CPTR addr) REGPARAM;

ULONG kickmem_alget(CPTR addr)
{
    UBYTE *m;
    addr -= kickmem_start & kickmem_mask;
    addr &= kickmem_size-1;
    m = kickmemory + addr;
    return (((ULONG)m[0] << 24) + ((ULONG)m[1] << 16) 
	    + ((ULONG)m[2] << 8) + ((ULONG)m[3]));
}

UWORD kickmem_awget(CPTR addr)
{
    UBYTE *m;
    addr -= kickmem_start & kickmem_mask;
    addr &= kickmem_size-1;
    m = kickmemory + addr;
    return ((UWORD)m[0] << 8) + m[1];
}

ULONG kickmem_lget(CPTR addr)
{
    UBYTE *m;
    addr -= kickmem_start & kickmem_mask;
    addr &= kickmem_size-1;
    m = kickmemory + addr;
    return (((ULONG)m[0] << 24) + ((ULONG)m[1] << 16) 
	    + ((ULONG)m[2] << 8) + ((ULONG)m[3]));
}

UWORD kickmem_wget(CPTR addr)
{
    UBYTE *m;
    addr -= kickmem_start & kickmem_mask;
    addr &= kickmem_size-1;
    m = kickmemory + addr;
    return ((UWORD)m[0] << 8) + m[1];
}

UBYTE kickmem_bget(CPTR addr)
{
    addr -= kickmem_start & kickmem_mask;
    addr &= kickmem_size-1;
    return kickmemory[addr];
}

void kickmem_lput(CPTR addr, ULONG b)
{
    if (illegal_mem) printf("Illegal kickmem lput at %08lx pc=%08lx\n",addr,m68k_getpc());
}

void kickmem_wput(CPTR addr, UWORD b)
{
    if (illegal_mem) printf("Illegal kickmem wput at %08lx pc=%08lx\n",addr,m68k_getpc());
}

void kickmem_bput(CPTR addr, UBYTE b)
{
    if (illegal_mem) printf("Illegal kickmem lput at %08lx pc=%08lx\n",addr,m68k_getpc());
}

int kickmem_check(CPTR addr, ULONG size)
{
    addr -= kickmem_start & (kickmem_size-1);
    addr &= kickmem_size-1;
    return (addr + size) < kickmem_size;
}

UBYTE *kickmem_xlate(CPTR addr)
{
    addr -= kickmem_start & (kickmem_size-1);
    addr &= kickmem_size-1;
    return kickmemory + addr;
}

static int load_kickstart(void)
{
    int i;
    ULONG cksum = 0, prevck = 0;
    unsigned char buffer[8];
    
    FILE *f = zfile_open(romfile, "rb");
    
    if (f == NULL) {	
    	fprintf(stderr, "No Kickstart ROM found.\n");
	return 0;
    }
    
    fread(buffer, 1, 8, f);
    if (buffer[4] == 0 && buffer[5] == 8 && buffer[6] == 0 && buffer[7] == 0) {
	fprintf(stderr, "You seem to have a ZKick file there... You probably lose.\n");
	zkickfile = 1;
    } else 
	fseek(f, 0, SEEK_SET);
    
    i = fread(kickmemory, 1, kickmem_size, f);
    if (i == kickmem_size/2) {
	fprintf(stderr, "Warning: Kickstart is only 256K.\n");
	memcpy (kickmemory + kickmem_size/2, kickmemory, kickmem_size/2);
    } else if (i != kickmem_size) {
	fprintf(stderr, "Error while reading Kickstart.\n");
	return 0;
    }
    zfile_close (f);
    
    for (i = 0; i < kickmem_size; i+=4) {
	ULONG data = kickmemory[i]*65536*256 + kickmemory[i+1]*65536 + kickmemory[i+2]*256 + kickmemory[i+3];
	cksum += data;
	if (cksum < prevck)
	    cksum++;
	prevck = cksum;
    }
    if (cksum != 0xFFFFFFFF) {
	fprintf(stderr, "Warning: Kickstart checksum incorrect. You probably have a corrupted ROM image.\n");
    }
    return 1;
}

/* Address banks */

addrbank dummy_bank = {
    default_alget, default_awget,
    dummy_lget, dummy_wget, dummy_bget,
    dummy_lput, dummy_wput, dummy_bput,
    default_xlate, dummy_check
};

addrbank chipmem_bank = {
    chipmem_alget, chipmem_awget,
    chipmem_lget, chipmem_wget, chipmem_bget,
    chipmem_lput, chipmem_wput, chipmem_bput,
    chipmem_xlate, chipmem_check
};

addrbank bogomem_bank = {
    bogomem_alget, bogomem_awget,
    bogomem_lget, bogomem_wget, bogomem_bget,
    bogomem_lput, bogomem_wput, bogomem_bput,
    bogomem_xlate, bogomem_check
};

addrbank kickmem_bank = {
    kickmem_alget, kickmem_awget,
    kickmem_lget, kickmem_wget, kickmem_bget,
    kickmem_lput, kickmem_wput, kickmem_bput,
    kickmem_xlate, kickmem_check
};

char *address_space, *good_address_map;
int good_address_fd;

void memory_init(void)
{
    char buffer[4096];
    char *nam;
    int i, fd;
    buserr = 0;

    chipmem_mask = chipmem_size - 1;
    kickmem_mask = kickmem_size - 1;
    bogomem_mask = bogomem_size - 1;
    
#ifdef USE_COMPILER
    fd = open("/dev/zero", O_RDWR);
    good_address_map = mmap(NULL, 1 << 24, PROT_READ, MAP_PRIVATE, fd, 0);
    address_space = mmap(NULL, 1 << 24, PROT_READ, MAP_PRIVATE, fd, 0);
    chipmemory = mmap(address_space, 0x200000, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd, 0);
    kickmemory = mmap(address_space + 0xF80000, 0x80000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, fd, 0);

    close(fd);

    good_address_fd = open(nam = tmpnam(NULL), O_CREAT|O_RDWR, 0600);
    memset(buffer,1,sizeof(buffer));
    write(good_address_fd, buffer,sizeof(buffer));
    unlink(nam);

    for (i = 0; i < chipmem_size; i += 4096)
	mmap(good_address_map + i, 4096, PROT_READ, MAP_FIXED | MAP_PRIVATE,
	     good_address_fd, 0);
    for (i = 0; i < kickmem_size; i += 4096)
	mmap(good_address_map + i + 0x1000000 - kickmem_size, 4096, PROT_READ,
	     MAP_FIXED | MAP_PRIVATE, good_address_fd, 0);
#else
    chipmemory = (UBYTE *)malloc(chipmem_size);
    kickmemory = (UBYTE *)malloc(kickmem_size);
#endif

    for(i = 0; i < 65536; i++)
	membanks[i] = dummy_bank;
    
    /* Map the chipmem into all of the lower 16MB */
    map_banks(&chipmem_bank, 0x00, 256);
    map_banks(&custom_bank, 0xC0, 0x20);
    map_banks(&cia_bank, 0xA0, 32);
    map_banks(&clock_bank, 0xDC, 1);
    
    if (bogomem_size > 0) {
	bogomemory = (UBYTE *)malloc(bogomem_size);
    	map_banks(&bogomem_bank, 0xC0, bogomem_size >> 16);
    }

    map_banks(&rtarea_bank, 0xF0, 1); 
    if (!load_kickstart()) {
	init_ersatz_rom(kickmemory);
	ersatzkickfile = 1;
    }
    if (zkickfile)
	map_banks(&kickmem_bank, 0x20, 8);
    
    map_banks(&kickmem_bank, 0xF8, 8);
    if (!zkickfile)
	map_banks(&expamem_bank, 0xE8, 1);
}

void map_banks(addrbank *bank, int start, int size)
{
    int bnr;
    int hioffs = 0;
#if 0 && CPU_LEVEL < 2
    for (hioffs = 0; hioffs < 256; hioffs++)
#endif
	for (bnr = start; bnr < start+size; bnr++) 
	    membanks[bnr + hioffs * 256] = *bank;
}
