/*
 *     Copyright (C) 2008 Delta Networks Inc.
 */
#include <common.h>
#include <command.h>
#include <net.h>
//#include "tftp.h"
#include "bootp.h"
#include "nmrp.h"
#include <dni_common.h>

typedef void nmrp_thand_f(void);
ulong NmrpOuripaddr;
ulong NmrpOuripSubnetMask;
ulong NmrpFwOption;
int NmrpFwUPOption = 0;
int NmrpSTUPOption = 0;
int NmrpStringTableUpdateCount = 0;
int NmrpStringTableUpdateIndex = 0;
int NmrpState = 0;
ulong Nmrp_active_start=0;
static ulong NmrpBlock;
static ulong NmrpLastBlock = 0;
int Nmrp_Listen_TimeoutCount = 0;
int Nmrp_REQ_TimeoutCount = 0;
static ulong NmrptimeStart;
static ulong NmrptimeDelta;
static nmrp_thand_f *NmrptimeHandler;
static uchar Nmrpproto;
static int Nmrp_Closing_TimeoutCount = 0;
struct in_addr NmrpClientIP;
uchar NmrpClientEther[6] = { 0, 0, 0, 0, 0, 0 };
uchar NmrpServerEther[6] = { 0, 0, 0, 0, 0, 0 };
static u16 NmrpDevRegionOption = 0;
static uchar NmrpFirmwareFilename[FIRMWARE_FILENAME_LEN] = FIRMWARE_FILENAME;
static u32 NmrpStringTableBitmask = 0;
static uchar NmrpStringTableFilename[STRING_TABLE_FILENAME_LEN] = {0};
static int NmrpStringTableUpdateList[STRING_TABLE_NUMBER_MAX] = {0};
ulong NmrpAliveTimerStart = 0;
ulong NmrpAliveTimerBase = 0;
int NmrpAliveTimerTimeout = NMRP_TIMEOUT_ACTIVE;
int NmrpAliveWaitACK = 0;

static void Nmrp_Listen_Timeout(void);
void NmrpSend(void);

void NmrpStart(void);

void NmrpSetTimeout(unchar, ulong, nmrp_thand_f *);

void Nmrp_Led_Flashing_Timeout();

extern void board_reset_default();

static int MyNetSetEther(volatile uchar * xet, uchar * addr, uint prot)
{
	struct ethernet_hdr *et = (struct ethernet_hdr *) xet;
	ushort myvlanid;
	myvlanid = ntohs(net_our_vlan);
	if (myvlanid == (ushort) - 1)
		myvlanid = VLAN_NONE;
	memcpy(et->et_dest, addr, 6);
    eth_env_get_enetaddr("ethaddr", net_server_ethaddr);
	memcpy(et->et_src, net_server_ethaddr, 6);
	if ((myvlanid & VLAN_IDMASK) == VLAN_NONE) {
		et->et_protlen = htons(prot);
		return ETHER_HDR_SIZE;
	} else {
		struct vlan_ethernet_hdr *vet =
				(struct vlan_ethernet_hdr *) xet;
		vet->vet_vlan_type = htons(PROT_VLAN);
		vet->vet_tag = htons((0 << 5) | (myvlanid & VLAN_IDMASK));
		vet->vet_type = htons(prot);
		return VLAN_ETHER_HDR_SIZE;
	}

	return ETHER_HDR_SIZE;
}

static void Nmrp_Conf_Timeout(void)
{
	if (++Nmrp_REQ_TimeoutCount > NMRP_MAX_RETRY_CONF) {
		puts("\n retry conf count exceeded;\n");
		Nmrp_REQ_TimeoutCount = 0;
		NmrpStart();
	} else {
		puts("T");
		NmrpSetTimeout(NMRP_CODE_CONF_ACK,
			       (NMRP_TIMEOUT_REQ * CONFIG_SYS_HZ) / 2,
			       Nmrp_Conf_Timeout);
		NmrpSend();
	}
}

void Nmrp_Closing_Timeout()
{

	if (++Nmrp_Closing_TimeoutCount > NMRP_MAX_RETRY_CLOSE) {
		puts("\n close retry count exceed;stay idle and blink\n");
		Nmrp_Closing_TimeoutCount = 0;
		//board_reset_default(); toso Silver

		puts("\nNMRP is complete. Please switch OFF power.\n");

		Nmrp_Led_Flashing_Timeout();
		/* Unreachable */

		ctrlc();
		console_assign(stdout, "nulldev");
		console_assign(stderr, "nulldev");
		NmrpState = STATE_CLOSED;
		net_set_state(NETLOOP_SUCCESS);
	} else {
		puts("T");
		NmrpSetTimeout(NMRP_CODE_CLOSE_ACK,
			       (CONFIG_SYS_HZ * NMRP_TIMEOUT_REQ) / 2,
			       Nmrp_Closing_Timeout);
		NmrpSend();
	}
}

#define LED_REG_IN_OUT		0xff800500
#define LED_TEST_MASK		0x08000000
#define LED_AL_AH_REG		0xff800528

void board_test_led(int status)
{
	volatile uint32_t *cled_al_ah_reg = (void *)(LED_AL_AH_REG);
	volatile uint32_t *cled_in_out_reg = (void *)(LED_REG_IN_OUT);
	uint32_t val32;

	val32 = *cled_in_out_reg;
	val32 |= LED_TEST_MASK;
	*cled_in_out_reg = val32;

	val32 = *cled_al_ah_reg;
    if( status == 0 )
	    val32 &= ~LED_TEST_MASK;
    else
	    val32 |= LED_TEST_MASK;
	*cled_al_ah_reg = val32;
}

void Nmrp_Led_Flashing_Timeout()
{
	static int NmrpLedCount = 0;
	while (1) {
		NmrpLedCount++;
		if ((NmrpLedCount % 2) == 1) {
			board_test_led(0);
			udelay(500000);
		} else {
			board_test_led(1);
			udelay(500000);
		}
	}
	/* Unreachable */

	/*press ctl+c, turn on test led,then normally boot*/
	board_test_led(0);
}

extern void NmrpSend(void)
{
	volatile u8 *pkt;
	volatile u8 *xp;
	int len = 0;
	int eth_len = 0;
	pkt = (u8 *) net_tx_packet;
	pkt += MyNetSetEther(pkt, NmrpServerEther, PROT_NMRP);
	eth_len = pkt - net_tx_packet;

	switch (NmrpState) {
	case STATE_LISTENING:
		xp = pkt;
		*((u16 *) pkt) = 0;
		pkt += 2;
		*((u8 *) pkt) = (NMRP_CODE_CONF_REQ);
		pkt++;
		*((u8 *) pkt) = 0;
		pkt++;
		*((u16 *) pkt) = htons(6);
		pkt += 2;

		len = pkt - xp;

		(void)net_send_packet((u8 *) net_tx_packet, eth_len + len);
		net_set_timeout_handler((NMRP_TIMEOUT_REQ * CONFIG_SYS_HZ) / 2,
			      Nmrp_Conf_Timeout);
		NmrpSetTimeout(NMRP_CODE_CONF_ACK,
			       (NMRP_TIMEOUT_REQ * CONFIG_SYS_HZ) / 2,
			       Nmrp_Conf_Timeout);
		break;
	case STATE_CONFIGING:
		xp = pkt;
		*((u16 *) pkt) = 0;
		pkt += 2;
		*((u8 *) pkt) = (NMRP_CODE_TFTP_UL_REQ);
		pkt++;
		*((u8 *) pkt) = 0;
		pkt++;
		/* Recv ST-UP option, upgrade string table.
		 * add FILE-NAME option to TFTP-UL-REQ
		 * value of FILE-NAME would like "string table 01"*/
		if (NmrpSTUPOption == 1) {
			/* Append the total length to packet
			 * NMRP_HEADER_LEN for the length of "Reserved", "Code", "Identifier", "Length"
			 * STRING_TABLE_FILENAME_OPT_LEN for the length of "Options". */
			*((u16 *) pkt) = htons((NMRP_HEADER_LEN + STRING_TABLE_FILENAME_OPT_LEN));
			pkt += 2;

			/* Append NMRP option type FILE-NAME */
			*((u16 *) pkt) = htons(NMRP_OPT_FILE_NAME);
			pkt += 2;

			/* Append the total length of NMRP option FILE-NAME */
			*((u16 *) pkt) = htons(STRING_TABLE_FILENAME_OPT_LEN);
			pkt += 2;

			/* Append the string table filename to FILE-NAME option value */
			int i;
			sprintf(NmrpStringTableFilename, "%s%02d", STRING_TABLE_FILENAME_PREFIX,\
				NmrpStringTableUpdateList[NmrpStringTableUpdateIndex]);
			for (i = 0; i < STRING_TABLE_FILENAME_LEN; i++) {
				*((u8 *) pkt) = NmrpStringTableFilename[i];
				pkt++;
			}
			printf("\nReq %s\n", NmrpStringTableFilename);
		/* No string table updates, or all string table updates finished.
		 * And received FW-UP option, upgrading firmware,
		 * add FILE-NAME option to TFTP-UL-REQ */
		} else {
			/* Append the total length to packet
			 * NMRP_HEADER_LEN for the length of "Reserved", "Code", "Identifier", "Length"
			 * STRING_TABLE_FILENAME_OPT_LEN for the length of "Options". */
			*((u16 *) pkt) = htons((NMRP_HEADER_LEN + FIRMWARE_FILENAME_OPT_LEN));
			pkt += 2;

			/* Append NMRP option type FILE-NAME */
			*((u16 *) pkt) = htons(NMRP_OPT_FILE_NAME);
			pkt += 2;

			/* Append the total length of NMRP option FILE-NAME for firmware*/
			*((u16 *) pkt) = htons(FIRMWARE_FILENAME_OPT_LEN);
			pkt += 2;

			/* Append the firmware filename to FILE-NAME option value */
			sprintf(NmrpFirmwareFilename, "%s\n", FIRMWARE_FILENAME);
			int i;
			for (i = 0; i < FIRMWARE_FILENAME_LEN; i++) {
				*((u8 *) pkt) = NmrpFirmwareFilename[i];
				pkt++;
			}
		}
		len = pkt - xp;
		(void)net_send_packet((u8 *) net_tx_packet, eth_len + len);
		int update_table_num = NmrpStringTableUpdateList[NmrpStringTableUpdateIndex];
		if (NmrpSTUPOption == 1)
			UpgradeStringTableFromNmrpServer(update_table_num);
		else
			UpgradeFirmwareFromNmrpServer();
		break;
	case STATE_TFTPUPLOADING:
		printf("TFTP upload done\n");
		NmrpAliveTimerStart = get_timer(0);
		NmrpAliveTimerBase = NMRP_TIMEOUT_ACTIVE / 4;
		NmrpState = STATE_KEEP_ALIVE;
		break;
	case STATE_KEEP_ALIVE:
		printf("NMRP Send Keep alive REQ\n");
		//workaround_ipq40xx_gmac_nmrp_hang_action();
		xp = pkt;
		*((u16 *) pkt) = 0;
		pkt += 2;
		*((u8 *) pkt) = (NMRP_CODE_KEEP_ALIVE_REQ);
		pkt++;
		*((u8 *) pkt) = 0;
		pkt++;
		*((u16 *) pkt) = htons(6);
		pkt += 2;
		len = pkt - xp;
		(void)net_send_packet((u8 *) net_tx_packet, eth_len + len);
		NmrpAliveWaitACK = 1;
		break;
	case STATE_CLOSING:
		printf("NMRP Send Closing REQ\n");
		//workaround_qca8337_gmac_nmrp_hang_action();
		//workaround_ipq40xx_gmac_nmrp_hang_action();
		eth_halt();
        mdelay(1000);
		eth_init();
        mdelay(500);
		xp = pkt;
		*((u16 *) pkt) = 0;
		pkt += 2;
		*((u8 *) pkt) = (NMRP_CODE_CLOSE_REQ);
		pkt++;
		*((u8 *) pkt) = 0;
		pkt++;
		*((u16 *) pkt) = htons(6);
		pkt += 2;
		len = pkt - xp;
		NmrpSetTimeout(NMRP_CODE_CLOSE_ACK, (1 * CONFIG_SYS_HZ),
			       Nmrp_Closing_Timeout);
		(void)net_send_packet((u8 *) net_tx_packet, eth_len + len);
		break;

	case STATE_CLOSED:
		run_command("sdk restoredefault", 0); //board_reset_default();
#if defined(CONFIG_HW29765352P32P4000P512P2X2P2X2P4X4) || \
	defined(CONFIG_HW29765352P0P4096P512P2X2P2X2P4X4) || \
	defined(CONFIG_HW29765515P0P4096P512P2X2P2X2P2X2)
		char runcmd[256];
		printf ("boot_partition_set 1\n");
		snprintf(runcmd, sizeof(runcmd), "boot_partition_set 1");
		run_command(runcmd, 0);
#endif
		NmrptimeHandler=NULL;
		puts("\nNMRP is complete. Please switch OFF power.\n");

		Nmrp_Led_Flashing_Timeout();
		/* Unreachable */

		ctrlc();
		console_assign(stdout, "nulldev");
		console_assign(stderr, "nulldev");
		net_set_state(NETLOOP_SUCCESS);
		break;
	default:
		break;

	}

}

static void Nmrp_Listen_Timeout(void)
{
	if (++Nmrp_Listen_TimeoutCount > NMRP_TIMEOUT_LISTEN) {
		puts("\nRetry count exceeded; boot the image as usual\n");
		Nmrp_Listen_TimeoutCount = 0;
		ResetBootup_usual();
	} else {
		puts("T");
		net_set_timeout_handler(CONFIG_SYS_HZ, Nmrp_Listen_Timeout);
	}
}

static NMRP_PARSED_OPT *Nmrp_Parse(uchar * pkt, ushort optType)
{
	NMRP_PARSED_MSG *msg = (NMRP_PARSED_MSG *) pkt;
	NMRP_PARSED_OPT *opt, *optEnd;
	optEnd = &msg->options[msg->numOptions];
	for (opt = msg->options; opt != optEnd; opt++)
		if (opt->type == ntohs(optType))
			break;
	return msg->numOptions == 0 ? NULL : (opt == optEnd ? NULL : opt);
}

void NmrpSetTimeout(unchar proto, ulong iv, nmrp_thand_f * f)
{
	if (iv == 0) {
		NmrptimeHandler = (nmrp_thand_f *) 0;
		Nmrpproto = 0;
	} else {

		NmrptimeHandler = f;
		NmrptimeStart = get_timer(0);
		NmrptimeDelta = iv;
		Nmrpproto = proto;
	}
}
/*
  Function to parse the NMRP options inside packet.
  If all options are parsed correctly, it returns 0.
 */
static int Nmrp_Parse_Opts(uchar *pkt, NMRP_PARSED_MSG *nmrp_parsed)
{
	nmrp_t *nmrphdr= (nmrp_t*) pkt;
	NMRP_OPT *nmrp_opt;
	int remain_len, opt_index = 0;

	nmrp_parsed->reserved = nmrphdr->reserved;
	nmrp_parsed->code     = nmrphdr->code;
	nmrp_parsed->id       = nmrphdr->id;
	nmrp_parsed->length   = nmrphdr->length;

	remain_len = ntohs(nmrphdr->length) - NMRP_HDR_LEN;

	nmrp_opt = &nmrphdr->opt;
	while (remain_len > 0){
		memcpy(&nmrp_parsed->options[opt_index], nmrp_opt, ntohs(nmrp_opt->len));
		remain_len -= ntohs(nmrp_opt->len);
		nmrp_opt = ((uchar *)nmrp_opt) + ntohs(nmrp_opt->len);
		opt_index++;
	}
	nmrp_parsed->numOptions=opt_index;
	return remain_len;
}

void string_table_bitmask_check()
{
	int update_bit;

	/* find string tables need to be update, begin with smallest bit */
	for (update_bit = 0; update_bit < STRING_TABLE_BITMASK_LEN; update_bit++) {
		if ((NmrpStringTableBitmask & (1 << update_bit)) != 0) {
			//if bit 0 is set, update ST 1, ... etc
			NmrpStringTableUpdateList[NmrpStringTableUpdateCount] = update_bit + 1;
			NmrpStringTableUpdateCount++;
		}
	}
}

void set_region(u16 RegionOption)
{
	uchar region[8];
	sprintf(region, "%04x", RegionOption);
	env_set("region", region);
	run_command("saveenv", 0);
}

void NmrpHandler(uchar * pkt, unsigned dest, struct in_addr src_ip, unsigned src,
                 unsigned type)
{
	nmrp_t *nmrphdr= (nmrp_t*) pkt;
	uchar proto;
	unchar *xp = pkt;
	int fwUpgrade;
	NMRP_PARSED_MSG nmrp_parsed;
	NMRP_PARSED_OPT *opt;
	proto = nmrphdr->code;
	if (type!=PROT_NMRP)
		return;

	/* check for timeout,and run the timeout handler
	   if we have one
	 */

	if (NmrptimeHandler && ((get_timer(0) - NmrptimeStart) > NmrptimeDelta)
	    && (proto != Nmrpproto)) {
		nmrp_thand_f *x;
		x = NmrptimeHandler;
		NmrptimeHandler = (nmrp_thand_f *) 0;
		(*x) ();
	}

	/*
	   Check if Reserved field is zero. Per the specification, the reserved
	   must be all zero in a valid NMRP packet.
	 */
	if (nmrphdr->reserved != 0){
		return;
	}
	memset(&nmrp_parsed, 0, sizeof(NMRP_PARSED_MSG));

	/*
	   Parse the options inside the packet and save it into nmrp_parsed for
	   future reference.
	 */
	if (Nmrp_Parse_Opts(pkt, &nmrp_parsed) != 0){
		/* Some wrong inside the packet, just discard it */
		return;
	}

	NmrpBlock = proto;

	// ignore same packet
	if (NmrpBlock == NmrpLastBlock)
		return;
	NmrpLastBlock = NmrpBlock;

	switch (proto) {
	case NMRP_CODE_ADVERTISE:	/*listening state * */
		if (NmrpState == 0) {
			/*
			   Check if we get the MAGIC-NO option and the content is match
			   with the MAGICNO.
			 */
			if ((opt = Nmrp_Parse(&nmrp_parsed, NMRP_OPT_MAGIC_NO)) != NULL){
				int opt_hdr_len = sizeof(opt->type) + sizeof(opt->len);
				if (memcmp(opt->value.magicno, MAGICNO, ntohs(opt->len) - opt_hdr_len) == 0){
					NmrpState = STATE_LISTENING;
					board_test_led(0);
					printf("\nNMRP CONFIGING");
					NmrpSend();
				}
			}
		}
		break;
	case NMRP_CODE_CONF_ACK:
		if (NmrpState == STATE_LISTENING) {
			/*
			   If there is no DEV-IP option inside the packet, it must be
			   something wrong in the packet, so just ignore this packet
			   without any action taken.
			 */
			if ((opt = Nmrp_Parse(&nmrp_parsed, NMRP_OPT_DEV_IP)) != NULL){
				memcpy(NetOurTftpIP, opt->value.ip.addr,IP_ADDR_LEN);
				/* Do we need the subnet mask? */
				memcpy(&NmrpOuripSubnetMask, opt->value.ip.mask,IP_ADDR_LEN);
				/*
				   FW-UP option is optional for CONF-ACK and it has no effect no
				   matter what is the content of this option, so we just skip the
				   process of this option for now, and will add it back when
				   this option is defined as mandatory.
				   The process for FW-UP would be similar as the action taken for
				   DEV-IP and MAGIC-NO.
				 */

				/*When NMRP Client get CONF-ACK with DEV-REGION option*/
				if ((opt = Nmrp_Parse(&nmrp_parsed, NMRP_OPT_DEV_REGION)) != NULL) {
					/* Save DEV-REGION value to board */
					printf("Get DEV-REGION option, value:0x%04x\n", opt->value.region);
					NmrpDevRegionOption = ntohs(opt->value.region);
					printf("Write Region Number 0x%04x to board\n", NmrpDevRegionOption);
					set_region(NmrpDevRegionOption);
				}
				/*Check if NMRP Client get CONF-ACK with FW-UP option*/
				if ((opt = Nmrp_Parse(&nmrp_parsed, NMRP_OPT_FW_UP)) != NULL) {
					printf("\nRecv FW-UP option\n");
					NmrpFwUPOption = 1;
				} else {
					printf("\nNo FW-UP option\n");
					NmrpFwUPOption = 0;
				}

				/*When NMRP Client get CONF-ACK with ST-UP option*/
				if ((opt = Nmrp_Parse(&nmrp_parsed, NMRP_OPT_ST_UP)) != NULL) {
					printf("\nRecv ST-UP option\n");
					NmrpSTUPOption = 1;
					/* Reset string tables' update related variables. */
					NmrpStringTableUpdateCount = 0;
					NmrpStringTableUpdateIndex = 0;
					memset(NmrpStringTableUpdateList, 0,\
						sizeof(NmrpStringTableUpdateList));

					/* Save from network byte-order to host byte-order. */
					NmrpStringTableBitmask = ntohl(opt->value.string_table_bitmask);

					string_table_bitmask_check();
					printf("\nTotal %d String Table need updating\n",\
						NmrpStringTableUpdateCount);
				} else {
					printf("\nNo ST-UP option\n");
					NmrpSTUPOption = 0;
				}
				if (NmrpFwUPOption == 0 && NmrpSTUPOption == 0) {
					NmrpState = STATE_CLOSING;
					printf("\nNo firmware update, nor string table update\n");
					NmrpSend();
				} else {
					NmrpState = STATE_CONFIGING;
					printf("\nNMRP WAITING FOR UPLOAD FIRMWARE or STRING TABLES!\n");
					NmrpSend();
				}
			}else
				break;
		}
		break;
	case NMRP_CODE_KEEP_ALIVE_ACK:
		if (NmrpState == STATE_KEEP_ALIVE) {
			if (NmrpAliveWaitACK == 1) {
				NmrpAliveTimerBase += NMRP_TIMEOUT_ACTIVE / 4;
				NmrpAliveWaitACK = 0;
			}
		}
		break;
	case NMRP_CODE_CLOSE_ACK:
		if (NmrpState == STATE_CLOSING) {
			NmrpState = STATE_CLOSED;
			printf("\nNMRP CLOSED");
			NmrpSend();
		}
		break;
	default:
		break;
	}

}

void NmrpStart(void)
{
	printf("\n Client starts...[Listening] for ADVERTISE...");

	net_set_timeout_handler(CONFIG_SYS_HZ / 10, Nmrp_Listen_Timeout);
	net_set_udp_handler(NmrpHandler);

	NmrpState = 0;
	Nmrp_Listen_TimeoutCount = 0;
	memset(NmrpClientEther, 0, 6);
	NmrpClientIP.s_addr = 0;
}
