/*
 * Note that prom_init() and anything called from prom_init()
 * may be running at an address that is different from the address
 * that it was linked at.  References to static data items are
 * handled by compiling this file with -mrelocatable-lib.
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/threads.h>
#include <linux/spinlock.h>
#include <linux/ioport.h>
#include <linux/pci.h>
#include <linux/slab.h>

#include <asm/sections.h>
#include <asm/prom.h>
#include <asm/page.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/smp.h>
#include <asm/bootx.h>
#include <asm/system.h>
#include <asm/mmu.h>
#include <asm/pgtable.h>
#include <asm/bitops.h>
#include <asm/bootinfo.h>
#include <asm/btext.h>
#include <asm/pci-bridge.h>
#include <asm/open_pic.h>
#include <asm/cacheflush.h>

#ifdef CONFIG_LOGO_LINUX_CLUT224
#include <linux/linux_logo.h>
extern const struct linux_logo logo_linux_clut224;
#endif

/*
 * Properties whose value is longer than this get excluded from our
 * copy of the device tree.  This way we don't waste space storing
 * things like "driver,AAPL,MacOS,PowerPC" properties.  But this value
 * does need to be big enough to ensure that we don't lose things
 * like the interrupt-map property on a PCI-PCI bridge.
 */
#define MAX_PROPERTY_LENGTH	4096

#ifndef FB_MAX			/* avoid pulling in all of the fb stuff */
#define FB_MAX	8
#endif

#define ALIGNUL(x) (((x) + sizeof(unsigned long)-1) & -sizeof(unsigned long))

struct prom_args {
	const char *service;
	int nargs;
	int nret;
	void *args[10];
};

struct pci_address {
	unsigned a_hi;
	unsigned a_mid;
	unsigned a_lo;
};

struct pci_reg_property {
	struct pci_address addr;
	unsigned size_hi;
	unsigned size_lo;
};

struct pci_range {
	struct pci_address addr;
	unsigned phys;
	unsigned size_hi;
	unsigned size_lo;
};

struct isa_reg_property {
	unsigned space;
	unsigned address;
	unsigned size;
};

struct pci_intr_map {
	struct pci_address addr;
	unsigned dunno;
	phandle int_ctrler;
	unsigned intr;
};

static void prom_exit(void);
static void *call_prom(const char *service, int nargs, int nret, ...);
static void *call_prom_ret(const char *service, int nargs, int nret,
			   void **rets, ...);
static void prom_print_hex(unsigned int v);
static int  prom_set_color(ihandle ih, int i, int r, int g, int b);
static int  prom_next_node(phandle *nodep);
static unsigned long check_display(unsigned long mem);
static void setup_disp_fake_bi(ihandle dp);
static unsigned long copy_device_tree(unsigned long mem_start,
				unsigned long mem_end);
static unsigned long inspect_node(phandle node, struct device_node *dad,
				unsigned long mem_start, unsigned long mem_end,
				struct device_node ***allnextpp);
static void prom_hold_cpus(unsigned long mem);
static void prom_instantiate_rtas(void);
static void * early_get_property(unsigned long base, unsigned long node,
				char *prop);

prom_entry prom __initdata;
ihandle prom_chosen __initdata;
ihandle prom_stdout __initdata;

char *prom_display_paths[FB_MAX] __initdata;
phandle prom_display_nodes[FB_MAX] __initdata;
unsigned int prom_num_displays __initdata;
char *of_stdout_device __initdata;
static ihandle prom_disp_node __initdata;

unsigned int rtas_data;   /* physical pointer */
unsigned int rtas_entry;  /* physical pointer */
unsigned int rtas_size;
unsigned int old_rtas;

boot_infos_t *boot_infos;
char *bootpath;
char *bootdevice;
struct device_node *allnodes;

extern char *klimit;

static void __init
prom_exit(void)
{
	struct prom_args args;

	args.service = "exit";
	args.nargs = 0;
	args.nret = 0;
	prom(&args);
	for (;;)			/* should never get here */
		;
}

static void * __init
call_prom(const char *service, int nargs, int nret, ...)
{
	va_list list;
	int i;
	struct prom_args prom_args;

	prom_args.service = service;
	prom_args.nargs = nargs;
	prom_args.nret = nret;
	va_start(list, nret);
	for (i = 0; i < nargs; ++i)
		prom_args.args[i] = va_arg(list, void *);
	va_end(list);
	for (i = 0; i < nret; ++i)
		prom_args.args[i + nargs] = NULL;
	prom(&prom_args);
	return prom_args.args[nargs];
}

static void * __init
call_prom_ret(const char *service, int nargs, int nret, void **rets, ...)
{
	va_list list;
	int i;
	struct prom_args prom_args;

	prom_args.service = service;
	prom_args.nargs = nargs;
	prom_args.nret = nret;
	va_start(list, rets);
	for (i = 0; i < nargs; ++i)
		prom_args.args[i] = va_arg(list, void *);
	va_end(list);
	for (i = 0; i < nret; ++i)
		prom_args.args[i + nargs] = NULL;
	prom(&prom_args);
	for (i = 1; i < nret; ++i)
		rets[i-1] = prom_args.args[nargs + i];
	return prom_args.args[nargs];
}

void __init
prom_print(const char *msg)
{
	const char *p, *q;

	if (prom_stdout == 0)
		return;

	for (p = msg; *p != 0; p = q) {
		for (q = p; *q != 0 && *q != '\n'; ++q)
			;
		if (q > p)
			call_prom("write", 3, 1, prom_stdout, p, q - p);
		if (*q != 0) {
			++q;
			call_prom("write", 3, 1, prom_stdout, "\r\n", 2);
		}
	}
}

static void __init
prom_print_hex(unsigned int v)
{
	char buf[16];
	int i, c;

	for (i = 0; i < 8; ++i) {
		c = (v >> ((7-i)*4)) & 0xf;
		c += (c >= 10)? ('a' - 10): '0';
		buf[i] = c;
	}
	buf[i] = ' ';
	buf[i+1] = 0;
	prom_print(buf);
}

static int __init
prom_set_color(ihandle ih, int i, int r, int g, int b)
{
	struct prom_args prom_args;

	prom_args.service = "call-method";
	prom_args.nargs = 6;
	prom_args.nret = 1;
	prom_args.args[0] = "color!";
	prom_args.args[1] = ih;
	prom_args.args[2] = (void *) i;
	prom_args.args[3] = (void *) b;
	prom_args.args[4] = (void *) g;
	prom_args.args[5] = (void *) r;
	prom(&prom_args);
	return (int) prom_args.args[6];
}

static int __init
prom_next_node(phandle *nodep)
{
	phandle node;

	if ((node = *nodep) != 0
	    && (*nodep = call_prom("child", 1, 1, node)) != 0)
		return 1;
	if ((*nodep = call_prom("peer", 1, 1, node)) != 0)
		return 1;
	for (;;) {
		if ((node = call_prom("parent", 1, 1, node)) == 0)
			return 0;
		if ((*nodep = call_prom("peer", 1, 1, node)) != 0)
			return 1;
	}
}

#ifdef CONFIG_POWER4
/*
 * Set up a hash table with a set of entries in it to map the
 * first 64MB of RAM.  This is used on 64-bit machines since
 * some of them don't have BATs.
 */

static inline void make_pte(unsigned long htab, unsigned int hsize,
			    unsigned int va, unsigned int pa, int mode)
{
	unsigned int *pteg;
	unsigned int hash, i, vsid;

	vsid = ((va >> 28) * 0x111) << 12;
	hash = ((va ^ vsid) >> 5) & 0x7fff80;
	pteg = (unsigned int *)(htab + (hash & (hsize - 1)));
	for (i = 0; i < 8; ++i, pteg += 4) {
		if ((pteg[1] & 1) == 0) {
			pteg[1] = vsid | ((va >> 16) & 0xf80) | 1;
			pteg[3] = pa | mode;
			break;
		}
	}
}

extern unsigned long _SDR1;
extern PTE *Hash;
extern unsigned long Hash_size;

static void __init
prom_alloc_htab(void)
{
	unsigned int hsize;
	unsigned long htab;
	unsigned int addr;

	/*
	 * Because of OF bugs we can't use the "claim" client
	 * interface to allocate memory for the hash table.
	 * This code is only used on 64-bit PPCs, and the only
	 * 64-bit PPCs at the moment are RS/6000s, and their
	 * OF is based at 0xc00000 (the 12M point), so we just
	 * arbitrarily use the 0x800000 - 0xc00000 region for the
	 * hash table.
	 *  -- paulus.
	 */
	hsize = 4 << 20;	/* POWER4 has no BATs */
	htab = (8 << 20);
	call_prom("claim", 3, 1, htab, hsize, 0);
	Hash = (void *)(htab + KERNELBASE);
	Hash_size = hsize;
	_SDR1 = htab + __ilog2(hsize) - 18;

	/*
	 * Put in PTEs for the first 64MB of RAM
	 */
	memset((void *)htab, 0, hsize);
	for (addr = 0; addr < 0x4000000; addr += 0x1000)
		make_pte(htab, hsize, addr + KERNELBASE, addr,
			 _PAGE_ACCESSED | _PAGE_COHERENT | PP_RWXX);
#if 0 /* DEBUG stuff mapping the SCC */
	make_pte(htab, hsize, 0x80013000, 0x80013000,
		 _PAGE_ACCESSED | _PAGE_NO_CACHE | _PAGE_GUARDED | PP_RWXX);
#endif
}
#endif /* CONFIG_POWER4 */


/*
 * If we have a display that we don't know how to drive,
 * we will want to try to execute OF's open method for it
 * later.  However, OF will probably fall over if we do that
 * we've taken over the MMU.
 * So we check whether we will need to open the display,
 * and if so, open it now.
 */
static unsigned long __init
check_display(unsigned long mem)
{
	phandle node;
	ihandle ih;
	int i, j;
	char type[16], *path;
	static unsigned char default_colors[] = {
		0x00, 0x00, 0x00,
		0x00, 0x00, 0xaa,
		0x00, 0xaa, 0x00,
		0x00, 0xaa, 0xaa,
		0xaa, 0x00, 0x00,
		0xaa, 0x00, 0xaa,
		0xaa, 0xaa, 0x00,
		0xaa, 0xaa, 0xaa,
		0x55, 0x55, 0x55,
		0x55, 0x55, 0xff,
		0x55, 0xff, 0x55,
		0x55, 0xff, 0xff,
		0xff, 0x55, 0x55,
		0xff, 0x55, 0xff,
		0xff, 0xff, 0x55,
		0xff, 0xff, 0xff
	};
	const unsigned char *clut;

	prom_disp_node = NULL;

	for (node = NULL; prom_next_node(&node); ) {
		type[0] = 0;
		call_prom("getprop", 4, 1, node, "device_type",
			  type, sizeof(type));
		if (strcmp(type, "display") != 0)
			continue;
		/* It seems OF doesn't null-terminate the path :-( */
		path = (char *) mem;
		memset(path, 0, 256);
		if ((int) call_prom("package-to-path", 3, 1,
				    node, path, 255) < 0)
			continue;

		/*
		 * If this display is the device that OF is using for stdout,
		 * move it to the front of the list.
		 */
		mem += strlen(path) + 1;
		i = prom_num_displays++;
		if (of_stdout_device != 0 && i > 0
		    && strcmp(of_stdout_device, path) == 0) {
			for (; i > 0; --i) {
				prom_display_paths[i]
					= prom_display_paths[i-1];
				prom_display_nodes[i]
					= prom_display_nodes[i-1];
			}
		}
		prom_display_paths[i] = path;
		prom_display_nodes[i] = node;
		if (i == 0)
			prom_disp_node = node;
		if (prom_num_displays >= FB_MAX)
			break;
	}

	for (j=0; j<prom_num_displays; j++) {
		path = prom_display_paths[j];
		prom_print("opening display ");
		prom_print(path);
		ih = call_prom("open", 1, 1, path);
		if (ih == 0 || ih == (ihandle) -1) {
			prom_print("... failed\n");
			for (i=j+1; i<prom_num_displays; i++) {
				prom_display_paths[i-1] = prom_display_paths[i];
				prom_display_nodes[i-1] = prom_display_nodes[i];
			}
			if (--prom_num_displays > 0) {
				prom_disp_node = prom_display_nodes[j];
				j--;
			} else
				prom_disp_node = NULL;
			continue;
		} else {
			prom_print("... ok\n");
			/*
			 * Setup a usable color table when the appropriate
			 * method is available.
			 * Should update this to use set-colors.
			 */
			clut = default_colors;
			for (i = 0; i < 32; i++, clut += 3)
				if (prom_set_color(ih, i, clut[0], clut[1],
						   clut[2]) != 0)
					break;

#ifdef CONFIG_LOGO_LINUX_CLUT224
			clut = PTRRELOC(logo_linux_clut224.clut);
			for (i = 0; i < logo_linux_clut224.clutsize;
			     i++, clut += 3)
				if (prom_set_color(ih, i + 32, clut[0],
						   clut[1], clut[2]) != 0)
					break;
#endif /* CONFIG_LOGO_LINUX_CLUT224 */
		}
	}

	return ALIGNUL(mem);
}

/* This function will enable the early boot text when doing OF booting. This
 * way, xmon output should work too
 */
static void __init
setup_disp_fake_bi(ihandle dp)
{
#ifdef CONFIG_BOOTX_TEXT
	int width = 640, height = 480, depth = 8, pitch;
	unsigned address;
	struct pci_reg_property addrs[8];
	int i, naddrs;
	char name[32];
	char *getprop = "getprop";

	prom_print("Initializing fake screen: ");

	memset(name, 0, sizeof(name));
	call_prom(getprop, 4, 1, dp, "name", name, sizeof(name));
	name[sizeof(name)-1] = 0;
	prom_print(name);
	prom_print("\n");
	call_prom(getprop, 4, 1, dp, "width", &width, sizeof(width));
	call_prom(getprop, 4, 1, dp, "height", &height, sizeof(height));
	call_prom(getprop, 4, 1, dp, "depth", &depth, sizeof(depth));
	pitch = width * ((depth + 7) / 8);
	call_prom(getprop, 4, 1, dp, "linebytes",
		  &pitch, sizeof(pitch));
	if (pitch == 1)
		pitch = 0x1000;		/* for strange IBM display */
	address = 0;
	call_prom(getprop, 4, 1, dp, "address",
		  &address, sizeof(address));
	if (address == 0) {
		/* look for an assigned address with a size of >= 1MB */
		naddrs = (int) call_prom(getprop, 4, 1, dp,
				"assigned-addresses",
				addrs, sizeof(addrs));
		naddrs /= sizeof(struct pci_reg_property);
		for (i = 0; i < naddrs; ++i) {
			if (addrs[i].size_lo >= (1 << 20)) {
				address = addrs[i].addr.a_lo;
				/* use the BE aperture if possible */
				if (addrs[i].size_lo >= (16 << 20))
					address += (8 << 20);
				break;
			}
		}
		if (address == 0) {
			prom_print("Failed to get address\n");
			return;
		}
	}
	/* kludge for valkyrie */
	if (strcmp(name, "valkyrie") == 0)
		address += 0x1000;

#ifdef CONFIG_POWER4
#if CONFIG_TASK_SIZE > 0x80000000
#error CONFIG_TASK_SIZE cannot be above 0x80000000 with BOOTX_TEXT on G5
#endif
	{
		extern boot_infos_t disp_bi;
		unsigned long va, pa, i, offset;
       		va = 0x90000000;
		pa = address & 0xfffff000ul;
		offset = address & 0x00000fff;

		for (i=0; i<0x4000; i++) {  
			make_pte((unsigned long)Hash - KERNELBASE, Hash_size, va, pa, 
				 _PAGE_ACCESSED | _PAGE_NO_CACHE |
				 _PAGE_GUARDED | PP_RWXX);
			va += 0x1000;
			pa += 0x1000;
		}
		btext_setup_display(width, height, depth, pitch, 0x90000000 | offset);
		disp_bi.dispDeviceBase = (u8 *)address;
	}
#else /* CONFIG_POWER4 */
	btext_setup_display(width, height, depth, pitch, address);
	btext_prepare_BAT();
#endif /* CONFIG_POWER4 */
#endif /* CONFIG_BOOTX_TEXT */
}

/*
 * Make a copy of the device tree from the PROM.
 */
static unsigned long __init
copy_device_tree(unsigned long mem_start, unsigned long mem_end)
{
	phandle root;
	unsigned long new_start;
	struct device_node **allnextp;

	root = call_prom("peer", 1, 1, (phandle)0);
	if (root == (phandle)0) {
		prom_print("couldn't get device tree root\n");
		prom_exit();
	}
	allnextp = &allnodes;
	mem_start = ALIGNUL(mem_start);
	new_start = inspect_node(root, NULL, mem_start, mem_end, &allnextp);
	*allnextp = NULL;
	return new_start;
}

static unsigned long __init
inspect_node(phandle node, struct device_node *dad,
	     unsigned long mem_start, unsigned long mem_end,
	     struct device_node ***allnextpp)
{
	int l;
	phandle child;
	struct device_node *np;
	struct property *pp, **prev_propp;
	char *prev_name, *namep;
	unsigned char *valp;

	np = (struct device_node *) mem_start;
	mem_start += sizeof(struct device_node);
	memset(np, 0, sizeof(*np));
	np->node = node;
	**allnextpp = PTRUNRELOC(np);
	*allnextpp = &np->allnext;
	if (dad != 0) {
		np->parent = PTRUNRELOC(dad);
		/* we temporarily use the `next' field as `last_child'. */
		if (dad->next == 0)
			dad->child = PTRUNRELOC(np);
		else
			dad->next->sibling = PTRUNRELOC(np);
		dad->next = np;
	}

	/* get and store all properties */
	prev_propp = &np->properties;
	prev_name = "";
	for (;;) {
		pp = (struct property *) mem_start;
		namep = (char *) (pp + 1);
		pp->name = PTRUNRELOC(namep);
		if ((int) call_prom("nextprop", 3, 1, node, prev_name,
				    namep) <= 0)
			break;
		mem_start = ALIGNUL((unsigned long)namep + strlen(namep) + 1);
		prev_name = namep;
		valp = (unsigned char *) mem_start;
		pp->value = PTRUNRELOC(valp);
		pp->length = (int)
			call_prom("getprop", 4, 1, node, namep,
				  valp, mem_end - mem_start);
		if (pp->length < 0)
			continue;
#ifdef MAX_PROPERTY_LENGTH
		if (pp->length > MAX_PROPERTY_LENGTH)
			continue; /* ignore this property */
#endif
		mem_start = ALIGNUL(mem_start + pp->length);
		*prev_propp = PTRUNRELOC(pp);
		prev_propp = &pp->next;
	}
	if (np->node != NULL) {
		/* Add a "linux,phandle" property" */
		pp = (struct property *) mem_start;
		*prev_propp = PTRUNRELOC(pp);
		prev_propp = &pp->next;
		namep = (char *) (pp + 1);
		pp->name = PTRUNRELOC(namep);
		strcpy(namep, "linux,phandle");
		mem_start = ALIGNUL((unsigned long)namep + strlen(namep) + 1);
		pp->value = (unsigned char *) PTRUNRELOC(&np->node);
		pp->length = sizeof(np->node);
	}
	*prev_propp = NULL;

	/* get the node's full name */
	l = (int) call_prom("package-to-path", 3, 1, node,
			    (char *) mem_start, mem_end - mem_start);
	if (l >= 0) {
		np->full_name = PTRUNRELOC((char *) mem_start);
		*(char *)(mem_start + l) = 0;
		mem_start = ALIGNUL(mem_start + l + 1);
	}

	/* do all our children */
	child = call_prom("child", 1, 1, node);
	while (child != (void *)0) {
		mem_start = inspect_node(child, np, mem_start, mem_end,
					 allnextpp);
		child = call_prom("peer", 1, 1, child);
	}

	return mem_start;
}

unsigned long smp_chrp_cpu_nr __initdata = 0;

/*
 * With CHRP SMP we need to use the OF to start the other
 * processors so we can't wait until smp_boot_cpus (the OF is
 * trashed by then) so we have to put the processors into
 * a holding pattern controlled by the kernel (not OF) before
 * we destroy the OF.
 *
 * This uses a chunk of high memory, puts some holding pattern
 * code there and sends the other processors off to there until
 * smp_boot_cpus tells them to do something.  We do that by using
 * physical address 0x0.  The holding pattern checks that address
 * until its cpu # is there, when it is that cpu jumps to
 * __secondary_start().  smp_boot_cpus() takes care of setting those
 * values.
 *
 * We also use physical address 0x4 here to tell when a cpu
 * is in its holding pattern code.
 *
 * -- Cort
 *
 * Note that we have to do this if we have more than one CPU,
 * even if this is a UP kernel.  Otherwise when we trash OF
 * the other CPUs will start executing some random instructions
 * and crash the system.  -- paulus
 */
static void __init
prom_hold_cpus(unsigned long mem)
{
	extern void __secondary_hold(void);
	unsigned long i;
	int cpu;
	phandle node;
	char type[16], *path;
	unsigned int reg;

	/*
	 * XXX: hack to make sure we're chrp, assume that if we're
	 *      chrp we have a device_type property -- Cort
	 */
	node = call_prom("finddevice", 1, 1, "/");
	if ((int)call_prom("getprop", 4, 1, node,
			   "device_type",type, sizeof(type)) <= 0)
		return;

	/* copy the holding pattern code to someplace safe (0) */
	/* the holding pattern is now within the first 0x100
	   bytes of the kernel image -- paulus */
	memcpy((void *)0, _stext, 0x100);
	flush_icache_range(0, 0x100);

	/* look for cpus */
	*(unsigned long *)(0x0) = 0;
	asm volatile("dcbf 0,%0": : "r" (0) : "memory");
	for (node = NULL; prom_next_node(&node); ) {
		type[0] = 0;
		call_prom("getprop", 4, 1, node, "device_type",
			  type, sizeof(type));
		if (strcmp(type, "cpu") != 0)
			continue;
		path = (char *) mem;
		memset(path, 0, 256);
		if ((int) call_prom("package-to-path", 3, 1,
				    node, path, 255) < 0)
			continue;
		reg = -1;
		call_prom("getprop", 4, 1, node, "reg", &reg, sizeof(reg));
		cpu = smp_chrp_cpu_nr++;
#ifdef CONFIG_SMP
		smp_hw_index[cpu] = reg;
#endif /* CONFIG_SMP */
		/* XXX: hack - don't start cpu 0, this cpu -- Cort */
		if (cpu == 0)
			continue;
		prom_print("starting cpu ");
		prom_print(path);
		*(ulong *)(0x4) = 0;
		call_prom("start-cpu", 3, 0, node,
			  (char *)__secondary_hold - _stext, cpu);
		prom_print("...");
		for ( i = 0 ; (i < 10000) && (*(ulong *)(0x4) == 0); i++ )
			;
		if (*(ulong *)(0x4) == cpu)
			prom_print("ok\n");
		else {
			prom_print("failed: ");
			prom_print_hex(*(ulong *)0x4);
			prom_print("\n");
		}
	}
}

static void __init
prom_instantiate_rtas(void)
{
	ihandle prom_rtas;
	unsigned int i;
	struct prom_args prom_args;

	prom_rtas = call_prom("finddevice", 1, 1, "/rtas");
	if (prom_rtas == (void *) -1)
		return;

	rtas_size = 0;
	call_prom("getprop", 4, 1, prom_rtas,
		  "rtas-size", &rtas_size, sizeof(rtas_size));
	prom_print("instantiating rtas");
	if (rtas_size == 0) {
		rtas_data = 0;
	} else {
		/*
		 * Ask OF for some space for RTAS.
		 * Actually OF has bugs so we just arbitrarily
		 * use memory at the 6MB point.
		 */
		rtas_data = 6 << 20;
		prom_print(" at ");
		prom_print_hex(rtas_data);
	}

	prom_rtas = call_prom("open", 1, 1, "/rtas");
	prom_print("...");
	prom_args.service = "call-method";
	prom_args.nargs = 3;
	prom_args.nret = 2;
	prom_args.args[0] = "instantiate-rtas";
	prom_args.args[1] = prom_rtas;
	prom_args.args[2] = (void *) rtas_data;
	prom(&prom_args);
	i = 0;
	if (prom_args.args[3] == 0)
		i = (unsigned int)prom_args.args[4];
	rtas_entry = i;
	if ((rtas_entry == -1) || (rtas_entry == 0))
		prom_print(" failed\n");
	else
		prom_print(" done\n");
}

/*
 * We enter here early on, when the Open Firmware prom is still
 * handling exceptions and the MMU hash table for us.
 */
unsigned long __init
prom_init(int r3, int r4, prom_entry pp)
{
	unsigned long mem;
	ihandle prom_mmu;
	unsigned long offset = reloc_offset();
	int i, l;
	char *p, *d;
 	unsigned long phys;
	void *result[3];

 	/* Default */
 	phys = (unsigned long) &_stext;

	/* First get a handle for the stdout device */
	prom = pp;
	prom_chosen = call_prom("finddevice", 1, 1, "/chosen");
	if (prom_chosen == (void *)-1)
		prom_exit();
	if ((int) call_prom("getprop", 4, 1, prom_chosen,
			    "stdout", &prom_stdout,
			    sizeof(prom_stdout)) <= 0)
		prom_exit();

	/* Get the full OF pathname of the stdout device */
	mem = (unsigned long) klimit + offset;
	p = (char *) mem;
	memset(p, 0, 256);
	call_prom("instance-to-path", 3, 1, prom_stdout, p, 255);
	of_stdout_device = p;
	mem += strlen(p) + 1;

	/* Get the boot device and translate it to a full OF pathname. */
	p = (char *) mem;
	l = (int) call_prom("getprop", 4, 1, prom_chosen,
			    "bootpath", p, 1<<20);
	if (l > 0) {
		p[l] = 0;	/* should already be null-terminated */
		bootpath = PTRUNRELOC(p);
		mem += l + 1;
		d = (char *) mem;
		*d = 0;
		call_prom("canon", 3, 1, p, d, 1<<20);
		bootdevice = PTRUNRELOC(d);
		mem = ALIGNUL(mem + strlen(d) + 1);
	}

	prom_instantiate_rtas();

#ifdef CONFIG_POWER4
	/*
	 * Find out how much memory we have and allocate a
	 * suitably-sized hash table.
	 */
	prom_alloc_htab();
#endif
	mem = check_display(mem);

	prom_print("copying OF device tree...");
	mem = copy_device_tree(mem, mem + (1<<20));
	prom_print("done\n");

	prom_hold_cpus(mem);

	klimit = (char *) (mem - offset);

	/* If we are already running at 0xc0000000, we assume we were
	 * loaded by an OF bootloader which did set a BAT for us.
	 * This breaks OF translate so we force phys to be 0.
	 */
	if (offset == 0) {
		prom_print("(already at 0xc0000000) phys=0\n");
		phys = 0;
	} else if ((int) call_prom("getprop", 4, 1, prom_chosen, "mmu",
				 &prom_mmu, sizeof(prom_mmu)) <= 0) {
		prom_print(" no MMU found\n");
	} else if ((int)call_prom_ret("call-method", 4, 4, result, "translate",
				      prom_mmu, &_stext, 1) != 0) {
		prom_print(" (translate failed)\n");
	} else {
		/* We assume the phys. address size is 3 cells */
		phys = (unsigned long)result[2];
	}

	if (prom_disp_node != 0)
		setup_disp_fake_bi(prom_disp_node);

	/* Use quiesce call to get OF to shut down any devices it's using */
	prom_print("Calling quiesce ...\n");
	call_prom("quiesce", 0, 0);

	/* Relocate various pointers which will be used once the
	   kernel is running at the address it was linked at. */
	for (i = 0; i < prom_num_displays; ++i)
		prom_display_paths[i] = PTRUNRELOC(prom_display_paths[i]);

#ifdef CONFIG_SERIAL_CORE_CONSOLE
	/* Relocate the of stdout for console autodetection */
	of_stdout_device = PTRUNRELOC(of_stdout_device);
#endif

	prom_print("returning 0x");
	prom_print_hex(phys);
	prom_print("from prom_init\n");
	prom_stdout = NULL;

	return phys;
}

/*
 * early_get_property is used to access the device tree image prepared
 * by BootX very early on, before the pointers in it have been relocated.
 */
static void * __init
early_get_property(unsigned long base, unsigned long node, char *prop)
{
	struct device_node *np = (struct device_node *)(base + node);
	struct property *pp;

	for (pp = np->properties; pp != 0; pp = pp->next) {
		pp = (struct property *) (base + (unsigned long)pp);
		if (strcmp((char *)((unsigned long)pp->name + base),
			   prop) == 0) {
			return (void *)((unsigned long)pp->value + base);
		}
	}
	return NULL;
}

/* Is boot-info compatible ? */
#define BOOT_INFO_IS_COMPATIBLE(bi)		((bi)->compatible_version <= BOOT_INFO_VERSION)
#define BOOT_INFO_IS_V2_COMPATIBLE(bi)	((bi)->version >= 2)
#define BOOT_INFO_IS_V4_COMPATIBLE(bi)	((bi)->version >= 4)

void __init
bootx_init(unsigned long r4, unsigned long phys)
{
	boot_infos_t *bi = (boot_infos_t *) r4;
	unsigned long space;
	unsigned long ptr, x;
	char *model;

	boot_infos = PTRUNRELOC(bi);
	if (!BOOT_INFO_IS_V2_COMPATIBLE(bi))
		bi->logicalDisplayBase = NULL;

#ifdef CONFIG_BOOTX_TEXT
	btext_init(bi);

	/*
	 * Test if boot-info is compatible.  Done only in config
	 * CONFIG_BOOTX_TEXT since there is nothing much we can do
	 * with an incompatible version, except display a message
	 * and eventually hang the processor...
	 *
	 * I'll try to keep enough of boot-info compatible in the
	 * future to always allow display of this message;
	 */
	if (!BOOT_INFO_IS_COMPATIBLE(bi)) {
		btext_drawstring(" !!! WARNING - Incompatible version of BootX !!!\n\n\n");
		btext_flushscreen();
	}
#endif	/* CONFIG_BOOTX_TEXT */

	/* New BootX enters kernel with MMU off, i/os are not allowed
	   here. This hack will have been done by the boostrap anyway.
	*/
	if (bi->version < 4) {
		/*
		 * XXX If this is an iMac, turn off the USB controller.
		 */
		model = (char *) early_get_property
			(r4 + bi->deviceTreeOffset, 4, "model");
		if (model
		    && (strcmp(model, "iMac,1") == 0
			|| strcmp(model, "PowerMac1,1") == 0)) {
			out_le32((unsigned *)0x80880008, 1);	/* XXX */
		}
	}

	/* Move klimit to enclose device tree, args, ramdisk, etc... */
	if (bi->version < 5) {
		space = bi->deviceTreeOffset + bi->deviceTreeSize;
		if (bi->ramDisk)
			space = bi->ramDisk + bi->ramDiskSize;
	} else
		space = bi->totalParamsSize;
	klimit = PTRUNRELOC((char *) bi + space);

	/* New BootX will have flushed all TLBs and enters kernel with
	   MMU switched OFF, so this should not be useful anymore.
	*/
	if (bi->version < 4) {
		/*
		 * Touch each page to make sure the PTEs for them
		 * are in the hash table - the aim is to try to avoid
		 * getting DSI exceptions while copying the kernel image.
		 */
		for (ptr = ((unsigned long) &_stext) & PAGE_MASK;
		     ptr < (unsigned long)bi + space; ptr += PAGE_SIZE)
			x = *(volatile unsigned long *)ptr;
	}

#ifdef CONFIG_BOOTX_TEXT
	/*
	 * Note that after we call btext_prepare_BAT, we can't do
	 * prom_draw*, flushscreen or clearscreen until we turn the MMU
	 * on, since btext_prepare_BAT sets disp_bi.logicalDisplayBase
	 * to a virtual address.
	 */
	btext_prepare_BAT();
#endif
}
