/* loader.c - fetches all needed information from the Amiga OS in a
 * more or less legal way, then it will load and run the minix kernel.
 * first it loads the kernel image file somewhere in memory, then it
 * allocs a transfer struct and fills it with the information from
 * Exec like available memory, keymap etc, in this struct it puts a
 * small assembler routine, then it executes it. This routine copies
 * the kernel image to the right location and then jumps to it.
 * the first thing the kernel does is saving the data from the transfer
 * struct into its private datastructures.
 * by Steven Reiz 16/12/88 - 26/6/89
 * It finds topaz.8 for you in rom but if a future genius at Commodore
 * ever gets the idea to put ALL fonts on disk, this program will break
 * In FindRam() a chunk (see MEMCHUNKSZ) with no FREE mem in it will
 * not show up in the list and if you have any free mem in a chunk it
 * will asume that you have that complete chunk, so if you have eg.
 * 128KB somewhere it will asume that there will be another 128KB next
 * to it, even if it is not really there. These two problems pull
 * MEMCHUNKSZ in different directions, if you have trouble with one of
 * them you could try changing MEMCHUNKSZ but then you might encounter
 * the other one.
 * You can have max. NUMMEMLIST different chunks of MEMCHUNKSZ.
 * (the rest will be ignored)
 * The libraries and devices that are opened are not closed, because I
 * didn't see the need to (Minix will reclaim all ram in the system
 * anyway).
 * usage: see below (usage routine)
 */

#include "loader.h"
#include "transfer.h"

/* main - controls loader control flow				       */
main(argc, argv)
int argc;
char *argv[]; {
    int i;

    debuglvl=0;
    for (i=1; i<argc; ++i) {
	if (!strcmp(argv[i], "?") || !strcmp(argv[i], "-?") ||
	 (argv[i][0]=='-' && argv[i][2]!='\0')) {
	    usage();
	    exit(0);
	}
	if (!strcmp(argv[i], "-d"))
	    debuglvl=strval(argv[i+1], 1);
    }
    loadkernelfile(argc-1, &argv[1]);
    buildtransdata(argc-1, &argv[1]);
    mlroutine(argc-1, &argv[1]);
    /* not reached */
}


/* usage - prints a message describing the arguments
 * that can be passed to the loader.
 */
usage() {
    printf("Amiga Minix Copyright Prentice Hall 1989\n");
    printf("by Andy Tanenbaum, Johan Stevenson, Raymond Michiels and Steven Reiz\n");
    printf("loader - start minix\n");
    printf("arguments you can supply to the loader:\n");
    printf("(use a $ prefix for hex numbers)\n");
    printf("-d <bits>      : debugging bits, 0=off, $ffffffff=all, default=0\n");
    printf("-e <address>   : kernel execute address, default=kernel destination address\n");
    printf("-f <address>   : fontdata eg. $fc1234, default=romfont\n");
    printf("-k <filename>  : kernel image data file, default=minix.img\n");
    printf("-l <address>   : kernel image already loaded, at address\n");
    printf("-m <address>   : memory chunk you want to include in list\n");
    printf("-n <address>   : memory chunk you want to exclude from list\n");
    printf("-q <keys/sec>  : keyboard repeat speed, default=15\n");
    printf("-r <microsecs> : disk seekrate, default=4000\n");
    printf("-s <address>   : kernel destination address, default=$000200\n");
    printf("-t <Hz>        : clockfreq, default pal=7093790, ntsc=7159090\n");
    printf("-? or ?        : this stuff you're reading\n");
}


/* loadkernelfile - parse arguments for -k <file>, then seek the file
 * for its size, alloc memory for it, load it into the memory
 * when the -l <address> argument is present skip all of this,
 * instead get the kernel image size at address, immediately
 * followed by the image.
 */
loadkernelfile(argc, argv)
int argc;
char **argv; {
    int i;
    char filename[40];
    long *p;

    for (i=0; i<argc-1; ++i)
	if (!strcmp(argv[i], "-l")) {
	    p=(long *)strval(argv[i+1], 1);
	    kernelsz=*p++;
	    kernel=(char *)p;
	    return;
	}

    strcpy(filename, "minix.img");
    for (i=0; i<argc-1; ++i)
	if (!strcmp(argv[i], "-k"))
	    strcpy(filename, argv[i+1]);
    file=Open(filename, OPEN_OLD);
    if (!file) {
	printf("couldn't open %s\n", filename);
	exit(20);
    }
    Seek(file, 0L, 1L); /* to end of file (Seek returns previous position) */
    kernelsz=Seek(file, 0L, -1L); /* Seek to start of file, returning size */
    kernel=AllocMem(kernelsz, ALLOC_ANY);
    if (!kernel) {
	printf("not enough memory to load kernel image\n");
	exit(20);
    }
    if (Read(file, kernel, kernelsz)!=kernelsz) {
	printf("couldn't read %s into ram\n", filename);
	exit(20);
    }
    Close(file);
}


/* buildtransdata - allocate the struct transferdata but take care that*/
/* it will not be overwritten when the kernel is copied to its	       */
/* destination address. Then fill it with all data needed.	       */
buildtransdata(argc, argv)
int argc;
char **argv; {
    int i;
    long tsize;
    long t;

    kernelstart=0x000200;
    for (i=0; i<argc-1; ++i)
	if (!strcmp(argv[i], "-s"))
	    kernelstart=strval(argv[i+1], 1);
    tsize=(long)sizeof(struct transferdata);
    while ((t=(long)AllocMem(tsize, ALLOC_ANY)) &&
     (t>=kernelstart-tsize && t<=kernelstart+kernelsz
     /* collides with kernel */
     ||
     t>=0x070000L && t<0x080000L));
     /* collides with screenmem or buffers, MEM-HACK */

    transdat=(struct transferdata *)t;
    if (!transdat) {
	printf("not enough memory to transfer data from loader to kernel\n");
	exit(20);
    }
    for (i=0; i<26; ++i)
	transdat->args[i]=-1;
    GfxBase=OpenLibrary("graphics.library", 0L);
    if (!GfxBase) { /* this would be very very very weird */
	printf("hey, your graphics library is gone !\n");
	exit(20);
    }
    transdat->args['d'-'a']=debuglvl;
/*  transdat->args['f'-'a']=(long)(GfxBase->DefaultFont->tf_CharData); */
    transdat->args['f'-'a']=(long)open_topaz();
    transdat->args['q'-'a']=15;
    transdat->args['r'-'a']=4000;
    transdat->args['t'-'a']=7159090; /* assume ntsc clock frequency */
    if ((int)(SysBase->VBlankFrequency)==50)
	transdat->args['t'-'a']=7093790;

    for (i=0; i<argc-1; ++i) {
	char flag=argv[i][1];

	if (argv[i][0]=='-' && argv[i][2]=='\0' &&
	 flag>='a' && flag<='z')
	    transdat->args[flag-'a']=strval(argv[i+1], 0);
    }
#ifdef DEBUG
    if (debuglvl&1) {
	printf("screen frequency: %d Hz, clock frequency %ld Hz\n",
	 (int)(SysBase->VBlankFrequency), transdat->args['t'-'a']);
     }
#endif

    FindRam();
    for (i=0; i<argc-1; ++i) {
	if (!strcmp(argv[i], "-m")) {
	    t=strval(argv[i+1], 1);
	    AddToRamList(t);
	}
	if (!strcmp(argv[i], "-n")) {
	    t=strval(argv[i+1], 1);
	    DeleteFromRamList(t);
	}
    }
#ifdef DEBUG
    if (debuglvl&1)
	PrintRamList();
#endif
    StealKeymap();
    StealSprite();
}


/* finds all ram in your system by checking the Exec holelist (MemList)*/
FindRam() {
    struct MemHeader *hdr;
    struct MemChunk *chunk;
    LONG blockadr, blockend;

    transdat->transmemlist[0]=-1L; /* a sentinel */
    transdat->numintranslist=1;
    Forbid(); /* disables taskswitching */
    hdr=(struct MemHeader *)(SysBase->MemList.lh_Head);
    while (hdr->mh_Node.ln_Succ) {
	for (chunk=hdr->mh_First; chunk; chunk=chunk->mc_Next) {
	    blockadr=(LONG)chunk;
	    blockend=blockadr+(chunk->mc_Bytes)-2;
	    while (blockadr<=blockend) {
		AddToRamList(blockadr);
		blockadr+=MEMCHUNKSZ;
	    }
	    AddToRamList(blockend);
	}
	hdr=(struct MemHeader *)(hdr->mh_Node.ln_Succ);
    }
    Permit(); /* enables taskswitching */
}


/* adds a memchunk to the sorted list				       */
AddToRamList(chunkadr)
LONG chunkadr; {
    chunkadr-=chunkadr%MEMCHUNKSZ;
    if (transdat->numintranslist<NUMMEMLIST) {
	register short i, j;
	register LONG a;

	for (i=transdat->numintranslist-1, a=chunkadr;
	 transdat->transmemlist[i]>a; --i);
	if (transdat->transmemlist[i]<a) {
	    for (j=transdat->numintranslist-1; j>i; --j)
		transdat->transmemlist[j+1]=transdat->transmemlist[j];
	    transdat->transmemlist[++i]=a;
	    ++transdat->numintranslist;
	}
    }
}


/* deletes a memchunk from the sorted list			       */
DeleteFromRamList(chunkadr)
LONG chunkadr; {
    register short i, j;
    register LONG a;

    chunkadr-=chunkadr%MEMCHUNKSZ;
    for (i=transdat->numintranslist-1, a=chunkadr;
     transdat->transmemlist[i]>a; --i);
    if (transdat->transmemlist[i]==a) {
	for (j=i; j<transdat->numintranslist-1; ++j)
	    transdat->transmemlist[j]=transdat->transmemlist[j+1];
	--transdat->numintranslist;
    }
}


#ifdef DEBUG
/* prints a compacted list of the memchunks in the system	       */
PrintRamList() {
    short i;

    printf("ramchunk    $%06lx - ", transdat->transmemlist[1]);
    for (i=2; i<transdat->numintranslist; ++i) {
	if (transdat->transmemlist[i]-MEMCHUNKSZ!=transdat->transmemlist[i-1]) {
	    printf("$%06lx\n", transdat->transmemlist[i-1]+MEMCHUNKSZ-1);
	    printf("ramchunk    $%06lx - ", transdat->transmemlist[i]);
	}
    }
    printf("$%06lx\n", transdat->transmemlist[transdat->numintranslist-1]+MEMCHUNKSZ-1);
    printf("total ram   %d KB\n", (short)((transdat->numintranslist-1)*MEMCHUNKSZ/1024));
}
#endif


/* extracts information from the amiga keymap structure to build the   */
/* minix keymap 						       */
StealKeymap() {
#define KBUFSZ 80L
    struct IOStdReq ioreq;
    struct InputEvent event;
    long actual, i;
    char buf[KBUFSZ], c;
    short code, qual, qualhelp;

    OpenDevice("console.device", -1L, &ioreq, 0L);
    ConsoleDevice=(char *)ioreq.io_Device;
    event.ie_NextEvent=NULL;	       /* build the InputEvent */
    event.ie_Class=IECLASS_RAWKEY;
    event.ie_SubClass=0;
    for (qualhelp=0; qualhelp<4; ++qualhelp) { /* all keys, shifted and alted too */
	qual=0;
	if (qualhelp&1) qual|=IEQUALIFIER_LSHIFT;
	if (qualhelp&2) qual|=IEQUALIFIER_LALT;
	event.ie_Qualifier=qual;
	for (code=0; code<MAXKEY; ++code) {
	    event.ie_Code=code;
	    actual=RawKeyConvert(&event, buf, KBUFSZ, 0L); /*ask the ascii code(s)*/
	    if (actual==0L || code>=0x50 && code<=0x59)
		transdat->mkeymap.ascii[MAXKEY*qualhelp+code]=0;
	    else
		transdat->mkeymap.ascii[MAXKEY*qualhelp+code]=buf[0];
	}
    }
    i=0; /* offset into kmap.functext */
    for (qualhelp=0; qualhelp<2; ++qualhelp) { /* the funckeys, shifted too */
	qual=0;
	if (qualhelp) qual|=IEQUALIFIER_LSHIFT;
	event.ie_Qualifier=qual;
	for (code=0x50; code<0x5a; ++code) {
	    event.ie_Code=code;
	    actual=RawKeyConvert(&event, buf, KBUFSZ, 0L);
	    if (actual<0L || actual>=KBUFSZ) /* string to big for buf */
		actual=KBUFSZ-1;
	    buf[actual]='\0';
	    transdat->mkeymap.func[10*qualhelp+code-0x50]=(char *)i;
	    if (actual+i<398) {
		strcpy(transdat->mkeymap.functext+i, buf);
		i=i+actual+1; /* 1 for eos */
	    }
	}
    }
    transdat->mkeymap.functext[i]='\0'; /* just to be on the safe side */
}


/* StealSprite - find out where the data for sprite 0 (the mouse-      */
/* pointer) is in ram and copy it to the transdat so it can be used as */
/* cursor there.						       */
StealSprite() {
    long *spritedata;
    int i;

    spritedata=(long *)(GfxBase->SimpleSprites[0]->posctldata);
    for (i=0; i<SPRITESZ+1; ++i)
	transdat->spritedata[i]=spritedata[i];
}


/* puts the assembler routine to move the kernel image in the transdat */
/* and puts some addresses in the transdat, then calls an assembler    */
/* routine to run the routine which will move and then run the kernel  */
mlroutine(argc, argv)
int argc;
char *argv[]; {
    int i, mlsize;
    char *src;

    mlsize=(unsigned long)movek_end-(unsigned long)movek_start;
    src=(char *)movek_start;    /* _movek_start sits in loaderasm.s    */
    for (i=0; i<mlsize; ++i)
	transdat->mlroutine[i]=src[i];
    transdat->loadkernel=(long)kernel;
    transdat->runkernel=kernelstart;
    transdat->runaddress=kernelstart;
    for (i=0; i<argc-1; ++i)
	if (!strcmp(argv[i], "-e"))
	    transdat->runaddress=strval(argv[i+1], 1);
    transdat->kernelsz=kernelsz;
#ifdef DEBUG
    if (debuglvl&1) {
	printf("transdat at                $%06lx\n", transdat);
	printf("kernel loaded at           $%06lx\n", transdat->loadkernel);
	printf("kernel will be moved to    $%06lx\n", transdat->runkernel);
	printf("kernel will be executed at $%06lx\n", transdat->runaddress);
	printf("kernel size                $%06lx\n", transdat->kernelsz);
	printf("press left mousebutton to continue\n");
	getmouse();
    }
#endif
    SuperState();               /* switch to supervisor mode           */
    *INTENA=0x7fff;		/* disable all interrupts	       */
    *INTREQ=0x7fff;		/* clear all pending interrupts        */
    *DMACON=0x07ff;		/* disable all dma		       */
    run_movek(transdat);        /* see loaderasm.s                     */
}


#ifdef DEBUG
/* waits for you to press the left mousebutton			       */
/* reads the port directly and uses busy waiting		       */
getmouse() {
    BYTE b;

    while (b=*PRAA, b & 1<<FIR0);
}
#endif


/*
 * returns the value of the string as a long
 * when it starts with $ it is assumed to be
 * hex, decimal otherwise
 */
long strval(str, fatal)
char *str;
int fatal; {
    long t;
    int s;

    if (*str=='$')
	s=sscanf(str+1, "%lx", &t);
    else
	s=sscanf(str, "%ld", &t);
    if (s!=1) {
	if (fatal)
	    printf("numeric argument missing\n");
	t=0;
    }
    return t;
}


/*
 * returns the location in rom where the topaz.8
 * font data starts
 */
char *open_topaz() {
    struct TextFont *tmp;

    tmp=OpenFont(&topaz8); /* see loader.h, struct filled in for topaz.8 */
    if (!tmp || tmp->tf_XSize!=8 || tmp->tf_YSize!=8) {
	printf("hey, your topaz font is gone !\n");
	exit(20);
    }
    return (char *)(tmp->tf_CharData);
}
