/*
 * linux/arch/m68k/boot/amiga/linuxboot.c -- Generic routine to boot Linux/m68k
 *                                           on Amiga, used by both Amiboot and
 *                                           Amiga-lilo.
 *
 *    Created 1996 by Geert Uytterhoeven
 *
 *
 * This file is based on the original bootstrap code (bootstrap.c):
 *
 *    Copyright (C) 1993, 1994 Hamish Macdonald, Greg Harp
 *
 *                with work by Michael Rausch
 *                             Geert Uytterhoeven
 *                             Frank Neumann
 *                             Andreas Schwab
 *                             
 *
 * 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.
 */


#ifndef __GNUC__
#error GNU CC is required to compile this program
#endif /* __GNUC__ */


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

#include <linux/a.out.h>
#include <linux/elf.h>
#include <linux/linkage.h>
#include <asm/bootinfo.h>
#include <asm/amigatypes.h>
#include <asm/amigahw.h>

#include "linuxboot.h"


#undef custom
#define custom ((*(volatile struct CUSTOM *)(CUSTOM_PHYSADDR)))

/* temporary stack size */
#define TEMP_STACKSIZE  (256)

static struct exec kexec;
static int elf_kernel = 0;
static Elf32_Ehdr kexec_elf;
static Elf32_Phdr *kernel_phdrs = NULL;
static char *memptr;
static u_long start_mem, mem_size, rd_size, kernel_size;
static struct bootinfo bi;
static int global_debugflag;


static u_long get_chipset(void);
static u_long get_cpu(void);
static int get_model(u_long chipset);
static int probe_resident(char *name);
static int probe_resource(char *name);
static void start_kernel(void (*startfunc)(), char *stackp, char *memptr,
                         u_long start_mem, u_long mem_size, u_long rd_size,
                         u_long kernel_size) __attribute__ ((noreturn));

extern char copyall, copyallend;
asmlinkage u_long maprommed(void);


#ifdef RESET_BOARDS
   /*
    *    Reset functions for nasty Zorro boards
    */

static void reset_rb3(struct ConfigDev *cd);
static void reset_piccolo(struct ConfigDev *cd);
static void reset_sd64(struct ConfigDev *cd);
static void reset_ariadne(struct ConfigDev *cd);
#if 0
static void reset_a2060(struct ConfigDev *cd);
#endif

struct boardreset {
   u_short manuf;
   u_short prod;
   char *name;
   void (*reset)(struct ConfigDev *cd);
};

static struct boardreset boardresetdb[] = {
   { MANUF_HELFRICH1, PROD_RAINBOW3, "Rainbow 3", reset_rb3 },
   { MANUF_HELFRICH2, PROD_PICCOLO_REG, "Piccolo", reset_piccolo },
   { MANUF_HELFRICH2, PROD_SD64_REG, "SD64", reset_sd64 },
   { MANUF_VILLAGE_TRONIC, PROD_ARIADNE, "Ariadne", reset_ariadne },
#if 0
   { MANUF_COMMODORE, PROD_A2060, "A2060", reset_a2060 },
#endif
};

static void (*boardresetfuncs[NUM_AUTO])(struct ConfigDev *cd);

   /*
    *    Reset functions for graphics boards should consider this flag
    */

static int global_keep_video;
#endif /* RESET_BOARDS */


#define MASK(model)     (1<<AMI_##model)


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;

    Printf( "\n" );
    if (bv->magic == BOOTINFOV_MAGIC) {
	for( i = 0; bv->machversions[i].machtype != 0; ++i ) {
	    if (bv->machversions[i].machtype == MACH_AMIGA) {
		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(AMIGA_BOOTI_VERSION);
    boots_minor  = BI_VERSION_MINOR(AMIGA_BOOTI_VERSION);
    Printf( "Bootstrap's bootinfo version: %ld.%ld\n",
	    boots_major, boots_minor );
    Printf( "Kernel's bootinfo version   : %ld.%ld\n",
	    kernel_major, kernel_minor );
    
    if (kernel_major != boots_major) {
	Printf( "\nThis bootstrap is too %s for this kernel!\n",
		boots_major < kernel_major ? "old" : "new" );
	return( 0 );
    }
    if (kernel_minor > boots_minor) {
	Printf( "Warning: Bootinfo version of bootstrap and kernel differ!\n" );
	Printf( "         Certain features may not work.\n" );
    }
    return( 1 );
}


   /*
    *    Boot The Linux/m68k Operating System
    */

u_long BootLinux(char *kernel_name, char *ramdisk_name, char *commandline,
                 int debugflag, int keep_video)
{
   int kfd, i;
   long fast_total = 0;          /* total Fast RAM in system */
   struct MemHeader *mnp;
   struct ConfigDev *cdp = NULL;
   u_long *stack, memreq, text_offset = 0, model_mask;
   void (*startfunc)(void);
   long startcodesize;
#ifdef USE_RAMDISK
   int rfd = -1;
#endif /* USE_RAMDISK */
#ifdef RESET_BOARDS
   u_short manuf;
   u_char prod;
   int j;
#endif /* RESET_BOARDS */

   global_debugflag = debugflag;

   /* print the greet message */
   Puts("\nLinux/m68k Amiga Bootstrap version " AMIBOOT_VERSION "\n");
   Puts("Copyright 1993,1994 by Hamish Macdonald and Greg Harp\n\n");

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

   /* determine chipset */
   bi.bi_amiga.chipset = get_chipset();

   /* determine CPU type */
   bi.cputype = get_cpu();

   /* determine Amiga model */
   bi.bi_amiga.model = get_model(bi.bi_amiga.chipset);
   model_mask = (bi.bi_amiga.model != AMI_UNKNOWN) ? 1<<bi.bi_amiga.model : 0;

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

   /* 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(&bi.bi_amiga.autocon[i], cdp, sizeof(struct ConfigDev));

      /* count this device */
      bi.bi_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.lh_Head;
        (bi.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;

      /* skip virtual memory */
      if (!(mh.mh_Attributes & MEMF_PUBLIC))
         continue;

      /* if we suspect that Kickstart is shadowed in an A3000,
         modify the entry to show 512K more at the top of RAM
         Check first for a MapROMmed A3640 board: overwriting the
         Kickstart image causes an infinite lock-up on reboot! */
      if ((mh.mh_Upper == (void *)0x07f80000) &&
          (model_mask & (MASK(3000) | MASK(3000T) | MASK(4000) | MASK(4000T))))
         if (((bi.cputype & CPU_MASK) == CPU_68040) && Supervisor(maprommed))
            Puts("A3640 MapROM detected.\n");
         else if (model_mask & (MASK(3000) | MASK(3000T))) {
            mh.mh_Upper = (void *)0x08000000;
            Puts("A3000 shadowed Kickstart detected.\n");
         }

      /* if we suspect that Kickstart is zkicked,
         modify the entry to show 512K more at the botton of RAM */
      if ((mh.mh_Lower == (void *)0x00280020) &&
          (model_mask & (MASK(500) | MASK(1000) | MASK(2000) | MASK(2500)))) {
         mh.mh_Lower = (void *)0x00200000;
         Puts("ZKick detected.\n");
      }

      /* 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) {
         u_long size;

         /* record the start */
         bi.memory[bi.num_memory].addr = (u_long)mh.mh_Lower;

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

         /* mask off to a 256K increment */
         size &= 0xfffc0000;

         if (size > 0)
            /* count this block */
            bi.memory[bi.num_memory++].size = size;

      } else if (mh.mh_Attributes & MEMF_CHIP)
         /* if CHIP memory, record the size */
         bi.bi_amiga.chip_size = (u_long)mh.mh_Upper;
   }

   /* get info from ExecBase */
   bi.bi_amiga.vblank = SysBase->VBlankFrequency;
   bi.bi_amiga.psfreq = SysBase->PowerSupplyFrequency;
   bi.bi_amiga.eclock = SysBase->ex_EClockFrequency;

   /* copy command line options into the kernel command line */
   strncpy(bi.command_line, commandline, CL_SIZE);
   bi.command_line[CL_SIZE-1] = '\0';


   /* modify the bootinfo, e.g. to change the memory configuration */
#ifdef MODIFY_BOOTINFO
   if (!ModifyBootInfo(&bi))
      return(FALSE);
#endif /* MODIFY_BOOTINFO */


   /* display Amiga model */
   switch (bi.bi_amiga.model) {
      case AMI_500:
         Puts("Amiga 500 ");
         break;
      case AMI_500PLUS:
         Puts("Amiga 500+ ");
         break;
      case AMI_600:
         Puts("Amiga 600 ");
         break;
      case AMI_1000:
         Puts("Amiga 1000 ");
         break;
      case AMI_1200:
         Puts("Amiga 1200 ");
         break;
      case AMI_2000:
         Puts("Amiga 2000 ");
         break;
      case AMI_2500:
         Puts("Amiga 2500 ");
         break;
      case AMI_3000:
         Puts("Amiga 3000 ");
         break;
      case AMI_3000T:
         Puts("Amiga 3000T ");
         break;
      case AMI_3000PLUS:
         Puts("Amiga 3000+ ");
         break;
      case AMI_4000:
         Puts("Amiga 4000 ");
         break;
      case AMI_4000T:
         Puts("Amiga 4000T ");
         break;
      case AMI_CDTV:
         Puts("CDTV ");
         break;
      case AMI_CD32:
         Puts("CD32 ");
         break;
      case AMI_DRACO:
         Puts("Draco ");
         break;
      default:
         Puts("Amiga ");
         break;
   }

   /* display the CPU type */
   Puts("CPU: ");
   switch (bi.cputype & CPU_MASK) {
      case CPU_68020:
         Puts("68020 (Do you have an MMU?)");
         break;
      case CPU_68030:
         Puts("68030");
         break;
      case CPU_68040:
         Puts("68040");
         break;
      case CPU_68060:
         Puts("68060");
         break;
      default:
         Puts("Insufficient for Linux.  Aborting...\n");
         Printf("SysBase->AttnFlags = 0x%08lx\n", SysBase->AttnFlags);
         return(FALSE);
   }
   switch (bi.cputype & ~CPU_MASK) {
      case FPU_68881:
         Puts(" with 68881 FPU");
         break;
      case FPU_68882:
         Puts(" with 68882 FPU");
         break;
      case FPU_68040:
      case FPU_68060:
         Puts(" with internal FPU");
         break;
      default:
         Puts(" without FPU");
         break;
   }

   /* display the chipset */
   switch(bi.bi_amiga.chipset) {
      case CS_STONEAGE:
         Puts(", old or unknown chipset");
         break;
      case CS_OCS:
         Puts(", OCS");
         break;
      case CS_ECS:
         Puts(", ECS");
         break;
      case CS_AGA:
         Puts(", AGA chipset");
         break;
   }

   Puts("\n\n");

   /* display the command line */
   Printf("Command line is '%s'\n", bi.command_line);

   /* display the clock statistics */
   Printf("Vertical Blank Frequency: %ldHz\nPower Supply Frequency: %ldHz\n",
          bi.bi_amiga.vblank, bi.bi_amiga.psfreq);
   Printf("EClock Frequency: %ldHz\n\n", bi.bi_amiga.eclock);

   /* display autoconfig devices */
   if (bi.bi_amiga.num_autocon) {
      Printf("Found %ld AutoConfig Device%s", bi.bi_amiga.num_autocon,
             bi.bi_amiga.num_autocon > 1 ? "s\n" : "\n");
      for (i = 0; i < bi.bi_amiga.num_autocon; i++) {
         Printf("Device %ld: addr = %08lx\n", i,
                (u_long)bi.bi_amiga.autocon[i].cd_BoardAddr);
#ifdef RESET_BOARDS
         boardresetfuncs[i] = NULL;
         manuf = bi.bi_amiga.autocon[i].cd_Rom.er_Manufacturer;
         prod = bi.bi_amiga.autocon[i].cd_Rom.er_Product;
         for (j = 0; j < sizeof(boardresetdb)/sizeof(*boardresetdb); j++)
            if ((manuf == boardresetdb[j].manuf) &&
                (prod == boardresetdb[j].prod)) {
               Printf("(%s board found - will be reset at kernel boot time)\n",
                      boardresetdb[j].name);
               boardresetfuncs[i] = boardresetdb[j].reset;
               break;
            }
#endif /* RESET_BOARDS */
      }
   } else
      Puts("No AutoConfig Devices Found\n");

   /* display memory */
   if (bi.num_memory) {
      Printf("\n%ld Block%sof Memory Found\n", bi.num_memory,
             bi.num_memory > 1 ? "s ":" ");
      for (i = 0; i < bi.num_memory; i++)
         Printf("Block %ld: %08lx to %08lx (%ldK)\n", i, bi.memory[i].addr,
                bi.memory[i].addr+bi.memory[i].size, bi.memory[i].size >> 10);
   } else {
      Puts("No memory found?!  Aborting...\n");
      return(FALSE);
   }

   /* display chip memory size */
   Printf("%ldK of CHIP memory\n", bi.bi_amiga.chip_size >> 10);

   start_mem = bi.memory[0].addr;
   mem_size = bi.memory[0].size;

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

   /* verify that there is enough Chip RAM */
   if (bi.bi_amiga.chip_size < 512*1024) {
      Puts("Not enough Chip RAM in this system.  Aborting...\n");
      return(FALSE);
   }

   /* verify that there is enough Fast RAM */
   for (i = 0; i < bi.num_memory; i++)
      fast_total += bi.memory[i].size;
   if (fast_total < 2*1024*1024) {
      Puts("Not enough Fast RAM in this system.  Aborting...\n");
      return(FALSE);
   }

   /* open kernel executable and read exec header */
   if ((kfd = Open(kernel_name)) == -1) {
      Printf("Unable to open kernel file %s\n", kernel_name);
      return(FALSE);
   }
   if (Read(kfd, (void *)&kexec, sizeof(kexec)) != sizeof(kexec)) {
      Printf("Unable to read exec header from %s\n", kernel_name);
      return(FALSE);
   }

   switch (N_MAGIC(kexec)) {
      case ZMAGIC:
         if (debugflag)
            Puts("\nLoading a.out (ZMAGIC) Linux/m68k kernel...\n");
         text_offset = N_TXTOFF(kexec);
         break;

      case QMAGIC:
         if (debugflag)
            Puts("\nLoading a.out (QMAGIC) Linux/m68k kernel...\n");
         text_offset = sizeof(kexec);
         /* the text size includes the exec header; remove this */
         kexec.a_text -= sizeof(kexec);
         break;

      default:
         /* Try to parse it as an ELF header */
         Seek(kfd, 0, SEEK_SET);
         if ((Read(kfd, (void *)&kexec_elf, sizeof(kexec_elf)) ==
              sizeof(kexec_elf)) &&
             (memcmp(&kexec_elf.e_ident[EI_MAG0], ELFMAG, SELFMAG) == 0)) {
            elf_kernel = 1;
            if (debugflag)
               Puts("\nLoading ELF Linux/m68k kernel...\n");
            /* A few plausibility checks */
            if ((kexec_elf.e_type != ET_EXEC) ||
                (kexec_elf.e_machine != EM_68K) ||
                (kexec_elf.e_version != EV_CURRENT)) {
               Puts("Invalid ELF header contents in kernel\n");
               return(FALSE);
            }
            /* Load the program headers */
            if (!(kernel_phdrs =
                  (Elf32_Phdr *)AllocMem(kexec_elf.e_phnum*sizeof(Elf32_Phdr),
                                         MEMF_FAST|MEMF_PUBLIC|MEMF_CLEAR))) {
               Puts("Unable to allocate memory for program headers\n");
               return(FALSE);
            }
            Seek(kfd, kexec_elf.e_phoff, SEEK_SET);
            if (Read(kfd, (void *)kernel_phdrs,
                     kexec_elf.e_phnum*sizeof(*kernel_phdrs)) !=
                kexec_elf.e_phnum*sizeof(*kernel_phdrs)) {
               Printf("Unable to read program headers from %s\n", kernel_name);
               return(FALSE);
            }
            break;
         }
         Printf("Wrong magic number 0x%08lx in kernel header\n",
                N_MAGIC(kexec));
         return(FALSE);
   }

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

#ifdef USE_RAMDISK
   if (ramdisk_name) {
      if ((rfd = Open(ramdisk_name)) == -1) {
         Printf("Unable to open ramdisk file %s\n", ramdisk_name);
         return(FALSE);
      }
      /* record ramdisk size */
      bi.ramdisk_size = (Seek(rfd, 0, SEEK_END)+1023) >> 10;
   } else
#endif /* USE_RAMDISK */
      bi.ramdisk_size = 0;
   rd_size = bi.ramdisk_size << 10;
   bi.ramdisk_addr = (u_long)start_mem+mem_size-rd_size;

   /* 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;
   } else
      kernel_size = kexec.a_text+kexec.a_data+kexec.a_bss;
   memreq = kernel_size+sizeof(struct bootinfo)+rd_size;
   if (!(memptr = (char *)AllocMem(memreq, MEMF_FAST|MEMF_PUBLIC|MEMF_CLEAR))) {
      Puts("Unable to allocate memory\n");
      return(FALSE);
   }

   /* read the text and data segments from the kernel image */
   if (elf_kernel)
      for (i = 0; i < kexec_elf.e_phnum; i++) {
         if (Seek(kfd, kernel_phdrs[i].p_offset, SEEK_SET) == -1) {
            Printf("Failed to seek to segment %d\n", i);
            FreeMem((void *)memptr, memreq);
            return(FALSE);
         }
         if (Read(kfd, memptr+kernel_phdrs[i].p_vaddr-PAGE_SIZE,
                  kernel_phdrs[i].p_filesz) != kernel_phdrs[i].p_filesz) {
            Printf("Failed to read segment %d\n", i);
            FreeMem((void *)memptr, memreq);
            return(FALSE);
         }
      }
   else {
      if (Seek(kfd, text_offset, SEEK_SET) == -1) {
         Printf("Failed to seek to text\n");
         FreeMem((void *)memptr, memreq);
         return(FALSE);
      }
      if (Read(kfd, memptr, kexec.a_text) != kexec.a_text) {
         Printf("Failed to read text\n");
         FreeMem((void *)memptr, memreq);
         return(FALSE);
      }
      /* data follows immediately after text */
      if (Read(kfd, memptr+kexec.a_text, kexec.a_data) != kexec.a_data) {
         Printf("Failed to read data\n");
         FreeMem((void *)memptr, memreq);
         return(FALSE);
      }
   }
   Close(kfd);

    /* Check kernel's bootinfo version */
    if (!check_bootinfo_version( memptr )) {
        FreeMem((void *)memptr, memreq);
        return(FALSE);
    }
    
   /* copy the boot_info struct to the end of the kernel image */
   memcpy((void *)(memptr+kernel_size), &bi, sizeof(struct bootinfo));

#ifdef USE_RAMDISK
   if (rfd != -1) {
      if (Seek(rfd, 0, SEEK_SET) == -1) {
         Printf("Failed to seek to beginning of ramdisk file\n");
         FreeMem((void *)memptr, memreq);
         return(FALSE);
      }
      if (Read(rfd, memptr+kernel_size+sizeof(bi), rd_size) != rd_size) {
         Printf("Failed to read ramdisk file\n");
         FreeMem((void *)memptr, memreq);
         return(FALSE);
      }
      Close(rfd);
   }
#endif /* USE_RAMDISK */

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

   /* allocate chip ram for copy of startup code */
   startcodesize = &copyallend-&copyall;
   if (!(startfunc = (void (*)(void))AllocMem(startcodesize,
                                              MEMF_CHIP|MEMF_CLEAR))) {
      Puts("Unable to allocate memory for code\n");
      FreeMem((void *)memptr, memreq);
      FreeMem((void *)stack, TEMP_STACKSIZE);
      return(FALSE);
   }

   /* copy startup code to CHIP RAM */
   memcpy(startfunc, &copyall, startcodesize);

   if (debugflag) {
#ifdef USE_RAMDISK
      if (bi.ramdisk_size)
         Printf("RAM disk at %08lx, size is %ldK\n", (u_long)memptr+kernel_size,
                bi.ramdisk_size);
#endif /* USE_RAMDISK */

      if (elf_kernel) {
         PutChar('\n');
         for (i = 0; i < kexec_elf.e_phnum; i++)
            Printf("Kernel segment %ld at 0x%08lx, size %ld\n", i,
                   start_mem+kernel_phdrs[i].p_vaddr-PAGE_SIZE,
                   kernel_phdrs[i].p_memsz);
         Printf("Boot info        at 0x%08lx\n", start_mem+kernel_size);
      } else {
         Printf("\nKernel text at 0x%08lx, code size 0x%08lx\n", start_mem,
                kexec.a_text);
         Printf("Kernel data at 0x%08lx, data size 0x%08lx\n",
                start_mem+kexec.a_text, kexec.a_data);
         Printf("Kernel bss  at 0x%08lx, bss  size 0x%08lx\n",
                start_mem+kexec.a_text+kexec.a_data, kexec.a_bss);
         Printf("Boot info   at 0x%08lx\n", start_mem+kernel_size);
      }
      Printf("\nKernel entry is 0x%08lx\n", elf_kernel ? kexec_elf.e_entry :
                                                         kexec.a_entry);

#ifdef USE_RAMDISK
      Printf("ramdisk dest top is %08lx\n", start_mem+mem_size);
      Printf("ramdisk lower limit is %08lx\n", (u_long)(memptr+kernel_size));
      Printf("ramdisk src top is %08lx\n",
             (u_long)(memptr+kernel_size+rd_size));
#endif /* USE_RAMDISK */

      Puts("\nType a key to continue the Linux/m68k boot...");
      GetChar();
      PutChar('\n');
   }

   /* wait for things to settle down */
   Sleep(1000000);

   if (!keep_video)
      /* set graphics mode to a nice normal one */
      LoadView(NULL);

   Disable();

#ifdef RESET_BOARDS
   /* reset nasty Zorro boards */
   global_keep_video = keep_video;
   for (i = 0; i < bi.bi_amiga.num_autocon; i++)
      if (boardresetfuncs[i])
         boardresetfuncs[i](&bi.bi_amiga.autocon[i]);
#endif /* RESET_BOARDS */

   /* Turn off all DMA */
   custom.dmacon = DMAF_ALL | DMAF_MASTER;

   /* turn off caches */
   CacheControl(0, ~0);

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

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

   /* execute the copy-and-go code (from CHIP RAM) */
   start_kernel(startfunc, (char *)stack+TEMP_STACKSIZE, memptr, start_mem,
                mem_size, rd_size, kernel_size);
}


   /*
    *    Determine the Chipset
    */

static u_long get_chipset(void)
{
   u_char cs;
   u_long chipset;

   if (GfxBase->Version >= 39)
      cs = SetChipRev(SETCHIPREV_BEST);
   else
      cs = GfxBase->ChipRevBits0;
   if ((cs & GFXG_AGA) == GFXG_AGA)
      chipset = CS_AGA;
   else if ((cs & GFXG_ECS) == GFXG_ECS)
      chipset = CS_ECS;
   else if ((cs & GFXG_OCS) == GFXG_OCS)
      chipset = CS_OCS;
   else
      chipset = CS_STONEAGE;
   return(chipset);
}


   /*
    *    Determine the CPU Type
    */

static u_long get_cpu(void)
{
   u_long cpu = 0;

   if (SysBase->AttnFlags & AFF_68060) {
      cpu = CPU_68060;
      if (SysBase->AttnFlags & AFF_FPU40)
         cpu |= FPU_68060;
   } else if (SysBase->AttnFlags & AFF_68040) {
      cpu = CPU_68040;
      if (SysBase->AttnFlags & AFF_FPU40)
         cpu |= FPU_68040;
   } else {
      if (SysBase->AttnFlags & AFF_68030)
         cpu = CPU_68030;
      else if (SysBase->AttnFlags & AFF_68020)
         cpu = CPU_68020;
      if (SysBase->AttnFlags & AFF_68882)
         cpu |= FPU_68882;
      else if (SysBase->AttnFlags & AFF_68881)
         cpu |= FPU_68881;
   }
   return(cpu);
}


   /*
    *    Determine the Amiga Model
    */

static int get_model(u_long chipset)
{
   int model = AMI_UNKNOWN;

   if (global_debugflag)
      Puts("Amiga model identification:\n");
   if (probe_resource("draco.resource"))
      model = AMI_DRACO;
   else {
      if (global_debugflag)
         Puts("   Chipset: ");
      switch(chipset) {
         case CS_STONEAGE:
            if (global_debugflag)
               Puts("Old or unknown\n");
            goto OCS;
            break;

         case CS_OCS:
            if (global_debugflag)
               Puts("OCS\n");
OCS:        if (probe_resident("cd.device"))
               model = AMI_CDTV;
            else
               /* let's call it an A2000 (may be A500, A1000, A2500) */
               model = AMI_2000;
            break;

         case CS_ECS:
            if (global_debugflag)
               Puts("ECS\n");
            if (probe_resident("Magic 36.7") || probe_resident("kickad 36.57") ||
                probe_resident("A3000 Bonus") || probe_resident("A3000 bonus"))
               /* let's call it an A3000 (may be A3000T) */
               model = AMI_3000;
            else if (probe_resource("card.resource"))
               model = AMI_600;
            else
               /* let's call it an A2000 (may be A500, A500+, A1000, A2500) */
               model = AMI_2000;
            break;

         case CS_AGA:
            if (global_debugflag)
               Puts("AGA\n");
            if (probe_resident("A1000 Bonus") || probe_resident("A4000 bonus"))
               model = probe_resident("NCR scsi.device") ? AMI_4000T : AMI_4000;
            else if (probe_resource("card.resource"))
               model = probe_resident("cd.device") ? AMI_CD32 : AMI_1200;
            else
               model = AMI_3000PLUS;
            break;
      }
   }
   if (global_debugflag) {
      Puts("\nType a key to continue...");
      GetChar();
      Puts("\n\n");
   }
   return(model);
}


   /*
    *    Probe for Resident Modules or available Resources
    */

static int probe_resident(char *name)
{
   struct Resident *res;

   if (global_debugflag)
      Printf("   Module `%s': ", name);
   res = FindResident(name);
   if (global_debugflag)
      if (res)
         Printf("0x%08lx\n", res);
      else
         Printf("not present\n");
   return(res ? TRUE : FALSE);
}


static int probe_resource(char *name)
{
   void *res;

   if (global_debugflag)
      Printf("   Resource `%s': ", name);
   res = OpenResource(name);
   if (global_debugflag)
      if (res)
         Printf("0x%08lx\n", res);
      else
         Printf("not present\n");
   return(res ? TRUE : FALSE);
}


   /*
    *    Call the copy-and-go-code
    */

static void start_kernel(void (*startfunc)(), char *stackp, char *memptr,
                         u_long start_mem, u_long mem_size, u_long rd_size,
                         u_long kernel_size)
{
   register void (*a0)() __asm(ASM_A0) = startfunc;
   register char *a2 __asm(ASM_A2) = stackp;
   register char *a3 __asm(ASM_A3) = memptr;
   register u_long a4 __asm(ASM_A4) = start_mem;
   register u_long d0 __asm(ASM_D0) = mem_size;
   register u_long d1 __asm(ASM_D1) = rd_size;
   register u_long d2 __asm(ASM_D2) = kernel_size;
   register u_long d3 __asm(ASM_D3) = sizeof(struct bootinfo);

   __asm __volatile ("movel %%a2,%%sp;"
                     "jmp %%a0@"
                     : /* no outputs */
                     : "r" (a0), "r" (a2), "r" (a3), "r" (a4), "r" (d0),
                       "r" (d1), "r" (d2), "r" (d3)
                     /* no return */);
   /* fake a noreturn */
   for (;;);
}


   /*
    *    This assembler code is copied to chip ram, and then executed.
    *    It copies the kernel to it's final resting place.
    *
    *    It is called with:
    *
    *       a3 = memptr
    *       a4 = start_mem
    *       d0 = mem_size
    *       d1 = rd_size
    *       d2 = kernel_size
    *       d3 = sizeof(struct bootinfo)
    */

asm(".text\n"
ALIGN_STR "\n"
SYMBOL_NAME_STR(copyall) ":
                              | /* copy kernel text and data */
      movel    %a3,%a0        | src = (u_long *)memptr;
      movel    %a0,%a2        | limit = (u_long *)(memptr+kernel_size);
      addl     %d2,%a2
      movel    %a4,%a1        | dest = (u_long *)start_mem;
1:    cmpl     %a0,%a2
      jeq      2f             | while (src < limit)
      moveb    %a0@+,%a1@+    |  *dest++ = *src++;
      jra      1b
2:
                              | /* copy bootinfo to end of bss */
      movel    %a3,%a0        | src = (u_long *)(memptr+kernel_size);
      addl     %d2,%a0        | dest = end of bss (already in a1)
      movel    %d3,%d7        | count = sizeof bi
      subql    #1,%d7
1:    moveb    %a0@+,%a1@+    | while (--count > -1)
      dbra     %d7,1b         |  *dest++ = *src++

                              | /* copy the ramdisk to the top of memory */
                              | /* (from back to front) */
      movel   %a4,%a1         | dest = (u_long *)(start_mem+mem_size);
      addl    %d0,%a1
      movel   %a3,%a2         | limit = (u_long *)(memptr+kernel_size +
      addl    %d2,%a2         |                    sizeof bi);
      addl    %d3,%a2
      movel   %a2,%a0         | src = (u_long *)((u_long)limit+rd_size);
      addl    %d1,%a0
1:    cmpl    %a0,%a2
      beqs    2f              | while (src > limit)
      moveb   %a0@-,%a1@-     |       *--dest = *--src;
      bras    1b
2:
                              | /* jump to start of kernel */
      movel    %a4,%a0        | jump_to (start_mem);
      jmp      %a0@
"
SYMBOL_NAME_STR(copyallend) ":
");


   /*
    *    Test for a MapROMmed A3640 Board
    */

asm(".text\n"
ALIGN_STR "\n"
SYMBOL_NAME_STR(maprommed) ":
      oriw     #0x0700,%sr
      moveml   #0x3f20,%sp@-
                              | /* Save cache settings */
      .long    0x4e7a1002     | movec %cacr,%d1 */
                              | /* Save MMU settings */
      .long    0x4e7a2003     | movec %tc,%d2
      .long    0x4e7a3004     | movec %itt0,%d3
      .long    0x4e7a4005     | movec %itt1,%d4
      .long    0x4e7a5006     | movec %dtt0,%d5
      .long    0x4e7a6007     | movec %dtt1,%d6
      moveq    #0,%d0
      movel    %d0,%a2
                              | /* Disable caches */
      .long    0x4e7b0002     | movec %d0,%cacr
                              | /* Disable MMU */
      .long    0x4e7b0003     | movec %d0,%tc
      .long    0x4e7b0004     | movec %d0,%itt0
      .long    0x4e7b0005     | movec %d0,%itt1
      .long    0x4e7b0006     | movec %d0,%dtt0
      .long    0x4e7b0007     | movec %d0,%dtt1
      lea      0x07f80000,%a0
      lea      0x00f80000,%a1
      movel    %a0@,%d7
      cmpl     %a1@,%d7
      jne      1f
      movel    %d7,%d0
      notl     %d0
      movel    %d0,%a0@
      nop                     | /* Thanks to Jörg Mayer! */
      cmpl     %a1@,%d0
      jne      1f
      moveq    #-1,%d0        | /* MapROMmed A3640 present */
      movel    %d0,%a2
1:    movel    %d7,%a0@
                              | /* Restore MMU settings */
      .long    0x4e7b2003     | movec %d2,%tc
      .long    0x4e7b3004     | movec %d3,%itt0
      .long    0x4e7b4005     | movec %d4,%itt1
      .long    0x4e7b5006     | movec %d5,%dtt0
      .long    0x4e7b6007     | movec %d6,%dtt1
                              | /* Restore cache settings */
      .long    0x4e7b1002     | movec %d1,%cacr
      movel    %a2,%d0
      moveml   %sp@+,#0x04fc
      rte
");


#ifdef RESET_BOARDS
   /*
    *    Reset functions for nasty Zorro boards
    */

static void reset_rb3(struct ConfigDev *cd)
{
   volatile u_char *rb3_reg = (u_char *)(cd->cd_BoardAddr+0x01002000);

   /* FN: If a Rainbow III board is present, reset it to disable */
   /* its (possibly activated) vertical blank interrupts as the */
   /* kernel is not yet prepared to handle them (level 6). */

   /* set RESET bit in special function register */
   *rb3_reg = 0x01;
   /* actually, only a few cycles delay are required... */
   Sleep(1000000);
   /* clear reset bit */
   *rb3_reg = 0x00;
}

static void reset_piccolo(struct ConfigDev *cd)
{
   volatile u_char *piccolo_reg = (u_char *)(cd->cd_BoardAddr+0x8000);

   /* FN: the same stuff as above, for the Piccolo board. */
   /* this also has the side effect of resetting the board's */
   /* output selection logic to use the Amiga's display in single */
   /* monitor systems - which is currently what we want. */

   /* set RESET bit in special function register */
   *piccolo_reg = 0x01;
   /* actually, only a few cycles delay are required... */
   Sleep(1000000);
   /* clear reset bit */
   *piccolo_reg = 0x51;
}

static void reset_sd64(struct ConfigDev *cd)
{
   volatile u_char *sd64_reg = (u_char *)(cd->cd_BoardAddr+0x8000);

   /* FN: the same stuff as above, for the SD64 board. */
   /* just as on the Piccolo, this also resets the monitor switch */

   /* set RESET bit in special function register */
   *sd64_reg = 0x1f;
   /* actually, only a few cycles delay are required... */
   Sleep(1000000);
   /* clear reset bit AND switch monitor bit (0x20) */
   *sd64_reg = 0x4f;
}

static void reset_ariadne(struct ConfigDev *cd)
{
   volatile u_short *lance_rdp = (u_short *)(cd->cd_BoardAddr+0x0370);
   volatile u_short *lance_rap = (u_short *)(cd->cd_BoardAddr+0x0372);
   volatile u_short *lance_reset = (u_short *)(cd->cd_BoardAddr+0x0374);

   volatile u_char *pit_paddr = (u_char *)(cd->cd_BoardAddr+0x1004);
   volatile u_char *pit_pbddr = (u_char *)(cd->cd_BoardAddr+0x1006);
   volatile u_char *pit_pacr = (u_char *)(cd->cd_BoardAddr+0x100b);
   volatile u_char *pit_pbcr = (u_char *)(cd->cd_BoardAddr+0x100e);
   volatile u_char *pit_psr = (u_char *)(cd->cd_BoardAddr+0x101a);

   u_short in;

   Disable();

   /*
    *   Reset the Ethernet part (Am79C960 PCnet-ISA)
    */

   in = *lance_reset;   /* Reset Chip on Read Access */
   *lance_rap = 0x0000; /* PCnet-ISA Controller Status (CSR0) */
   *lance_rdp = 0x0400; /* STOP */

   /*
    *   Reset the Parallel part (MC68230 PI/T)
    */

   *pit_pacr &= 0xfd;   /* Port A Control Register */
   *pit_pbcr &= 0xfd;   /* Port B Control Register */
   *pit_psr = 0x05;     /* Port Status Register */
   *pit_paddr = 0x00;   /* Port A Data Direction Register */
   *pit_pbddr = 0x00;   /* Port B Data Direction Register */

   Enable();
}

#if 0
static void reset_a2060(struct ConfigDev *cd)
{
#error reset_a2060: not yet implemented
}
#endif
#endif /* RESET_BOARDS */
