/*
 *   Creation Date: <2003/12/04 17:07:05 samuel>
 *   Time-stamp: <2004/01/07 19:36:09 samuel>
 *
 *	<mac-parts.c>
 *
 *	macintosh partition support
 *
 *   Copyright (C) 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
 *   version 2
 *
 */

#include "config.h"
#include "libopenbios/bindings.h"
#include "libopenbios/load.h"
#include "mac-parts.h"
#include "libc/byteorder.h"
#include "libc/vsprintf.h"
#include "packages.h"

//#define CONFIG_DEBUG_MAC_PARTS

#ifdef CONFIG_DEBUG_MAC_PARTS
#define DPRINTF(fmt, args...) \
do { printk("MAC-PARTS: " fmt , ##args); } while (0)
#else
#define DPRINTF(fmt, args...) do {} while(0)
#endif

typedef struct {
	xt_t		seek_xt, read_xt;
	ucell	        offs_hi, offs_lo;
        ucell	        size_hi, size_lo;
	ucell		bootcode_addr, bootcode_entry;
	unsigned int	blocksize;
	phandle_t	filesystem_ph;
} macparts_info_t;

DECLARE_NODE( macparts, INSTALL_OPEN, sizeof(macparts_info_t), "+/packages/mac-parts" );

#define SEEK( pos )		({ DPUSH(pos); call_parent(di->seek_xt); POP(); })
#define READ( buf, size )	({ PUSH(pointer2cell(buf)); PUSH(size); call_parent(di->read_xt); POP(); })

/* ( open -- flag ) */
static void
macparts_open( macparts_info_t *di )
{
	char *str = my_args_copy();
	char *parstr = NULL, *argstr = NULL;
	char *tmpstr;
	int bs, parnum=-1, apple_parnum=-1;
	int parlist[2], parlist_size = 0;
	desc_map_t dmap;
	part_entry_t par;
	int ret = 0, i = 0, j = 0;
	int want_bootcode = 0;
	phandle_t ph;
	ducell offs = 0, size = -1;

	DPRINTF("macparts_open '%s'\n", str );

	/* 
		Arguments that we accept:
		id: [0-7]
		[(id)][,][filespec]
	*/
	
	if ( str && strlen(str) ) {
		/* Detect the arguments */
		if ((*str >= '0' && *str <= '9') || (*str == ',')) {
		    push_str(str);
		    PUSH(',');
		    fword("left-parse-string");
		    parstr = pop_fstr_copy();
		    argstr = pop_fstr_copy();
		} else {
		    argstr = str;
		}

		/* Make sure argstr is not null */
		if (argstr == NULL)
		    argstr = strdup("");	
		
		/* Convert the id to a partition number */
		if (parstr && strlen(parstr))
		    parnum = atol(parstr);

		/* Detect if we are looking for the bootcode */
		if (strcmp(argstr, "%BOOT") == 0) {
		    want_bootcode = 1;
		}
	}

	DPRINTF("parstr: %s  argstr: %s  parnum: %d\n", parstr, argstr, parnum);

	DPRINTF("want_bootcode %d\n", want_bootcode);
	DPRINTF("macparts_open %d\n", parnum);

	di->filesystem_ph = 0;
	di->read_xt = find_parent_method("read");
	di->seek_xt = find_parent_method("seek");

	SEEK( 0 );
	if( READ(&dmap, sizeof(dmap)) != sizeof(dmap) )
		goto out;

	/* partition maps might support multiple block sizes; in this case,
	 * pmPyPartStart is typically given in terms of 512 byte blocks.
	 */
	bs = __be16_to_cpu(dmap.sbBlockSize);
	if( bs != 512 ) {
		SEEK( 512 );
		READ( &par, sizeof(par) );
		if( __be16_to_cpu(par.pmSig) == DESC_PART_SIGNATURE )
			bs = 512;
	}
	SEEK( bs );
	if( READ(&par, sizeof(par)) != sizeof(par) )
		goto out;
        if (__be16_to_cpu(par.pmSig) != DESC_PART_SIGNATURE)
		goto out;

	/*
	 * Implement partition selection as per the PowerPC Microprocessor CHRP bindings
	 */

	if (argstr == NULL || parnum == 0) {
		/* According to the spec, partition 0 as well as no arguments means the whole disk */
		offs = (long long)0;
		size = (long long)__be32_to_cpu(dmap.sbBlkCount) * bs;

		di->blocksize = (unsigned int)bs;

		di->offs_hi = offs >> BITS;
		di->offs_lo = offs & (ucell) -1;
	
		di->size_hi = size >> BITS;
		di->size_lo = size & (ucell) -1;

		ret = -1;
		goto out;

	} else if (parnum == -1) {

		DPRINTF("mac-parts: counted %d partitions\n", __be32_to_cpu(par.pmMapBlkCnt));

		/* No partition was explicitly requested so let's find a suitable partition... */
		for (i = 1; i <= __be32_to_cpu(par.pmMapBlkCnt); i++) {
			SEEK( bs * i );
			READ( &par, sizeof(par) );
			if ( __be16_to_cpu(par.pmSig) != DESC_PART_SIGNATURE ||
                            !__be32_to_cpu(par.pmPartBlkCnt) )
				continue;

			DPRINTF("found partition %d type: %s with status %x\n", i, par.pmPartType, __be32_to_cpu(par.pmPartStatus));

			/* Unfortunately Apple's OF implementation doesn't follow the OF PowerPC CHRP bindings
			* and instead will brute-force boot the first valid partition it finds with a
			* type of either "Apple_Boot", "Apple_HFS" or "DOS_FAT_". Here we store the id
			* of the first partition that matches these criteria to use as a fallback later
			* if required. */
			if (apple_parnum == -1 &&
				(strcmp(par.pmPartType, "Apple_Boot") == 0 || 
				strcmp(par.pmPartType, "Apple_Bootstrap") == 0 || 
				strcmp(par.pmPartType, "Apple_HFS") == 0 ||
				strcmp(par.pmPartType, "DOS_FAT_") == 0)) {
				apple_parnum = i;
				
				DPRINTF("Located Apple OF fallback partition %d\n", apple_parnum);
			}
				
			/* If we have a valid, allocated and readable partition... */
			if( (__be32_to_cpu(par.pmPartStatus) & kPartitionAUXIsValid) &&
				(__be32_to_cpu(par.pmPartStatus) & kPartitionAUXIsAllocated) &&
				(__be32_to_cpu(par.pmPartStatus) & kPartitionAUXIsReadable) ) {
				
				/* If the partition is also bootable and the pmProcessor field matches "PowerPC" (insensitive
				 * match), then according to the CHRP bindings this is our chosen partition */
				for (j = 0; j < strlen(par.pmProcessor); j++) {
				    par.pmProcessor[j] = tolower(par.pmProcessor[j]);
				}
				
				if ((__be32_to_cpu(par.pmPartStatus) & kPartitionAUXIsBootValid) &&
				    strcmp(par.pmProcessor, "powerpc") == 0) {
				    parnum = i;
				
				    DPRINTF("Located CHRP-compliant boot partition %d\n", parnum);
				}
			}
		}
		
		/* If we found a valid CHRP partition, add it to the list */
		if (parnum > 0) {
		    parlist[parlist_size++] = parnum;
		}

		/* If we found an Apple OF fallback partition, add it to the list */
		if (apple_parnum > 0 && apple_parnum != parnum) {
		    parlist[parlist_size++] = apple_parnum;
		}
		
	} else {
		/* Another partition was explicitly requested */
		parlist[parlist_size++] = parnum;
		
		DPRINTF("Partition %d explicitly requested\n", parnum);
	}

	/* Attempt to use our CHRP partition, optionally followed by our Apple OF fallback partition */
	for (j = 0; j < parlist_size; j++) {
	
	    /* Make sure our partition is valid */
	    parnum = parlist[j];
	    
	    DPRINTF("Selected partition %d\n", parnum);
	    
	    SEEK( bs * parnum );
	    READ( &par, sizeof(par) );	

	    if(! ((__be32_to_cpu(par.pmPartStatus) & kPartitionAUXIsValid) &&
			(__be32_to_cpu(par.pmPartStatus) & kPartitionAUXIsAllocated) &&
			(__be32_to_cpu(par.pmPartStatus) & kPartitionAUXIsReadable)) ) {
		DPRINTF("WARNING: Partition %d is not valid, allocated and readable\n", parnum);
	    }
	    
	    ret = -1;

	    offs = (long long)__be32_to_cpu(par.pmPyPartStart) * bs;
	    size = (long long)__be32_to_cpu(par.pmPartBlkCnt) * bs;	
	    
	    if (want_bootcode) {
		/* If size == 0 then fail because we requested bootcode but it doesn't exist */
		size = (long long)__be32_to_cpu(par.pmBootSize);
		if (!size) {
		    ret = 0;
		    goto out;
		}

		/* Adjust seek position so 0 = start of bootcode */
		offs += (long long)__be32_to_cpu(par.pmLgBootStart) * bs;

		di->bootcode_addr = __be32_to_cpu(par.pmBootLoad);
		di->bootcode_entry = __be32_to_cpu(par.pmBootEntry);
	    }
	    
	    di->blocksize = (unsigned int)bs;	
	    
	    di->offs_hi = offs >> BITS;
	    di->offs_lo = offs & (ucell) -1;

	    di->size_hi = size >> BITS;
	    di->size_lo = size & (ucell) -1;

	    /* If we're trying to execute bootcode then we're all done */
	    if (want_bootcode) {
	        goto out;
	    }

	    /* We have a valid partition - so probe for a filesystem at the current offset */
	    DPRINTF("mac-parts: about to probe for fs\n");
	    DPUSH( offs );
	    PUSH_ih( my_parent() );
	    parword("find-filesystem");
	    DPRINTF("mac-parts: done fs probe\n");

	    ph = POP_ph();
	    if( ph ) {
		    DPRINTF("mac-parts: filesystem found on partition %d with ph " FMT_ucellx " and args %s\n", parnum, ph, argstr);
		    di->filesystem_ph = ph;
		    
		    /* In case no partition was specified, set a special selected-partition-args property
		       giving the device parameters that we can use to generate bootpath */
		    tmpstr = malloc(strlen(argstr) + 2 + 1);
		    if (strlen(argstr)) {
			sprintf(tmpstr, "%d,%s", parnum, argstr);
		    } else {
			sprintf(tmpstr, "%d", parnum);
		    }

		    push_str(tmpstr);
		    feval("strdup encode-string \" selected-partition-args\" property");

		    free(tmpstr);
		
		    /* If we have been asked to open a particular file, interpose the filesystem package with 
		    the passed filename as an argument */
		    if (strlen(argstr)) {
			    push_str( argstr );
			    PUSH_ph( ph );
			    fword("interpose");
		    }
		    
		    goto out;
	    } else {
		    DPRINTF("mac-parts: no filesystem found on partition %d; bypassing misc-files interpose\n", parnum);
		    
		    /* Here we have a valid partition; however if we tried to pass in a file argument for a
		       partition that doesn't contain a filesystem, then we must fail */
		    if (strlen(argstr)) {
			ret = 0;
		    }
	    }
	}
	    
	free( str );

out:
	PUSH( ret );
}

/* ( block0 -- flag? ) */
static void
macparts_probe( macparts_info_t *dummy )
{
	desc_map_t *dmap = (desc_map_t*)cell2pointer(POP());

	DPRINTF("macparts_probe %x ?= %x\n", dmap->sbSig, DESC_MAP_SIGNATURE);
	if( __be16_to_cpu(dmap->sbSig) != DESC_MAP_SIGNATURE )
		RET(0);
	RET(-1);
}

/* ( -- type offset.d size.d ) */
static void
macparts_get_info( macparts_info_t *di )
{
	DPRINTF("macparts_get_info");

	PUSH( -1 );		/* no type */
	PUSH( di->offs_lo );
	PUSH( di->offs_hi );
	PUSH( di->size_lo );
	PUSH( di->size_hi );
}

/* ( -- size entry addr ) */
static void
macparts_get_bootcode_info( macparts_info_t *di )
{
	DPRINTF("macparts_get_bootcode_info");

	PUSH( di->size_lo );
	PUSH( di->bootcode_entry );
	PUSH( di->bootcode_addr );
}

static void
macparts_block_size( macparts_info_t *di )
{
	DPRINTF("macparts_block_size = %x\n", di->blocksize);
	PUSH(di->blocksize);
}

static void
macparts_initialize( macparts_info_t *di )
{
	fword("register-partition-package");
}

/* ( pos.d -- status ) */
static void
macparts_seek(macparts_info_t *di )
{
	long long pos = DPOP();
	long long offs, size;

	DPRINTF("macparts_seek %llx:\n", pos);

	/* Seek is invalid if we reach the end of the device */
	size = ((ducell)di->size_hi << BITS) | di->size_lo;
	if (pos > size)
		RET( -1 );

	/* Calculate the seek offset for the parent */
	offs = ((ducell)di->offs_hi << BITS) | di->offs_lo;
	offs += pos;
	DPUSH(offs);

	DPRINTF("macparts_seek parent offset %llx:\n", offs);

	call_package(di->seek_xt, my_parent());
}

/* ( buf len -- actlen ) */
static void
macparts_read(macparts_info_t *di )
{
	DPRINTF("macparts_read\n");

	/* Pass the read back up to the parent */
	call_package(di->read_xt, my_parent());
}

/* ( addr -- size ) */
static void
macparts_load( __attribute__((unused))macparts_info_t *di )
{
	/* Invoke the loader */
	load(my_self());
}

/* ( pathstr len -- ) */
static void
macparts_dir( macparts_info_t *di )
{
	/* On PPC Mac, the first partition chosen according to the CHRP boot
	specification (i.e. marked as bootable) may not necessarily contain 
	a valid FS */
	if ( di->filesystem_ph ) {
		PUSH( my_self() );
		push_str("dir");
		PUSH( di->filesystem_ph );
		fword("find-method");
		POP();
		fword("execute");
	} else {
		forth_printf("mac-parts: Unable to determine filesystem\n");
		POP();
		POP();
	}
}

NODE_METHODS( macparts ) = {
	{ "probe",		macparts_probe	 		},
	{ "open",		macparts_open 			},
	{ "seek",		macparts_seek 			},
	{ "read",		macparts_read 			},
	{ "load",		macparts_load 			},
	{ "dir",		macparts_dir 			},
	{ "get-info",		macparts_get_info 		},
	{ "get-bootcode-info",	macparts_get_bootcode_info	},
	{ "block-size",		macparts_block_size 		},
	{ NULL,			macparts_initialize		},
};

void
macparts_init( void )
{
	REGISTER_NODE( macparts );
}
