/**************************************************************************
Etherboot -  Network Bootstrap Program

Literature dealing with the network protocols:
	ARP - RFC826
	RARP - RFC903
	UDP - RFC768
	BOOTP - RFC951, RFC2132 (vendor extensions)
	DHCP - RFC2131, RFC2132 (options)
	TFTP - RFC1350, RFC2347 (options), RFC2348 (blocksize), RFC2349 (tsize)
	RPC - RFC1831, RFC1832 (XDR), RFC1833 (rpcbind/portmapper)
	NFS - RFC1094, RFC1813 (v3, useful for clarifications, not implemented)
	IGMP - RFC1112

**************************************************************************/

/* #define MDEBUG */

#include "etherboot.h"
#include "dev.h"
#include "nic.h"
#include "disk.h"
#include "http.h"
#include "timer.h"
#include "cpu.h"
#include <stdarg.h>

#ifdef CONSOLE_BTEXT
#include "btext.h"
#endif

#ifdef CONFIG_FILO
#include <lib.h>
#endif

jmp_buf	restart_etherboot;
int	url_port;		

char as_main_program = 1;

/* EthrLink  configuration */
etherlink elink;
int auto_mode = 0;


#ifdef	IMAGE_FREEBSD
int freebsd_howto = 0;
char freebsd_kernel_env[FREEBSD_KERNEL_ENV_SIZE];
#endif

/* in_call(): the entry point to Etherboot.  Generally called from
 * arch_in_call(), which in turn will have been invoked from
 * platform-specific assembly code.
 */
int in_call ( in_call_data_t *data, uint32_t opcode, va_list params ) {
	int old_as_main_program = as_main_program;
	int ret = 0;

	/* Set flat to indicate that we are not running as the main
	 * program (i.e. we are something like a PXE stack).
	 */
	as_main_program = 0;

	/* NOTE: params will cease to be valid if we relocate, since
	 * it represents a virtual address
	 */

	switch ( EB_OPCODE(opcode) ) {
		
	case EB_OPCODE_CHECK:
		/* Installation check
		 */
		ret = EB_CHECK_RESULT;
		break;
	case EB_OPCODE_MAIN:
		/* Start up Etherboot as a standalone program. */
		as_main_program = 1;

		//printf ( "Unsupported API\r\n");
		//return 0;

		ret = main ( data, params );
		break;
#ifdef PXE_EXPORT
	case EB_OPCODE_PXE:
		/* !PXE API call */
		ret = pxe_in_call ( data, params );
		break;
#endif
	default:
		printf ( "Unsupported API \"%c%c\"\n",
			 EB_OPCODE(opcode) >> 8, EB_OPCODE(opcode) & 0xff );
		ret = -1;
		break;
	}

	as_main_program = old_as_main_program;

	/* There is no return from the program	*/
	/* Perform reboot						*/

	//printf ("Before reboot\n");getchar();
	
#ifdef CM_IVC3
		pcibios_write_config_byte (0, 0x11,0x4f,1);
#endif 
#ifdef CM_I986
		bios_reset_cm_i986();
#endif 
#ifdef CM_IGLX
		bios_reset_cm_iglx();
#endif 
	while(1);
	return ret;
}

static inline int etherlink_config(etherlink *elink)
{
	static int state = 0;
	static char temp [20];
	int c = 0,i;
	uint32_t res;
	int again =1;
	static in_addr zero = {0L};
	static in_addr ip ;

	if (elink->action > 0)
		state =0;

	while (again)
	{
		switch (state) {
			case 0:
				bios_clear_screen();
				
				printf ("Wellcome to EthrLink v1.0 menu. Compulab 2005\n\n");
				printf ("Choose from the following options:\n");
				printf (" 1. Configure.\n");

				if (!elink->auto_config.enable_nand && !elink->auto_config.enable_nor && !elink->auto_config.enable_config)
					sprintf(temp,"none");
				else {
					i=0;
					if (elink->auto_config.enable_nand){
						sprintf(temp,"nand");
						i+=4;
					}
					if (elink->auto_config.enable_nor){
						if (i > 0){
							sprintf(temp+i,", bios");
							i+=2;
						}
						else
							sprintf(temp,"bios");
						i+=4;
					}
					if (elink->auto_config.enable_config){
						if (i > 0){
							sprintf(temp+i,", configblock");
							i+=2;
						}
						else
							sprintf(temp,"confiblock");
						i+=4;
					}
				}
				printf (" 2. Setup automated downloads (%s).\n",temp);
				printf (" 3. Download nand image (from tftp://%@/%s - %s image).\n"
					,elink->server_config.server_ip
					,elink->server_config.nand_fname
					,((elink->download_config.nand_start == 0) && (elink->download_config.nand_end == 0xffffffff)) ? "full" : "partial");
				printf (" 4. Upload nand image (to tftp://%@/%s - %s image).\n"
					,elink->server_config.server_ip
					,elink->server_config.nand_fname
					,((elink->upload_config.nand_start == 0) && (elink->upload_config.nand_end == 0xffffffff)) ? "full" : "partial");
				printf (" 5. Download bios image (from tftp://%@/%s).\n",elink->server_config.server_ip,elink->server_config.nor_fname);
				printf (" 6. Download configblock (from tftp://%@/%s).\n",elink->server_config.server_ip,elink->server_config.config_fname);
				printf (" 7. Upload configblock (to tftp://%@/%s).\n",elink->server_config.server_ip,elink->server_config.config_fname);
				printf (" 8. Run automated downloads.\n");
				printf (" 0. Save configurations.\n");

				printf ("\n Q. Quit EthrLink and reboot.\n");

				printf ("\nYour selection: ");


				do {
					c = getchar();
				}while ((c < '0' || c >'9') && (c != 'q') && (c != 'Q'));
				putchar(c);
				putchar('\n');

				

				state = c - '0';
				elink->action =  state;
				if (c == 'q' || c == 'Q') {
					again =0;
					elink->action =  255;
					break;
				}else if (elink->action == 0)
					break;

				continue;
			
			case 1:
				bios_clear_screen();

				printf ("EthrLink configuration.\n\n");
				printf ("Choose from the following options:\n");
				printf (" 1. Use DHCP to obtain ip address for the device. (%s)\n",((elink->net_config.use_dhcp == 1) ? "Yes" : "No"));
				printf (" 2. Set static ip address for the device. (%@)\n",((elink->net_config.use_dhcp != 1) ? elink->net_config.device_ip : zero));
				printf (" 3. Set subnet mask for the device. (%@)\n",((elink->net_config.use_dhcp != 1) ? elink->net_config.device_subnet_mask : zero));
				printf (" 4. Set ip address of the tftp server. (%@)\n",elink->server_config.server_ip);
				printf (" 5. Set nand image file name. (%s)\n",elink->server_config.nand_fname);
				printf (" 6. Set bios image file name. (%s)\n",elink->server_config.nor_fname);
				printf (" 7. Set configblock file name. (%s)\n",elink->server_config.config_fname);
				printf (" 8. Set nand image start sector (%xh).\n",elink->download_config.nand_start);
				printf (" 9. Set nand image end sector (%xh).\n",elink->download_config.nand_end);
				/*printf (" D. USB Harddisk drive. (%c:).\n",elink->drive);*/
				printf ("\n 0. Return to main menu.\n");
				printf ("\nYour selection: ");
				do {
					c = getchar();
				}while ( (c < '0' || c >'9') /*&& (c != 'd') && (c != 'D')*/);
				putchar(c);
				putchar('\n');

				if (c == 'd' || c == 'D')
					state = 100;
				else 
				{
 					state = c - '0';
					if (state > 0) 
						state += 10;
				}	

				continue;

			case 2:
				bios_clear_screen();

				printf ("EthrLink automated downloads configuration.\n\n");
				printf ("Choose from the following options:\n");
				printf (" 1. %s nand image download. (%s)\n",((elink->auto_config.enable_nand == 1) ? "Disable" : "Enable")
					,((elink->auto_config.enable_nand == 1) ? "Enabled" : "Disabled"));
				printf (" 2. %s bios image download. (%s)\n",((elink->auto_config.enable_nor == 1) ? "Disable" : "Enable")
					,((elink->auto_config.enable_nor == 1) ? "Enabled" : "Disabled"));
				printf (" 3. %s configblock download. (%s)\n",((elink->auto_config.enable_config == 1) ? "Disable" : "Enable")
					,((elink->auto_config.enable_config == 1) ? "Enabled" : "Disabled"));
				
				printf ("\n 0. Return to main menu.\n");
				printf ("\nYour selection: ");
				do {
					c = getchar();
				}while ( c < '0' || c >'9');
				putchar(c);
				putchar('\n');
				state = c - '0';
				if (state > 0)
					state += 20;
				
				continue;


			case 5:
				bios_clear_screen();

				printf ("Are you sure to update the bios (Y/N)?\n");
				do {
					c = getchar();
				}while ( (c != 'Y') && (c != 'N') && (c != 'y') && (c != 'n'));
				putchar(c);

				if (c == 'Y' || c == 'y'){
					state = 51;
				}
				else 
					state = 0;
				
				continue;

			case 11:
				elink->net_config.use_dhcp = 1;
				state = 1;
				continue;
			case 12:
				bios_clear_screen();
				printf ("Enter device static IP address (patern xx.xx.xx.xx):\n");
				i=0;
				do {
					c = getchar();
					temp[i++] = c;
					putchar(c);
				}while (c != 10);

				if (i>20)
					continue;

				inet_aton(temp,&ip);
				memcpy(&(elink->net_config.device_ip),&ip,sizeof(in_addr));
				elink->net_config.use_dhcp = 0;
				state = 1;
				continue;

			case 13:
				bios_clear_screen();

				printf ("Enter device subnet mask (patern xx.xx.xx.xx):\n");
				i=0;
				do {
					c = getchar();
					temp[i++] = c;
					putchar(c);
				}while (c != 10);

				if (i>20)
					continue;

				inet_aton(temp,&ip);
				memcpy(&(elink->net_config.device_subnet_mask),&ip,sizeof(in_addr));
				elink->net_config.use_dhcp = 0;
				state = 1;
				continue;

			case 14:
				bios_clear_screen();
				
				printf ("Enter tftp server ip address (patern xx.xx.xx.xx):\n");
				i=0;
				do {
					c = getchar();
					temp[i++] = c;
					putchar(c);
				}while (c != 10);

				if (i>20)
					continue;

				inet_aton(temp,&ip);
				memcpy(&(elink->server_config.server_ip),&ip,sizeof(in_addr));
				state = 1;
				continue;

			case 15:
				bios_clear_screen();
				
				printf ("Enter nand image file name (patern xxxxxxxx.xxx):\n");
				i=0;
				do {
					c = getchar();
					temp[i++] = c;
					putchar(c);
				}while (c != 10);

				if (i>12)
					continue;
				temp[i-1] = '\0';

				memcpy(elink->server_config.nand_fname,temp,i);
				state = 1;
				continue;

			case 16:
				bios_clear_screen();
				
				printf ("Enter bios image file name (patern xxxxxxxx.xxx):\n");
				i=0;
				do {
					c = getchar();
					temp[i++] = c;
					putchar(c);
				}while (c != 10);

				if (i>12)
					continue;
				temp[i-1] = '\0';

				memcpy(elink->server_config.nor_fname,temp,i);
				state = 1;
				continue;

			case 17:
				bios_clear_screen();
				
				printf ("Enter configblock file name (patern xxxxxxxx.xxx):\n");
				i=0;
				do {
					c = getchar();
					temp[i++] = c;
					putchar(c);
				}while (c != 10);

				if (i>12)
					continue;
				temp[i-1] = '\0';

				memcpy(elink->server_config.config_fname,temp,i);
				state = 1;
				continue;

			case 18:
				bios_clear_screen();
				
				printf ("Enter nand image start sector (hex value):\n");
				i=0;
				do {
					c = getchar();
					if (c >= '0' && c <='9')
						temp[i++] = c-'0';
					else if (c >='A' && c <='F') 
						temp[i++] = c-'A'+10;
					else if (c >='a' && c <='f') 
						temp[i++] = c-'a'+10;
					else if (c != 10)
						continue;

					putchar(c);

				}while (c != 10);

				if (i>8)
					continue;
				c= i;
				for (i=0,res=0;i<c;i++){
					res += temp[i]* (1 << (4*(c-i-1)));
				}

				if (res > 0x40000000)
					res = 0xffffffff;
				elink->download_config.nand_start = res;
				elink->upload_config.nand_start = res;
				
				state = 1;
				continue;

			case 19:
				bios_clear_screen();
				
				printf ("Enter nand image end sector (hex value: enter 0xffffffff for full image):\n");
				i=0;
				do {
					c = getchar();
					if (c >= '0' && c <='9')
						temp[i++] = c-'0';
					else if (c >='A' && c <='F') 
						temp[i++] = c-'A'+10;
					else if (c >='a' && c <='f') 
						temp[i++] = c-'a'+10;
					else if (c != 10)
						continue;

					putchar(c);

				}while (c != 10);

				if (i>8)
					continue;
				c= i;
				for (i=0,res=0;i<c;i++){
					res += temp[i]* (1 << (4*(c-i-1)));
				}

				if (res > 0x40000000)
					res = 0xffffffff;
				elink->download_config.nand_end = res;
				elink->upload_config.nand_end = res;
				
				state = 1;
				continue;

			case 21:
				elink->auto_config.enable_nand = (elink->auto_config.enable_nand == 1) ? 0 : 1;
				state = 2;
				continue;

			case 22:
				elink->auto_config.enable_nor = (elink->auto_config.enable_nor == 1) ? 0 : 1;
				state = 2;
				continue;

			case 23:
				elink->auto_config.enable_config = (elink->auto_config.enable_config == 1) ? 0 : 1;
				state = 2;
				continue;

			case 100:
				bios_clear_screen();
				
				printf ("Enter USB harddisk drive letter (C,D,E,...):\n");
			
				do {
					c = getchar();
					if (c >='C' && c <='F') 
						temp[0] = c;
					else if (c >='c' && c <='f') 
						temp[0] = c-'c'+'C';
					else if (c != 10)
						continue;

					putchar(c);

				}while (c != 10);

				elink->drive  = temp[0];
				
				state = 1;
				continue;
		
			default :		
				bios_clear_screen();
				
				if (state == 51)
					state = 5;

				printf ("Your selection: %d\n\n",state);

				elink->action =  state;
				state = 0;
		}
		again =0;
	}
	return 1;

}


void console_init(void)
{
#ifdef	CONSOLE_SERIAL
	(void)serial_init();
#endif
#ifdef 	CONSOLE_DIRECT_VGA
       	video_init();
#endif
#ifdef	CONSOLE_BTEXT
	map_boot_text();
#endif
}

static void console_fini(void)
{
#ifdef	CONSOLE_SERIAL
	(void)serial_fini();
#endif
}

static struct class_operations {
	struct dev *dev;
	int (*probe)(struct dev *dev);
	int (*load_configuration)(etherlink *elink);
	int (*load)(etherlink *elink);
}
operations[] = {
	{ &nic.dev,  eth_probe,  eth_load_configuration,  eth_load  },
	{ &nic.dev,  eth_probe,  eth_load_configuration,  eth_load  },
	{ &nic.dev,  eth_probe,  eth_load_configuration,  eth_load  },
};


static int main_loop(int state);
static int exit_ok;
static int exit_status;
static int initialized;

/**************************************************************************
MAIN - Kick off routine
**************************************************************************/
int main(in_call_data_t *data, va_list params)
{
	char *p;
	int state;

	for (p = _bss; p < _ebss; p++)
		*p = 0;	/* Zero BSS */

	console_init();
	arch_main(data,params);
	

#if 0
#ifdef  CONSOLE_BTEXT
        btext_init();
        map_boot_text();
        btext_clearscreen();
#endif
#endif 

	if ( rom.rom_segment ) {
		printf ( "ROM segment %#hx length %#hx reloc %#x\n",
			 rom.rom_segment, rom.rom_length, _text );
	}

	cpu_setup();
	setup_timers();
	gateA20_set();
	//print_config();
	get_memsizes();
	cleanup();

#ifdef CONFIG_PCMCIA
	pcmcia_init_all();
#endif

	/* -1:	timeout or ESC
	   -2:	error return from loader
	   -3:  finish the current run.
	   0:	retry booting bootp and tftp
	   1:   retry tftp with possibly modified bootp reply
	   2:   retry bootp and tftp
	   3:   retry probe bootp and tftp
	   4:   start with the next device and retry from there...
	   255: exit Etherboot
	   256: retry after relocation
	*/
	state = setjmp(restart_etherboot);
	exit_ok = 1;
	for(;state != 255;) {
		state = main_loop(state);
	}
	arch_on_exit(exit_status);
#ifdef CONFIG_PCMCIA
	pcmcia_shutdown_all();
#endif

	return exit_status;
}

void exit(int status)
{
	while(!exit_ok)
		;
	exit_status = status;
	longjmp(restart_etherboot, 255);
}

static int main_loop(int state)
{
	/* Splitting main into 2 pieces makes the semantics of 
	 * which variables are preserved across a longjmp clean
	 * and predictable.
	 */
	static unsigned long order;
	static unsigned boot_index;
	static struct dev * dev = 0;
	static struct class_operations *ops;
	static void *heap_base;
	static int type;
	static int i;
	static int stack_initialized = 0;

	if (!initialized) {
		initialized = 1;
		console_init();
		if (dev && (state >= 1) && (state <= 2)) {
			dev->how_probe = PROBE_AWAKE;
			dev->how_probe = ops->probe(dev);
			if (dev->how_probe == PROBE_FAILED) {
				state = -1;
			}
		}
	}
	switch(state) {
	case 0:
	{
		static int firsttime = 1;
		/* First time through */
		if (firsttime) {
			relocate();
			cleanup();
			console_init();
			init_heap();
#ifdef  CONSOLE_BTEXT
			//I need to all allot
		        btext_init(); 
		        map_boot_text();
		        btext_clearscreen();
#else

#endif

			firsttime = 0;
		} 

		heap_base = allot(0);
		i = -1;
		state = 6;
		dev = 0;

		led_ioctl(2,1);
		load_etherlink_config(&elink);

		//auto_mode = 1;
		if (auto_mode == 1) {

			//printf ("Auto mode detected. Press any key.\n");getchar();
			if (stack_initialized)
				state = 2;
			else
				state = 4;
			elink.action = 8;
			break;
		}

	}
	case 6:
		etherlink_config(&elink);

		if (elink.action == 255){
			cleanup();
			console_init();
			forget(heap_base);
			state = 255;
		}
		else if (elink.action == 0)
			state = 5;
		else 
			state = 4;
		break;
	case 5:
		printf ("Saving configurations...\n");
		save_etherlink_config(&elink);
		led_ioctl(2,1);
		state = 6 ;
		break;

	case 4:

		if (stack_initialized) {
			state = 2;
			break;
		}

		cleanup();
		console_init();
		forget(heap_base);
		/* Find a dev entry to probe with */
		if (!dev) {	

			/* Advance to the next device type */
			i++;
	
			ops = &operations[BOOT_NIC];
			dev = ops->dev;
			dev->how_probe = PROBE_FIRST;
			dev->type = BOOT_NIC;
			dev->failsafe = 0;
			dev->type_index = 0;
		} else {
			/* Advance to the next device of the same type */
			dev->how_probe = PROBE_NEXT;
		}
		state = 3;
		break;
	case 3:
		state = -1;
		heap_base = allot(0);
		dev->how_probe = ops->probe(dev);
		if (dev->how_probe == PROBE_FAILED) {
			dev = 0;
			state = 4;
		} else if (boot_index && (i == 0) && (boot_index != (unsigned)dev->type_index)) {
			printf("Wrong index\n");
			state = 4;
		}
		else {
			state = 2;
			stack_initialized  = 1;
		}
		break;
	case 2:
		state = -1;

		if (ops->load_configuration(&elink) >= 0) {
			state = 1;
		}
		break;
	case 1:

		/*if (auto_mode)
				state = 0;
		else
				state = 6;
		*/
		state = 6;
	
		if (ops->load(&elink) != 1)
			led_ioctl(3,1); /* Turn off leds to indicate error */
		else
			led_ioctl(2,1); /* Turn on leds to indicate good job finished */

		printf ("\nPress any key to return into main menu.\n");
		getchar();

		//state = 255;
		break;
	case 256:
		state = 0;
		break;
	case -3:
		i = MAX_BOOT_ENTRIES;
		type = BOOT_NOTHING;
		/* fall through */
	default:
		printf("<abort>\n");
		state = 4;
		/* At the end goto state 0 */
		if ((type >= BOOT_NOTHING) || (i >= MAX_BOOT_ENTRIES)) {
			state = 0;
		}
		break;
	}
	return state;
}


/**************************************************************************
LOADKERNEL - Try to load kernel image
**************************************************************************/
struct proto {
	char *name;
	int (*load)(const char *name,
		int (*fnc)(unsigned char *, unsigned int, unsigned int, int));
	int (*save)(const char *name,
		int (*fnc)(unsigned char **, unsigned int *, unsigned int *, int *));
};
static const struct proto protos[] = {
#ifdef DOWNLOAD_PROTO_TFTM
	{ "x-tftm", url_tftm },
#endif
#ifdef DOWNLOAD_PROTO_SLAM
	{ "x-slam", url_slam },
#endif
#ifdef DOWNLOAD_PROTO_NFS
	{ "nfs", nfs },
#endif
#ifdef DOWNLOAD_PROTO_DISK
	{ "file", url_file },
#endif
#ifdef DOWNLOAD_PROTO_TFTP
	{ "tftp", tftp_download ,tftp_upload},
#endif
#ifdef DOWNLOAD_PROTO_HTTP
        { "http", http },
#endif
#ifdef DOWNLOAD_PROTO_FSP
        { "fsp", url_fsp },
#endif
};


/**************************************************************************
CLEANUP - shut down networking and console so that the OS may be called 
**************************************************************************/
void cleanup(void)
{
#ifdef	DOWNLOAD_PROTO_NFS
	nfs_umountall(ARP_SERVER);
#endif
	/* Stop receiving packets */
	eth_disable();
	//disk_disable();
	console_fini();
	initialized = 0;
}

/*
 * Local variables:
 *  c-basic-offset: 8
 * End:
 */
