/*
 * linuxboot.c -- q40 linux loader
 *
 * (c) Richard Zidlicky - GPL version 1 applies
 *
 * ELF and bi_* stuff borrowed from atari linux booter
 *
 * 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.
 */

/* VERSON info in main()  */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <qdos.h>

#include "linuxboot.h"

int is060=0;

int _currfile_;
#define sopen(_name_)	(_currfile_=open(_name_,O_RDONLY))
#define sseek(_off_,_type_)  (lseek(_currfile_,_off_,_type_))
#define sread(_buf_,_size_)  (read(_currfile_,_buf_,_size_))
#define sclose()	(close(_currfile_))


static struct atari_bootinfo bi;

#ifdef BOOTINFO_COMPAT_1_0
static struct compat_bootinfo compat_bootinfo;
#endif /* BOOTINFO_COMPAT_1_0 */

#define MAX_BI_SIZE     (4096)
static u_long bi_size;
static union {
struct bi_record record;
    u_char fake[MAX_BI_SIZE];
} bi_union;
 

char kernel_name[100];          	/* name of kernel image */
char ramdisk_name[100];			/* name of ramdisk image */
char command_line[CL_SIZE];		/* kernel command line */

/* these are defined in the mover code */
extern char copyall, copyallend; 
extern void linux_init();


/***************************** Prototypes *****************************/

static void get_cpu_infos( void );

static void get_mem_infos( void );
static char *format_mb( unsigned long size );
static int check_bootinfo_version( char *memptr);
static int create_bootinfo( void);
static int add_bi_record( u_short tag, u_short size, const void *data);
static int add_bi_string( u_short tag, const u_char *s);




/************************* End of Prototypes **************************/

#define error(fmt,x) {printf(fmt,x);exit(-1);}
#define serror error
#define error0(fmt) {printf(fmt);exit(-1);}
#define serror0 error0


void usage()
{
  printf("Linux loader for Q40, V1.00\n");
  printf("usage: loader-args [-- kernel-commandline]\n"
	 "   loader args:\n"
	 "      -k kernel_name : linux image file (vmlinux)\n"
	 "      -m mem         : physical ram in MB\n"
	 "     optional loader args:\n"
	 "      -v             : verbose\n"
	 "      -d	       : do SRAM debug\n"
	 "      -r ramdisk_name:\n"
	 "      -p             : pause for ramdisk insertion after loading kernel\n"
	 "      -c CPU         : CPU type\n"
	 "      -f             : do not ask any questions or wait for confirmation\n"
	 "     -- kernel args (not checked by loader)\n"
        );
  exit(2);
}

static int mem=0;
static int mem_set=0;
static int kn_set=0;
static int rd_set=0;
static int rd_pause=0;
static int verbose=0;
static int debug=0;
static int do_force=0;

main(int argc, char **argv)
{
 int c;
 char *version="1.00";

 printf("Linux Loader %s\n",version);

 if (argc==0)
	usage();

 while((c=getopt(argc,argv,"k:m:vr:pc:df")) != EOF)
  { 
    switch(c)
     {
     case 'k': kernel_name[0]=0;strcpy(kernel_name,optarg);
	       kn_set++; break;
     case 'm': mem=atol(optarg);
	       mem_set++; break;
     case 'v': verbose=1;break;
     case 'r': ramdisk_name[0]=0;strcpy(ramdisk_name,optarg);
	       rd_set++;break;
     case 'p': rd_pause=1;break;
     case 'c': if (strstr(optarg,"060")) is060=1;
	       else is060=0;
	       break;
     case 'd': debug++;
               break;
     case 'f': do_force++;
               break;
     }
  }

 if (do_force && rd_pause)
   {
     printf("sorry, '-p' and '-p' flags together doesn't work\n");
     usage();
   }
 if (kn_set==0 || mem_set==0)
   {
    printf("kernel name or memory size not specified, both are required args\n");
    usage();
  }
 printf("kernel name %s, memory %dMB\n",kernel_name,mem);
 
 command_line[0]=0;
 /*printf("remaining args %d, argc %d, optind %d\n",argc-optind,argc,optind);*/
 { 
   int i=optind;
   int j=argc-optind;
   while(j-->0)
     {
       /*printf("arg[%d]=%s\n",i,argv[i]);*/
       strcat(command_line,argv[i]);
       strcat(command_line," ");
       i++;
     }
 } 
 
 if (strstr(command_line,"debug=mem") && !debug)
   {
     printf("warning: debug=mem used without '-d', turning it on\n");
     debug++;
   }
 if (verbose)
   printf("command line: %s\n",command_line);
 
 linux_boot();
}

u_long kernel_size;		/* size of kernel image */
char *memptr;		/* addr and size of load region */
u_long start_mem,mem_size;

long arg1,arg2,arg3,arg4,arg5,arg6;

void linux_boot( void )
{
    int i,c;
    char *kname;
    void *bi_ptr;		/* pointer to bootinfo */
    Elf32_Ehdr kexec_elf;	/* header of kernel executable */
    Elf32_Phdr *kernel_phdrs = NULL;
#if 0
    struct exec kexec;
#endif

    u_long memreq;
    u_long rd_size = 0;		/* size of ramdisk and array of pointers to
				 * its data */
    char **rdptr = NULL;
#ifdef AOUT_KERNEL
    int elf_kernel;
    u_long text_offset = 0;
#else
#define elf_kernel 1
#endif

    if(debug)
      {
      /* display and clear SRAM (debugging) */
        printf("\nSRAM contents\n");
        for(i=0;i<2039;i++)
        if((c=*(unsigned char*)(0xff020000+i*4))!=0){
	  *(unsigned char *)(0xff020000+i*4)=0;
          if (c>31 || c==10) putchar(c);
	  else printf("%2x",c);
        }
	if (!do_force)
	  {
	    printf("\n**** end SRAM: enter to continue\n");
	    fflush(stdout);
	    (void)getchar();
	  }

    /* put '%LX$' as signature into SRAM as flag for kernel */
        *(unsigned char *)(0xff020000)   ='%';
        *(unsigned char *)(0xff020000+4) ='L';
        *(unsigned char *)(0xff020000+8) ='X';
        *(unsigned char *)(0xff020000+12)='$';

    }

    /* machine is Q40 */
    bi.machtype = MACH_Q40;

    /* Copy command line options into the kernel command line */
    strcpy( bi.command_line, command_line );

    if (verbose)
      printf("kernel name:%s\n", kernel_name);

    kname = kernel_name;
    if (strncmp( kernel_name, "local:", 6 ) == 0)
	kname += 6;
    if (strlen(kname)+12 < CL_SIZE-1) {
	if (*bi.command_line)
	    strcat( bi.command_line, " " );
	strcat( bi.command_line, "BOOT_IMAGE=" );
	strcat( bi.command_line, kname );
    }

    if (verbose)
       printf ("Kernel command line: %s\n", bi.command_line );

    get_cpu_infos();
    
    get_mem_infos();

    /* extract data of first chunk, there goes the kernel to */
    start_mem = bi.memory[0].addr;
    mem_size = bi.memory[0].size;

    if (verbose)
      printf("mem start: %x\t\t size: %x\n\n\n",start_mem,mem_size);


    /*
     * load the ramdisk
     *
     * This must be done before creating the bootinfo (there the ramdisk size
     * is needed). But the ramdisk image must also reside physically *after*
     * the kernel image. (This condition is needed by the mover to avoid
     * overwriting data.) This is ensured by using only one malloc() for
     * kernel+ramdisk, but the size for that malloc() is known only after the
     * ramdisk is loaded... I can't see a really nice solution for this :-(
     * I've choosen the scheme below because it is faster than using realloc()
     * to dynamically extend the ramdisk block (avoids potentially many copy
     * actions). 
     */
    if ( rd_set ) {
	int n, n_rdptrs = 0;

	if(rd_pause)
	 {
           printf ("\ninsert medium with RAMDISK file %s and press ENTER\n",ramdisk_name);
	   fflush (stdout);
	   getchar();
	 }

	if (sopen( ramdisk_name ) < 0)
	    error( "Unable to open ramdisk file %s\n", ramdisk_name );
	
#define RD_CHUNK_SIZE	(128*1024)
	do {
	    rdptr = realloc( rdptr, (n_rdptrs+1)*sizeof(char *) );
	    if (!rdptr || !(rdptr[n_rdptrs] = malloc( RD_CHUNK_SIZE )))
		serror0( "Out of memory for ramdisk image\n" );

	    n = sread( rdptr[n_rdptrs], RD_CHUNK_SIZE );
	    if (n < 0)
		serror0( "Error while reading ramdisk image\n" );
	    if (n == 0) {
		free( rdptr[n_rdptrs]);
		break;
	    }
	    rd_size += n;
	    n_rdptrs++;
	} while( n == RD_CHUNK_SIZE );
	sclose();
    }
    bi.ramdisk.size = rd_size;
    
    /*
     * Open the kernel image and analyze it
     */

    if(rd_pause)
	 {
           printf ("\ninsert medium with kernel image file and press ENTER\n");
	   fflush (stdout);
	   getchar();
	 }

    if (verbose) 
      printf("opening kernel image....\n");
    if (sopen( kernel_name ) < 0)
	error( "Unable to get kernel image %s\n", kernel_name );


    if (sread( &kexec_elf, sizeof(kexec_elf) ) != sizeof(kexec_elf))
	serror0( "Cannot read ELF header of kernel image\n" );

	
    if (memcmp( &kexec_elf.e_ident[EI_MAG0], ELFMAG, SELFMAG ) == 0) {
#ifdef AOUT_KERNEL
	elf_kernel = 1;
#endif
	if (kexec_elf.e_type != ET_EXEC || kexec_elf.e_machine != EM_68K ||
	    kexec_elf.e_version != EV_CURRENT)
	    serror0( "Invalid ELF header contents in kernel\n" );

	/* Load the program headers */
	kernel_phdrs = (Elf32_Phdr *)malloc( kexec_elf.e_phnum *
					     sizeof (Elf32_Phdr) );
	if (!kernel_phdrs)
	    serror0( "Unable to allocate memory for program headers\n" );
	sseek( kexec_elf.e_phoff, SEEK_SET );
	if (sread( kernel_phdrs, kexec_elf.e_phnum * sizeof (*kernel_phdrs) )
	    != kexec_elf.e_phnum * sizeof (*kernel_phdrs))
	    serror( "Unable to read program headers from %s\n", kernel_name );
    }
    else {
#ifdef AOUT_KERNEL
	/* try to interprete as a.out kernel */
	if (sread( &kexec, sizeof(kexec) ) != sizeof(kexec))
	    serror( "Unable to read exec header from %s\n", kernel_name );
	switch (N_MAGIC(kexec)) {
	  case ZMAGIC:
	    text_offset = N_TXTOFF(kexec);
	    break;
	  case QMAGIC:
	    text_offset = sizeof(kexec);
	    /* the text size includes the exec header; remove this */
	    kexec.a_text -= sizeof(kexec);
	    break;
	  default:
	    serror( "Wrong magic number %lo in kernel header\n",
		    N_MAGIC(kexec) );
	}
	elf_kernel = 0;
#else
	serror0( "Kernel image is no ELF executable\n" );
#endif
    }


    /* Load the kernel one page after start of mem */
    start_mem += PAGE_SIZE;
    mem_size -= PAGE_SIZE;
    /* Align bss size to multiple of four */
#if 0
    if (!elf_kernel)
      kexec.a_bss = (kexec.a_bss + 3) & ~3;
#endif

    /* calculate the total required amount of memory */
    if (elf_kernel) {
	u_long min_addr = 0xffffffff, max_addr = 0;
	for (i = 0; i < kexec_elf.e_phnum; i++) {
	    if (min_addr > kernel_phdrs[i].p_vaddr)
	      min_addr = kernel_phdrs[i].p_vaddr;
	    if (max_addr < kernel_phdrs[i].p_vaddr + kernel_phdrs[i].p_memsz)
	      max_addr = kernel_phdrs[i].p_vaddr + kernel_phdrs[i].p_memsz;
	}
	/* This is needed for newer linkers that include the header in
	   the first segment.  */
	if (min_addr == 0) {
	    min_addr = PAGE_SIZE;
	    kernel_phdrs[0].p_vaddr += PAGE_SIZE;
	    kernel_phdrs[0].p_offset += PAGE_SIZE;
	    kernel_phdrs[0].p_filesz -= PAGE_SIZE;
	    kernel_phdrs[0].p_memsz -= PAGE_SIZE;
	}
	kernel_size = max_addr - min_addr;
    }
#ifdef AOUT_KERNEL
    else
	kernel_size = kexec.a_text + kexec.a_data + kexec.a_bss;
#endif

    /* Locate ramdisk in dest. memory */
	bi.ramdisk.addr = (u_long)start_mem + mem_size - rd_size;


    /* create the bootinfo structure */
    if (!create_bootinfo())
	serror0( "Couldn't create bootinfo\n" );

    memreq = kernel_size + bi_size;
#ifdef BOOTINFO_COMPAT_1_0
    if (sizeof(compat_bootinfo) > bi_size)
	memreq = kernel_size+sizeof(compat_bootinfo);
#endif /* BOOTINFO_COMPAT_1_0 */
    /* align load address of ramdisk image, read() is sloooow on odd addr. */
    memreq = ((memreq + 3) & (u_long)~3) + rd_size;
    
    /* allocate RAM for the kernel */
    if (!(memptr = malloc( memreq )))
	serror0( "Unable to allocate memory for kernel\n" );


    /* clearing the kernel's memory perhaps avoids "uninitialized bss"
     * types of bugs... */
    memset(memptr, 0, memreq - rd_size);


    /* if we have a ramdisk, move it above the kernel code; the moving scheme
     * at boot time requires the ramdisk to be physically above the kernel! */
    if (rd_size) {
	char **srcp = rdptr, *dst = memptr + memreq - rd_size;
	u_long left = rd_size;

	/* keep the non-constant-length part out of this loop, the memcpy can
	 * be better optimized then */
	for( ; left > RD_CHUNK_SIZE;
	     ++srcp, dst += RD_CHUNK_SIZE, left -= RD_CHUNK_SIZE ) {
	    memcpy( dst, *srcp, RD_CHUNK_SIZE );
	    free( *srcp );
	}
	if (left) {
	    memcpy( dst, *srcp, left );
	    free( *srcp );
	}
	free( rdptr );
    }
    
    if(verbose)
       printf("\nread kernel text and data\n");
    /* read the text and data segments from the kernel image */
    if (elf_kernel) {
	if(verbose) printf("segments: %d\n",kexec_elf.e_phnum);
	for (i = 0; i < kexec_elf.e_phnum; i++) {
	    if(verbose)printf("read segment %d : off in file %d, off in mem %d length %d \n",i, kernel_phdrs[i].p_offset, kernel_phdrs[i].p_vaddr - PAGE_SIZE,kernel_phdrs[i].p_filesz );	    

	    if (sseek( kernel_phdrs[i].p_offset, SEEK_SET) == -1)
		serror( "Failed to seek to segment %d\n", i );
	    if (sread( memptr + kernel_phdrs[i].p_vaddr - PAGE_SIZE,
		       kernel_phdrs[i].p_filesz )
		!= kernel_phdrs[i].p_filesz)
		serror( "Failed to read segment %d\n", i );

	}
	if(verbose) printf("... kernel loaded\n");
    }
#ifdef AOUT_KERNEL
    else {
	if (sseek( text_offset, SEEK_SET) == -1)
	    serror0( "Failed to seek to text segment\n" );
	if (sread( memptr, kexec.a_text) != kexec.a_text)
	    serror0( "Failed to read text segment\n" );
	/* data follows immediately after text */
	if (sread( memptr + kexec.a_text, kexec.a_data) != kexec.a_data)
	    serror0( "Failed to read data segment\n" );
    }
#endif
    sclose();

    /* Check kernel's bootinfo version */
    switch (check_bootinfo_version(memptr)) {
      case BI_VERSION_MAJOR(Q40_BOOTI_VERSION):
	bi_ptr = &bi_union.record;
	break;

#ifdef BOOTINFO_COMPAT_1_0
      case BI_VERSION_MAJOR(COMPAT_ATARI_BOOTI_VERSION):
	if (!create_compat_bootinfo())
	    error0( "Couldn't create compat bootinfo\n" );
	bi_ptr = &compat_bootinfo;
	bi_size = sizeof(compat_bootinfo);
	break;
#endif /* BOOTINFO_COMPAT_1_0 */

      default:
	error0( "Kernel has unsupported bootinfo version\n" );
    }

    /* copy the boot_info struct to the end of the kernel image */
    memcpy( memptr + kernel_size, bi_ptr, bi_size );
    if (verbose)
      printf("bootinfo copied into kernel\n");


    /* for those who want to debug */
    if (verbose) {
	if (rd_size) {
	    printf ("ramdisk src at %#lx, size is %ld\n",
		    (u_long)memptr - rd_size, bi.ramdisk.size);
	    printf ("ramdisk dest is %#lx ... %#lx\n",
		    bi.ramdisk.addr, bi.ramdisk.addr + rd_size - 1 );
	}

	if (elf_kernel) {
	    for (i = 0; i < kexec_elf.e_phnum; i++) {
		printf ("Kernel segment %d at %#lx, size %d\n", i,
			start_mem + kernel_phdrs[i].p_vaddr - PAGE_SIZE,
			kernel_phdrs[i].p_memsz);
	    }
	}
#ifdef AOUT_KERNEL
	else {
	    printf ("\nKernel text at %#lx, code size %d\n",
		    start_mem, kexec.a_text);
	    printf ("Kernel data at %#lx, data size %d\n",
		    start_mem + kexec.a_text, kexec.a_data );
	    printf ("Kernel bss  at %#lx, bss  size %d\n",
		    start_mem + kexec.a_text + kexec.a_data, kexec.a_bss );
	}
#endif
        if(verbose)
	  printf ("boot_info is at %#lx\n",
		  start_mem + kernel_size);

	if (do_force) printf("booting Linux in 5 seconds\n");
	else printf ("\nType a key to continue the Linux boot...\n");
	fflush(NULL);
	fflush (stdout);
	if (do_force) /* wait some seconds in the hope any buffers get written */
	  {
	    mt_susjb(-1,250,NULL);
	  }
	else getchar();
    }

#if 0
    /* make sure LED is ON */
    *(unsigned char*)0xff000030 = 1;
#endif

    /* now here's the point of no return... */
    printf("Booting Linux...\n");


#if 1 /* do this in linux_init.s */

    arg1=start_mem;
    arg2=(long) memptr;
    arg3=bi.ramdisk.addr+rd_size;
    arg4=(long) memptr+memreq;
    arg5=kernel_size+bi_size;
    arg6=rd_size;


    linux_init();
#else
    _super();  /* go into QDOS su mode */

    /* turn off interrupts... */
    disable_interrupts();

    /* turn off caches... */
    disable_cache();

    /* ..and any MMU translation */
    disable_mmu();

    /* copy mover code to a safe place if needed */
    memcpy ((void *) 0x18000, &copyall, &copyallend - &copyall);

    /* setup stack */
    change_stack ((void *) PAGE_SIZE);

    /*
     * To avoid that the kernel or the ramdisk overwrite the code moving them
     * to there, we first copy the mover code to a safe place.
     * Then this program jumps to the mover code. After the mover code
     * has finished it jumps to the start of the kernel in its new position.
     * I thought the memory just after the interrupt vector table was a safe
     * place because it is used by TOS to store some system variables.
     * This range goes from 0x400 to approx. 0x5B0.
     *
     * Q40: use the otherwise useless RAM at 0x18000
     * This is more than enough for the miniscule mover routine (16 bytes).
     *
     * Some care is also needed to bring both the kernel and the ramdisk into
     * their final resting position without corrupting them (overwriting some
     * data while copying in source or destination areas). It's solved as
     * follows: The source area is a load region, where we have the kernel
     * code and ramdisk data in memory currently. It is important that the
     * ramdisk is above the kernel. The mover then copies the kernel (upwards)
     * to start of memory, and the ramdisk (downwards) to the end of RAM. This
     * ensures none of the copy actions can overwrite the load region. (E.g.,
     * the kernel can't overwrite the ramdisk source, because that is above
     * the kernel source.) If the load region or the ramdisk destination are
     * in another memory block, we don't have any problem anyway.
     */

arg1=start_mem;
arg2=memptr;
arg3=bi.ramdisk.addr+rd_size;
arg4=memptr+memreq;
arg5=kernel_size+bi_size,
arg6=rd_size


    jump_to_mover((char *) start_mem, memptr,
		  (char *) bi.ramdisk.addr + rd_size, memptr + memreq,
		  kernel_size + bi_size, rd_size,
		  (void *) 0x18000);
#endif

    for (;;);
    /* NOTREACHED */
}

static void get_cpu_infos( void )
{
    u_long cpu_type;

if (!is060)
   {
      bi.cputype = CPU_68040; bi.mmutype = MMU_68040;
      bi.fputype = FPU_68040;
   }
else
   {
      bi.cputype = CPU_68060; bi.mmutype = MMU_68060; 
      bi.fputype = FPU_68060;
   }
}


#define GRANULARITY (256*1024) /* min unit for memory */
#define ADD_CHUNK(start,siz,kindstr)			\
    do {							\
	unsigned long _start = (start);				\
	unsigned long _size  = (siz) & ~(GRANULARITY-1);	\
							        \
	if (_size > 0) {					\
	    bi.memory[chunk].addr = _start;			\
	    bi.memory[chunk].size = _size;			\
	    total += _size;					\
	    printf( kindstr ": %s MB at 0x%08lx\n",		\
		    format_mb(_size), _start );			\
	    chunk++;						\
	}							\
    } while(0)


static void get_mem_infos( void )
{
    int chunk = 0;
    unsigned long total = 0;

	ADD_CHUNK( 256*1024, (mem-1)*1024*1024+3*256*1024,
		   "Q40 Main RAM:" );
        bi.num_memory = 1;
}

static char *format_mb( unsigned long size )
{
    static char buf[12];

    sprintf( buf, "%ld", size/MB );
    if (size % MB) {
	size %= MB;
	sprintf( buf+strlen(buf), ".%02ld", (size*100/MB) );
    }
    return( buf );
}


static int check_bootinfo_version(char *memptr)
{
    struct bootversion *bv = (struct bootversion *)memptr;
    unsigned long version = 0;
    int i, kernel_major, kernel_minor, boots_major, boots_minor;
    struct bootversion_machversion *mvi=&(bv->machversions[0]);


    if (bv->magic == BOOTINFOV_MAGIC) {
	for( i = 0; mvi[i].machtype/*bv->machversions[i].machtype*/ != 0; ++i ) {
	    if (mvi[i].machtype/*bv->machversions[i].machtype*/ == MACH_Q40) {
		version = mvi[i].version/*bv->machversions[i].version*/;
		break;
	    }
	}
    }
    if (!version)
	printf("Kernel has no bootinfo version info, assuming 0.0\n");

    kernel_major = BI_VERSION_MAJOR(version);
    kernel_minor = BI_VERSION_MINOR(version);
    boots_major  = BI_VERSION_MAJOR(Q40_BOOTI_VERSION);
    boots_minor  = BI_VERSION_MINOR(Q40_BOOTI_VERSION);
    if (verbose)
      { 
          printf("Bootstrap's bootinfo version: %d.%d\n",
	      boots_major, boots_minor);
          printf("Kernel's bootinfo version   : %d.%d\n",
	      kernel_major, kernel_minor);
      }
    switch (kernel_major) {
	case BI_VERSION_MAJOR(Q40_BOOTI_VERSION):
	    if (kernel_minor > boots_minor) {
		printf("Warning: Bootinfo version of bootstrap and kernel "
		       "differ!\n");
		printf("         Certain features may not work.\n");
	    }
	    break;

#ifdef BOOTINFO_COMPAT_1_0
	case BI_VERSION_MAJOR(COMPAT_ATARI_BOOTI_VERSION):
	    printf("(using backwards compatibility mode)\n");
	    break;
#endif /* BOOTINFO_COMPAT_1_0 */

	default:
	    printf("\nThis bootstrap is too %s for this kernel!\n",
		   boots_major < kernel_major ? "old" : "new");
	    return 0;
    }
    return kernel_major;
}

    /*
     *  Create the Bootinfo Structure
     */

static int create_bootinfo(void)
{
    int i;
    struct bi_record *record;

    /* Initialization */
    bi_size = 0;

    /* Generic tags */
    if (!add_bi_record(BI_MACHTYPE, sizeof(bi.machtype), &bi.machtype))
	return(0);
    if (!add_bi_record(BI_CPUTYPE, sizeof(bi.cputype), &bi.cputype))
	return(0);
    if (!add_bi_record(BI_FPUTYPE, sizeof(bi.fputype), &bi.fputype))
	return(0);
    if (!add_bi_record(BI_MMUTYPE, sizeof(bi.mmutype), &bi.mmutype))
	return(0);
    for (i = 0; i < bi.num_memory; i++)
	if (!add_bi_record(BI_MEMCHUNK, sizeof(bi.memory[i]), &bi.memory[i]))
	    return(0);
    if (bi.ramdisk.size)
	if (!add_bi_record(BI_RAMDISK, sizeof(bi.ramdisk), &bi.ramdisk))
	    return(0);
    if (!add_bi_string(BI_COMMAND_LINE, bi.command_line))
	return(0);

    /* Trailer */
    record = (struct bi_record *)((u_long)&bi_union.record+bi_size);
    record->tag = BI_LAST;
    bi_size += sizeof(bi_union.record.tag);

    return(1);
}

    /*
     *  Add a Record to the Bootinfo Structure
     */

static int add_bi_record(u_short tag, u_short size, const void *data)
{
    struct bi_record *record;
    u_short size2;

    size2 = (sizeof(struct bi_record)+size+3)&(u_long)-4;
    if (bi_size+size2+sizeof(bi_union.record.tag) > MAX_BI_SIZE) {
	fprintf (stderr, "Can't add bootinfo record. Ask a wizard to enlarge me.\n");
	return(0);
    }
    record = (struct bi_record *)((u_long)&bi_union.record+bi_size);
    record->tag = tag;
    record->size = size2;
    memcpy((void *)((long)record+sizeof(struct bi_record)), data, size);  /* HACK :-( */
    bi_size += size2;
    return(1);
}

    /*
     *  Add a String Record to the Bootinfo Structure
     */

static int add_bi_string(u_short tag, const u_char *s)
{
    return add_bi_record(tag, strlen(s)+1, (const void *)s);
}


#ifdef BOOTINFO_COMPAT_1_0

    /*
     *  Create the Bootinfo structure for backwards compatibility mode
     */

static int create_compat_bootinfo(void)
{
    u_int i;

    compat_bootinfo.machtype = bi.machtype;
    if (bi.cputype & CPU_68020)
	compat_bootinfo.cputype = COMPAT_CPU_68020;
    else if (bi.cputype & CPU_68030)
	compat_bootinfo.cputype = COMPAT_CPU_68030;
    else if (bi.cputype & CPU_68040)
	compat_bootinfo.cputype = COMPAT_CPU_68040;
    else if (bi.cputype & CPU_68060)
	compat_bootinfo.cputype = COMPAT_CPU_68060;
    else {
	printf("CPU type 0x%08lx not supported by kernel\n", bi.cputype);
	return(0);
    }
    if (bi.fputype & FPU_68881)
	compat_bootinfo.cputype |= COMPAT_FPU_68881;
    else if (bi.fputype & FPU_68882)
	compat_bootinfo.cputype |= COMPAT_FPU_68882;
    else if (bi.fputype & FPU_68040)
	compat_bootinfo.cputype |= COMPAT_FPU_68040;
    else if (bi.fputype & FPU_68060)
	compat_bootinfo.cputype |= COMPAT_FPU_68060;
    else if (bi.fputype) {
	printf("FPU type 0x%08lx not supported by kernel\n", bi.fputype);
	return(0);
    }
    compat_bootinfo.num_memory = bi.num_memory;
    if (compat_bootinfo.num_memory > COMPAT_NUM_MEMINFO) {
	printf("Warning: using only %d blocks of memory\n",
	       COMPAT_NUM_MEMINFO);
	compat_bootinfo.num_memory = COMPAT_NUM_MEMINFO;
    }
    for (i = 0; i < compat_bootinfo.num_memory; i++) {
	compat_bootinfo.memory[i].addr = bi.memory[i].addr;
	compat_bootinfo.memory[i].size = bi.memory[i].size;
    }
    if (bi.ramdisk.size) {
	bi.ramdisk.addr &= 0xfffffc00;
	compat_bootinfo.ramdisk_size = (bi.ramdisk.size+1023)/1024;
	compat_bootinfo.ramdisk_addr = bi.ramdisk.addr;
    } else {
	compat_bootinfo.ramdisk_size = 0;
	compat_bootinfo.ramdisk_addr = 0;
    }
    strncpy(compat_bootinfo.command_line, bi.command_line, COMPAT_CL_SIZE);
    compat_bootinfo.command_line[COMPAT_CL_SIZE-1] = '\0';

    compat_bootinfo.bi_atari.hw_present = 0;
    compat_bootinfo.bi_atari.mch_cookie = bi.mch_cookie;
    return(1);
}
#endif /* BOOTINFO_COMPAT_1_0 */

/*
 * Copy the kernel and the ramdisk to their final resting places.
 *
 * I assume that the kernel data and the ramdisk reside somewhere
 * in the middle of the memory.
 *
 * This program itself should be somewhere in the first 4096 bytes of memory
 * where the kernel never will be. In this way it can never be overwritten
 * by itself.
 *
 * At this point the registers have:
 * a0: the start of the final kernel
 * a1: the start of the current kernel
 * a2: the end of the final ramdisk
 * a3: the end of the current ramdisk
 * d0: the kernel size
 * d1: the ramdisk size 
 */
#if 0
asm ("
.text
_copyall:

	movel	a0,a4	    	/* save the start of the kernel for booting */

1:	movel	a1@+,a0@+   	/* copy the kernel starting at the beginning */
	subql	#4,d0
	jcc	1b

	tstl	d1
	beq		3f

2:	movel	a3@-,a2@-   	/* copy the ramdisk starting at the end */
	subql	#4,d1
	jcc	2b

3:	jmp	a4@ 	    	/* jump to the start of the kernel */
_copyallend:
");



#endif



/* Local Variables: */
/* mode: c 	    */
/* tab-width: 8     */
/* End:             */
