#include <common.h>
#include <command.h>
#include <ext2fs.h>
#include "hvideo.h"
#include "sys_dep.h"
#include "sam_ide.h"

#include "malloc.h"

extern int do_bootm (cmd_tbl_t *, int, int, char *[]);

#ifdef CONFIG_CMD_SCSI
extern block_dev_desc_t * scsi_get_dev(int);
#else
block_dev_desc_t * scsi_get_dev(int a)
{
	return (block_dev_desc_t *)0;
}
#endif

#include <usb.h> // I need usb_stor_get_dev()
#define MAX_USB_UNITNUM USB_MAX_STOR_DEV

//System-dependent support for the A1 first and second level bootloaders.

#define MAX_IDE_UNITNUM CFG_IDE_MAXDEVICE
//#define MAX_SCSI_UNITNUM CFG_SCSI_MAX_DEVICE

#include <fdc.h>

static struct MinList dev_list; //Made out of uboot_scan_handles
static SCAN_HANDLE curr_disk;

struct dev_access_entry * find_dae(const char * s)
{
	static struct dev_access_entry devices_access_table[]={
		{NULL,	NULL,	DEV_TYPE_NETBOOT,	1,					BUSTYPE_NET},
		{NULL,	NULL,	DEV_TYPE_CDROM,		MAX_USB_UNITNUM,	BUSTYPE_USB},
		{NULL,	NULL,	DEV_TYPE_HARDDISK,	MAX_USB_UNITNUM,	BUSTYPE_USB},
		{NULL,	NULL,	DEV_TYPE_HARDDISK,	MAX_S_SII_UNITS,	BUSTYPE_SIL_SERIAL},
		{NULL,	NULL,	DEV_TYPE_CDROM,		MAX_S_SII_UNITS,	BUSTYPE_SIL_SERIAL},
		{NULL,	NULL,	DEV_TYPE_HARDDISK,	MAX_S_4_SII_UNITS,	BUSTYPE_SIL_4_SERIAL},
		{NULL,	NULL,	DEV_TYPE_CDROM,		MAX_S_4_SII_UNITS,	BUSTYPE_SIL_4_SERIAL},
		{NULL,	NULL,	DEV_TYPE_HARDDISK,	MAX_P_SII_UNITS,	BUSTYPE_SIL_PARALLEL},
		{NULL,	NULL,	DEV_TYPE_CDROM,		MAX_P_SII_UNITS,	BUSTYPE_SIL_PARALLEL},
#ifdef CONFIG_SAM460EX
		{NULL,	NULL,	DEV_TYPE_HARDDISK,	MAX_SATA2_460_UNITS,BUSTYPE_SATA2_460},
#endif
	 	{NULL,	NULL,	DEV_TYPE_DUMMY_END, 0,					0}
		//{NULL,	NULL,	DEV_TYPE_CDROM,		MAX_SCSI_UNITNUM,	BUSTYPE_SCSI},
		//{NULL,	NULL,	DEV_TYPE_HARDDISK,	MAX_SCSI_UNITNUM,	BUSTYPE_SCSI},
	};

	devices_access_table[0].dae_identifier = "net";
	devices_access_table[1].dae_identifier = "ucdrom";
	devices_access_table[2].dae_identifier = "usb";
	devices_access_table[3].dae_identifier = "ssii";
	devices_access_table[4].dae_identifier = "ssiicdrom";
	devices_access_table[5].dae_identifier = "s4sii";
	devices_access_table[6].dae_identifier = "s4siicdrom";
	devices_access_table[7].dae_identifier = "psii";
	devices_access_table[8].dae_identifier = "psiicdrom";
#ifdef CONFIG_SAM460EX
	devices_access_table[9].dae_identifier = "sata2-460";
	devices_access_table[10].dae_identifier = NULL;
#else
	devices_access_table[9].dae_identifier = NULL;
#endif
	//devices_access_table[1].dae_identifier = "scdrom";
	//devices_access_table[2].dae_identifier = "scsi";

	struct dev_access_entry * current;

	for(current = devices_access_table;
		current->dae_request_type != DEV_TYPE_DUMMY_END;
		current++)
	{
		if(!strcmp(s, current->dae_identifier))
		{
			/* The get_dev() hooks MUST be assigned at runtime, since uboot is relocated! */
			switch(current->dae_bustype)
			{
				//case BUSTYPE_SCSI:
				//	current->dae_get_dev = scsi_get_dev;
				//	break;

				case BUSTYPE_USB:
					current->dae_get_dev = usb_stor_get_dev;
					break;

				case BUSTYPE_SIL_SERIAL:
					current->dae_get_dev = s_sii_get_dev;
					break;

				case BUSTYPE_SIL_4_SERIAL:
					current->dae_get_dev = s_4_sii_get_dev;
					break;

				case BUSTYPE_SIL_PARALLEL:
					current->dae_get_dev = p_sii_get_dev;
					break;

#ifdef CONFIG_SAM460EX
				case BUSTYPE_SATA2_460:
					current->dae_get_dev = sata2_460_get_dev;
					break;
#endif

				default:
					break;
			}

			return current;
		}
	}

	return NULL;
}

SCAN_HANDLE next_unit_scan(SCAN_HANDLE h, ULONG * const blocksize)
{
	SCAN_HANDLE next = (SCAN_HANDLE)h->ush_link.mln_Succ;

	h->ush_already_scanned = TRUE; //Mark the past handle as already scanned.

	//printf("Entered next_unit_scan\n");
 	if(next->ush_link.mln_Succ) //more to go ?
	{
		*blocksize = next->ush_device.blksz;

		/*
		printf("NUS: returning device with data: interface type %d, devnumber %d, type %u\n",
		curr_disk->ush_device.if_type, curr_disk->ush_device.dev, curr_disk->ush_device.type);
		*/

		return (curr_disk = next);
	}
	else
	{
		//printf("NUS: no more units in list\n");
		return NULL;
	}
}

SCAN_HANDLE start_unit_scan(const void * scan_list, ULONG * const blocksize)
{
	const char ** opt_list;
	struct dev_access_entry * dae_found;
	//printf("entered start_unit_scan\n");
	if (!scan_list || !blocksize) return NULL;
	//floppy_like_an_hd.block_read = internal_floppy_block_read;

	/* This routine builds a list of scan handles from the env-vars passed in scan_list. */

	NewList(&dev_list);

	for(opt_list = (const char **)scan_list;*opt_list;opt_list++) //Iterates till empty string.
	{
		//printf("Now examining boot source '%s'\n", *opt_list);

		dae_found = find_dae(*opt_list);

		//printf("Done find_dae\n");
		if(dae_found)
		{
			SCAN_HANDLE s_h;
			block_dev_desc_t * newdev;
			UBYTE curr_unit;

			//printf("Found dae matching identifier %s, looping\n", dae_found->dae_identifier);
			for(curr_unit = 0; curr_unit < dae_found->dae_max_unitnum; curr_unit++)
			{
				if(dae_found->dae_bustype == BUSTYPE_NET) //Special case!!
				{
					//printf("Found special boot type network.\n");
					s_h = alloc_mem_for_anythingelse(sizeof (struct uboot_scan_handle));

					memset(s_h, 0, sizeof(struct uboot_scan_handle));

					s_h->ush_bustype = dae_found->dae_bustype;
					s_h->ush_device.blksz = 1500; //Fake value.
					s_h->ush_device.type = DEV_TYPE_NETBOOT;
					AddTail(&dev_list, &s_h->ush_link);
					continue;
				}

				if((newdev = dae_found->dae_get_dev(curr_unit)))
					if((newdev->blksz) && (newdev->type == dae_found->dae_request_type))
					//uboot sei sempre una merda.
					{
						s_h = alloc_mem_for_anythingelse(sizeof (struct uboot_scan_handle));
						memset(s_h, 0, sizeof(struct uboot_scan_handle));
						s_h->ush_device = *newdev; //Full structure copy.
						s_h->ush_bustype = dae_found->dae_bustype;

						/*
						printf("SUN: found unit; data as follows: interface type %d, devnumber %d, type %u, device points to %08lx\n",
							s_h->ush_device.if_type, s_h->ush_device.dev, s_h->ush_device.type, &s_h->ush_device);
						*/

						AddTail(&dev_list, &s_h->ush_link);
					}
			}
		}
	}

	curr_disk = (SCAN_HANDLE)dev_list.mlh_Head;

	if(curr_disk->ush_device.blksz)
	{
		*blocksize = curr_disk->ush_device.blksz;
		//printf("Blocksize for first device is %lu, type %u\n", *blocksize, curr_disk->ush_bustype);
	}
	//printf("Exiting start_unit_scan\n");
	return curr_disk;
}


BOOL open_specific_unit(const SCAN_HANDLE h)
{
	curr_disk = h; //We simply set the current disk to be the one given in.
	/*
	printf("Opening specific unit; data as follows: interface type %d, devnumber %d, type %u\n",
		curr_disk->ush_device.if_type, curr_disk->ush_device.dev, curr_disk->ush_device.type);
	*/
	return TRUE;
}

BOOL old_open_specific_unit(const OLD_SCAN_HANDLE h)
{
	//printf("Old_open_specific unit; real handle to %08lx\n", h->ush_new_reference);

	curr_disk = h->ush_device.backpointer; //We simply set the current disk to be the one given in.
	/*
	printf("Opening specific unit; data as follows: interface type %d, devnumber %d, type %u\n",
		curr_disk->ush_device.if_type, curr_disk->ush_device.dev, curr_disk->ush_device.type);
	*/
	return TRUE;
}

void end_unit_scan(SCAN_HANDLE h)
{
	//Nothing: no special resources are opened while scanning
}

void end_global_scan(void)
{
	// Just like a1_end_unit_scan()
}

BOOL loadsector(const ULONG sectn, const ULONG sect_size, const ULONG numb_sects, void * const dest_buf)
{
	block_dev_desc_t * dev = &curr_disk->ush_device;

	//printf("Loadsector: sectornum. %lu, current device %08lx\n", sectn, dev);

	if(sect_size) //Sector size check is only performed when passing a non-zero value
	{
		if(dev->blksz != sect_size)
		{
			//printf("Loadsector: Bad blocksize: current is %lu, supplied is %lu (base structure at %p)\n", dev->blksz, sect_size, &curr_disk);
		  return FALSE;
		}
	}

	if(dev->block_read(dev->dev, sectn, numb_sects, dest_buf) != numb_sects)
	{
		//printf("Loadsector: error when reading from block %lu\n", sectn);
		return FALSE;
	}
	else return TRUE;
}

void * uboot_4aligned_malloc(const size_t size)
{
	unsigned long temp;

	//printf("Allocating %d bytes, jumping to %08lx\n", size, malloc);
	temp = (unsigned long)malloc(size + 4);
	temp = (temp + 3) & ~3;

	//  printf("uboot_4aligned_malloc : asked for memsize %d, returned pointer at %08lx\n", size, (ULONG)temp);
	return (void *)temp;
}

void * alloc_mem_for_iobuffers(const unsigned long size)
{
	return uboot_4aligned_malloc((const size_t)size);
}

void * alloc_mem_for_kickmodule(const unsigned long size)
{
	return uboot_4aligned_malloc((const size_t) size);
}

void * alloc_mem_for_execNG(const unsigned long size)
{
  return uboot_4aligned_malloc((const size_t) size);
}

void * alloc_mem_for_anythingelse(const unsigned long size)
{
	return uboot_4aligned_malloc((const size_t) size);
}

void * alloc_mem_for_bootloader(const unsigned long size)
{
	return uboot_4aligned_malloc((const size_t) size);
}

/*
void * alloc_mem_for_bootloader_ABS(const unsigned long size, void * addr)
{
  return addr; //Yeah, bulldozing anything that comes in the way!
}

//No longer used, since the absolute bulldozing will be done by the ELF loader & relocator.
*/

/*
static int local_get_part_info(int part, disk_partition_t *info)
	 //This function will use the file-global curr_disk to get the partition info from uboot
{
  return get_partition_info(curr_disk, part, info);
}
*/

void * get_board_info(void)
{
	DECLARE_GLOBAL_DATA_PTR;
	return (void *)gd->bd;
}

int my_NetLoop(char * fn, void * buff)
{
	//printf("Entering %s\n", __PRETTY_FUNCTION__);
	copy_filename(BootFile, fn, sizeof(BootFile));
	load_addr = (ulong) buff;
	//The cast is necessary because someone was so smart to declare an address
	//as something else than a pointer.

	return NetLoop(TFTP);
}

static void set_load_addr(void * const new_load_addr)
{
	load_addr = (ulong)new_load_addr;
}

struct sbl_callback_context * build_callback_context(void * scanopts)
{
	static struct sbl_callback_context context;

	context.ssc_version = CALLBACK_VERSION;
	context.ssc_printf_like = printf; //video_null;
	context.ssc_getc_like = getc;

	context.ssc_scan_list = scanopts;
	context.ssc_devices_list = &dev_list;
	context.ssc_curr_device = curr_disk;

	context.ssc_start_unit_scan = start_unit_scan;
	context.ssc_next_unit_scan = next_unit_scan;
	context.ssc_open_specific_unit = open_specific_unit;
	context.ssc_end_unit_scan = end_unit_scan;
	context.ssc_end_global_scan = end_global_scan;
	context.ssc_loadsector = loadsector;

	context.ssc_my_netloop = my_NetLoop;

	context.ssc_getenv = getenv;
	context.ssc_setenv = setenv;

	context.ssc_alloc_mem_for_iobuffers =	uboot_4aligned_malloc;
	context.ssc_alloc_mem_for_kickmodule = uboot_4aligned_malloc;
	context.ssc_alloc_mem_for_execNG = uboot_4aligned_malloc;
	context.ssc_alloc_mem_for_anythingelse = uboot_4aligned_malloc;
	context.ssc_alloc_mem_for_bootloader = uboot_4aligned_malloc;
	context.ssc_free_mem = free;

	context.ssc_get_board_info = get_board_info;

#ifdef CONFIG_BZIP2
	context.ssc_BZ2_bzBuffToBuffDecompress = BZ2_bzBuffToBuffDecompress;
#endif

	context.ssc_video_clear = video_clear;
	context.ssc_video_draw_box = video_draw_box; //video_null;
	context.ssc_video_draw_text = video_draw_text2;
	context.ssc_video_repeat_char = video_repeat_char2;

	context.ssc_set_partial_scroll_limits = set_partial_scroll_limits;
	context.ssc_get_partial_scroll_limits = get_partial_scroll_limits;
	context.ssc_video_get_key = video_get_key;

	//printf("Allocation functions pointing at %08lx\n", uboot_4aligned_malloc);

	context.ssc_do_bootm = do_bootm;
	context.ssc_memmove = memmove;

	context.ssc_set_load_addr = set_load_addr;

	context.ssc_tstc = tstc;
	context.ssc_udelay = udelay;
	context.ssc_sprintf = sprintf;

	context.ssc_ext2fs_set_blk_dev_full=ext2fs_set_blk_dev_full;
	context.ssc_ext2fs_open=ext2fs_open;
	context.ssc_ext2fs_read=ext2fs_read;
	context.ssc_ext2fs_mount=ext2fs_mount;
	context.ssc_ext2fs_close=ext2fs_close;

	return &context;
}
/*
void degrade_to_old_frigging_interface(struct sbl_callback_context * c)
{
  SCAN_HANDLE actual;
  NewList(&old_dev_list);
  c->ssc_version = 3;

  for(actual = (SCAN_HANDLE)c->ssc_devices_list->mlh_Head; actual->ush_link.mln_Succ; actual = (SCAN_HANDLE)actual->ush_link.mln_Succ)
  	{
	OLD_SCAN_HANDLE old_h = alloc_mem_for_anythingelse(sizeof(struct uboot_old_scan_handle));

	old_h->ush_bustype = actual->ush_bustype;
	old_h->ush_already_scanned = actual->ush_already_scanned;

	old_h->ush_device.if_type	= actual->ush_device.if_type ;
	old_h->ush_device.dev		= actual->ush_device.dev;
	old_h->ush_device.part_type	= actual->ush_device.part_type;
	old_h->ush_device.target	= actual->ush_device.target;
	old_h->ush_device.lun		= actual->ush_device.lun;
	old_h->ush_device.type		= actual->ush_device.type;
	old_h->ush_device.lba		= (unsigned long)actual->ush_device.lba;
	old_h->ush_device.blksz		= actual->ush_device.blksz;
	memcpy(&old_h->ush_device.vendor, &actual->ush_device,40);
	memcpy(&old_h->ush_device.product, &actual->ush_device,20);
	memcpy(&old_h->ush_device.revision, &actual->ush_device,4);
		old_h->ush_device.removable	= actual->ush_device.removable;
	old_h->ush_device.block_read	= actual->ush_device.block_read;

	old_h->ush_device.backpointer	= actual; //Backpointer used by old_open_specific_unit()

	AddTail(&old_dev_list, &old_h->ush_link);
	if(c->ssc_curr_device == actual)
		c->ssc_curr_device = (SCAN_HANDLE)old_h;
	}

  c->ssc_devices_list = &old_dev_list;
  c->ssc_open_specific_unit = old_open_specific_unit;
}
*/
