 /* 
  * UAE - The Un*x Amiga Emulator
  * 
  * AutoConfig devices
  *
  * (c) 1995 Bernd Schmidt
  * (c) 1996 Ed Hanway
  */

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

#include "config.h"
#include "options.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"
#include "disk.h"
#include "xwin.h"
#include "autoconf.h"

static struct {
    CPTR libbase;
    TrapFunction functions[300]; /* I don't think any library has more than 300 functions */
} libpatches[20];
static int n_libpatches = 0;

ULONG Call68k_retaddr(CPTR address, int saveregs, CPTR retaddr)
{
    ULONG retval;
    CPTR oldpc = m68k_getpc();

    struct regstruct backup_regs;
    if (saveregs)
	backup_regs = regs;
    regs.a[7] -= 4;
    put_long (regs.a[7], retaddr);
    m68k_setpc(address);
    m68k_go(0);
    retval = regs.d[0];
    if (saveregs)
	regs = backup_regs;
    m68k_setpc(oldpc);
    return retval;
}

ULONG Call68k(CPTR address, int saveregs)
{
    return Call68k_retaddr(address, saveregs, 0xF0FFF0);
}

ULONG CallLib(CPTR base, WORD offset)
{
    int i;    
    CPTR olda6 = regs.a[6];
    ULONG retval;

    for (i = 0; i < n_libpatches; i++) {
	if (libpatches[i].libbase == base && libpatches[i].functions[-offset/6] != NULL)
	    return (*libpatches[i].functions[-offset/6])();
    }
    
    regs.a[6] = base;
    retval = Call68k(base + offset, 1);
    regs.a[6] = olda6;
    return retval;
}

/* @$%&§ compiler bugs */
static volatile int four = 4;

CPTR libemu_InstallFunctionFlags(TrapFunction f, CPTR libbase, int offset,
				 int flags)
{
    int i;
    CPTR retval;
    CPTR execbase = get_long(four);
    ULONG addr = here();
    calltrap2(deftrap2(f, flags));
    dw(RTS);
    
    regs.a[1] = libbase;
    regs.a[0] = offset;
    regs.d[0] = addr;
    retval = CallLib(execbase, -420);
    for (i = 0; i < n_libpatches; i++) {
	if (libpatches[i].libbase == libbase)
	    break;
    }
    if (i == n_libpatches) {
	int j;
	libpatches[i].libbase = libbase;
	for (j = 0; j < 300; j++)
	    libpatches[i].functions[j] = NULL;
	n_libpatches++;
    }
    libpatches[i].functions[-offset/6] = f;
    return retval;
}

CPTR libemu_InstallFunction(TrapFunction f, CPTR libbase, int offset)
{
    return libemu_InstallFunctionFlags(f, libbase, offset, 0);
}

/* Commonly used autoconfig strings */

CPTR EXPANSION_explibname, EXPANSION_doslibname, EXPANSION_uaeversion;
CPTR EXPANSION_uaedevname, EXPANSION_explibbase = 0, EXPANSION_haveV36;
CPTR EXPANSION_bootcode;

static int current_deviceno = 0;

int get_new_device(char **devname, CPTR *devname_amiga)
{
    char buffer[80];
    
    sprintf(buffer,"UAE%d", current_deviceno);

    *devname_amiga = ds(*devname = strdup(buffer));
    return current_deviceno++;
}

/* ROM tag area memory access */

static UBYTE rtarea[65536];

static ULONG rtarea_lget(CPTR) REGPARAM;
static UWORD rtarea_wget(CPTR) REGPARAM;
static UBYTE rtarea_bget(CPTR) REGPARAM;
static void  rtarea_lput(CPTR, ULONG) REGPARAM;
static void  rtarea_wput(CPTR, UWORD) REGPARAM;
static void  rtarea_bput(CPTR, UBYTE) REGPARAM;
static UBYTE *rtarea_xlate(CPTR) REGPARAM;

addrbank rtarea_bank = {
    rtarea_lget, rtarea_wget,
    rtarea_lget, rtarea_wget, rtarea_bget,
    rtarea_lput, rtarea_wput, rtarea_bput,
    rtarea_xlate, default_check
};

UBYTE *rtarea_xlate(CPTR addr)
{
    addr &= 0xFFFF;
    return rtarea + addr;
}

ULONG rtarea_lget(CPTR addr)
{
    addr &= 0xFFFF;
    return (ULONG)(rtarea_wget(addr) << 16) + rtarea_wget(addr+2);
}

UWORD rtarea_wget(CPTR addr)
{
    addr &= 0xFFFF;
    return (rtarea[addr]<<8) + rtarea[addr+1];
}

UBYTE rtarea_bget(CPTR addr)
{
    UWORD data;
    addr &= 0xFFFF;
    return rtarea[addr];
}

void rtarea_lput(CPTR addr, ULONG value) { }
void rtarea_bput(CPTR addr, UBYTE value) { }

/* Don't start at 0 -- can get bogus writes there. */
static ULONG trap_base_addr = 0x00F00180;

/* We'll need a lot of these. */
TrapFunction traps[16384];
char trapmode[16384];

static int max_trap = 0;

int lasttrap;

void do_emultrap(int tr)
{
    struct regstruct backup_regs;
    ULONG retval;

    if ((trapmode[tr] & 1) == 0)
	backup_regs = regs;
    retval = (*traps[tr])();
    if ((trapmode[tr] & 1) == 0)
        regs = backup_regs;
    if ((trapmode[tr] & 2) == 0)
	regs.d[0] = retval;
}

void rtarea_wput(CPTR addr, UWORD value) 
{
    /* Save all registers */
    struct regstruct backup_regs;
    ULONG retval = 0;
    ULONG func = ((addr  - trap_base_addr) & 0xFFFF) >> 1;

    if (func == 0) {
	lasttrap = get_long(regs.a[7]);
	regs.a[7] += 4;
	regs.spcflags |= SPCFLAG_EMULTRAP;
	return;
    }
    
    backup_regs = regs;
    if(func < max_trap) {
	retval = (*traps[func])();
    } else {
	fprintf(stderr, "illegal emulator trap\n");
    }
    regs = backup_regs;
    regs.d[0] = retval;
}

/* some quick & dirty code to fill in the rt area and save me a lot of
 * scratch paper
 */

static int rt_addr = 0;
static int rt_straddr = 0xFF00 - 2;

ULONG addr(int ptr)
{
    return (ULONG)ptr + 0x00F00000;
}

void dw(UWORD data)
{
    rtarea[rt_addr++] = data >> 8;
    rtarea[rt_addr++] = data;
}

void dl(ULONG data)
{
    rtarea[rt_addr++] = data >> 24;
    rtarea[rt_addr++] = data >> 16;
    rtarea[rt_addr++] = data >> 8;
    rtarea[rt_addr++] = data;
}

/* store strings starting at the end of the rt area and working
 * backward.  store pointer at current address
 */

ULONG ds(char *str)
{
    int len = strlen(str) + 1;

    rt_straddr -= len;
    strcpy(rtarea + rt_straddr, str);
    
    return addr(rt_straddr);
}

void calltrap2(ULONG n)
{
    dw(0x4878); /* PEA.L n.w */
    dw(n);
    /* Call trap #0 is reserved for this */
    dw(0x33C0);	/* MOVE.W D0,abs32 */
    dl(trap_base_addr);
}

void calltrap(ULONG n)
{
#if 1
    dw(0x33C0);	/* MOVE.W D0,abs32 */
    dl(n*2 + trap_base_addr);
#else
    calltrap2(n);
#endif
}

void org(ULONG a)
{
    rt_addr = a - 0x00F00000;
}

ULONG here(void)
{
    return addr(rt_addr);
}

int deftrap(TrapFunction func)
{
    int num = max_trap++;
    traps[num] = func;
    trapmode[num] = 0;
    return num;
}

int deftrap2(TrapFunction func, int mode)
{
    int num = max_trap++;
    traps[num] = func;
    trapmode[num] = mode;
    return num;
}

void align(int b)
{
    rt_addr = (rt_addr + (b-1)) & ~(b-1);
}

static ULONG bootcode(void)
{
    regs.a[1] = EXPANSION_doslibname;
    regs.d[0] = CallLib (regs.a[6], -96); /* FindResident() */
    regs.a[0] = get_long(regs.d[0]+22); /* get dos.library rt_init */
    return regs.a[0];
}

void rtarea_init()
{
    ULONG a;
    char uaever[100];
    sprintf(uaever, "uae-%d.%d.%d", (version / 100) % 10, (version / 10) % 10, (version / 1) % 10);

    EXPANSION_uaeversion = ds(uaever);
    EXPANSION_explibname = ds("expansion.library");
    EXPANSION_doslibname = ds("dos.library");
    EXPANSION_uaedevname = ds("uae.device");

    deftrap(NULL); /* Generic emulator trap */
    lasttrap = 0;

    align(4);
    EXPANSION_bootcode = here();
    calltrap2(deftrap2(bootcode, 1));
    dw(0x2040);			/* move.l d0,a0 */
    dw(0x4e90);                 /* jsr (a0) */
    dw(RTS);

    a = here();

    /* Idle task */
    org(0xF0FFF0 - 4);
    dw(0x4E72); dw (0x2000); /* STOP 0x2000 */
    
    /* Standard "return from 68k mode" trap */
    /*org(0xF0FFF0); */
    calltrap2(0);
    dw(RTS);
    org(a);
}
