#include <piggybacker/piggyback.h>
#include <piggybacker/kip.h>
#include <piggybacker/io.h>
#include <piggybacker/elf.h>
#include <piggybacker/string.h>
#include <piggybacker/ieee1275.h>
#include <piggybacker/1275tree.h>
#include <piggybacker/powerpc64/page.h>
typedef struct {
    word_t vaddr;
    word_t size;
    word_t paddr;
    word_t mode;
} of1275_map_t;
typedef union {
    struct {
	L4_Word_t base;
	L4_Word_t size;
    } u64;
    struct u32 {
	L4_Word32_t base;
	L4_Word32_t size;
	L4_Word32_t dummy1;
	L4_Word32_t dummy2;
    } u32;
} range_t;
kip_manager_t kip_manager;
extern "C" void 
enter_kernel( L4_Word_t r3, L4_Word_t r4, L4_Word_t r5, L4_Word_t ip );
void boot_fatal( const char *msg )
{
    puts( msg );
    puts( "Aborting ..." );
    prom_exit();
}
static L4_Word32_t cellsize = 1;
bool detect_of1275_memory( device_t *mem_node, L4_Word_t tot_mem )
{
    
    item_t *available = item_find( mem_node, "available" );
    if( !available )
	return false;
    puts( "[==== Detecting OpenFirmware Memory ====]" );
    range_t *ranges = (range_t *)available->data;
    int cnt = available->len / (sizeof(L4_Word32_t) * 2 * cellsize);
    
    
    
    L4_Word_t last = 0;
    L4_Word_t me = (L4_Word_t)detect_of1275_memory;
    for( int i = 0; i < cnt; i++ )
    {
	switch( cellsize ) {
	case 1:
	    if( ((me < last) || (me > ranges->u32.base)) &&
		(ranges->u32.base > last) )
	    {
		print_hex("  reserved : ", last);
		print_hex(" - ", ranges->u32.base);
		puts("");
		
		kip_manager.dedicate_memory( last, ranges->u32.base, 
			L4_BootLoaderSpecificMemoryType, 0xe );
	    }
	    last = ranges->u32.base + ranges->u32.size;
	    ranges = (range_t *)((L4_Word_t)ranges + (sizeof(L4_Word32_t) * 2));
	    break;
	case 2:
	    if( ((me < last) || (me > ranges[i].u64.base)) &&
		(ranges[i].u64.base > last) )
	    {
		print_hex("  reserved : ", last);
		print_hex(" - ", ranges[i].u64.base);
		puts("");
		
		kip_manager.dedicate_memory( last, ranges[i].u64.base, 
			L4_BootLoaderSpecificMemoryType, 0xe );
	    }
	    last = ranges[i].u64.base + ranges[i].u64.size;
	    break;
	}
    }
    
    
    if( (me < last) && (last < tot_mem) )
	kip_manager.dedicate_memory( last, tot_mem,
		L4_BootLoaderSpecificMemoryType, 0xe );
    return true;
}
word_t size_of_memory( char *devtree )
{
    device_t *node = device_first( devtree );
    L4_Word_t total = 0;
    item_t *item_name, *item_data;
    item_data = item_find( node, "#size-cells" );
    if (!item_data)
    {
	puts( "No cell size found\n" );
	return 0;
    }
    cellsize = *(L4_Word32_t *)item_data->data;
    while( node->handle )
    {
	item_name = item_first( node );
	item_data = item_next( item_name );
	if( !strcmp(item_data->data, "memory") )	
	{
	    item_t *reg = item_find( node, "reg" );
	    range_t *ranges = (range_t *)®->data;
	    L4_Word_t i;
	    L4_Word_t tot = 0;
	    L4_Word_t cnt = reg->len / (sizeof(L4_Word32_t) * 2 * cellsize);
	    for( i = 0; i < cnt; i ++ )
	    {
		switch (cellsize) {
		case 1:
		    tot += ranges->u32.size;
		    ranges = (range_t *)((L4_Word_t)ranges + (sizeof(L4_Word32_t) * 2));
		    break;
		case 2: tot += ranges[i].u64.size; break;
		}
	    }
	    total += tot;
	}
	node = device_next( node );
    }
    return total;
}
void detect_platform_memory( char *devtree )
{
    device_t *node, *list_head = device_first( devtree );
    device_t *mem_node;
    
    node = device_find( list_head, "/chosen" );
    if( !node )
	boot_fatal( "Error: unable to find the OpenFirmware /chosen node." );
    item_t *memory = item_find( node, "memory" );
    if( !memory )
	boot_fatal( "Error: unable to find the OpenFirmware /chosen/memory "
		"property." );
    L4_Word32_t mem_handle = *(L4_Word32_t *)memory->data;
    
    mem_node = device_find_handle( list_head, mem_handle );
    if( !mem_node )
	boot_fatal( "Error: unable to lookup the OpenFirmware memory node "
		"handle." );
    
    
    word_t tot = size_of_memory( devtree );
    if( tot == 0 )
	boot_fatal( "Error: didn't detect any platform memory." );
    print_hex( "Detected memory (bytes)", tot );
    puts( "" );
    
    kip_manager.setup_main_memory( 0, tot );
    kip_manager.dedicate_memory( 0, tot, L4_ConventionalMemoryType, 0 );
    
    if( !detect_of1275_memory(mem_node, tot) )
	puts( "Warning: unable to detect Open Firmware's memory "
		"requirements.\n" );
}
void start_kernel( L4_Word_t r3, L4_Word_t r4, L4_Word_t r5 )
{
    elf_ehdr_t *ehdr = (elf_ehdr_t *)get_kernel_start();
    elf_phdr_t *phdr = (elf_phdr_t *)((L4_Word_t)ehdr + ehdr->e_phoff);
    
    L4_Word_t kernel_start_ip = ehdr->e_entry - phdr->p_vaddr + phdr->p_paddr;
    print_hex( "Kernel physical entry point", kernel_start_ip );
    puts( "" );
    puts( "" );
    puts( "[ L4 PowerPC64 ]" );
    enter_kernel( r3, r4, r5, kernel_start_ip );
}
extern "C" void loader_main( L4_Word_t r3, L4_Word_t r4, L4_Word_t of1275_entry)
    
{
    prom_init( of1275_entry );
    puts( "[==== Pistachio PowerPC64 Open Firmware Boot Loader ====]\n" );
    kip_manager.init();
    
    kip_manager.install_sigma0( get_sigma0_start(), get_sigma0_end() );
    kip_manager.install_root_task( get_root_task_start(), get_root_task_end() );
    kip_manager.install_kernel( get_kernel_start(), get_kernel_end() );
    
    L4_Word_t devtree_start = kip_manager.first_avail_page();
    L4_Word_t devtree_size = build_device_tree( (char *)devtree_start );
    L4_Word_t devtree_end = wrap_up( devtree_start + devtree_size, PAGE_SIZE );
    
    print_hex( "Device tree page", devtree_start );
    print_hex( ", length", devtree_size );
    puts( "" );
    
    if( !kip_manager.find_kip(get_kernel_start()) ) 
	boot_fatal( "Error: unable to locate the kernel interface page!" );
    detect_platform_memory( (char *)devtree_start );
    kip_manager.dedicate_memory( devtree_start, devtree_end, 
	    L4_BootLoaderSpecificMemoryType, 0xf );
    kip_manager.update_kip();	
    start_kernel( r3, r4, of1275_entry );
}
extern word_t call_addr;
L4_Word32_t prom_entry ( void * args )
{
    register L4_Word32_t result;
    asm volatile (
	"mtctr	%1; "
	"mr	3, %2;"
	"stdu	1, -1024(1);"	
	"std	2,  -8(1);"
	"std	3, -16(1);"
	"std	4, -24(1);"
	"std	5, -32(1);"
	"std	6, -40(1);"
	"std	7, -48(1);"
	"std	8, -56(1);"
	"std	9, -64(1);"
	"std	10,-72(1);"
	"std	11,-80(1);"
	"std	12,-88(1);"
	"std	13,-96(1);"
	"std	14,-104(1);"
	"std	15,-112(1);"
	"std	16,-120(1);"
	"std	17,-128(1);"
	"std	18,-136(1);"
	"std	19,-144(1);"
	"std	20,-152(1);"
	"std	21,-160(1);"
	"std	22,-168(1);"
	"std	23,-176(1);"
	"std	24,-184(1);"
	"std	25,-192(1);"
	"std	26,-200(1);"
	"std	27,-208(1);"
	"std	28,-216(1);"
	"std	29,-224(1);"
	"std	30,-232(1);"
	"std	31,-240(1);"
	"mfcr   4;"
	"std    4,-248(1);"
	"mfctr  5;"
	"std    5,-256(1);"
	"mfxer  6;"
	"std    6,-264(1);"
	"mfdar  7;"
	"std    7,-272(1);"
	"mfdsisr 8;"
	"std    8,-280(1);"
	"mfsrr0 9;"
	"std    9,-288(1);"
	"mfsrr1 10;"
	"std    10,-296(1);"
	"mfmsr  11;"
	"std    11,-304(1);"
	
	"mtsprg  2,1;"
	"mfmsr   11;"                     
	"li      12,1;"
	"rldicr  12,12,63,(63-63);"
	"andc    11,11,12;"
	"li      12,1;"
	"rldicr  12,12,61,(63-61);"
	"andc    11,11,12;"
	"mtmsrd  11;"
	"isync;"
	"bctrl;	    "
	"mfsprg  1, 2;"                
	"ld      6,-304(1);"             
	"mtmsrd  6;"
	"isync;"
	"ld	2, -8(1);"		
	"ld     13, -96(1);"            
	
	"ld	14,-104(1);"
	"ld	15,-112(1);"
	"ld	16,-120(1);"
	"ld	17,-128(1);"
	"ld	18,-136(1);"
	"ld	19,-144(1);"
	"ld	20,-152(1);"
	"ld	21,-160(1);"
	"ld	22,-168(1);"
	"ld	23,-176(1);"
	"ld	24,-184(1);"
	"ld	25,-192(1);"
	"ld	26,-200(1);"
	"ld	27,-208(1);"
	"ld	28,-216(1);"
	"ld	29,-224(1);"
	"ld	30,-232(1);"
	"ld	31,-240(1);"
	"ld      4,-248(1);"
	"mtcr    4;"
	"ld      5,-256(1);"
	"mtctr   5;"
	"ld      6,-264(1);"
	"mtxer   6;"
	"ld      7,-272(1);"
	"mtdar   7;"
	"ld      8,-280(1);"
	"mtdsisr 8;"
	"ld      9,-288(1);"
	"mtsrr0  9;"
	"ld      10,-296(1);"
	"mtsrr1  10;"
	"addi	1, 1, 1024;"	
	: "=r" (result)
	: "r" (call_addr),
	  "r" (args)
	: "lr", "memory"
    );
    return result;
}