/* 
 *   Creation Date: <2001/06/16 18:43:50 samuel>
 *   Time-stamp: <2003/06/12 14:04:58 samuel>
 *   
 *	<elf.c>
 *	
 *	Loads and run a ELF-file
 *   
 *   Copyright (C) 2001, 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 "mol_config.h"
#include <sys/param.h>
#include "booter.h"
#include "elfload.h"
#include "memory.h"
#include "mac_registers.h"
#include "res_manager.h"
#include "video.h"
#include "processor.h"
#include "../drivers/include/pci.h"
#include "mol_assert.h"
#include "os_interface.h"
#include "wrapper.h"
#include "mmu_mappings.h"
#include "pseudofs.h"

static mmu_mapping_t	mregs_map;


/************************************************************************/
/*	common code							*/
/************************************************************************/

static ulong
common_startup( char *image_name )
{
	Elf32_Ehdr ehdr;
	Elf32_Phdr *phdr;
	ulong mem_top;
	int fd, i;

	if( !(fd=open(image_name, O_RDONLY)) )
		fatal("Could not open '%s'\n", image_name );
	if( !is_elf32(fd, 0) )
		fatal("%s is not an ELF image\n", image_name );
	if( !(phdr=elf32_readhdrs(fd, 0, &ehdr)) )
		fatal("elf32_readhdrs failed\n");

	mem_top = 0;
	for( i=0; i<ehdr.e_phnum; i++ ) {
		size_t s = MIN( phdr[i].p_filesz, phdr[i].p_memsz );
		char *addr;
		/* printm("filesz: %08X memsz: %08X p_offset: %08X p_vaddr %08X\n", 
		   phdr[i].p_filesz, phdr[i].p_memsz, phdr[i].p_offset, phdr[i].p_vaddr ); */

		if( mem_top < phdr[i].p_memsz + phdr[i].p_paddr )
			mem_top = phdr[i].p_memsz + phdr[i].p_paddr;

		if( !(addr=transl_mphys( phdr[i].p_paddr )) ) {
			printm("ELF: program segment not in RAM\n");
			continue;
		}
		lseek( fd, phdr[i].p_offset, SEEK_SET );
		if( read(fd, addr, s) != s )
			fatal("read failed\n");
		flush_icache_range( addr, addr+s );
	}
	mregs->nip = ehdr.e_entry;
	free( phdr );
	close( fd );
	
	return mem_top;
}

static void
common_cleanup( void )
{
	if( mregs_map.id )
		_remove_mmu_mapping( &mregs_map );
}

/* mapin mregs at physical address specified by client */ 
static int
osip_mapin_mregs( int sel, int *params )
{
	ulong mphys = params[0];

	if( mregs_map.id )
		_remove_mmu_mapping( &mregs_map );

	if( mphys ) {
		mregs_map.mbase = mphys;
		mregs_map.lvbase = (void*)_get_mregs_phys();
		mregs_map.size = sizeof(mac_regs_t);
		mregs_map.flags = MAPPING_PHYSICAL | MAPPING_RO;
		if( mphys < ram.size )
			mregs_map.flags |= MAPPING_PUT_FIRST;
		_add_mmu_mapping( &mregs_map );
		
		/* printm("mregs mapin @%08lx -> %0lx\n", mphys, (ulong)mregs_map.lvbase ); */
	}
	return 0;
}

/* EMUACCEL_xxx, nip -- branch_target */
static int
osip_emuaccel( int sel, int *params )
{
	int ret, emuaccel_inst = params[0];
	int par = params[1];
	int addr = params[2];
	
	if( !emuaccel_inst )
		return _mapin_emuaccel_page( addr );

	ret = _alloc_emuaccel_slot( emuaccel_inst, par, addr );
	/* printm("emuaccel [%d]: %08x (%d)\n", ret, addr, emuaccel_inst ); */

	return ret;
}


/************************************************************************/
/*	generic ELF booting						*/
/************************************************************************/

static void
elf_startup( void )
{
	char *name;

	if( !(name=get_filename_res("elf_image")) )
		missing_config_exit("elf_image");

	(void) common_startup( name );

	/* this is mostly for debugging */
	if( get_bool_res("assign_pci") )
		pci_assign_addresses();

	mregs->msr = 0;
	mregs->spr[ S_SDR1 ] = ram.size - 65536;
	mregs->gpr[1] = ram.size - 65536 - 32;

	/* os_interface_add_proc( OSI_MAPIN_MREGS, osip_mapin_mregs ); */
}


/************************************************************************/
/*	MacOS X boot loader						*/
/************************************************************************/

static void
osx_startup( void )
{
	char *name;
	
	if( !(name=get_filename_res("bootx_image")) )
		missing_config_exit("bootx_image");
	
	(void) common_startup( name );

	pci_assign_addresses();

	os_interface_add_proc( OSI_MAPIN_MREGS, osip_mapin_mregs );
	os_interface_add_proc( OSI_EMUACCEL, osip_emuaccel );
}


/************************************************************************/
/*	OpenFirmware booting (newworld MacOS, linux)			*/
/************************************************************************/

static void
of_startup( void )
{
	char *name;

	/* the OF loader requires 32 MB of RAM */
	if( ram.size < 32 * 0x100000 )
		fatal("Specify at least 32 MB of RAM!\n");

	if( !(name = get_filename_res("of_image")) )
		missing_config_exit("of_image");

	(void) common_startup( name );
	pci_assign_addresses();
	rtas_init();
}


/************************************************************************/
/*	inits								*/
/************************************************************************/

void
elf_booter_init( void )
{
	gPE.booter_startup = elf_startup;
	gPE.booter_cleanup = common_cleanup;
}

void
linux_booter_init( void )
{
	gPE.booter_startup = of_startup;
	gPE.booter_cleanup = common_cleanup;
}

void
newworld_booter_init( void )
{
	gPE.booter_startup = of_startup;
	gPE.booter_cleanup = common_cleanup;

	/* MacOS uselessly loads/clears the BAT register in the idle
	 * loop. This has a performance impact which we work around by
	 * avoiding flushing the BAT mapping.
	 */
	mregs->use_bat_hack = 1;
}

void
osx_booter_init( void )
{
	gPE.booter_startup = osx_startup;
	gPE.booter_cleanup = common_cleanup;
}
