/*
 * A Command parser for RTL Switch configuration
 */

// #define DEBUG
// #define REGDBG 1

#include "rtl837x_common.h"
#include "rtl837x_port.h"
#include "rtl837x_flash.h"
#include "rtl837x_phy.h"
#include "rtl837x_regs.h"
#include "rtl837x_sfr.h"
#include "rtl837x_stp.h"
#include "uip/uip.h"
#include "version.h"

#pragma codeseg BANK1
#pragma constseg BANK1

extern __xdata uint8_t minPort;
extern __xdata uint8_t maxPort;
extern __xdata uint8_t nSFPPorts;
extern __xdata uint8_t isRTL8373;
extern __xdata uint16_t mpos;
extern __xdata uint8_t stpEnabled;
extern __code uint8_t log_to_phys_port[9];

extern volatile __xdata uint32_t ticks;
extern volatile __xdata uint8_t sfr_data[4];

extern __code uint8_t * __code greeting;
extern __code uint8_t * __code hex;

extern __xdata uint8_t flash_buf[512];
extern __xdata struct flash_region_t flash_region;

__xdata uint8_t vlan_names[VLAN_NAMES_SIZE];
__xdata uint16_t vlan_ptr;
__xdata uint8_t gpio_last_value[8] = { 0 };

// Temporatly for str to hex convertion value.
// Support up to 32_bits.
__xdata uint8_t hexvalue[4] = { 0 };


// Buffer for writing to flash 0x1fd000, copy to 0x1fe000
__xdata uint8_t cmd_buffer[SBUF_SIZE];
__xdata uint8_t cmd_available;

__xdata	uint8_t l;
__xdata uint8_t line_ptr;
__xdata	char is_white;
__xdata	char save_cmd;

__xdata uint8_t ip[4];

#define N_WORDS SBUF_SIZE
__xdata signed char cmd_words_b[N_WORDS];

__xdata uint8_t cmd_history[CMD_HISTORY_SIZE];
__xdata uint16_t cmd_history_ptr;

// Maps the physical port (starting from 0) to the logical port
__code uint8_t phys_to_log_port[6] = {
	4, 5, 6, 7, 3, 8
};


inline uint8_t isletter(uint8_t l)
{
	// return (l >= 'a' && l <= 'z') || (l >= 'A' && l <= 'Z');

	// Make it lowercase
	l |= 0x20;
	l -= 'a';
	return (l <= ('z'-'a'));
}


inline uint8_t isnumber(uint8_t l)
{
	// return (l >= '0' && l <= '9');
	l -= '0';
	return (l <= ('9'-'0'));
}


uint8_t cmd_compare(uint8_t start, uint8_t * __code cmd)
{
	signed char i;
	signed char j = 0;

	for (i = cmd_words_b[start]; i != cmd_words_b[start + 1] && cmd_buffer[i] != ' '; i++) {
		i &= SBUF_SIZE - 1;
//		print_short(i); write_char(':'); print_short(j); write_char('#'); print_string("\n");
//		write_char('>'); write_char(cmd[j]); write_char('-'); write_char(cmd_buffer[i]); print_string("\n");
		if (!cmd[j])
			return 1;
		if (cmd_buffer[i] != cmd[j++])
			break;
	}
//	write_char('.'); print_short(i); write_char(':'); print_short(i);
	if (i == cmd_words_b[start + 1] || cmd_buffer[i] == ' ')
		return 1;
	return 0;
}


/* Converts ascii-hex array into value.
	returns number of hexvalue[] entries has been written.
	return value = 0 means error.
*/
uint8_t atoi_hex(uint8_t idx)
{
	uint8_t h_idx = 0;
	uint8_t val = 0;
	uint8_t c;

	while(1) {
		c = cmd_buffer[idx];

		if (c == '\0' || c == ' ') {
			break;
		}

		// swap hex nibbles
		val = (val >> 4) | (val << 4);

		if (c - '0' < 10) {
			val |= c - '0';
		} else {
			c |= 0x20;
			c -= 'a';
			if (c > 5) {
				h_idx = 0;
				break;
			}
			val |= c + 10;
		}

		idx++;
		hexvalue[h_idx >> 1] = val;

		if (h_idx & 1 == 1) {
			val = 0;
		}
		h_idx++;
	}

	return ((h_idx + 1) >> 1);
}

uint8_t atoi_short(register uint16_t *vlan, register uint8_t idx)
{
	uint8_t err = 1;
	*vlan = 0;

	while (isnumber(cmd_buffer[idx])) {
		err = 0;
		*vlan = (*vlan * 10) + cmd_buffer[idx] - '0';
		idx++;
	}
	return err;
}


uint8_t parse_ip(register uint8_t idx)
{
	__xdata uint8_t b;

	for (b = 0; b < 4; b++) {
		ip[b] = 0;
		while (isnumber(cmd_buffer[idx])) {
			ip[b] = (ip[b] * 10) + cmd_buffer[idx] - '0';
			idx++;
		}
		if (b < 3 && cmd_buffer[idx++] != '.') {
			print_string("Error in IP format, expecting '.'\n");
			return -1;
		}
	}
	return 0;
}


void parse_trunk(void)
{
	__xdata uint8_t group;
	__xdata uint16_t members = 0;

	group = cmd_buffer[cmd_words_b[1]] - '0';

	uint8_t w = 2;
	while (cmd_words_b[w] > 0) {
		uint8_t port;
		if (isnumber(cmd_buffer[cmd_words_b[w]])) {
			port = cmd_buffer[cmd_words_b[w]] - '1';
			if (port > maxPort)
				goto err;
			members |= ((uint16_t)1) << port;
		}
		w++;
	}
	trunk_set(group, members);
	return;
err:
	print_string("Error: trunk <trunk-id> [port]...");
}


void parse_vlan(void)
{
	__xdata uint16_t vlan;
	__xdata uint16_t members = 0;
	__xdata uint16_t tagged = 0;
	if (!atoi_short(&vlan, cmd_words_b[1])) {
		if (cmd_words_b[2] > 0 && cmd_buffer[cmd_words_b[2]] == 'd' && cmd_words_b[3] < 0) {
			vlan_delete(vlan);
			return;
		}
		uint8_t w = 2;
		write_char('#');
		print_byte(cmd_words_b[w] );
		write_char('#'); write_char(cmd_buffer[cmd_words_b[w]]);
		if (cmd_words_b[w] > 0 && isletter(cmd_buffer[cmd_words_b[w]])) {
			register uint8_t i = 0;
			vlan_names[vlan_ptr++] = hex[(vlan >> 8) & 0xf];
			vlan_names[vlan_ptr++] = hex[(vlan >> 4) & 0xf] ;
			vlan_names[vlan_ptr++] = hex[vlan & 0xf];
			print_string("COPYING: >");
			while(cmd_buffer[cmd_words_b[w] + i] != ' ') {
				write_char(cmd_buffer[cmd_words_b[w] + i]);
				vlan_names[vlan_ptr++] = cmd_buffer[cmd_words_b[w] + i++];
			}
			vlan_names[vlan_ptr++] = ' '; vlan_names[vlan_ptr] = '\0';
			w++;
			print_string("<\n");
		}
		while (cmd_words_b[w] > 0) {
			uint8_t port;
			if (isnumber(cmd_buffer[cmd_words_b[w]])) {
				port = cmd_buffer[cmd_words_b[w]] - '1';
				if (isnumber(cmd_buffer[cmd_words_b[w] + 1])) {
					port = (port + 1) * 10 + cmd_buffer[cmd_words_b[w] + 1] - '1';
					if (cmd_buffer[cmd_words_b[w] + 2] == 't')
						tagged |= ((uint16_t)1) << port;
				} else {
					if (!isRTL8373)
						port = phys_to_log_port[port];
					if (cmd_buffer[cmd_words_b[w] + 1] == 't')
						tagged |= ((uint16_t)1) << port;
				}
				if (port > maxPort)
					goto err;
				members |= ((uint16_t)1) << port;
			}
			w++;
		}
		vlan_create(vlan, members, tagged);
	}
	if (cmd_words_b[2] > 0 && isletter(cmd_buffer[cmd_words_b[2]])) {
		print_string("vlan_ptr "); print_short(vlan_ptr); write_char(':');
		write_char('>'); print_string_x(&vlan_names[0]); write_char('<'); write_char('\n');
	}
	return;
err:
	print_string("Error: vlan <vlan-id> [port][t/u]...");
}


void parse_mirror(void)
{
	__xdata uint8_t mirroring_port;
	__xdata uint16_t rx_pmask = 0;
	__xdata uint16_t tx_pmask = 0;

	if (cmd_words_b[1] > 0 && cmd_compare(1, "status")) {
		reg_read_m(RTL837x_MIRROR_CTRL);
		uint8_t mPort = sfr_data[3];
		if (mPort & 1) {
			print_string("Enabled: ");
		} else {
			print_string("NOT Enabled: ");
		}
		print_string("Mirroring port: ");
		if (!isRTL8373)
			write_char('0' + log_to_phys_port[mPort >> 1]);
		else
			write_char('0' + (mPort >> 1) + 1);
		reg_read_m(RTL837x_MIRROR_CONF);
		uint16_t m = sfr_data[0];
		m = (m << 8) | sfr_data[1];
		print_string(", Port mask RX: ");
		print_short(m);
		m = sfr_data[2];
		m = (m << 8) | sfr_data[3];
		print_string(", Port mask TX: ");
		print_short(m);
		write_char('\n');
		return;
	} else if (cmd_words_b[1] > 0 && cmd_compare(1, "off")) {
		port_mirror_del();
		return;
	}

	if (!isnumber(cmd_buffer[cmd_words_b[1]])) {
		print_string("Port missing: mirror <mirroring port> [port][t/r]...");
		return;
	}

	mirroring_port = cmd_buffer[cmd_words_b[1]] - '1';
	if (isnumber(cmd_buffer[cmd_words_b[1] + 1]))
		mirroring_port = (mirroring_port + 1) * 10 + cmd_buffer[cmd_words_b[1] + 1] - '1';
	if (!isRTL8373)
		mirroring_port = phys_to_log_port[mirroring_port];

	uint8_t w = 2;
	while (cmd_words_b[w] > 0) {
		uint8_t port;
		if (isnumber(cmd_buffer[cmd_words_b[w]])) {
			port = cmd_buffer[cmd_words_b[w]] - '1';
			if (isnumber(cmd_buffer[cmd_words_b[w] + 1])) {
				port = (port + 1) * 10 + cmd_buffer[cmd_words_b[w] + 1] - '1';
				if (!isRTL8373)
					port = phys_to_log_port[port];
				if (cmd_buffer[cmd_words_b[w] + 2] == 'r')
					rx_pmask |= ((uint16_t)1) << port;
				else if (cmd_buffer[cmd_words_b[w] + 2] == 't')
					tx_pmask |= ((uint16_t)1) << port;
				else {
					rx_pmask |= ((uint16_t)1) << port;
					tx_pmask |= ((uint16_t)1) << port;
				}
			} else {
				if (!isRTL8373)
					port = phys_to_log_port[port];
				if (cmd_buffer[cmd_words_b[w] + 1] == 'r')
					rx_pmask |= ((uint16_t)1) << port;
				else if (cmd_buffer[cmd_words_b[w] + 1] == 't')
					tx_pmask |= ((uint16_t)1) << port;
				else {
					rx_pmask |= ((uint16_t)1) << port;
					tx_pmask |= ((uint16_t)1) << port;
				}
			}
		}
		w++;
	}
	port_mirror_set(mirroring_port, rx_pmask, tx_pmask);
}


void parse_regget(void)
{
	uint16_t reg = 0;

	if (cmd_words_b[1] < 0) {
		goto err;
	}

	uint8_t hex_size = atoi_hex(cmd_words_b[1]);

	if (hex_size == 0 || hex_size > 2) {
		goto err;
	}

	reg = hexvalue[0];
	if (hex_size == 2) {
		reg <<= 8;
		reg |= hexvalue[1];
	}

	print_string("REGGET: ");
	print_short(reg);
	print_string(": VAL: ");

	reg_read_m(reg);
	print_sfr_data();
	return;

err:
	print_string("usage: regget <hexvalue>\n\tlike: regget 0BB0 or regget 0c");
	return;
}


void parse_regset(void)
{
	uint16_t reg = 0;

	if (cmd_words_b[2] < 0) {
		goto err;
	}

	uint8_t hex_size = atoi_hex(cmd_words_b[1]);
	if (hex_size == 0 || hex_size > 2) {
		goto err;
	}

	reg = hexvalue[0];
	if (hex_size == 2) {
		reg <<= 8;
		reg |= hexvalue[1];
	}

	hex_size = atoi_hex(cmd_words_b[2]);
	if (hex_size == 0 || hex_size > 4) {
		goto err;
	}

	// zero sfr memory data
	sfr_set_zero();

	// copy data over sfr memory
	uint8_t offset = 4 - hex_size;
	while(hex_size) {
		hex_size -= 1;
		sfr_data[offset + hex_size] = hexvalue[hex_size];
	}
	print_string("REGSET: ");
	print_short(reg);

	reg_write_m(reg);

	print_string(": VAL: ");
	print_sfr_data();
	return;

err:
	print_string("usage: regset <hexvalue> <hexvalue>\n\tlike regset 0b abcd1234.");
}


// Parse command into words
uint8_t cmd_tokenize(void) __banked
{
#ifdef DEBUG
	print_string("Tokenizing command\n");
	print_string_x(&cmd_buffer[0]);
	write_char('<'); write_char('\n');
#endif
	line_ptr = 0;
	is_white = 1;
	uint8_t word = 0;
	cmd_words_b[0] = -1;
	while (cmd_buffer[line_ptr] && line_ptr < SBUF_SIZE - 1) {
		if (is_white && cmd_buffer[line_ptr] != ' ') {
			is_white = 0;
			cmd_words_b[word++] = line_ptr;
		}
		if (cmd_buffer[line_ptr] == ' ')
			is_white = 1;
		line_ptr++;
		if (word >= N_WORDS - 1) {
			print_string("\ntoo many arguments, truncated");
			return 1;
		}
	}
	if (line_ptr == SBUF_SIZE - 1)
		return 1;
	cmd_words_b[word++] = line_ptr;
	cmd_words_b[word++] = -1;

	return 0;
}

// Print GPIO status
void print_gpio_status(void) {
	for (uint8_t idx = 0; idx < 2; idx++) {
		reg_read(RTL837X_REG_GPIO_00_31_INPUT + (idx * 4));
		print_string("GPIO ");
		write_char(idx + '0');
		write_char(':');
		write_char(' ');

		print_byte(SFR_DATA_24);
		print_byte(SFR_DATA_16);
		print_byte(SFR_DATA_8);
		print_byte(SFR_DATA_0);

		write_char(' ');
		print_byte( gpio_last_value[(idx *4)] ^ SFR_DATA_24);
		gpio_last_value[(idx *4)] = SFR_DATA_24;
		print_byte( gpio_last_value[(idx *4) + 1] ^ SFR_DATA_16);
		gpio_last_value[(idx *4) + 1] = SFR_DATA_16;
		print_byte( gpio_last_value[(idx *4) + 2] ^ SFR_DATA_8);
		gpio_last_value[(idx *4) + 2] = SFR_DATA_8;
		print_byte( gpio_last_value[(idx *4) + 3] ^ SFR_DATA_0);
		gpio_last_value[(idx *4) + 3] = SFR_DATA_0;
		write_char('\n');
	}
}

// Show software version
void print_sw_version(void) __banked {
	print_string("Software version: ");
	print_string(VERSION_SW);
	write_char('\n');
}


// Identify command
void cmd_parser(void) __banked
{
#ifdef DEBUG
	print_long(ticks);
	print_string("Parsing command\n");
	print_string_x(&cmd_buffer[0]);
	write_char('<'); write_char('\n');
	print_string("CMD-words: ");
	print_byte(cmd_words_b[0]); write_char(' ');
	print_byte(cmd_words_b[1]); write_char(' ');
	print_byte(cmd_words_b[2]); write_char(' ');
	print_byte(cmd_words_b[3]); write_char(' ');
	print_byte(cmd_words_b[4]); write_char(' ');
	print_byte(cmd_words_b[5]); write_char(' ');
	print_byte(cmd_words_b[6]); write_char('\n');
#endif
	if (cmd_words_b[0] >= 0 && cmd_words_b[1] >= 0) {
		if (cmd_compare(0, "reset")) {
			print_string("\nRESET\n\n");
			reset_chip();
		}
		if (cmd_compare(0, "sfp")) {
			uint8_t rate = sfp_read_reg(0, 12);
			print_string("\nRate: "); print_byte(rate);
			print_string("  Encoding: "); print_byte(sfp_read_reg(0, 11));
			print_string("\n");
			for (uint8_t i = 20; i < 60; i++) {
				uint8_t c = sfp_read_reg(0, i);
				if (c)
					write_char(c);
			}
		}
		if (cmd_compare(0, "stat")) {
			port_stats_print();
		}
		if (cmd_compare(0, "flash") && cmd_words_b[1] > 0 && cmd_buffer[cmd_words_b[1]] == 'r') {
			print_string("\nPRINT SECURITY REGISTERS\n");
			// The following will only show something else than 0xff if it was programmed for a managed switch
			flash_region.addr = 0x0001000;
			flash_region.len = 40;
			flash_read_security();
			flash_region.addr = 0x0002000;
			flash_region.len = 40;
			flash_read_security();
			flash_region.addr = 0x0003000;
			flash_region.len = 40;
			flash_read_security();
		}
		if (cmd_compare(0, "flash") && cmd_words_b[1] > 0 && cmd_buffer[cmd_words_b[1]] == 'd') {
			print_string("\nDUMPING FLASH\n");
			flash_region.addr = 0;
			flash_region.len = 255;
			flash_dump(255);
		}
		if (cmd_compare(0, "flash") && cmd_words_b[1] > 0 && cmd_buffer[cmd_words_b[1]] == 'j') {
			print_string("\nJEDEC ID\n");
			flash_read_jedecid();
		}
		if (cmd_compare(0, "flash") && cmd_words_b[1] > 0 && cmd_buffer[cmd_words_b[1]] == 'u') {
			print_string("\nUNIQUE ID\n");
			flash_read_uid();
		}
		// Switch to flash 62.5 MHz mode
		if (cmd_compare(0, "flash") && cmd_words_b[1] > 0 && cmd_buffer[cmd_words_b[1]] == 's') {
			print_string("\nFLASH FAST MODE\n");
			flash_init(1);
			print_string("\nNow dumping flash\n");
			flash_region.addr = 0;
			flash_region.len = 255;
			flash_dump(255);
		}
		if (cmd_compare(0, "flash") && cmd_words_b[1] > 0 && cmd_buffer[cmd_words_b[1]] == 'e') {
			print_string("\nFLASH erase\n");
			flash_region.addr = 0x20000;
			flash_sector_erase();
		}
		if (cmd_compare(0, "flash") && cmd_words_b[1] > 0 && cmd_buffer[cmd_words_b[1]] == 'w') {
			print_string("\nFLASH write\n");
			for (uint8_t i = 0; i < 20; i++)
				flash_buf[i] = greeting[i];
			flash_region.addr = 0x200000;
			flash_region.len = 20;
			flash_write_bytes(flash_buf);
		}
		if (cmd_compare(0, "port") && cmd_words_b[1] > 0) {
			print_string("\nPORT ");
			uint8_t p = cmd_buffer[cmd_words_b[1]] - '1';
			print_byte(p);
			if (cmd_words_b[2] > 0 && cmd_compare(2, "2g5")) {
				print_string(" 2.5G\n");
				phy_set_mode(p, PHY_SPEED_2G5, 0, 0);
			}
			if (cmd_words_b[2] > 0 && cmd_compare(2, "1g")) {
				print_string(" 1G\n");
				phy_set_mode(p, PHY_SPEED_1G, 0, 0);
			}
			if (cmd_words_b[2] > 0 && cmd_compare(2, "auto")) {
				print_string(" AUTO\n");
				phy_set_mode(p, PHY_SPEED_AUTO, 0, 0);
			}
			if (cmd_words_b[2] > 0 && cmd_compare(2, "off")) {
				print_string(" OFF\n");
				phy_set_mode(p, PHY_OFF, 0, 0);
			}
		}
		if (cmd_compare(0, "ip")) {
			print_string("Got ip command: ");
			if (!parse_ip(cmd_words_b[1]))
				uip_ipaddr(&uip_hostaddr, ip[0], ip[1], ip[2], ip[3]);
			else
				print_string("Invalid IP address\n");
			print_byte(ip[0]); print_byte(ip[1]); print_byte(ip[2]); print_byte(ip[3]);
			write_char('\n');
		}
		if (cmd_compare(0, "gw")) {
			print_string("Got gw command: ");
			if (!parse_ip(cmd_words_b[1]))
				uip_ipaddr(&uip_draddr, ip[0], ip[1], ip[2], ip[3]);
			else
				print_string("Invalid IP address\n");
			print_byte(ip[0]); print_byte(ip[1]); print_byte(ip[2]); print_byte(ip[3]);
			write_char('\n');
		}
		if (cmd_compare(0, "netmask")) {
			print_string("Got netmask command: ");
			if (!parse_ip(cmd_words_b[1]))
				uip_ipaddr(&uip_netmask, ip[0], ip[1], ip[2], ip[3]);
			else
				print_string("Invalid IP address\n");
			print_byte(ip[0]);print_byte(ip[1]);print_byte(ip[2]);print_byte(ip[3]);
			write_char('\n');
		}
		if (cmd_compare(0, "l2")) {
			if (cmd_words_b[1] > 0 && cmd_compare(1, "forget"))
				port_l2_forget();
			else
				port_l2_learned();
		}
		if (cmd_compare(0, "stp")) {
			if (cmd_words_b[1] > 0 && cmd_compare(1, "on")) {
				print_string("STP enabled\n");
				stpEnabled = 1;
				stp_setup();
			} else {
				print_string("STP disabled\n");
				stp_off();
				stpEnabled = 0;
			}
		}
		if (cmd_compare(0, "pvid") && cmd_words_b[1] > 0 && cmd_words_b[2] > 0) {
			__xdata uint16_t pvid;
			uint8_t port;
			port = cmd_buffer[cmd_words_b[1]] - '1';
			if (!isRTL8373)
				port = phys_to_log_port[port];
			if (!atoi_short(&pvid, cmd_words_b[2]))
				port_pvid_set(port, pvid);
		}
		if (cmd_compare(0, "vlan")) {
			parse_vlan();
		}
		if (cmd_compare(0, "mirror")) {
			parse_mirror();
		}
		if (cmd_compare(0, "trunk")) {
			parse_trunk();
		}
		if (cmd_compare(0, "sds")) {
			print_reg(RTL837X_REG_SDS_MODES);
		}
		if (cmd_compare(0, "gpio")) {
			print_gpio_status();
		}
		if (cmd_compare(0, "regget")) {
			parse_regget();
		}
		if (cmd_compare(0, "regset")) {
			parse_regset();
		}
		if (cmd_compare(0, "eee")) {
			int8_t port = -1;
			if (cmd_words_b[3] > 0) {
				port = cmd_buffer[cmd_words_b[2]] - '1';
				if (!isRTL8373)
					port = phys_to_log_port[port];
			}
			if (cmd_words_b[1] > 0 && cmd_compare(1, "on")) {
				if (port >= 0)
					port_eee_enable(port);
				else
					port_eee_enable_all();
			} else if (cmd_words_b[1] > 0 && cmd_compare(1, "off")) {
				if (port >= 0)
					port_eee_disable(port);
				else
					port_eee_disable_all();
			} else if (cmd_words_b[1] > 0 && cmd_compare(1, "status")) {
				if (port >= 0)
					port_eee_status(port);
				else
					port_eee_status_all();
			}
		}
		if (cmd_compare(0, "version")) {
			print_sw_version();
		}
		if (cmd_compare(0, "history")) {
			__xdata uint16_t p = (cmd_history_ptr + 1) & CMD_HISTORY_MASK;
			__xdata uint8_t found_begin = 0;
			print_string("History ptr: ");
			print_short(cmd_history_ptr); write_char('\n');
			while (p != cmd_history_ptr) {
				print_short(p); write_char(' ');
				if (!cmd_history[p] || cmd_history[p] == '\n')
					found_begin = 1;
				if (found_begin && cmd_history[p])
					write_char(cmd_history[p]);
				p = (p + 1) & CMD_HISTORY_MASK;
			}
		}
		if (save_cmd) {
			uint8_t i;
			for (i = 0; i < N_WORDS; i++) {
				if (cmd_words_b[i] < 0)
					break;
			}
			if (i < N_WORDS) {
				i = cmd_words_b[--i];
				cmd_history_ptr = (cmd_history_ptr + i) & CMD_HISTORY_MASK;
				__xdata uint16_t p = cmd_history_ptr;
				cmd_history[cmd_history_ptr++] = '\n';
				do {
					i--;
					cmd_history[--p & CMD_HISTORY_MASK] = cmd_buffer[i];
				} while (i);
			}
		}
	}
}

#define FLASH_READ_BURST_SIZE 0x100;
void execute_config(void) __banked
{
	__xdata uint32_t pos = CONFIG_START;
	__xdata uint16_t len_left = CONFIG_LEN;

	save_cmd = 0;

	do {
		flash_region.addr = pos;
		flash_region.len = FLASH_READ_BURST_SIZE;
		write_char('-'); print_long(flash_region.addr); write_char(':'); print_short(flash_region.len); write_char('\n');

		flash_read_bulk(flash_buf);

		uint8_t cfg_idx = 0;
		uint8_t c = 0;
		do {
			for (uint8_t cmd_idx = 0; cmd_idx < (SBUF_SIZE - 1); cmd_idx++) {
				c = flash_buf[cfg_idx++];
				print_byte(c);

				if (c == 0 || c == '\n') {
					cmd_buffer[cmd_idx] = '\0';
					write_char('\n'); write_char('#');  print_short(cfg_idx); write_char('-'); print_short(cmd_idx); write_char('-'); print_string_x(cmd_buffer); write_char('\n');
					if (cmd_idx && !cmd_tokenize())
						cmd_parser();
					if (c == 0)
						goto config_done;
					break;
				}

				cmd_buffer[cmd_idx] = c;
			}
			write_char('N');
		} while(cfg_idx);

		len_left -= FLASH_READ_BURST_SIZE;
		pos += FLASH_READ_BURST_SIZE;
	} while(len_left);

config_done:
	// Start saving commands to cmd_history
	save_cmd = 1;
	for (cmd_history_ptr = 0; cmd_history_ptr < CMD_HISTORY_SIZE; cmd_history_ptr++)
		cmd_history[cmd_history_ptr] = 0;
	cmd_history_ptr = 0;
}
