/*
 * Copyright 1991-1998 by Open Software Foundation, Inc. 
 *              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 above copyright notice appears in all copies and 
 * that both the copyright notice and this permission notice appear in 
 * supporting documentation. 
 *  
 * OSF DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE 
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 * FOR A PARTICULAR PURPOSE. 
 *  
 * IN NO EVENT SHALL OSF BE LIABLE FOR ANY SPECIAL, INDIRECT, OR 
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT, 
 * NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 
 */

/*
 * MkLinux
 */

#include <debug.h>
#include <mach_kgdb.h>
#include <serial_console_default.h>
#include <lan.h>

#include <kern/misc_protos.h>
#include <kgdb/kgdb_defs.h>


#include <ppc/boot.h>
#include <ppc/asm.h>
#include <ppc/proc_reg.h>
#include <ppc/misc_protos.h>
#include <ppc/kgdb_defs.h>
#include <ppc/pmap.h>
#include <ppc/new_screen.h>
#include <ppc/exception.h>
#include <ppc/POWERMAC/device_tree.h>
#include <ppc/POWERMAC/serial_io.h>
#include <ppc/POWERMAC/powermac.h>
#include <ppc/POWERMAC/powermac_gestalt.h>
#include <ppc/POWERMAC/video_pdm.h>

//#define FIX_BOGUS_GESTALTS

static int s=0x12345678;

extern void parse_args(boot_args *args);

extern unsigned char per_proc_area[];	/* Declared in ppc_init.c */

extern unsigned int intstack_top_ss;	/* declared in start.s */
#if	MACH_KGDB
extern unsigned int gdbstackptr;	/* declared in start.s */
extern unsigned int gdbstack_top_ss;	/* declared in start.s */
#endif	/* MACH_KGDB */

#if DEBUG

boot_args* boot_args_debug;

extern void test_varargs(int count,...);
extern int pmap_test_bitfields(void);

void test_varargs(int count,...)
{
	int i,j;
	va_list listp;

	i = (int)&count;
	va_start(listp, count);

	for (i=0; i< count; i++) {
		cnputc(48+i);
		cnputc(':');
		j = va_arg(listp, int);
		cnputc(48+j);
		cnputc(' ');
		if (i+1 != j) {
			printf("ERROR IN VARARGS!\n");
			while (1)
				;
		}
	}
	return;
}

#endif /* DEBUG */

extern int exception_exit;
extern const char version[];

extern unsigned long _ExceptionVectorsStart;
extern unsigned long _ExceptionVectorsEnd;


unsigned int kernel_seg_regs[] = {
  KERNEL_SEG_REG0_VALUE,	/* 0 */
  KERNEL_SEG_REG0_VALUE + 1,	/* 1 */
  KERNEL_SEG_REG0_VALUE + 2,	/* 2 */
  SEG_REG_INVALID, /* 3 */
  SEG_REG_INVALID, /* 4 */
  KERNEL_SEG_REG5_VALUE, /* 5 - I/O segment */
  SEG_REG_INVALID, /* 6 */
  SEG_REG_INVALID, /* 7 */
  KERNEL_SEG_REG8_VALUE, /* 8-F are possible IO space */
  KERNEL_SEG_REG9_VALUE,
  KERNEL_SEG_REG10_VALUE,
  KERNEL_SEG_REG11_VALUE,
  KERNEL_SEG_REG12_VALUE,
  KERNEL_SEG_REG13_VALUE,
  KERNEL_SEG_REG14_VALUE, /* 14 - A/V video */
  KERNEL_SEG_REG15_VALUE /* 15 - NuBus etc */
};

#ifdef FIX_BOGUS_GESTALTS
unsigned long	hwid;
#endif

void go(unsigned int memory_size, boot_args *args)
{
	int i;
	unsigned long *src,*dst;
	char *str;
	unsigned long	addr;

	/*
	 * Setup per_proc info for first cpu.
	 */
	 
	/* First align the per_proc_info to a cache line */
	per_proc_info = (struct per_proc_info *)(((unsigned int)
						  (&per_proc_area[0])+31)
						 &(-32));

	per_proc_info[0].cpu_number = 0;
	per_proc_info[0].istackptr = 0;	/* we're on the interrupt stack */
	per_proc_info[0].intstack_top_ss = intstack_top_ss;
#if	MACH_KGDB
	per_proc_info[0].gdbstackptr = gdbstackptr;
	per_proc_info[0].gdbstack_top_ss = gdbstack_top_ss;
#endif	/* MACH_KGDB */
	per_proc_info[0].virt_per_proc_info = (unsigned int)
		&per_proc_info[0];
	per_proc_info[0].active_kloaded = (unsigned int)
		&active_kloaded[0];
	per_proc_info[0].cpu_data = (unsigned int)
		&cpu_data[0];
	per_proc_info[0].active_stacks = (unsigned int)
		&active_stacks[0];
	per_proc_info[0].need_ast = (unsigned int)
		&need_ast[0];
	per_proc_info[0].fpu_pcb = 0;

	/* r2 points to our per_proc structure whilst we're in the kernel */
	sync(); __asm__ ("mr 2, %0" : : "r" (&per_proc_info[0])); sync();

	/* Do this VERY early since it might contain important arguments
	 * which may even concern machine issues (bus speed, video etc)
	 */
	parse_args(args);


#ifdef FIX_BOGUS_GESTALTS
#define CPU_ID_REGISTER 0x5FFFFFFC

	hwid = 0xDEADBEEF;

	/* Some machines don't work correctly with their native
	 * gestalt numbers, presumably due to booter bugs.  This
	 * gets around the problem, and will be a large step towards
	 * supporting these non-PDM non-PowerBook NuBus machines.
	 *
	 * This has to be done here because otherwise it could
	 * invalidate the bat that contains video memory mapping.
	 */

	/* Is this dangerous?  Heck yeah! */
	if (PROCESSOR_VERSION == PROCESSOR_VERSION_601 || 
		PROCESSOR_VERSION == PROCESSOR_VERSION_603 ||
		PROCESSOR_VERSION == PROCESSOR_VERSION_603e) {

		/* This is either a 7200, a PDM, a POWERBOOK,
		   a PERFORMA or a LEGACY class machine.... */

		if (!args->deviceTreeSize) {
		    /* It's a PDM, PERFORMA, POWERBOOK, or LEGACY */
		    /* First, throw a BAT over the CPU ID */

		    unsigned long addr=CPU_ID_REGISTER;

		    // We moved this code -- printf not yet working
		    // printf("mapping memory.\n");

		    if (PROCESSOR_VERSION == PROCESSOR_VERSION_601) {
			bat601_t	      bat;


			bat.upper.word       = CPU_ID_REGISTER & 0xFFF00000;
			bat.lower.word       = CPU_ID_REGISTER & 0xFFF00000;

			bat.upper.bits.wim   = PTE_WIMG_IO >> 1;
			bat.upper.bits.ks    = 0;
			bat.upper.bits.ku    = 0;
			bat.upper.bits.pp    = 0;       /* read/write access */

			bat.lower.bits.bsm   = 0x7;     /* size = 1M */
			bat.lower.bits.valid = 0x1;     /* mark BAT valid */

			sync();isync();

			//  534=IBAT3U 535=IBAT3L
			//  Do NOT use macros here, gnu as gives
			//  Unsupported relocation type error. --DAG 19990825
			mtspr(535, BAT_INVALID); /* invalidate old mapping */
			mtspr(534, bat.upper.word);
			mtspr(535, bat.lower.word);

			sync();isync();

		    } else {
			bat_t		      bat;


			bat.upper.word       = CPU_ID_REGISTER & 0xFFF00000;
			bat.lower.word       = CPU_ID_REGISTER & 0xFFF00000;

			bat.upper.bits.bl    = 0x7;     /* size = 1M */
			bat.upper.bits.vs    = 1;
			bat.upper.bits.vp    = 0;

			bat.lower.bits.wimg  = PTE_WIMG_IO;
			bat.lower.bits.pp    = 2;       /* read/write access */

			sync();isync();
			mtdbatu(3, BAT_INVALID); /* invalidate old mapping */
			mtdbatl(3, bat.lower.word);
			mtdbatu(3, bat.upper.word);

			sync();isync();

		    }

		    if (_setjmp(&faultbuf)) {
			/* If we get here, then the idptr read caused
			   an explosion */
			memset((void *)&faultbuf, 0, sizeof(faultbuf));
			goto failed;
		    }

		    // printf("checking value.\n");
		    hwid = *(volatile unsigned long *)addr; eieio();

failed:

		    // Even if we fail, we still want to free up the BAT
		    // mappings.  If we come across 0xDEADBEEF as the
		    // machine model number, it's probably a 68k mac.

		    if (PROCESSOR_VERSION == PROCESSOR_VERSION_601)
			mtspr(535, BAT_INVALID); /* invalidate old mapping */
		    else
			mtdbatu(3, BAT_INVALID); /* invalidate mapping */

		    sync();isync();

		}
	}
#endif /* 1 */


#if 0
	{
	volatile char *vptr=(volatile char*)0x50f04000;
	/* Write something out of both serial ports to say that we are alive */
	*(vptr+4) = 0x40;
	eieio();
	*(vptr+6) = 0x40;
	eieio();
	}
#endif /* 0 */
	/* Give ourselves the virtual map that we would like */
	if (PROCESSOR_VERSION == PROCESSOR_VERSION_601) {
		/* Set up segment registers for PDM and PCI machines */
		for (i=0; i<=15; i++) {
			isync();
			mtsrin(kernel_seg_regs[i], i * 0x10000000);
			sync();
		}
	} else {
		bat_t		      bat;

		/* Make sure that the BATs map what we expect. Note
		 * that we assume BAT0 maps kernel text, BAT1 the data
		 */

		/* 
		 * BIG ASSUMPTION..
		 *
		 * If a device tree was passed in, then its a 
	 	 * OpenFirmware class machine and typically the I/O
		 * space is up in the 0xf0000000 region.
		 *
		 * Else, its a pre-OFW machines and its I/O space
		 * lives at 0x50000000. (More like 0x50f00000).
		 */

		if (args->deviceTreeSize) 
			addr = 0xf0000000;
		else
			addr = 0x50000000;


		/* Use DBAT2 to map segment F 1-1 */
		bat.upper.word	     = addr;
		bat.upper.bits.bl    = 0x7ff;	/* size = 256M */
		bat.upper.bits.vs    = 1;
		bat.upper.bits.vp    = 0;

		bat.lower.word       = addr;
		bat.lower.bits.wimg  = PTE_WIMG_IO;
		bat.lower.bits.pp    = 2;	/* read/write access */

		sync();isync();
		mtdbatu(2, BAT_INVALID);	/* invalidate old mapping */
		mtdbatl(2, bat.lower.word);
		mtdbatu(2, bat.upper.word);	/* update with new mapping */

		/* Use DBAT3 to map the video segment */
		if (args->Video.v_baseAddr < 0x50000000) {
			/* KAM 990104 -- This is a PDM machine with video
			   in RAM; map over the RAM, but only 1 MB */
			/* start off specifying 1-1 mapping of video seg */
			bat.upper.word       = VPDM_BASEADDR;
			bat.lower.word       = VPDM_PHYSADDR;

			bat.upper.bits.bl    = 0x7;     /* size = 1M */
			bat.upper.bits.vs    = 1;
			bat.upper.bits.vp    = 0;

			bat.lower.bits.wimg  = PTE_WIMG_IO;
			bat.lower.bits.pp    = 2;       /* read/write access */

			sync();isync();
			mtdbatu(3, BAT_INVALID); /* invalidate old mapping */
			mtdbatl(3, bat.lower.word);
			mtdbatu(3, bat.upper.word);

			sync();isync();
		}
		else if ((args->Video.v_baseAddr & 0xf0000000) != addr) {
			/* start off specifying 1-1 mapping of video seg */
			bat.upper.word	     =
				args->Video.v_baseAddr & 0xf0000000;
			bat.lower.word	     = bat.upper.word;

			bat.upper.bits.bl    = 0x7ff;	/* size = 256M */
			bat.upper.bits.vs    = 1;
			bat.upper.bits.vp    = 0;

			bat.lower.bits.wimg  = PTE_WIMG_IO;
			bat.lower.bits.pp    = 2;	/* read/write access */
			
			sync();isync();
			mtdbatu(3, BAT_INVALID); /* invalidate old mapping */
			mtdbatl(3, bat.lower.word);
			mtdbatu(3, bat.upper.word);

			sync();isync();
		}
		/* Set up segment registers as VM through space 0 */
		for (i=0; i<=15; i++) {
			isync();
			mtsrin(KERNEL_SEG_REG0_VALUE | i, i * 0x10000000);
			sync();
		}

	}

	/*
	 * Setup some processor related structures to satisfy funnels.
	 * Must be done before using unparallelized device drivers.
	 */
	processor_ptr[0] = &processor_array[0];
	master_cpu = 0;
	master_processor = cpu_to_processor(master_cpu);

	/* 
	 * Setup the OpenFirmware Device Tree routines
	 * so the console can be found and the right I/O space 
	 * can be used..
	 */

	ofw_init(args);

	/* Setup machine.. */
	if (args->Version <= KBOOTARGS_VERSION_PDM_ONLY) {
		/* machine type wasn't passed in older boot info,
		 * we give a random PDM machine class as the particular
		 * PDM machine type or speed isn't needed.
		 */
		powermac_define_machine(gestaltPowerMac8100_80);
	} else {
		powermac_define_machine(args->machineType);
	}

#if !SERIAL_CONSOLE_DEFAULT
	initialize_screen((void *) &args->Video);

	printf("video base address = 0x%08x\n",args->Video.v_baseAddr);

#endif /* !SERIAL_CONSOLE_DEFAULT */

#ifdef FIX_BOGUS_GESTALTS
	if (powermac_info.class == POWERMAC_CLASS_PERFORMA ||
	    powermac_info.class == POWERMAC_CLASS_PDM)
		// pdm_display_model_code();
		{
		printf("Machine Code: ");
		printf("0x%x", hwid);
		}
	cnputc('G');
#endif /* FIX_BOGUS_GESTALTS */

	if (powermac_info.class == POWERMAC_CLASS_PDM ||
	    powermac_info.class == POWERMAC_CLASS_PERFORMA ||
	    powermac_info.class == POWERMAC_CLASS_POWERBOOK ||
	    (powermac_info.class == POWERMAC_CLASS_PCI &&
	     (find_devices("scc") || find_devices("escc"))))
		initialize_serial();

#if DEBUG
	cnputc('E');
	cnputc('L');
	cnputc('F');
	printf("video base address = 0x%08x\n",args->Video.v_baseAddr);
	printf("machine gestalt type = %d\n", args->machineType);
	test_varargs(10,1,2,3,4,5,6,7,8,9,10);

	printf("calling bitfield test from pmap\n");
	if (pmap_test_bitfields()) {
		printf("BITFIELD TEST FAILED\n");
		while (1)
			;
	}
#if	MACH_KGDB
	kgdb_debug=0;
#endif	/* MACH_KGDB */
#endif /* DEBUG */
#if !SERIAL_CONSOLE_DEFAULT
	for (i=0; i<strlen(version);i++)
		screen_put_char(version[i]);
#endif /* !SERIAL_CONSOLE_DEFAULT */

	printf("\n");	/* version string doesn't end in newline */

	/* Copy the exception vectors to their rightful place at 0
	 * and make sure icache is clean
	 */
	
#if DEBUG
	printf("Copying exception vectors from 0x%08x end 0x%08x to zero\n",
	       &_ExceptionVectorsStart,&_ExceptionVectorsEnd);
#endif
	src = &_ExceptionVectorsStart;
	dst = (unsigned long *) 0;

	while (src < &_ExceptionVectorsEnd) {
		*(dst++) = *(src++);
	}
	sync_cache(0, (&_ExceptionVectorsEnd - &_ExceptionVectorsStart)*sizeof(long));

#if DEBUG 
	printf("Done\n");
#endif
	/* ARGH! printf must have bss zeroed before it'll work if
	 * assertions are enabled, since it uses a lock.
	 *
	 * The elf-loader has zeroed bss
	 */
#if DEBUG
	printf("\n\n\nThis program was compiled using gcc %d.%d for powerpc\n",
	       __GNUC__,__GNUC_MINOR__);
	printf("Initialised static variable test : s=0x%x\n\n",s);
	if (s != 0x12345678) {
		printf("INITIALISED STATIC TEST FAILED\n");
		while (1)
			;
	}

	printf("mem_size = %d M\n",memory_size / (1024 * 1024));
	boot_args_debug = args;		/* keep pointer to our boot_args */

	/* Processor version information */
	{
		unsigned int pvr;
		__asm__ ("mfpvr %0" : "=r" (pvr));
		printf("processor version register : 0x%08x\n",pvr);
	}

#if DEBUG
	for (i = 0; i < kMaxDRAMBanks; i++) {
		if (args->PhysicalDRAM[i].size) 
			printf("DRAM at 0x%08x size 0x%08x\n",
			       args->PhysicalDRAM[i].base,
			       args->PhysicalDRAM[i].size);
	}
#endif /* DEBUG */
	
	for (i = 0; i < args->kern_info.region_count; i++) {
		DPRINTF(("kernel addr : 0x%08x	   size : 0x%08x    phys_addr : 0x%08x\n",
			 args->kern_info.regions[i].addr,
			 args->kern_info.regions[i].size,
			 args->kern_info.base_addr + 
			 args->kern_info.regions[i].offset));
	}
	DPRINTF(("entry : 0x%08x\n",args->kern_info.entry));
#endif /* DEBUG */

	if ((powermac_info.class != POWERMAC_CLASS_PDM && powermac_info.class != POWERMAC_CLASS_PERFORMA &&
	     powermac_info.class != POWERMAC_CLASS_POWERBOOK) &&
	    ((args->Version < kBootHaveOFWVersion) ||
	     (args->deviceTreeSize == 0))) {
		printf("Sorry! You need to upgrade your MkLinux Booter "
		       "to boot this OSF MK kernel.\n"
		       "(It can be found in your MacOS's "
		       "System Folder:Extensions folder)\n");
	}

	switch (powermac_info.class) {
	case	POWERMAC_CLASS_PDM:
		str = "NuBus";
		break;

	case	POWERMAC_CLASS_PCI:
		str = "PCI";
		break;

	case	POWERMAC_CLASS_PERFORMA:
		str = "Performa (unsupported)";
		break;

	case	POWERMAC_CLASS_POWERBOOK:
		str = "PowerBook";
		break;
	default:
		str = "unsupported";
		break;
	}
	printf("MACH microkernel is booting on a "
	       "Power Macintosh %s class machine via %s...\n", str,
		(args->Version <= LastAppleMkLinuxBooterVersion)?
		"Apple MkLinux Booter": "BootX");

}
