/*
 * Load and launch Linux/MIPS kernel.
 *
 * Copyright (C) 1994, 1995 by Waldorf Electronics,
 * written by Ralf Baechle and Andreas Busse
 *
 * Loosly based on bootstrap.c for Linux/68k,
 * Copyright (C) 1993 by Hamish Macdonald, Greg Harp
 *
 * This file is subject to the terms and conditions of the
 * GNU General Public License. See the file COPYING in the
 * main directory of this archive for more details.
 */
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>

#include <sys/file.h>
#include <sys/types.h>

#include <linux/string.h>
#include <linux/tty.h>
#include <linux/a.out.h>

#include <asm/asm.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/bootinfo.h>
#include <asm/segment.h>

#include <arc/arc.h>

#include <stand/identify.h>
#include <stand/loader.h>
#include <stand/stdio.h>
#include <stand/mkargs.h>

/*
 * Prototypes
 */
void Deskstation_Tyne_init(void);
void usage(void);

/*
 * Defaults, may be overiden by option or environment.
 */
static char *kernel_name = "multi(0)disk(0)fdisk(0)\\VMLINUX";
static char *ramdisk_name = NULL;
int debugflag = 1;

extern char *optarg;

extern volatile void launch(char *memptr, u_long kernel_size, u_long rd_size);
static char *memptr;

u_long loadaddr    = LOADADDR;	    /* mallocinit() needs that */
u_long kernel_base = KERNELBASE;    /* whereever that is... */
u_long start_mem;                   /* always true on an ARC system */
u_long mem_size;
u_long rd_size;
u_long kernel_entry;
u_long kernel_size;
u_long kernel_bss_end;

struct bootinfo bi;		    /* Linux boot info */
struct screen_info si;              /* Linux screen info */
struct DisplayInfo *di;             /* ARC display info */

/*
 * For now we just use the aout loader
 */
extern struct loader loader_aout;
struct loader *kld = &loader_aout;

/*
 * main()
 */
int main(int argc, char *argv[])
{
    int ch, kfd, rfd = -1, i;
    int interactive = 1;
    u_long memreq;
    struct nlist *nl;
    u_long kbi_offset, ksi_offset;
    int realargc = argc;
    char **realargv = argv;
    char args[1024];
    char *newargv[256];
    char *OSLoadOptions;
  
    start_mem = KSEGX(LOADADDR);

    /*
     * Print the greet message
     */
    printf("Linux/MIPS ARC Bootstrap Loader V" STR(VERSION) "\r\n");
    printf("Copyright (C) Waldorf Electronics 1994, 1995\r\n\r\n");
    
    /*
     * Analyse arguments
     */
    OSLoadOptions = getenv("OSLoadOptions");
    realargc = argc;
    if (argc <= 1)
    {
        /*
         * If we don't get arguments, we'll default to the environment
         * variables values. Happens only when run manual.
         */
	if(OSLoadOptions)
        {
            strncpy(args, argv[0], 1023);
            strncat(args, " ", 1023);
            if(OSLoadOptions) strncat(args, OSLoadOptions, 1023);
            *(args + strlen(args)) = '\0';
            mkargs(&realargc, argv, args);
        }
    }
    else
    {
        int i;
        /*
         * Grep the OSLoadOptions from the option string
         * and use it as the real option string
	 */
        realargc = argc;
        realargv = argv;
        for(i=1;i < argc;i++)
        {
            if(strncasecmp("OSLoadOptions=", argv[i], 13) == 0)
            {
                strncpy(args, argv[0], 1023);
                strncat(args, " ", 1023);
                strncat(args, argv[i] + 14, 1023);
		*(args + strlen(args)) = '\0';
                mkargs(&realargc, newargv, args);
                realargv = newargv;
            }
        }
    }

    if(realargc > 1)
    {
        while ((ch = getopt(realargc, realargv, "dik:r:")) != EOF)
            switch (ch)
            {
            case 'd':
                debugflag = 1;
                break;
            case 'i':
                interactive = 1;
                break;
            case 'k':
                kernel_name = optarg;
                break;
            case 'r':
                ramdisk_name = optarg;
                break;
            case '?':
            default:
                usage();
            }
    }

    /*
     * Identify the system we're running on
     */
    identify();

    if (interactive)
        debug();
    
    /*
     * verify that there is enough RAM
     */
    mem_size = bi.memupper - bi.memlower;
    if (mem_size < 0x800000)
    {
        fprintf(stderr,
		"Insufficient Memory to load Linux/MIPS, aborting\n\r");
        exit(5);
    }

    /*
     * Open kernel and gather some data from the executable
     */
    if (kld->ld_open_kernel(kernel_name) < 0)
    {
	fprintf(stderr, "Error opening kernel file %s.\n\r", kernel_name);
	exit(5);
    }
    kernel_base = kld->ld_get_kbase();
    kernel_size = kld->ld_get_ksize();
  
    bi.ramdisk_size = 0;		/* default: no ramdisk */
    if (ramdisk_name)
    {
        if ((rfd = open (ramdisk_name, O_RDONLY)) == -1)
        {
            fprintf (stderr, "Unable to open ramdisk file %s\n\r", ramdisk_name);
            exit (EXIT_FAILURE);
        }

        /*
         * record ramdisk size
         */
        bi.ramdisk_size = (lseek (rfd, 0, L_XTND) + 1023) >> 10;

        rd_size = lseek (rfd, 0, L_XTND);
	if (rd_size & ((1<<10)-1))
        {
            /*
             * Most probably the file is no image at all or has been
             * corrupted, so terminate.
             */
            printf("Error: Ramdisk size is not multiple of 1024 bytes.\n");
        }
        bi.ramdisk_size = rd_size >> 10;
    }

    bi.ramdisk_base = (u_long)start_mem + mem_size - rd_size;
  
    /*
     * find offset to boot_info structure
     */
    if (!(nl = kld->ld_get_nlist (kld->ld_isymbol("boot_info"))))
    {
        perror ("get_nlist1");
        exit (EXIT_FAILURE);
    }
    else
    {
        kbi_offset = nl->n_value - kernel_base;
        free (nl);
    }
  
    /*
     * Find offset to screen_info structure
     */
    if (!(nl = kld->ld_get_nlist (kld->ld_isymbol("screen_info"))))
    {
        perror ("get_nlist2");
        exit (EXIT_FAILURE);
    }
    else
    {
        ksi_offset = nl->n_value - kernel_base;
        free (nl);
    }

    /*
     * Find kernel entry point
     */
    if (!(nl = kld->ld_get_nlist (kld->ld_isymbol("kernel_entry"))))
    {
        perror ("get_nlist3");
        exit (EXIT_FAILURE);
    }
    else
    {
        kernel_entry = nl->n_value;
        free (nl);
    }

    /*
     * End of bss segment - ramdisk will be placed there
     */
    if (!(nl = kld->ld_get_nlist (kld->ld_isymbol("_end"))))
    {
        perror ("get_nlist3");
        exit (EXIT_FAILURE);
    }
    else
    {
        kernel_bss_end = nl->n_value;
        free (nl);
    }

    memreq = kernel_size + rd_size;
    if (!(memptr = (char *) malloc(memreq)))
    {
        fprintf (stderr, "Unable to allocate %d bytes of memory\n\r", memreq);
        exit (EXIT_FAILURE);
    }
    memset(memptr, 0, memreq);

    /*
     * Should check whether kernel & RAMdisk moving doesn't overwrite
     * the bootstraper during startup.
     */
    if (debugflag)
    {
	/*
	 * The following text should be printed by the loader module
	 */
        printf ("\r\n");
	kld->ld_print_stats();
        printf ("Kernel entry at 0x%08lx\n\r", kernel_entry);

        if (bi.ramdisk_size)
        {
            printf ("RAM disk image at 0x%08lx, size is %ldK\n\r",
                    (u_long)memptr + kernel_size,
                    bi.ramdisk_size);
            printf ("Ramdisk src. top is 0x%08lx\n\r", start_mem + mem_size);
            printf ("Ramdisk low limit is 0x%08lx\n\r",
                    (u_long)memptr + kernel_size);
            printf ("Ramdisk dest. top is 0x%08lx\n\r",
                    (u_long)memptr + kernel_size + rd_size);
        }
    }

    /*
     * Now do the real loading
     */
    if (kld->ld_load_kernel(memptr) < 0)
    {
        fprintf (stderr, "Failed to load kernel executable\r\n");
        free(memptr);
        exit (EXIT_FAILURE);
    }
    kld->ld_close_kernel();

    /*
     * Load the ramdisk image
     */
    if (rfd != -1)
    {
        if (lseek (rfd, 0, L_SET) == -1)
        {
            fprintf(stderr, "Failed to seek to beginning of ramdisk file\n\r");
            free(memptr);
            exit (EXIT_FAILURE);
        }
        if (read(rfd, memptr + kernel_size, rd_size) != rd_size)
        {
            fprintf (stderr, "Failed to read ramdisk file\n\r");
            free(memptr);
            exit (EXIT_FAILURE);
        }
        close (rfd);
    }

    if (debugflag)
    {
        printf("\r\n");
        printf ("Hit [Enter] to continue the Linux boot...");
        fflush (stdout);
        getchar();
        printf ("\r\n");
    }

    printf("Starting the Kernel...\r\n");

    /*
     * Initialize the Linux display with ARC data
     */
    if (di = getDisplayInfo())
    {
        si.orig_x            = di->cursor_x;
        si.orig_y            = di->cursor_y;
        si.orig_video_cols   = di->columns;
        si.orig_video_lines  = di->lines;
    }
    else
    {
        printf("Display Initialization failed. Exiting...\r\n");
        sleep(2);
        exit(EXIT_FAILURE);
    }

    /*
     * copy the boot and screen info structures to the kernel image,
     * then execute the copy-and-go code
     */
    memcpy ((void *)(memptr + kbi_offset), &bi, sizeof(bi));
    memcpy ((void *)(memptr + ksi_offset), &si, sizeof(si));
  
    /*
     * Run machine initialization as late as possible
     */
    if (bi.machtype == MACH_DESKSTATION_TYNE)
    {
        Deskstation_Tyne_init();	/* Tynes need some special initialization */
        sleep(2);			/* wait for things to settle down */
    }
  
    launch(memptr, kernel_size, rd_size);

    /* NOTREACHED */
  
    printf("Kernel returned -- exiting...\r\n");
    sleep(10);
    return(0);
}

/*
 * Deskstation_init()
 */
void Deskstation_Tyne_init(void)
{
    /*
     * Grr... The ARC BIOS doesn't setup the hardware like
     * a Intel PC BIOS would do...
     */
    outb_p(0x6,0x3ce);
    outb_p(0xe,0x3cf);

    /* FIXME: This is a hack for Tyne boards. Unfortunally
     * we cannot execute the VGA card bios, so we hardcode
     * some variables here... Will change later.
     */
    
    si.orig_x = 0;
    si.orig_y = 0;
    si.orig_video_page = 52;
    si.orig_video_mode = 3;
    si.orig_video_cols = 80;
    si.orig_video_ega_ax = 4626;
    si.orig_video_ega_bx = 3;
    si.orig_video_ega_cx = 9;
    si.orig_video_lines = 50;
}

/*
 * usage()
 *
 * Options:
 * -d - enable debugging (default is no debugging)
 * -i - interactive (default is noninteractive)
 * -k - kernel executable (default multi(0)disk(0)fdisk(0)\VMLINUX)
 * -r - ramdisk image (default is no ramdisk)
 */
void usage(void)
{
    fprintf (stderr, "Usage:\n"
           "\tmilo [-d] [-i] [-k kernel_executable] [-r ramdisk_file]"
           " [option...]\n");
    exit (EXIT_FAILURE);
}
