/*
** bootstrap.c -- This program loads the Amiga Linux kernel and launches it.
**
** Copyright 1993 by Hamish Macdonald, Greg Harp
**
** This file is subject to the terms and conditions of the GNU General Public
** License.  See the file README.legal in the main directory of this archive
** for more details.
**
*/

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/file.h>
#include <sys/types.h>
#include <unistd.h>

/* Amiga linux include files */
#include <linux/a.out.h>
#include <machine/ktypes.h>
#include <machine/sys_info.h>

/* minimum fast memory required to run Linux (in 4096 byte pages) */
#define MIN_MEMORY   512

/* temporary stack size */
#define TEMP_STACKSIZE	256

/* Exec Base */
extern struct ExecBase *SysBase;

extern char *optarg;

struct exec kexec;
char *memptr;

struct ExpansionBase
    *ExpansionBase;

int kmem_index = 0;
int chip_index = 0;

struct sys_info si, *ksi;

#define START_MEM    (si.memory[kmem_index].addr)
#define MEM_SIZE     (si.memory[kmem_index].size)

extern struct nlist *get_nlist (const char *fname, const char *symname);

extern void change_stack (u_long *);
extern void disable_cache (void);
extern void disable_mmu (void);
extern void jump_to (void *);

void usage(void)
{
    fprintf (stderr, "Usage:\n"
	     "\tbootstrap [-d] [-k kernel_executable] [-r ramdisk_file]\n");
    exit (EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    int ch, debugflag = 0, kfd, rfd = -1, i;
    long fast_total = 0;	     /* total Fast RAM in system */
    struct MemHeader *mnp;
    struct ConfigDev *cdp = NULL;
    void *mem_top = NULL;
    char *kernel_name = "vmlinux";
    char *ramdisk_name = NULL;
    u_long memreq;
    struct nlist *nl;
    u_long *stack;

    /* print the greet message */
    puts("Amiga Linux Bootstrap version 1.2");
    puts("Copyright 1993 by Hamish Macdonald and Greg Harp\n");

    /* machine is Amiga */
    si.machtype = MACH_AMIGA;

    /* check arguments */
    while ((ch = getopt(argc, argv, "dk:r:")) != EOF)
	switch (ch) {
	  case 'd':
	    debugflag = 1;
	    break;
	  case 'k':
	    kernel_name = optarg;
	    break;
	  case 'r':
	    ramdisk_name = optarg;
	    break;
	  case '?':
	  default:
	    usage();
	}

    SysBase = *(struct ExecBase **)4;

    /* Memory & AutoConfig based on 'unix_boot.c' by C= */

    /* open Expansion Library */
    ExpansionBase = (struct ExpansionBase *)OpenLibrary("expansion.library",
							36);
    if (!ExpansionBase) {
	puts("Unable to open expansion.library V36 or greater!  Aborting...");
	exit(EXIT_FAILURE);
    }

    /* find all of the autoconfig boards in the system */
    cdp = (struct ConfigDev *)FindConfigDev(cdp, -1, -1);
    for (i=0; (i < NUM_AUTO) && cdp; i++) {
	/* copy the contents of each structure into our boot info */
	memcpy(&si.si_amiga.autocon[i], cdp, sizeof(struct ConfigDev));

	/* count this device */
	si.si_amiga.num_autocon++;

	/* get next device */
	cdp = (struct ConfigDev *)FindConfigDev(cdp, -1, -1);
    }

    /* find out the memory in the system */
    for (mnp = (struct MemHeader *)SysBase->MemList.l_head;
	 (si.num_memory < NUM_MEMINFO) && mnp->mh_Node.ln_Succ;
	 mnp = (struct MemHeader *)mnp->mh_Node.ln_Succ)
    {
	struct MemHeader mh;

	/* copy the information */
	mh = *mnp;

	/* if we suspect that Kickstart is shadowed in an A3000,
	   modify the entry to show 512K more at the top of RAM */
	if (mh.mh_Upper == (void *)0x07f80000)
	    mh.mh_Upper = (void *)0x08000000;

	/* mask the memory limit values */
	mh.mh_Upper = (void *)((u_long)mh.mh_Upper & 0xfffff000);
	mh.mh_Lower = (void *)((u_long)mh.mh_Lower & 0xfffff000);

	/* if fast memory */
	if (mh.mh_Attributes & MEMF_FAST) {
	    /* record the start */
	    si.memory[si.num_memory].addr = (void *)mh.mh_Lower;

	    /* set the size value to the size of this block */
	    si.memory[si.num_memory].size =
		(u_long)mh.mh_Upper - (u_long)mh.mh_Lower;

	    fast_total += si.memory[si.num_memory].size;

	    /* remember the highest addressed RAM */
	    if (mem_top < mh.mh_Lower) {
		mem_top = mh.mh_Lower;
		kmem_index = si.num_memory;
	    }

	    /* count this block */
	    si.num_memory++;
	} else if (mh.mh_Attributes & MEMF_CHIP) {
	    /* if CHIP memory, record the size */
	    si.si_amiga.chip_size =
		(u_long)mh.mh_Upper - (u_long)mh.mh_Lower;
	}
    }

    CloseLibrary((struct Library *)ExpansionBase);

    /* get info from ExecBase */
    si.si_amiga.vblank = SysBase->VBlankFrequency;
    si.si_amiga.psfreq = SysBase->PowerSupplyFrequency;
    si.si_amiga.eclock = SysBase->EClockFrequency;

    /* display and set the CPU <type */
    printf("CPU: ");
    if (!(SysBase->AttnFlags & (AFF_68040 | AFF_68030 | AFF_68020))) {
	puts ("Insufficient for Linux.  Aborting...");
	printf ("SysBase->AttnFlags = %#x\n", SysBase->AttnFlags);
	exit (EXIT_FAILURE);
    }

    if (SysBase->AttnFlags & AFF_68040) {
	puts ("68040 -- Alright!");
	si.cputype = CPU_68040;
    } else if (SysBase->AttnFlags & AFF_68030) {
	printf ("68030");
	si.cputype = CPU_68030;
    } else if (SysBase->AttnFlags & AFF_68020) {
	printf ("68020 (Do you have an MMU?)");
	si.cputype = CPU_68020;
    }

    if (SysBase->AttnFlags & AFF_68882) {
	printf (" with 68882 FPU");
	si.cputype |= FPU_68882;
    } else if (SysBase->AttnFlags & AFF_68881) {
	printf (" with 68881 FPU");
	si.cputype |= FPU_68881;
    }

    putchar ('\n');
    putchar ('\n');

    /* TEMPORARY RESTRICTION ON CPU/MMU TYPE */
    if (!(si.cputype & CPU_68030)) {
	fprintf (stderr, "Amiga Linux currently only supported on MC68030\n");
	exit(EXIT_FAILURE);
    }

    /* display the clock statistics */
    printf("Vertical Blank Frequency: %dHz\nPower Supply Frequency: %dHz\n",
	   si.si_amiga.vblank, si.si_amiga.psfreq);
    printf("EClock Frequency: %7.5fMHz\n\n",
	   (float)si.si_amiga.eclock / 100000);

    /* display autoconfig devices */
    if (si.si_amiga.num_autocon) {
	printf("Found %d AutoConfig Device%s", si.si_amiga.num_autocon,
	       (si.si_amiga.num_autocon > 1)?"s\n":"\n");
	for (i=0; i<si.si_amiga.num_autocon; i++)
	    printf("Device %d: addr = %08lx\n", i,
		   (u_long)si.si_amiga.autocon[i].cd_BoardAddr);
    } else
    puts("No AutoConfig Devices Found");

    /* display memory */
    if (si.num_memory) {
	printf("\n%d Block%sof Memory Found\n", si.num_memory,
	       (si.num_memory > 1)?"s ":" ");
	for (i=0; i<si.num_memory; i++) {
	    printf("Block %d: %08lx to %08lx (%dKB)\n",
		   i, (u_long)si.memory[i].addr,
		   (u_long)si.memory[i].addr + si.memory[i].size,
		   si.memory[i].size/1024);
	    putchar('\n');
	}
    } else {
	puts("No memory found?!  Aborting...");
	exit(10);
    }

    /* display chip memory size */
    printf ("%dK of CHIP memory\n", si.si_amiga.chip_size/1024);

    /* tell us where the kernel will go */
    printf("\nThe kernel will be located at %08lx\n", (u_long)mem_top);

    /* verify that there is enough Chip RAM */
    if (si.si_amiga.chip_size < 512*1024) {
	puts("\nNot enough Chip RAM in this system.  Aborting...");
	exit(10);
    }

    /* verify that there is enough Fast RAM */
    if (fast_total < 2*1024*1024) {
	puts("\nNot enough Fast RAM in this system.  Aborting...");
	exit(10);
    }

    /* open kernel executable and read exec header */
    if ((kfd = open (kernel_name, O_RDONLY)) == -1) {
	fprintf (stderr, "Unable to open kernel file %s\n", kernel_name);
	exit (EXIT_FAILURE);
    }

    if (read (kfd, (void *)&kexec, sizeof(kexec)) != sizeof(kexec)) {
	fprintf (stderr, "Unable to read exec header from %s\n",
		 kernel_name);
	exit (EXIT_FAILURE);
    }

    if (ramdisk_name) {
	if ((rfd = open (ramdisk_name, O_RDONLY)) == -1) {
	    fprintf (stderr, "Unable to open ramdisk file %s\n",
		     ramdisk_name);
	    exit (EXIT_FAILURE);
	}
	/* record ramdisk size */
	si.ramdisk_size = (lseek (rfd, 0, L_XTND)) >> 10;
    } else
	si.ramdisk_size = 0;

    /* find offset to sys_info structure */
    if (!(nl = get_nlist (kernel_name, "_sys_info"))) {
	perror ("get_nlist");
	exit (EXIT_FAILURE);
    } else {
	ksi = (struct sys_info *)((u_long)START_MEM
				  + nl->n_value - kexec.a_entry);
	free (nl);
    }

    memreq = kexec.a_text + kexec.a_data + (si.ramdisk_size << 10);
    if (!(memptr = (char *)AllocMem (memreq, MEMF_FAST | MEMF_CLEAR))) {
	fprintf (stderr, "Unable to allocate memory\n");
	exit (EXIT_FAILURE);
    }

    if (lseek (kfd, N_TXTOFF(kexec), L_SET) == -1) {
	fprintf (stderr, "Failed to seek to text\n");
	FreeMem ((void *)memptr, memreq);
	exit (EXIT_FAILURE);
    }
    if (read (kfd, memptr, kexec.a_text) != kexec.a_text) {
	fprintf (stderr, "Failed to read text\n");
	FreeMem ((void *)memptr, memreq);
	exit (EXIT_FAILURE);
    }
    if (lseek (kfd, N_DATOFF(kexec), L_SET) == -1) {
	fprintf (stderr, "Failed to seek to data\n");
	FreeMem ((void *)memptr, memreq);
	exit (EXIT_FAILURE);
    }
    if (read (kfd, memptr + kexec.a_text, kexec.a_data) != kexec.a_data) {
	fprintf (stderr, "Failed to read data\n");
	FreeMem ((void *)memptr, memreq);
	exit (EXIT_FAILURE);
    }
    close (kfd);

    if (rfd != -1) {
	if (lseek (rfd, 0, L_SET) == -1) {
	    fprintf (stderr, "Failed to seek to beginning of ramdisk file\n");
	    FreeMem ((void *)memptr, memreq);
	    exit (EXIT_FAILURE);
	}
	if (read (rfd, memptr + kexec.a_text + kexec.a_data,
	      si.ramdisk_size << 10) != (si.ramdisk_size << 10)) {
	    fprintf (stderr, "Failed to read ramdisk file\n");
	    FreeMem ((void *)memptr, memreq);
	    exit (EXIT_FAILURE);
	}
	close (rfd);
    }

    /* allocate temporary chip ram stack */
    stack = (u_long *)AllocMem( TEMP_STACKSIZE, MEMF_CHIP|MEMF_CLEAR);
    if (!stack) {
	fprintf (stderr, "Unable to allocate memory for stack\n");
	FreeMem ((void *)memptr, memreq);
	exit (EXIT_FAILURE);
    }

    if (debugflag) {
	if (si.ramdisk_size)
	    printf ("RAM disk at %#lx, size is %dK\n",
		    (u_long)memptr + kexec.a_text + kexec.a_data,
		    si.ramdisk_size);

	printf ("\nKernel text at %#lx, code size %d\n",
	       (u_long)START_MEM + N_TXTADDR(kexec), kexec.a_text);
	printf ("Kernel data at %#lx, data size %d\n",
	       (u_long)START_MEM + N_DATADDR(kexec), kexec.a_data );
	printf ("Kernel bss  at %#lx, bss  size %d\n",
	       (u_long)START_MEM + N_BSSADDR(kexec), kexec.a_bss );

	printf ("\nKernel sys_info is at %#lx physical, %#lx virtual\n",
		(u_long)ksi, (u_long)ksi - (u_long)START_MEM + kexec.a_entry);

	printf ("\nKernel entry is %#lx\n", kexec.a_entry );

	printf ("ramdisk dest top is %#lx\n", (u_long)START_MEM + MEM_SIZE);
	printf ("ramdisk lower limit is %#lx\n",
		(u_long)(memptr + kexec.a_text + kexec.a_data));
	printf ("ramdisk src top is %#lx\n",
		(u_long)(memptr + kexec.a_text + kexec.a_data)
		+ (si.ramdisk_size << 10));

	printf ("Type a key to continue the Linux boot...");
	fflush (stdout);
	getchar();
    }

    /* wait for things to settle down */
    sleep(2);

    Disable();

    /* Go into supervisor state */
    SuperState ();

    /* setup stack */
    change_stack (stack);

    /* turn off caches */
    disable_cache ();

    /* turn off any mmu translation */
    disable_mmu ();

    {
	/*
	 * there may be problems here if the compiler optimizer
	 * doesn't put these variables in registers, and the
	 * stack space was allocated at the beginning of the function
	 * and we've changed the stack.
	 */
	u_long *src, *dest, *limit;
	u_char *csrc, *cdest, *climit;

	/*
	 * copy the kernel text and data to their final resting place.
	 * The text is padded out (in the a.out file) to a multiple of
	 * the page size.
	 */

	src = (u_long *)memptr;
	dest = (u_long *)START_MEM;
	limit = (u_long *)(memptr + kexec.a_text + kexec.a_data);
	while (src < limit)
	    *dest++ = *src++;

	/* clear kernel bss */
	dest = (u_long *)((u_long)START_MEM + kexec.a_text + kexec.a_data);
	limit = dest + kexec.a_bss / sizeof(u_long);
	while (dest < limit)
	    *dest++ = 0;

	/* copy the ramdisk to the top of memory (from back to front) */
	dest = (u_long *)((u_long)START_MEM + MEM_SIZE);
	limit = (u_long *)(memptr + kexec.a_text + kexec.a_data);
	src = (u_long *)((u_long)limit + (si.ramdisk_size << 10));
	while (src > limit)
	    *--dest = *--src;
	si.ramdisk_addr = (u_long)dest;

	/* copy the sys_info struct to the correct location */
	cdest = (u_char *)ksi;
	climit = cdest + sizeof (si);
	csrc = (u_char *)&si;
	while (cdest < climit)
	    *cdest++ = *csrc++;
    }

    /* jump to start of kernel */
    jump_to (START_MEM);

    /* NOTREACHED */
}
