/*
 *  Original Code:
 *  Copyright (C) 1991, Commodore Business Machines, Inc.
 *
 *  Assumes that copying code placed in chip ram is safe from being
 *  overwritten.  Also makes some assumptions about the ELF kernel,
 *  including assuming that the loadable, merged text and data segment
 *  is the first program segment (the first program header table entry)
 *  for fully linked kernels.
 *
 *  Uses bootmethod 3 (EXEC1):
 *
 *	D0 = 3				// boot method 3
 *	D1 = (struct bootinfo *)	// pointer to boot information
 *
 *  91-12-10	mw	Converted original boot2.c file into a standalone
 *			program runnable from AmigaDOS.
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/nodes.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/execbase.h>
#include <devices/trackdisk.h>
#include <hardware/cia.h>
#include <libraries/configregs.h>
#include <libraries/configvars.h>
#include <libraries/expansion.h>
#include <libraries/expansionbase.h>

#include <inline/exec.h>

#define ABSEXECBASE ( (struct ExecBase **)4 )

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/elf.h>
#include <stdio.h>

extern int errno;

/* I like even numbers ;-) */
#define CHIP_BUF_SIZE 1024

#define reloc(adr) (adr)

#include "infoblock.h"
#include "bootdata.h"
#include "bootinfo.h"		/* NAUTO, etc */

#define min(a,b)	((a)<(b)?(a):(b))

/*
 *	Parameters to assembler "copyit" function.
 */

struct copyinfo {
    unsigned char *ci_loadbuf;
    unsigned long ci_vaddr;
    unsigned long ci_size;
    unsigned long ci_entry;
    unsigned long ci_d0;
    unsigned long ci_d1;
    unsigned long ci_copyit[1];	/* Code gets copied here */
};


int
main (int argc, char *argv[])
{
    struct infoblock *infobuf;		/* Buffer for kernel info block */
    unsigned long infobase;		/* Offset of kernel info block */
    unsigned long filebase;		/* Offset of kernel on boot dev */
    struct ExpansionBase *ExpansionBase;
    struct bootinfo *bi;
    struct BootData *bd;
    struct ConfigDev *cdp;
    struct ConfigDev *acp;
    struct MemHeader *mhp;
    struct MemHeader *mnp;
    unsigned long nbytes;
    unsigned long temp;
    struct copyinfo *copyinfo;
    Elf32_Addr bindaddr = 0xffffffff;
    extern void copyit ();

    unsigned char *kernel_image, *chip_buf;
    char *kernel_name = "unix";
    int fd;
    struct stat stb;

    
    switch (argc)
      {
      case 3:
	if (argv[2][0] == '$')
	  sscanf (argv[2], "$%x", &bindaddr);
	else if (argv[2][0] == '0' &&
	         (argv[2][1] == 'x' || argv[2][1] == 'X'))
	  sscanf (argv[2]+2, "%x", &bindaddr);
	else
	  sscanf (argv[2], "%d", &bindaddr);
	/* fall into */

      case 2:
        kernel_name = argv[1];
	/* fall into */
      
      case 1:
	break;

      default:
        fprintf (stderr, "%s [kernel_file [bind_address]]\n", argv[0]);
        return 20;
      }

    if (stat (kernel_name, &stb) == -1)
      {
        fprintf (stderr, "%s: can't access %s, %s.\n",
        	 argv[0], kernel_name, strerror (errno));
	return 20;
      }

    if (bindaddr <= 0x200000)
      {
        fprintf (stderr, "%s: can't bind into CHIP memory.\n", argv[0]);
        return 20;
      }


    ExpansionBase = (struct ExpansionBase *) 
		    OpenLibrary (reloc (EXPANSIONNAME), 0);
    if (ExpansionBase == NULL) 
      {
	fprintf (stderr, "%s: can't open expansion.library.\n", argv[0]);
	return 20;
      }

    chip_buf = AllocMem (CHIP_BUF_SIZE, MEMF_CHIP);
    if (! chip_buf)
      {
        /* well.. if we can't get that much chip mem, displaying some 
         * message probably won't work either, so quit quietly .. */
        CloseLibrary (ExpansionBase);
        return 20;
      }

    /*
     * Open the kernel file, and read it into a large enough buffer
     */
    kernel_image = AllocMem (stb.st_size, MEMF_FAST);
    if (! kernel_image)
      {
        CloseLibrary (ExpansionBase);
	FreeMem (chip_buf, CHIP_BUF_SIZE);
        fprintf (stderr, "%s: out of memory loading kernel.\n", argv[0]);
        return 20;
      }
    
    if ((fd = open (kernel_name, 0)) < 0)
      {
	fprintf (stderr, "%s: can't open %s for read access, %s\n",        
		 argv[0], kernel_name, strerror (errno));
shutup:
        CloseLibrary (ExpansionBase);
	FreeMem (chip_buf, CHIP_BUF_SIZE);
	FreeMem (kernel_image, stb.st_size);
	return 20;
      }
	
    if (read (fd, kernel_image, stb.st_size) != stb.st_size)
      {
        fprintf (stderr, "%s: can't read %s, %s.\n",
        	 argv[0], kernel_name, strerror (errno));
        goto shutup;
      }
    close (fd);

    /*
     *  Allocate space in chip memory to hold the bootinfo structure, and
     *	then go through the various lists and initialize bootinfo.
     */

    nbytes = sizeof (*bi);
    bi = (struct bootinfo *) AllocMem (nbytes, MEMF_CLEAR | MEMF_CHIP);

    acp = bi -> autocon;
    for (cdp = (struct ConfigDev *) ExpansionBase -> BoardList.lh_Head ;
	 cdp -> cd_Node.ln_Succ ;
	 cdp = (struct ConfigDev *) cdp -> cd_Node.ln_Succ) {
	*acp++ = *cdp;
    }

    mhp = bi -> memory;
    for (mnp = (struct MemHeader *) (*ABSEXECBASE) -> MemList.lh_Head;
	 mnp -> mh_Node.ln_Succ;
	 mnp = (struct MemHeader *) mnp -> mh_Node.ln_Succ) 
      {
	*mhp = *mnp;
#ifdef SUPERKICKSTART_KLUDGE	
	/* this trick gets us the 512K back that are used as kick-`WORM' */
	if (mhp->mh_Upper == (APTR)0x07f80000) mhp->mh_Upper = (APTR)0x08000000;
#endif
	mhp++;
      }

    /*
     *  Check to see if a specific binding address has been specified
     *  in the info block.  A value of 0xFFFFFFFF indicates that the
     *  boot code is to do automatic binding (the default) by scanning
     *  the system's memory list and picking an appropriate relocation
     *  address.
     */

    if (bindaddr == 0xFFFFFFFF) bindaddr = bind ();

    /*
     *  Do the relocation.  Note that for now, the text and data segments
     *  must be contiguous in the relocatable ELF file, with text first.
     *  This condition is tested for and enforced by this version.  At some
     *  point, the information passed to the copyit() function needs to be
     *  expanded to allow the text and data segments to occupy noncontiguous
     *  memory prior to transfer to the final execution location by copyit().
     */

    bd = &(bi -> bootdata);
    rel (kernel_image, bindaddr, bd);
    if ((bd -> bd_toffset + bd -> bd_tsize != bd -> bd_doffset)
	|| (bd -> bd_tvaddr + bd -> bd_tsize != bd -> bd_dvaddr)) {
	fprintf (stderr, "%s: Text and data not contiguous!\n",
		 argv[0]);
	goto shutup;
    }

    /*
     *	Initialize the copyinfo structure, copy the code for the copyit
     *	function into chip memory, and then call the copy function in
     *	supervisor mode.
     */

    copyinfo = (struct copyinfo *) chip_buf;
    copyinfo -> ci_loadbuf = kernel_image + bd -> bd_toffset;
    copyinfo -> ci_vaddr   = bd -> bd_tvaddr;
    copyinfo -> ci_size    = bd -> bd_tsize + bd -> bd_dsize;
    copyinfo -> ci_entry   = bd -> bd_entry;
    copyinfo -> ci_d0      = EXEC1;			/* boot method */
    copyinfo -> ci_d1      = (unsigned long) bi;	/* bootinfo struct */
    memcpy (copyinfo -> ci_copyit, reloc (copyit), TD_SECTOR);
    return (supervisor (copyinfo -> ci_copyit, copyinfo));
}
