/*****************************************************************************

       Copyright © 1995, 1996 Digital Equipment Corporation,
                       Maynard, Massachusetts.

                        All Rights Reserved

Permission to use, copy, modify, and distribute this software and its 
documentation for any purpose and without fee is hereby granted, provided  
that the copyright notice and this permission notice appear in all copies  
of software and supporting documentation, and that the name of Digital not  
be used in advertising or publicity pertaining to distribution of the software 
without specific, written prior permission. Digital grants this permission 
provided that you prominently mark, as not part of the original, any 
modifications made to this software or documentation.

Digital Equipment Corporation disclaims all warranties and/or guarantees  
with regard to this software, including all implied warranties of fitness for 
a particular purpose and merchantability, and makes no representations 
regarding the use of, or the results of the use of, the software and 
documentation in terms of correctness, accuracy, reliability, currentness or
otherwise; and you rely on the software, documentation and results solely at 
your own risk. 

******************************************************************************/
/*
 *  MIniLOader command interface - MILO
 *
 *  david.rusling@reo.mts.dec.com
 */
#include <linux/kernel.h>
#include <linux/config.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/kernel_stat.h>
#include <linux/pci.h>
#include <linux/bios32.h>
#include <linux/version.h>

#include <asm/system.h>
#include <asm/io.h>
#include <asm/console.h>
#include <asm/hwrpb.h>
#include <asm/page.h>
#include <asm/pgtable.h>

#include <stdarg.h>

#include "milo.h"
#include "osfboot.h"
#include "impure.h"
#include "uart.h"
#include "fs.h"

#ifndef NULL
#define NULL ((void *)0)
#endif

/*
 *  External things - all from boot_main.c
 */
extern U64 *pkernel_args;
extern U64 kernel_at;
extern U64 kernel_entry;
extern U64 kernel_pages;

extern struct pcb_struct boot_pcb;

extern unsigned int ptbr;
extern U64 vptbr;

#if 0
#define DEBUG_MILO 1
#endif

char *scratch = NULL;

#ifndef MINI_EMBED_LINUX
static void ls_parse(void);
static void run_parse(void);
static void read_parse(void);
static void resetenv_parse(void);
static void setenv_parse(void);
static void unsetenv_parse(void);
static void printenv_parse(void);
static void show_parse(void);
#endif
static void boot_parse(void);
static void help_parse(void);
#if defined(CONFIG_ALPHA_CABRIOLET) || defined(CONFIG_ALPHA_EB66P) || defined(CONFIG_ALPHA_EB164) || defined(CONFIG_ALPHA_PC164)

static void bootopt_parse(void);
#endif
#ifdef DEBUG_MILO
static void reboot_parse(void);
#endif

static void parse_command_string(char *string);

struct command {
    char *name;
    char *help_args;
    char *help_text;
    void (*action_rtn)(void);
};

#ifdef MINI_EMBED_LINUX
static struct command commands[] = {
/*
 *  Device specific stuff, showing, booting, running, listing.
 */
    {"boot", 
      "[boot string]", 
      "Boot the Linux embedded image", 
      &boot_parse}, 
/*
 *  Help
 */
    {"help", 
      "", 
      "Print this help text", 
      &help_parse}, 
      };
#else
static struct command commands[] = {
/*
 *  Device specific stuff, showing, booting, running, listing.
 */
    {"ls", 
      "[-t fs] [dev:[dir]]", 
      "List files in directory on device", 
      &ls_parse}, 
    {"boot", 
      "[-t fs] [dev:file] [boot string]", 
      "Boot Linux from the specified device and file", 
      &boot_parse}, 
    {"read", 
      "dev: blknum", 
      "Read a block (256 bytes) from given dev at given block", 
      &read_parse}, 
    {"run", 
      "[-t fs] dev:file", 
      "Run the standalone program dev:file", 
      &run_parse}, 
    {"show", 
      "", 
      "Display all known devices and file systems", 
      &show_parse}, 
/*
 *  Environment variable stuff.
 */
#if defined(MINI_NVRAM) 
      {"setenv", 
      "VAR VALUE", 
      "Set the variable VAR to the specified VALUE", 
      &setenv_parse}, 
      {"unsetenv", 
      "VAR", 
      "Delete the specified environment variable", 
      &unsetenv_parse}, 
      {"resetenv", 
      "", 
      "Delete all environment variables", 
      &resetenv_parse}, 
      {"printenv", 
      "", 
      "Display current environment variable settings", 
      &printenv_parse}, 
#else
      {"set", 
      "VAR VALUE", 
      "Set the variable VAR to the specified VALUE", 
      &setenv_parse}, 
      {"unset", 
      "VAR", 
      "Delete the specified variable", 
      &unsetenv_parse}, 
      {"reset", 
      "", 
      "Delete all variables", 
      &resetenv_parse}, 
      {"print", 
      "", 
      "Display current variable settings", 
      &printenv_parse}, 
#endif
/*
 *  System specific commands
 */
#if defined(CONFIG_ALPHA_CABRIOLET) || defined(CONFIG_ALPHA_EB66P) || defined(CONFIG_ALPHA_EB164) || defined(CONFIG_ALPHA_PC164)
      {"bootopt", 
      "num", 
      "Select firmware type to use on next power up", 
      &bootopt_parse}, 
#endif
#ifdef DEBUG_MILO
/*
 *  Debug commands.
 */
      {"reboot", 
      "", 
      "Reset milo (reboot)", 
      &reboot_parse}, 
#endif
/*
 *  Help
 */
      {"help", 
#if defined(MINI_NVRAM) 
      "[env]", 
#else
      "[var]", 
#endif
      "Print this help text", 
      &help_parse}, 
      };
#endif

#define NUM_MILO_COMMANDS (sizeof(commands) / sizeof(struct command))

#define MAX_TOKENS 10
    char *tokens[10];
    int ntokens = 0;

    static char *__PULL()
{
    char *value = NULL;

    if (ntokens > 0) {
	int i;

	value = tokens[0];
	for (i = 1; i < ntokens; i++)
	     tokens[i - 1] = tokens[i];
	ntokens--;
    }
    return value;
}

#define __ADD(value) tokens[ntokens++] = value

/*****************************************************************************
 *  Action routines                                                          *
 *****************************************************************************/
/*
 *  The linux kernel is either on one of the known devices or it is linked
 *  into this image at a known location.
 *  
 *  If MINI_EMBED_LINUX is defined and the user boots from device
 * "embedded:", then we assume an embedded linux image.
 */

static void boot_cmd(char *fs_type, 
  char *devname, 
  char *filename, 
  char *boot_string)
{
    U64 entry;
    U64 *kernel_args;

#ifdef MINI_EMBED_LINUX
    char *where;

    /* Uncompress it into some temporary memory. */
    where = 
      (char *) kmalloc(START_SIZE + 512, 0) + 512;
#ifdef DEBUG_MINIBOOT
    printk("..uncompressing image into 0x%p\n", where);
#endif
    uncompress_kernel(-1, where);

    entry = load_image_from_memory(where, 
      _PHYSICAL_TO_PFN(START_SIZE));
#else
    entry = load_image_from_device(fs_type, devname, filename);
#endif

    if (entry == 0) {
	printk("MILO: Failed to load the kernel\n");
	return;
    }

    /* now we've loaded the kernel, mark the memory that it occupies as in use
     */
#ifdef BOOT_VERBOSE
    printk("...freeing original marked kernel pages: ");
#endif
    mark_many_pfn(_PHYSICAL_TO_PFN(KERNEL_START), 
      _PHYSICAL_TO_PFN(START_SIZE), 
      FREE_PAGE);
#ifdef BOOT_VERBOSE
    printk("...marking real kernel pages as occupied: ");
#endif
    mark_many_pfn(_PHYSICAL_TO_PFN(KERNEL_START), kernel_pages, 
      ALLOCATED_PAGE);


/*****************************************************************************
 *  Setup the reboot block for when we reboot.                               *
 *****************************************************************************/
    if (milo_reboot_block != NULL) {
#ifdef BOOT_VERBOSE
      printk("Setting up Milo reboot parameter block at 0x%p\n", milo_reboot_block) ;
#endif
      milo_reboot_block->boot_filesystem = fs_name_to_type(fs_type) ;
      milo_reboot_block->boot_device = device_name_to_number(devname) ;
      strncpy(milo_reboot_block->boot_filename, filename, 
	      sizeof(milo_reboot_block->boot_filename)) ;
      if (boot_string == NULL) 
	milo_reboot_block->boot_string[0] = '\0';
      else
	strncpy(milo_reboot_block->boot_string, boot_string, 
		sizeof(milo_reboot_block->boot_string)) ;
      /* now set the reboot flag so that we do the right thing next time */
      milo_reboot_block->flags |= REBOOT_REBOOT ;
    }

/*****************************************************************************
 *  Build the final memory map                                               *
 *****************************************************************************/

    /* This must be done when all required memory has been allocated */
    make_HWRPB_virtual() ;
    build_HWRPB_mem_map() ;

/*****************************************************************************
 *  Set up the area where we pass any boot arguments.                        *
 *****************************************************************************/

    /* for now, just make sure that it is zero */
    memset((void *) ZERO_PGE, 0, PAGE_SIZE);

    strcat((char *) ZERO_PGE, "bootdevice=");
    strcat((char *) ZERO_PGE, devname);
    strcat((char *) ZERO_PGE, " bootfile=");
    strcat((char *) ZERO_PGE, filename);
    if (boot_string != NULL) {
	strcat((char *) ZERO_PGE, " ");
	strcat((char *) ZERO_PGE, boot_string);
    }

    printk("Boot line is %s\n", (char *) ZERO_PGE);

/*****************************************************************************
 *  Set up the Kernel calling information block                              *
 *****************************************************************************/
/*
 *  This is located below this image in memory.  It is 7*8 bytes long and
 *  contains the following:
 * 
 *  Offset      Contents
 *    0*8       Linux kernel entry point
 */
    pkernel_args = kernel_args = (U64 *) LOADER_AT + 8;
    kernel_args[0] = entry;

    printk("Bootstrap complete, kernel @ 0x%Lx, sp @ 0x%Lx\n", entry, 
      (U64)(INIT_STACK + PAGE_SIZE));

/*****************************************************************************
 *  Swap to the new page tables and jump to the Linux Kernel                 *
 *****************************************************************************/

    /* change context */
    printk("...turning on virtual addressing and jumping to the Linux Kernel\n");

    /* build the pcb */
    boot_pcb.ksp = INIT_STACK + PAGE_SIZE;
    boot_pcb.usp = 0;
    boot_pcb.ptbr = ptbr;
    boot_pcb.pcc = 0;
    boot_pcb.asn = 0;
    boot_pcb.unique = 0;
    boot_pcb.flags = 1;

    /* bump up the ipl */
    cli();

    /* now switch to the new scheme of things. */
    printk("\n------------------------------------------------------------\n\n");
    swap_to_palcode((void *) PALCODE_AT, 
      (void *) entry, (void *) INIT_STACK + PAGE_SIZE, 
      ptbr, (void *) vptbr);
/*
 *  If we ever get to this point, something has gone seriously wrong!!!
 */
    printk("If you can read this, something went VERY wrong!\n");
#ifdef NEVER
    while (1)
	;
#endif

}

#ifndef MINI_EMBED_LINUX
/* Load a standalone executable file from disk and run it.  This is 
 * different from boot in that we don't have to set up the system to run
 * Linux.  At the moment, updateflash is the only such executable.
 */
static int run_cmd(char *fs_type, char *devname, char *filename)
{
    U64 entry;
    unsigned long flags;

    entry = load_image_from_device(fs_type, devname, filename);
    if (entry) {
	void (*routine_address)(void);

	/* call the image that we have just loaded. */
	routine_address = (void (*) (void))entry;

	save_flags(flags);
	cli();
	(routine_address)();
	restore_flags(flags);

	/* If we get here, then I guess we can return to the command loop...
	 * although there's no guarantees as to what the program may have done 
	 * to our state... */
	return (0);
    }
    return (-1);
}
#endif

#ifndef MINI_EMBED_LINUX
static void ls_cmd(char *fs_type, char *lsdev, char *lsdir)
{
    int channel;

    channel = device_mount(lsdev, fs_type);
    if (channel < 0) {
	printk("MILO: Cannot access device %s\n", lsdev);
	return;
    }
    __ls(lsdir, lsdev);
    __close(channel);
}
#endif

#ifndef MINI_EMBED_LINUX
static void read_cmd(char *lsdev, int blknum)
{
    unsigned char buf[256];
    int rc, i, n;
    int dummyfd = -1;

    dummyfd = device_open(lsdev);
    if (dummyfd < 0) {
	printk("read: Could not open device: %s\n", lsdev);
	return;
    }
    rc = device_read(dummyfd, buf, sizeof(buf), blknum * 512);
    device_close(dummyfd);

    if (rc < 0) {
	printk("Read of block from %s:[%d] failed\n", lsdev, blknum);
	return;
    }
    printk("\n");
    for (n = 0; n < rc; n += 16) {
	printk(" %3x:", n);
	for (i = 0; i < 16; ++i)
	    printk(" %02X", buf[n + i]);
	printk("  |");
	for (i = 0; i < 16; ++i) {
	    int c = buf[n + i];

	    printk("%c", (c <= ' ' || c > 126) ? '.' : c);
	}
	printk("|\n");
    }
}
#endif

/*****************************************************************************
 *  Parsing action routines                                                  *
 *****************************************************************************/

#ifndef MINI_EMBED_LINUX
static void ls_parse(void) 
{
    char *default_lsdev = getenv("BOOT_DEV");
    char *default_lsdir = "/";
    char *default_fs = "ext2";
    char *token;

    token = __PULL();
    if (token != NULL) {
	if (strcmp("-t", token) == 0) {
	    token = __PULL();
	    if (token == NULL) {
		printk("MILO: 'ls' -t used without file system argument\n");
		return;
	    }
	    default_fs = token;
	    token = __PULL();
	}
    }

    if (token != NULL) {
	char *lsdev, *lsdir;

	/* the user has passed one or more arguments which  supercede any
	 * environment variables and defaults. */

	lsdev = strtok(token, ": \t");
	lsdir = strtok(NULL, " \t");
	default_lsdev = lsdev;
	if (lsdir != NULL)
	     default_lsdir = lsdir;
    }

    if (default_lsdev == NULL) {
	printk("MILO: 'ls' command requires a device name unless\n");
	printk("      BOOT_DEV is set\n");
	return;
    }

    ls_cmd(default_fs, default_lsdev, default_lsdir);
}
#endif

#ifdef MINI_EMBED_LINUX
static void boot_parse(void) 
{
    char *default_bootstring;
    char *token;

    /* did the user supply a bootstring? */
    token = __PULL();
    if (token == NULL) {

	/* the user has not supplied a bootstring */
	default_bootstring = getenv("BOOT_STRING");
    } else {

	/* the user has supplied a bootstring */
	char *bootstring = scratch;

	memset(bootstring, 0, 256);
	while (token != NULL) {
	    strcat(bootstring, token);
	    strcat(bootstring, " ");
	    token = __PULL();
	}
	default_bootstring = bootstring;
    }

    /* strip leading and trailing quotes from the boot string, if there is one.
     */
    if (default_bootstring != NULL) {
	if (strlen(default_bootstring) != 0) {
	    if (default_bootstring[0] == '"')
		 default_bootstring[0] = ' ';
	    if (default_bootstring[strlen(default_bootstring) - 1] == '"')
		default_bootstring[strlen(default_bootstring) - 1] = ' ';
	}
    }

    boot_cmd(NULL, NULL, NULL, default_bootstring);
}

#else
static void boot_parse(void) 
{
    char *default_bootdev;
    char *default_bootfile;
    char *default_bootstring;
    char *default_fs = "ext2";
    char *token;

    default_bootdev = getenv("BOOT_DEV");
    default_bootfile = getenv("BOOT_FILE");

    token = __PULL();
    if (token != NULL) {
	if (strcmp("-t", token) == 0) {
	    token = __PULL();
	    if (token == NULL) {
		printk("MILO: 'boot' -t used without file system argument\n");
		return;
	    }
	    default_fs = token;
	    token = __PULL();
	}
    }

    if (token == NULL) {
	if ((default_bootdev == NULL) || (default_bootfile == NULL)) {
	    printk("MILO: 'boot' with no args requires that the BOOT_DEV and\n");
	    printk("       BOOT_FILE environment variables both be set.\n");
	    return;
	}
	default_bootstring = getenv("BOOT_STRING");
    } else {
	char *bootdev, *bootfile;

	/* the user has passed one or more arguments which  supercede any
	 * environment variables. */

	bootdev = strtok(token, ": \t");
	bootfile = strtok(NULL, " \t");
	if ((bootdev == NULL) || (bootfile == NULL)) {
	    printk("MILO: 'boot' command requires a device name and a file name\n");
	    printk("      separated by a colon, e.g.\n");
	    printk("      MILO> boot fd0:vmlinux\n");
	    return;
	}
	default_bootdev = bootdev;
	default_bootfile = bootfile;

	/* did the user supply a bootstring? */
	token = __PULL();
	if (token == NULL) {

	    /* the user has not supplied a bootstring */
	    default_bootstring = getenv("BOOT_STRING");
	} else {

	    /* the user has supplied a bootstring */
	    char *bootstring = scratch;

	    memset(bootstring, 0, 256);
	    while (token != NULL) {
		strcat(bootstring, token);
		strcat(bootstring, " ");
		token = __PULL();
	    }
	    default_bootstring = bootstring;
	}
    }

    /* strip leading and trailing quotes from the boot string, if there is one.
     */
    if (default_bootstring != NULL) {
	if (strlen(default_bootstring) != 0) {
	    if (default_bootstring[0] == '"')
		 default_bootstring[0] = ' ';
	    if (default_bootstring[strlen(default_bootstring) - 1] == '"')
		default_bootstring[strlen(default_bootstring) - 1] = ' ';
	}
    }

    boot_cmd(default_fs, default_bootdev, default_bootfile,
      default_bootstring);
}
#endif

#ifndef MINI_EMBED_LINUX
static void run_parse(void) 
{
    char *rundev = NULL;
    char *runfile = NULL;
    char *fs = "ext2";
    char *token;

    token = __PULL();
    if (token != NULL) {
	if (strcmp("-t", token) == 0) {
	    token = __PULL();
	    if (token == NULL) {
		printk("MILO: 'ls' -t used without file system argument\n");
		return;
	    }
	    fs = token;
	    token = __PULL();
	}
    }

    if (token != NULL) {
	rundev = strtok(token, ": \t");
	runfile = strtok(NULL, " \t");
    }
    if ((rundev == NULL) || (runfile == NULL)) {
	printk("MILO: 'run' command requires a device name and a file name\n");
	printk("      separated by a colon, e.g.\n");
	printk("      MILO> run fd0:updateflash\n");
    } else {
	run_cmd(fs, rundev, runfile);
    }
}
#endif

#ifndef MINI_EMBED_LINUX
static void read_parse(void) 
{
    char *readdev = NULL;
    int readblk = 0;
    char *token;

    token = __PULL();

    if (token != NULL) {
	int len = strlen(readdev);

	readdev = token;
	if (len > 0 && readdev[len - 1] == ':')
	    readdev[len - 1] = 0;
	token = __PULL();
    }
    if ((token != NULL && (readblk = atoi(token)) < 0) || 
      (readdev == NULL) || (token == NULL)) {
	printk("MILO: 'read' command requires a device name and block number\n");
	printk("      MILO> run fd0: blknum\n");
    } else {
	read_cmd(readdev, readblk);
    }
}
#endif

#if defined(CONFIG_ALPHA_CABRIOLET) || defined(CONFIG_ALPHA_EB66P) || defined(CONFIG_ALPHA_EB164) || defined(CONFIG_ALPHA_PC164)
static void bootopt_parse(void) 
{
    unsigned int opt = 0;
    char *token;

    token = __PULL();
    if (token != NULL)
	 opt = atoi(tokens[1]);

    if (opt > 255) {
	printk("%u is not a valid value for the bootopt argument %s\n", 
	  opt, token);
    } else {
	int ch;

	outb(0x3f, 0x70);
	printk("\nWarning: Changing the bootoption value may render your system\n"
	  "       unusable.  If you boot the miniloader, be sure to connect\n"
	  "       a terminal to /dev/ttyS0 at 9600bps.\n\n"
	  "Current setting: %d\n"
	  "Changing to %d, ok (y/n)? ", inb(0x71), opt);
	ch = kbd_getc();
	printk("%c\n", ch);
	if (ch == 'y') {
	    outb(0x3f, 0x70);
	    outb(opt, 0x71);
	    outb(0x3f, 0x70);
	    printk("New setting: %d\n", opt);
	}
    }
}
#endif

#ifndef MINI_EMBED_LINUX
static void resetenv_parse(void) 
{
    resetenv();
}
#endif

#ifndef MINI_EMBED_LINUX
static void setenv_parse(void) 
{
    extern void dev_config(void);
    char *name, *value;
    char buf[20];
    int i;

    name = __PULL();
    value = __PULL();

    if ((name == NULL) || (value == NULL)) {
	printk("MILO: setenv requires two arguments\n");
    } else {
/*
 *  Check for the special variable MEMORY_SIZE.
 */
	if (strcmp(name, "MEMORY_SIZE") == 0) {
	    int megs = atoi(value);
	    int size;

	    size = megs * 1024 * 1024;
	    if (size < MILO_MIN_MEMORY_SIZE) {
		printk("MILO: invalid memory size\n");
		return;
	    }
	    printk("Setting system memory size to %d megabytes.\n", megs);
	    printk("Re-initializing memory configuration...\n");
	    reset_mem(size);
	}

/*
 *  Check for the special variable BOOT_STRING.
 */
	if (strcmp(name, "BOOT_STRING") == 0) {
	    char *bootstring = scratch;
	    char *token;

	    memset(scratch, 0, 256);
	    token = value;
	    while (token != NULL) {
		strcat(bootstring, token);
		strcat(bootstring, " ");
		token = __PULL();
	    }
	    value = bootstring;
	}

	/* now set the environment variable. */
	setenv(name, value);

/*
 *  Check for the special variables MEMORY_SIZE.
 *  This has to be done *after* the variable has been set.
 */
	for (i = 0; i < 8; i++) {
	    sprintf(buf, "SCSI%d_HOSTID", i);
	    if (strcmp(buf, name) == 0) {
		dev_config();
		break;
	    }
	}
    }
}
#endif

#ifndef MINI_EMBED_LINUX
static void unsetenv_parse(void) 
{
    char *token;

    token = __PULL();
    if (token == NULL) {
	printk("MILO: unsetenv requires at least one argument\n");
    } else {
	unsetenv(token);
    }
}
#endif

#ifndef MINI_EMBED_LINUX
static void printenv_parse(void) 
{
    printenv();
}
#endif

#ifdef DEBUG_MILO
static void reboot_parse(void) 
{
    printk("Calling halt");
    halt();
}
#endif

#ifndef MINI_EMBED_LINUX
static void show_parse(void) 
{
    show_devices();
    show_known_fs();
}
#endif

static void help_parse(void) 
{
#define COMMAND_SIZE 20
    int i;
    int count;
    char *token;

#ifdef MINI_EMBED_LINUX

    printk("MILO command summary:\n\n");

    for (i = 0; i < NUM_MILO_COMMANDS; i++) {
	printk("%s ", commands[i].name);
	count = strlen(commands[i].name);
	count++;
	printk("%s ", commands[i].help_args);
	count += strlen(commands[i].help_args);
	count++;
	if (count < COMMAND_SIZE) {
	    count = COMMAND_SIZE - count;
	} else {
	    printk("\n");
	    count = COMMAND_SIZE;
	}
	while (count--)
	     printk(" ");
	printk("- %s\n", commands[i].help_text);
    }

#else
    token = __PULL();
    if (token == NULL) {
	printk("MILO command summary:\n\n");

	for (i = 0; i < NUM_MILO_COMMANDS; i++) {
	    printk("%s ", commands[i].name);
	    count = strlen(commands[i].name);
	    count++;
	    printk("%s ", commands[i].help_args);
	    count += strlen(commands[i].help_args);
	    count++;
	    if (count < COMMAND_SIZE) {
		count = COMMAND_SIZE - count;
	    } else {
		printk("\n");
		count = COMMAND_SIZE;
	    }
	    while (count--)
		 printk(" ");
	    printk("- %s\n", commands[i].help_text);
	}

	/* some extra information */
	printk("\nDevices are specified as: fd0, hda1, hda2, sda1...\n");
	printk("Use the '-t filesystem-name' option if you want to use\n");
	printk("  anything but the default filesystem  ('ext2').\n");
	printk("Use the 'show' command to show known devices and filesystems.\n");

#if defined(MINI_NVRAM) 
	printk("Type 'help env' for a list of environment variables.\n\n");
#else
	printk("Type 'help var' for a list of variables.\n\n");
#endif
    } else {

#if defined(MINI_NVRAM) 
	if (strcmp(token, "env")) {
	    printk("MILO: unrecognised help subject\n");
	    return;
	}

	printk("Environment variables that MILO cares about:\n");
	printk("  MEMORY_SIZE      - System memory size in megabytes\n");
	printk("  AUTOBOOT         - If set, MILO attempts to boot on powerup\n");
	printk("                     and enters command loop only on failure\n");
	printk("  AUTOBOOT_TIMEOUT - Seconds to wait before auto-booting on powerup\n");
	printk("  BOOT_DEV         - Specifies the default boot device\n");
	printk("  BOOT_FILE        - Specifies the default boot file\n");
	printk("  BOOT_STRING      - Specifies the boot string to pass to the kernel\n");
	printk("  SCSIn_HOSTID     - Specifies the host id of the n-th SCSI controller.\n\n");
#else
	if (strcmp(token, "var")) {
	    printk("MILO: unrecognised help subject\n");
	    return;
	}

	printk("Variables that MILO cares about:\n");
	printk("  MEMORY_SIZE      - System memory size in megabytes\n");
	printk("  BOOT_DEV         - Specifies the default boot device\n");
	printk("  BOOT_FILE        - Specifies the default boot file\n");
	printk("  BOOT_STRING      - Specifies the boot string to pass to the kernel\n");
	printk("  SCSIn_HOSTID     - Specifies the host id of the n-th SCSI controller.\n\n");
#endif
    }
#endif
}


/*****************************************************************************
 *  MILO Command processing.                                                 *
 *****************************************************************************/
/*
 *  And what are little Milo's made of?
 */
static void milo_whatami(void) 
{
    extern U64 milo_memory_size;

    printk("[");
#ifdef MINI_BIOSEMU
    printk("BIOS Emulation");
#else
#ifdef MINI_TGA
    printk("TGA");
#else
    printk("ISA VGA");
#endif
#endif
    if (milo_reboot_block != NULL) {
      if (milo_reboot_block->flags & REBOOT_WNT) 
	printk(", WNT ARC Loaded");
    }
#ifdef CONFIG_BLK_DEV_IDE
    printk(", IDE");
#endif
#ifdef CONFIG_SCSI
    printk(", SCSI");
#endif
    printk(", Floppy");
    printk(", Memory size is %LdMBytes", milo_memory_size / 0x100000);
    printk("]\n");
}

int tolower(int c)
{
    if ((c >= 'A') && (c <= 'Z'))
	 c = c - 'A' + 'a';
    return c;
}

static int partial_compare(char *token, char *command)
{
    int len;
    int i;

    if (strlen(command) < strlen(token))
	return FALSE;
    len = strlen(token);

    for (i = 0; i < len; i++)
	if (tolower(*token++) != tolower(*command++))
	    return FALSE;

    return TRUE;
}

static int compare(char *token, char *command)
{
    int len;
    int i;

    if (strlen(command) != strlen(token))
	return FALSE;
    len = strlen(token);

    for (i = 0; i < len; i++)
	if (tolower(*token++) != tolower(*command++))
	    return FALSE;

    return TRUE;
}

static void parse_command()
{
    char *token;
    int i;
    int found = 0;
    int index = -1;

    /* now work out what the command was from the first token */
    token = __PULL();
    if (token != NULL) {

	/* first, look for an absolute match */
	for (i = 0; i < NUM_MILO_COMMANDS; i++) {
	    if (compare(token, commands[i].name) == TRUE) {
		(commands[i].action_rtn)();
		return;
	    }
	}

	/* now, look for a partial absolute match */
	for (i = 0; i < NUM_MILO_COMMANDS; i++) {
	    if (partial_compare(token, commands[i].name) == TRUE) {
		found++;
		index = i;
	    }
	}
	if (found == 0)
	    printk("MILO: unknown command, try typing Help\n");
	else {
	    if (found > 1)
		printk("MILO: more than one command matches, try typing Help\n");
	    else
		(commands[index].action_rtn)();
	}
    }
}

static void parse_command_string(char *string)
{
    char *token;

    /* Grab all of the command tokens */
    ntokens = 0;
    token = strtok(string, " \t");
    while (token != NULL) {

	/* treat ';' as end of line, new command */
	if (strcmp(token, ";") == 0) {
	    parse_command();
	    ntokens = 0;
	} else {
	    __ADD(token);
	}
	token = strtok(NULL, " \t");
    }

    /* now that we've found it, parse it. */
    parse_command();

}

static int do_autoboot_timeout (unsigned timeout)
{
#   define ASCII_ESCAPE	0x1b	/* ASCII code of ESC key */
    int key;

    printk("Hit any key to enter command mode, ESC to boot immediately\n");
    printk("Seconds remaining: ");
    while (timeout--) {
	printk("%4d    \b\b\b\b\b\b\b\b", timeout);
	key = kbd_getc_with_timeout(1);
	if (key >= 0) {
	    if (key == ASCII_ESCAPE) {
		printk("0\n");
		break;
	    } else {
		printk("stopped.\nEntering command mode.\n");
		return -1;	/* argh, user whimped out on us... */
	    }
	}
    }
    return 0;	/* OK, go ahead and boot */
}

static inline void autoboot(void) 
{
    char *default_bootdev;
    char *default_bootfile;
    char *default_bootstring;
    int autoboot_timeout = 0;

    if (!getenv("AUTOBOOT")) return ;

    default_bootdev = getenv("BOOT_DEV");
    default_bootfile = getenv("BOOT_FILE");
    default_bootstring = getenv("BOOT_STRING");

    if (getenv("AUTOBOOT_TIMEOUT")) {
	autoboot_timeout = atoi(getenv("AUTOBOOT_TIMEOUT"));
    }

    if (default_bootdev && default_bootfile) {

	/* We've got something to try.  Do the timeout if necessary... */
	printk("MILO: About to boot %s:%s.\n", default_bootdev, 
	  default_bootfile);
	if (autoboot_timeout && do_autoboot_timeout(autoboot_timeout) < 0) {
	    return;
	}

	/* If boot_cmd returns, then the boot failed and we fall through to the
	 * command loop. */
	boot_cmd("ext2", default_bootdev, default_bootfile,
	  default_bootstring);
    }
}

static inline void reboot(void) 
{
    char bootdev[32] ;
    int reboot_timeout = 30 ;

    /* figure out the device name from the number */
    device_number_to_name(milo_reboot_block->boot_device, bootdev) ;

    /* We've got something to try.  Do the timeout if necessary... */
    printk("MILO: About to reboot %s:%s.\n", bootdev, 
	   milo_reboot_block->boot_filename);
    if (do_autoboot_timeout(reboot_timeout) < 0) {
	return;
    }

    /* If boot_cmd returns, then the boot failed and we fall through to the
     * command loop. */
    boot_cmd(fs_type_to_name(milo_reboot_block->boot_filesystem), 
	     bootdev, milo_reboot_block->boot_filename,
	     milo_reboot_block->boot_string);
}

void milo(void) 
{
    char *command_string;
    extern const char *linux_banner;
/*
 * If AUTOBOOT is set and we have something to boot from, then do so here. 
 * If AUTOBOOT_TIMEOUT is set, give the user a chance to bail out first...
 */

    printk("Linux/Alpha Miniloader (MILO)\n");
    printk(linux_banner);
    milo_whatami();

    /* allocate some memory for command parsing */
    command_string = (char *) kmalloc(2 * 256, 0);
    if (command_string == NULL) {
	printk("MILO: error failed to allocate memory\n");
	while (1)
	    ;
    }
    scratch = command_string + 256;

    sti();

    /* Handle boot/reboot/autoboot */
    if (milo_reboot_block == NULL) 
      autoboot() ;
    else {
      /* Are we rebooting? */
      if (milo_reboot_block->flags & REBOOT_REBOOT) 
	reboot() ;
      else {
	/* We didn't reboot, are there any WNT ARC
	   arguments that we should know about? */
	if (milo_reboot_block->flags & REBOOT_WNT) {
	  /*
	   * OSLOADOPTIONS has been saved in ZERO_PGE, go and parse it to see
	   * if it makes sense.
	   */
	  if (strncmp((char *) ZERO_PGE, "MILO", 4) == 0) {
	    char *ptr = (char *) ZERO_PGE + 4;
	
	    printk("%s\n", ptr);
	    ptr += sizeof("OSLOADOPTIONS");
	    parse_command_string(ptr);
	  }
	}
	autoboot();
      }
    }

    /* parse commands forever */
    while (1) {

	printk("MILO> ");
	kbd_gets(command_string, 256);
	parse_command_string(command_string);
    }
}

