/*
 *   Creation Date: <2004/08/28 18:38:22 greg>
 *   Time-stamp: <2004/08/28 18:38:22 greg>
 *
 *	<main.c>
 *
 *   Copyright (C) 2004 Greg Watson
 *
 *   Based on MOL specific code which is
 *   Copyright (C) 2002, 2003, 2004 Samuel Rydh (samuel@ibrium.se)
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation
 *
 */


#include "config.h"
#include "libopenbios/bindings.h"
#include "libopenbios/elfload.h"
#include "arch/common/nvram.h"
#include "libc/diskio.h"
#include "libc/vsprintf.h"
#include "pearpc/pearpc.h"
#include "libopenbios/ofmem.h"

static void
transfer_control_to_elf( unsigned long entry )
{
	extern void call_elf( unsigned long entry );
	printk("Starting ELF image at 0x%08lX\n", entry);
	call_elf( 0x400000 );
	//call_elf( entry );

	fatal_error("call_elf returned unexpectedly\n");
}

static int
load_elf_rom( unsigned long *entry, int fd )
{
	int i, lszz_offs, elf_offs;
	char buf[128], *addr;
	Elf_ehdr ehdr;
	Elf_phdr *phdr;
	size_t s;

	printk("Loading '%s'\n", get_file_path(fd));

	/* the ELF-image (usually) starts at offset 0x4000 */
	if( (elf_offs=find_elf(fd)) < 0 ) {
		printk("----> %s is not an ELF image\n", buf );
		exit(1);
	}
	if( !(phdr=elf_readhdrs(fd, elf_offs, &ehdr)) )
		fatal_error("elf_readhdrs failed\n");

	*entry = ehdr.e_entry;

	/* load segments. Compressed ROM-image assumed to be located immediately
	 * after the last segment */
	lszz_offs = elf_offs;
	for( i=0; i<ehdr.e_phnum; i++ ) {
		/* p_memsz, p_flags */
		s = MIN( phdr[i].p_filesz, phdr[i].p_memsz );
		seek_io( fd, elf_offs + phdr[i].p_offset );

		/* printk("filesz: %08lX memsz: %08lX p_offset: %08lX p_vaddr %08lX\n",
		   phdr[i].p_filesz, phdr[i].p_memsz, phdr[i].p_offset,
		   phdr[i].p_vaddr ); */

		if( phdr[i].p_vaddr != phdr[i].p_paddr )
			printk("WARNING: ELF segment virtual addr != physical addr\n");
		lszz_offs = MAX( lszz_offs, elf_offs + phdr[i].p_offset + phdr[i].p_filesz );
		if( !s )
			continue;
		if( ofmem_claim( phdr[i].p_vaddr, phdr[i].p_memsz, 0 ) == -1 )
			fatal_error("Claim failed!\n");

		addr = (char*)phdr[i].p_vaddr;
		if( read_io(fd, addr, s) != s )
			fatal_error("read failed\n");

#if 0
		/* patch CODE segment */
		if( *entry >= phdr[i].p_vaddr && *entry < phdr[i].p_vaddr + s ) {
			patch_newworld_rom( (char*)phdr[i].p_vaddr, s );
			newworld_timer_hack( (char*)phdr[i].p_vaddr, s );
		}
#endif
		flush_icache_range( addr, addr+s );

		/*printk("ELF ROM-section loaded at %08lX (size %08lX)\n",
		   (unsigned long)phdr[i].p_vaddr, (unsigned long)phdr[i].p_memsz );*/
	}
	free( phdr );
	return lszz_offs;
}


static void
encode_bootpath( const char *spec, const char *args )
{
	phandle_t chosen_ph = find_dev("/chosen");
	set_property( chosen_ph, "bootpath", spec, strlen(spec)+1 );
	set_property( chosen_ph, "bootargs", args, strlen(args)+1 );
}

/************************************************************************/
/*	pearpc booting							*/
/************************************************************************/

static void
pearpc_startup( void )
{
	const char *paths[] = { "hd:0,\\zImage.chrp", NULL };
	const char *args[] = { "root=/dev/hda2 console=ttyS0,115200", NULL };
	unsigned long entry;
	int i, fd;

	for( i=0; paths[i]; i++ ) {
		if( (fd=open_io(paths[i])) == -1 )
			continue;
		(void) load_elf_rom( &entry, fd );
		close_io( fd );
		encode_bootpath( paths[i], args[i] );

		update_nvram();
		transfer_control_to_elf( entry );
		/* won't come here */
	}
	printk("*** Boot failure! No secondary bootloader specified ***\n");
}


/************************************************************************/
/*	entry								*/
/************************************************************************/

void
boot( void )
{
	fword("update-chosen");
	pearpc_startup();
}
