/******************************************************************************
 * Copyright 2015 Espressif Systems
 *
 * Description: A stub to make the ESP8266 debuggable by GDB over the serial
 * port.
 *
 * License: ESPRESSIF MIT License
 *******************************************************************************/

#include <GDBStub.h>
#include <stddef.h>
#include <Arduino.h>
#include "ets_sys.h"
#include "eagle_soc.h"
#include "c_types.h"
#include "gpio.h"
#include "xtensa/corebits.h"
#include "uart_register.h"

#include "gdbstub-entry.h"
#include "gdbstub-cfg.h"


//From xtruntime-frames.h
struct XTensa_exception_frame_s {
	uint32_t pc;
	uint32_t ps;
	uint32_t sar;
	uint32_t vpri;
	uint32_t a[16]; //a0..a15
//These are added manually by the exception code; the HAL doesn't set these on an exception.
	uint32_t litbase;
	uint32_t sr176;
	uint32_t sr208;
	 //'reason' is abused for both the debug and the exception vector: if bit 7 is set,
	//this contains an exception reason, otherwise it contains a debug vector bitmap.
	uint32_t reason;
};

#if GDBSTUB_FREERTOS

struct XTensa_rtos_int_frame_s {
	uint32_t exitPtr;
	uint32_t pc;
	uint32_t ps;
	uint32_t a[16];
	uint32_t sar;
};

/*
Definitions for FreeRTOS. This redefines some os_* functions to use their non-os* counterparts. It
also sets up some function pointers for ROM functions that aren't in the FreeRTOS ld files.
*/
#include <string.h>
#include <stdio.h>
void os_isr_attach(int inum, void *fn);
void os_install_putc1(void (*p)(char c));
#define os_printf(...) printf(__VA_ARGS__)
#define os_strncmp(...) strncmp(__VA_ARGS__)
typedef void wdtfntype();
static wdtfntype *ets_wdt_disable = (wdtfntype *)0x400030f0;
static wdtfntype *ets_wdt_enable = (wdtfntype *)0x40002fa0;

#else
/*
OS-less SDK defines. Defines some headers for things that aren't in the include files, plus
the xthal stack frame struct.
*/
#include "osapi.h"
#include "user_interface.h"

void _xtos_set_exception_handler(int cause, void (exhandler)(struct XTensa_exception_frame_s *frame));

#endif

#define EXCEPTION_GDB_SP_OFFSET 0x100


//Length of buffer used to reserve GDB commands. Has to be at least able to fit the G command, which
//implies a minimum size of about 190 bytes.
#define PBUFLEN 256

//The asm stub saves the Xtensa registers here when a debugging exception happens.
struct XTensa_exception_frame_s gdbstub_savedRegs;
#if GDBSTUB_USE_OWN_STACK
//This is the debugging exception stack.
int exceptionStack[256];
#endif

static bool gdb_attached = false;
static unsigned char cmd[PBUFLEN];		//GDB command input buffer
static char chsum;						//Running checksum of the output packet
#if GDBSTUB_CTRLC_BREAK
static void (*uart_isr_callback)(void*, uint8_t) = NULL;
static void* uart_isr_arg = NULL;
#endif
#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
static void (*uart_putc1_callback)(char) = NULL;
#endif
//Stores ps when single-stepping instruction. -1 when not in use.
static int32_t singleStepPs = -1;

//Uart libs can reference these to see if gdb is attaching to them
bool gdbstub_has_putc1_control() {
#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
	return true;
#else
	return false;
#endif
}
bool gdbstub_has_uart_isr_control() {
#if GDBSTUB_CTRLC_BREAK
	return true;
#else
	return false;
#endif
}

//Small function to feed the hardware watchdog. Needed to stop the ESP from resetting
//due to a watchdog timeout while reading a command.
static void ATTR_GDBFN keepWDTalive() {
	uint64_t *wdtval = (uint64_t*)0x3ff21048;
	uint64_t *wdtovf = (uint64_t*)0x3ff210cc;
	int *wdtctl = (int*)0x3ff210c8;
	*wdtovf = *wdtval + 1600000;
	*wdtctl |= 1 << 31;
}


//Error states used by the routines that grab stuff from the incoming gdb packet
#define ST_ENDPACKET -1
#define ST_ERR -2
#define ST_OK -3
#define ST_CONT -4
#define ST_DETACH -5

//Grab a hex value from the gdb packet. Ptr will get positioned on the end
//of the hex string, as far as the routine has read into it. Bits/4 indicates
//the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much
//hex chars as possible.
static long  gdbGetHexVal(unsigned char **ptr, int bits) {
	int i;
	int no;
	unsigned int v = 0;
	char c;
	no = bits / 4;
	if (bits == -1)
		no = 64;
	for (i = 0; i < no; i++) {
		c = **ptr;
		(*ptr)++;
		if (c >= '0' && c <= '9') {
			v <<= 4;
			v |= (c-'0');
		} else if (c >= 'A' && c <= 'F') {
			v <<= 4;
			v |= (c-'A') + 10;
		} else if (c >= 'a' && c <= 'f') {
			v <<= 4;
			v |= (c-'a') + 10;
		} else if (c == '#') {
			if (bits == -1) {
				(*ptr)--;
				return v;
			}
			return ST_ENDPACKET;
		} else {
			if (bits == -1) {
				(*ptr)--;
				return v;
			}
			return ST_ERR;
		}
	}
	return v;
}

//Swap an int into the form gdb wants it
static int iswap(int i) {
	return ((i >> 24) & 0xff)
		| (((i >> 16) & 0xff) << 8)
		| (((i >> 8) & 0xff) << 16)
		| (((i >> 0) & 0xff) << 24);
}

//Read a byte from the ESP8266 memory.
static unsigned char readbyte(unsigned int p) {
	if (p < 0x20000000 || p >= 0x60000000) return -1;
	int *i = (int*)(p & ~3);
	return *i >> ((p & 3) * 8);
}

//Write a byte to the ESP8266 memory.
static void writeByte(unsigned int p, unsigned char d) {
	if (p < 0x20000000 || p >= 0x60000000) return;
	int *i = (int*)(p & ~3);
	if ((p & 3) == 0) *i = (*i & 0xffffff00) | (d << 0);
	else if ((p & 3) == 1) *i = (*i & 0xffff00ff) | (d << 8);
	else if ((p & 3) == 2) *i = (*i & 0xff00ffff) | (d << 16);
	else if ((p & 3) == 3) *i = (*i & 0x00ffffff) | (d << 24);
}

//Returns 1 if it makes sense to write to addr p
static int validWrAddr(int p) {
	return (p >= 0x3ff00000 && p < 0x40000000)
		|| (p >= 0x40100000 && p < 0x40140000)
		|| (p >= 0x60000000 && p < 0x60002000);
}


static inline bool ATTR_GDBFN gdbRxFifoIsEmpty() {
	return ((READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S) & UART_RXFIFO_CNT) == 0;
}

static inline bool ATTR_GDBFN gdbTxFifoIsFull() {
	return ((READ_PERI_REG(UART_STATUS(0)) >> UART_TXFIFO_CNT_S) & UART_TXFIFO_CNT) >= 126;
}

//Receive a char from the uart. Uses polling and feeds the watchdog.
static inline int gdbRecvChar() {
	while (gdbRxFifoIsEmpty()) {
		keepWDTalive();
	}
	return READ_PERI_REG(UART_FIFO(0));
}

//Send a char to the uart.
static void gdbSendChar(char c) {
	while (gdbTxFifoIsFull())
		;
	WRITE_PERI_REG(UART_FIFO(0), c);
}


//Send the start of a packet; reset checksum calculation.
static void gdbPacketStart() {
	chsum = 0;
	gdbSendChar('$');
}

//Send a char as part of a packet
static void gdbPacketChar(char c) {
	if (c == '#' || c == '$' || c == '}' || c == '*') {
		gdbSendChar('}');
		chsum += '}';
		c ^= 0x20;
	}
	gdbSendChar(c);
	chsum += c;
}

//Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent.
static void gdbPacketHex(int val, int bits) {
	static const char hexChars[] = "0123456789abcdef";
	int i;
	for (i = bits; i > 0; i -= 4) {
		gdbPacketChar(hexChars[(val >> (i - 4)) & 0xf]);
	}
}

//Send a hex val as part of a packet. 'bits'/4 dictates the number of hex chars sent.
static void gdbPacketSwappedHexInt(int val) {
	gdbPacketHex(iswap(val), 32);
}

static void gdbPacketXXXXInt() {
	for (int i=0; i<8; i++) gdbPacketChar('x');
}

//Finish sending a packet.
static void gdbPacketEnd() {
	gdbSendChar('#');
	//Ok to use packet version here since hex char can never be an
	//excape-requiring character
	gdbPacketHex(chsum, 8);
}

// Send a complete packet containing str
static void gdbSendPacketStr(const char *c) {
	gdbPacketStart();
	while (*c != 0) {
		gdbPacketChar(*c);
		c++;
	}
	gdbPacketEnd();
}

// Send a complete packet containing str as an output message
static inline void ATTR_GDBEXTERNFN gdbSendOutputPacketStr(const unsigned char* buf, size_t size) {
	size_t i;
	gdbPacketStart();
	gdbPacketChar('O');
	for (i = 0; i < size; i++)
		gdbPacketHex(buf[i], 8);
	gdbPacketEnd();
}

// Send a complete packet containing c as an output message
static inline void ATTR_GDBEXTERNFN gdbSendOutputPacketChar(unsigned char c) {
	gdbPacketStart();
	gdbPacketChar('O');
	gdbPacketHex(c, 8);
	gdbPacketEnd();
}

static long gdbGetSwappedHexInt(unsigned char **ptr) {
	return iswap(gdbGetHexVal(ptr, 32));
}


//Send the reason execution is stopped to GDB.
static void sendReason() {
	static const char exceptionSignal[] = {4,31,11,11,2,6,8,0,6,7,0,0,7,7,7,7};
#if 0
	char *reason=""; //default
#endif
	//exception-to-signal mapping
	size_t i;
	gdbPacketStart();
	gdbPacketChar('T');
	if (gdbstub_savedRegs.reason == 0xff) {
		gdbPacketHex(2, 8); //sigint
	} else if (gdbstub_savedRegs.reason & 0x80) {
		//We stopped because of an exception. Convert exception code to a signal number and send it.
		i = gdbstub_savedRegs.reason & 0x7f;
		if (i < sizeof(exceptionSignal))
			gdbPacketHex(exceptionSignal[i], 8);
		else
			gdbPacketHex(11, 8);
	} else {
		//We stopped because of a debugging exception.
		gdbPacketHex(5, 8); //sigtrap
//Current Xtensa GDB versions don't seem to request this, so let's leave it off.
#if 0
		if (gdbstub_savedRegs.reason&(1<<0)) reason="break";
		if (gdbstub_savedRegs.reason&(1<<1)) reason="hwbreak";
		if (gdbstub_savedRegs.reason&(1<<2)) reason="watch";
		if (gdbstub_savedRegs.reason&(1<<3)) reason="swbreak";
		if (gdbstub_savedRegs.reason&(1<<4)) reason="swbreak";

		gdbPacketStr(reason);
		gdbPacketChar(':');
		//ToDo: watch: send address
#endif
	}
	gdbPacketEnd();
}

static inline void ATTR_GDBFN gdbSendPacketOK() {
	gdbSendPacketStr("OK");
}

static inline void ATTR_GDBFN gdbSendPacketE01() {
	gdbSendPacketStr("E01");
}

static inline void ATTR_GDBFN gdbSendEmptyPacket() {
	gdbPacketStart();
	gdbPacketEnd();
}

void ATTR_GDBEXTERNFN gdbstub_write_char(char c) {
	if (gdb_attached) {
		ETS_UART_INTR_DISABLE();
		gdbSendOutputPacketChar(c);
		ETS_UART_INTR_ENABLE();
	} else {
		gdbSendChar(c);
	}
}

void ATTR_GDBEXTERNFN gdbstub_write(const char* buf, size_t size) {
	size_t i;
	if (gdb_attached) {
		ETS_UART_INTR_DISABLE();
		gdbSendOutputPacketStr((const unsigned char *)buf, size);
		ETS_UART_INTR_ENABLE();
	} else {
		for (i = 0; i < size; i++) {
			gdbSendChar(buf[i]);
		}
	}
}

/*
Register file in the format lx106 gdb port expects it.
Inspired by gdb/regformats/reg-xtensa.dat from
https://github.com/jcmvbkbc/crosstool-NG/blob/lx106-g%2B%2B/overlays/xtensa_lx106.tar
As decoded by Cesanta.

struct regfile {
	uint32_t a[16];
	uint32_t pc;
	uint32_t sar;
	uint32_t litbase;
	uint32_t sr176;
	uint32_t sr208;
	uint32_t ps;
};
*/

//Handle a command as received from GDB.
static inline int gdbHandleCommand() {
	//Handle a command
	int i, j, k;
	unsigned char *data = cmd + 1;
        if (cmd[0]=='g') {              //send all registers to gdb
                gdbPacketStart();
                gdbPacketSwappedHexInt(gdbstub_savedRegs.pc);
                for (int i=1; i<=35; i++) gdbPacketXXXXInt();
                gdbPacketSwappedHexInt(gdbstub_savedRegs.sar);
                gdbPacketSwappedHexInt(gdbstub_savedRegs.litbase);
                for (int i=38; i<=39; i++) gdbPacketXXXXInt();
                gdbPacketSwappedHexInt(gdbstub_savedRegs.sr176);
                for (int i=41; i<=41; i++) gdbPacketXXXXInt();
                gdbPacketSwappedHexInt(gdbstub_savedRegs.ps);
                for (int i=43; i<=96; i++) gdbPacketXXXXInt();
                for (i=0; i<16; i++) gdbPacketSwappedHexInt(gdbstub_savedRegs.a[i]);
                gdbPacketEnd();
        } else if (cmd[0]=='G') {       //receive content for all registers from gdb
                gdbstub_savedRegs.pc=gdbGetSwappedHexInt(&data);
                for (int i=1; i<=35; i++) gdbGetHexVal(&data, 32);
                gdbstub_savedRegs.sar=gdbGetSwappedHexInt(&data);
                gdbstub_savedRegs.litbase=gdbGetSwappedHexInt(&data);
                for (int i=38; i<=39; i++) gdbGetHexVal(&data, 32);
                gdbstub_savedRegs.sr176=gdbGetSwappedHexInt(&data);
                for (int i=41; i<=41; i++) gdbGetHexVal(&data, 32);
                gdbstub_savedRegs.ps=gdbGetSwappedHexInt(&data);
                for (int i=43; i<=96; i++) gdbGetHexVal(&data, 32);
                for (i=0; i<16; i++) gdbstub_savedRegs.a[i]=gdbGetSwappedHexInt(&data);
		gdbSendPacketOK();
	} else if ((cmd[0] | 0x20) == 'm') {	//read/write memory to gdb
		i = gdbGetHexVal(&data, -1); //addr
		data++;
		j = gdbGetHexVal(&data, -1); //length
		if (cmd[0] == 'm') {	//read memory to gdb
			gdbPacketStart();
			for (k = 0; k < j; k++) {
				gdbPacketHex(readbyte(i++), 8);
			}
			gdbPacketEnd();
		} else {	//write memory from gdb
			if (validWrAddr(i) && validWrAddr(i + j)) {
				data++; //skip :
				for (k = 0; k < j; k++, i++) {
					writeByte(i, gdbGetHexVal(&data, 8));
				}
				//Make sure caches are up-to-date. Procedure according to Xtensa ISA document, ISYNC inst desc.
				asm volatile("ISYNC\nISYNC\n");
				gdbSendPacketOK();
			} else {
				//Trying to do a software breakpoint on a flash proc, perhaps?
				gdbSendPacketE01();
			}
		}
	} else if (cmd[0] == '?') {	//Reply with stop reason
		sendReason();
	} else if (cmd[0] == 'c') {	//continue execution
		return ST_CONT;
	} else if (cmd[0] == 's') {	//single-step instruction
		//Single-stepping can go wrong if an interrupt is pending, especially when it is e.g. a task switch:
		//the ICOUNT register will overflow in the task switch code. That is why we disable interupts when
		//doing single-instruction stepping.
		singleStepPs=gdbstub_savedRegs.ps;
		gdbstub_savedRegs.ps=(gdbstub_savedRegs.ps & ~0xf) | (XCHAL_DEBUGLEVEL - 1);
		gdbstub_icount_ena_single_step();
		return ST_CONT;
	} else if (cmd[0] == 'D') {	//detach
		gdbSendPacketOK();
		return ST_DETACH;
	} else if (cmd[0] == 'k') {	//kill
		system_restart_core();
	} else if (cmd[0] == 'q') {	//Extended query
		if (os_strncmp((char*)&cmd[1], "Supported", 9) == 0) { //Capabilities query
			gdbSendPacketStr("swbreak+;hwbreak+;PacketSize=FF"); //PacketSize is in hex
		} else if (os_strncmp((char*)&cmd[1], "Attached", 8) == 0) {
			//Let gdb know that it is attaching to a running program
			//In general that just means it detaches instead of killing when it exits
			gdbSendPacketStr("1");
		} else {
			//We don't support other queries.
			gdbSendEmptyPacket();
		}
	// case insensitive compare matches 'Z' or 'z'
	} else if ((cmd[0] | 0x20) == 'z' && cmd[1] >= '1' && cmd[2] <= '4') {	//hardware break/watchpoint
		int result;
		data += 2; //skip 'x,'
		i = gdbGetHexVal(&data, -1);
		data++; //skip ','
		j = gdbGetHexVal(&data, -1);
		if (cmd[0] == 'Z') {	//Set hardware break/watchpoint
			if (cmd[1] == '1') {	//Set breakpoint
				result = gdbstub_set_hw_breakpoint(i, j);
			} else {	//Set watchpoint
				int access;
				unsigned int mask = 0;
				if (cmd[1] == '2') access = 2; //write
				if (cmd[1] == '3') access = 1; //read
				if (cmd[1] == '4') access = 3; //access
				if (j == 1) mask = 0x3F;
				if (j == 2) mask = 0x3E;
				if (j == 4) mask = 0x3C;
				if (j == 8) mask = 0x38;
				if (j == 16) mask = 0x30;
				if (j == 32) mask = 0x20;
				result = mask != 0 && gdbstub_set_hw_watchpoint(i, mask, access);
			}
		} else {	//Clear hardware break/watchpoint
			if (cmd[1] == '1') {	//hardware breakpoint
				result = gdbstub_del_hw_breakpoint(i);
			} else {	//hardware watchpoint
				result = gdbstub_del_hw_watchpoint(i);
			}
		}
		if (result) {
			gdbSendPacketOK();
		} else {
			gdbSendPacketE01();
		}
	} else {
		//We don't recognize or support whatever GDB just sent us.
		gdbSendEmptyPacket();
	}
	return ST_OK;
}

//Lower layer: grab a command packet and check the checksum
//Calls gdbHandleCommand on the packet if the checksum is OK
//Returns only if execution of the user program should continue
//Otherwise keeps reading uart data and executing commands
//Flags that gdb has been attached whenever a gdb formatted
// packet is received
//While gdb is attached, checks for ctl-c (\x03) if it's not
// already paused
//Keeps reading commands if it is paused, until either a
// continue, detach, or kill command is received
//It is not necessary for gdb to be attached for it to be paused
//For example, during an exception break, the program is
// paused but gdb might not be attached yet
static int gdbReadCommand() {
	unsigned char chsum;
	unsigned char sentchs[2];
	size_t p;
	unsigned char c;
	unsigned char *ptr;
	int result;
	ETS_UART_INTR_DISABLE();
	ets_wdt_disable();
	sendReason();
	while (true) {
gdbReadCommand_start:
		while (gdbRecvChar() != '$')
			;
gdbReadCommand_packetBegin:
		chsum = 0;
		p = 0;
		while ((c = gdbRecvChar()) != '#') {	//end of packet, checksum follows
			if (c == '$') {
				//Wut, restart packet?
				goto gdbReadCommand_packetBegin;
			}
			if (c == '}') {	//escape the next char
				c = gdbRecvChar() ^ 0x20;
			}
			chsum += c;
			cmd[p++] = c;
			if (p >= PBUFLEN) {
				//Received more than the size of the command buffer
				goto gdbReadCommand_start;
			}
		}
		cmd[p] = 0;
		sentchs[0] = gdbRecvChar();
		sentchs[1] = gdbRecvChar();
		ptr = &sentchs[0];
		if (gdbGetHexVal(&ptr, 8) == chsum) {
			gdb_attached = true;
			gdbSendChar('+');
			result = gdbHandleCommand();
			if (result != ST_OK) {
				break;
			}
		} else {
			gdbSendChar('-');
		}
	}
	if (result == ST_DETACH) {
		gdb_attached = false;
	}
	ets_wdt_enable();
	ETS_UART_INTR_ENABLE();
	return result;
}



//Get the value of one of the A registers
static unsigned int ATTR_GDBFN getaregval(int reg) {
	return gdbstub_savedRegs.a[reg];
}

//Set the value of one of the A registers
static inline void ATTR_GDBFN setaregval(int reg, unsigned int val) {
	// os_printf("%x -> %x\n", val, reg);
	gdbstub_savedRegs.a[reg] = val;
}

//Emulate the l32i/s32i instruction we're stopped at.
static inline void emulLdSt() {
	unsigned char i0 = readbyte(gdbstub_savedRegs.pc);
	unsigned char i1 = readbyte(gdbstub_savedRegs.pc + 1);
	unsigned char i2;
	int *p;

	if ((i0 & 0xf) == 2 && (i1 & 0xb0) == 0x20) {
		//l32i or s32i
		i2 = readbyte(gdbstub_savedRegs.pc + 2);
		p = (int*)getaregval(i1 & 0xf) + (i2 * 4);
		i0 >>= 4;
		if ((i1 & 0xf0) == 0x20) { //l32i
			setaregval(i0, *p);
		} else { //s32i
			*p = getaregval(i0);
		}
		gdbstub_savedRegs.pc += 3;
	} else if ((i0 & 0xe) == 0x8) {
		//l32i.n or s32i.n
		p = (int*)getaregval(i1 & 0xf) + ((i1 >> 4) * 4);
		if ((i0 & 0xf) == 0x8) { //l32i.n
			setaregval(i0 >> 4, *p);
		} else {
			*p = getaregval(i0 >> 4);
		}
		gdbstub_savedRegs.pc += 2;
	// } else {
	// 	os_printf("GDBSTUB: No l32i/s32i instruction: %x %x. Huh?", i1, i0);
	}
}

//We just caught a debug exception and need to handle it. This is called from an assembly
//routine in gdbstub-entry.S
static void gdbstub_handle_debug_exception_flash();
void ATTR_GDBFN gdbstub_handle_debug_exception() {
	Cache_Read_Enable_New();
	gdbstub_handle_debug_exception_flash();
}

static void __attribute__((noinline)) gdbstub_handle_debug_exception_flash() {
	if (singleStepPs != -1) {
		//We come here after single-stepping an instruction. Interrupts are disabled
		//for the single step. Re-enable them here.
		gdbstub_savedRegs.ps = (gdbstub_savedRegs.ps & ~0xf) | (singleStepPs & 0xf);
		singleStepPs =- 1;
	}

	gdbReadCommand();
	if ((gdbstub_savedRegs.reason & 0x80) == 0) { //Watchpoint/BREAK/BREAK.N
		if ((gdbstub_savedRegs.reason & 0x4) != 0) {
			//We stopped due to a watchpoint. We can't re-execute the current instruction
			//because it will happily re-trigger the same watchpoint, so we emulate it
			//while we're still in debugger space.
			emulLdSt();
		} else if ((((gdbstub_savedRegs.reason & 0x8) != 0)
				//We stopped due to a BREAK instruction. Skip over it.
				//Check the instruction first; gdb may have replaced it with the original instruction
				//if it's one of the breakpoints it set.
				&& readbyte(gdbstub_savedRegs.pc + 2) == 0
				&& (readbyte(gdbstub_savedRegs.pc + 1) & 0xf0) == 0x40
				&& (readbyte(gdbstub_savedRegs.pc) & 0x0f) == 0x00)
			|| (((gdbstub_savedRegs.reason & 0x10) != 0)
				//We stopped due to a BREAK.N instruction. Skip over it, after making sure the instruction
				//actually is a BREAK.N
				&& (readbyte(gdbstub_savedRegs.pc + 1) & 0xf0) == 0xf0
				&& readbyte(gdbstub_savedRegs.pc) == 0x2d)) {
			gdbstub_savedRegs.pc += 3;
		}
	}
}



#if GDBSTUB_BREAK_ON_EXCEPTION || GDBSTUB_CTRLC_BREAK

#if !GDBSTUB_FREERTOS
static inline int gdbReadCommandWithFrame(void* frame) {
	//Copy registers the Xtensa HAL did save to gdbstub_savedRegs
	os_memcpy(&gdbstub_savedRegs, frame, 5 * 4);
	os_memcpy(&gdbstub_savedRegs.a[2], ((uint32_t*)frame) + 5, 14 * 4);
	//Credits go to Cesanta for this trick. A1 seems to be destroyed, but because it
	//has a fixed offset from the address of the passed frame, we can recover it.
	gdbstub_savedRegs.a[1] = (uint32_t)frame + EXCEPTION_GDB_SP_OFFSET;

	int result = gdbReadCommand();

	//Copy any changed registers back to the frame the Xtensa HAL uses.
	os_memcpy(frame, &gdbstub_savedRegs, 5 * 4);
	os_memcpy(((uint32_t*)frame) + 5, &gdbstub_savedRegs.a[2], 14 * 4);

	return result;
}
#endif

#endif

#if GDBSTUB_BREAK_ON_EXCEPTION

#if GDBSTUB_FREERTOS
//Freertos exception. This routine is called by an assembly routine in gdbstub-entry.S
void ATTR_GDBFN gdbstub_handle_user_exception() {
	gdbstub_savedRegs.reason |= 0x80; //mark as an exception reason
	while (gdbReadCommand() != ST_CONT)
		;
}

//FreeRTOS doesn't use the Xtensa HAL for exceptions, but uses its own fatal exception handler.
//We use a small hack to replace that with a jump to our own handler, which then has the task of
//decyphering and re-instating the registers the FreeRTOS code left.
extern void user_fatal_exception_handler();
extern void gdbstub_user_exception_entry();

static void ATTR_GDBINIT install_exceptions() {
	//Replace the user_fatal_exception_handler by a jump to our own code
	int *ufe = (int*)user_fatal_exception_handler;
	//This mess encodes as a relative jump instruction to user_fatal_exception_handler
	*ufe = ((((int)gdbstub_user_exception_entry - (int)user_fatal_exception_handler) - 4) << 6) | 6;
}
#else
//Non-OS exception handler. Gets called by the Xtensa HAL.
static void gdbstub_exception_handler_flash(struct XTensa_exception_frame_s *frame);
static void ATTR_GDBFN gdbstub_exception_handler(struct XTensa_exception_frame_s *frame) {
	//Save the extra registers the Xtensa HAL doesn't save
	gdbstub_save_extra_sfrs_for_exception();
	Cache_Read_Enable_New();
	gdbstub_exception_handler_flash(frame);
}

static void __attribute__((noinline)) gdbstub_exception_handler_flash(struct XTensa_exception_frame_s *frame) {
	gdbstub_savedRegs.reason |= 0x80; //mark as an exception reason
	while (gdbReadCommandWithFrame((void*)frame) != ST_CONT)
		;
}

//The OS-less SDK uses the Xtensa HAL to handle exceptions. We can use those functions to catch any
//fatal exceptions and invoke the debugger when this happens.
static void ATTR_GDBINIT install_exceptions() {
	static int exno[] = {EXCCAUSE_ILLEGAL, EXCCAUSE_SYSCALL, EXCCAUSE_INSTR_ERROR, EXCCAUSE_LOAD_STORE_ERROR,
			EXCCAUSE_DIVIDE_BY_ZERO, EXCCAUSE_UNALIGNED, EXCCAUSE_INSTR_DATA_ERROR, EXCCAUSE_LOAD_STORE_DATA_ERROR,
			EXCCAUSE_INSTR_ADDR_ERROR, EXCCAUSE_LOAD_STORE_ADDR_ERROR, EXCCAUSE_INSTR_PROHIBITED,
			EXCCAUSE_LOAD_PROHIBITED, EXCCAUSE_STORE_PROHIBITED};
	unsigned int i;
	for (i = 0; i < (sizeof(exno) / sizeof(exno[0])); i++) {
		_xtos_set_exception_handler(exno[i], gdbstub_exception_handler);
	}
}
#endif

#endif



#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
//Replacement putchar1 routine. Instead of spitting out the character directly, it will buffer up to
//OBUFLEN characters (or up to a \n, whichever comes earlier) and send it out as a gdb stdout packet.
static void ATTR_GDBEXTERNFN gdbstub_semihost_putchar1(char c) {
	if (!gdb_attached && uart_putc1_callback != NULL) {
		uart_putc1_callback(c);
	} else {
		gdbstub_write_char(c);
	}
}

void ATTR_GDBINIT gdbstub_set_putc1_callback(void (*func)(char)) {
	uart_putc1_callback = func;
}
#endif



#if GDBSTUB_FREERTOS
static void ATTR_GDBINIT configure_uart() {}
#else
static void ATTR_GDBINIT configure_uart() {

#ifdef ARDUINO
	// Set the UART input/output pins to TX=1, RX=3
	pinMode(3, SPECIAL);
	pinMode(1, FUNCTION_0);
#endif

	WRITE_PERI_REG(UART_CONF0(0), 0b00011100); //8N1

	SET_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST | UART_TXFIFO_RST);    //RESET FIFO
	CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_RXFIFO_RST | UART_TXFIFO_RST);
}
#endif

#if GDBSTUB_CTRLC_BREAK

#if GDBSTUB_FREERTOS

void ATTR_GDBFN gdbstub_handle_uart_int(struct XTensa_rtos_int_frame_s *frame) {
	int doDebug = 0, fifolen, x;

	fifolen = (READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
	while (fifolen != 0) {
		//Check if any of the chars is control-C. Throw away rest.
		if ((READ_PERI_REG(UART_FIFO(0)) & 0xFF) == 0x3)
			doDebug = 1;
		fifolen--;
	}
	WRITE_PERI_REG(UART_INT_CLR(0), UART_RXFIFO_FULL_INT_CLR | UART_RXFIFO_TOUT_INT_CLR);

	if (doDebug) {
		//Copy registers the Xtensa HAL did save to gdbstub_savedRegs
		gdbstub_savedRegs.pc = frame->pc;
		gdbstub_savedRegs.ps = frame->ps;
		gdbstub_savedRegs.sar = frame->sar;
		for (x = 0; x < 16; x++)
			gdbstub_savedRegs.a[x] = frame->a[x];
//		gdbstub_savedRegs.a1=(uint32_t)frame+EXCEPTION_GDB_SP_OFFSET;
		gdbstub_savedRegs.reason = 0xff; //mark as user break reason

		gdbReadCommand();
		//Copy any changed registers back to the frame the Xtensa HAL uses.
		frame->pc = gdbstub_savedRegs.pc;
		frame->ps = gdbstub_savedRegs.ps;
		frame->sar = gdbstub_savedRegs.sar;
		for (x = 0; x < 16; x++)
			frame->a[x] = gdbstub_savedRegs.a[x];
	}
}

static void ATTR_GDBINIT install_uart_hdlr() {
	os_isr_attach(ETS_UART_INUM, gdbstub_uart_entry);
	SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
	ETS_UART_INTR_ENABLE();
}

#else

static void ATTR_GDBFN gdbstub_uart_hdlr(void* arg, void* frame) {
	(void) arg;
	unsigned char c;
	//Save the extra registers the Xtensa HAL doesn't save
	gdbstub_save_extra_sfrs_for_exception();

	ETS_UART_INTR_DISABLE();
	WRITE_PERI_REG(UART_INT_CLR(0), UART_RXFIFO_FULL_INT_CLR | UART_RXFIFO_TOUT_INT_CLR);

	int fifolen = (READ_PERI_REG(UART_STATUS(0)) >> UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT;
	while (true) {
		if (fifolen == 0) {
			ETS_UART_INTR_ENABLE();
			return;
		}
		c = READ_PERI_REG(UART_FIFO(0)) & 0xFF;
		//Check if any of the chars is control-C
		if (c == 0x3) {
			break;
		}
#if GDBSTUB_CTRLC_BREAK
		if (!gdb_attached && uart_isr_callback != NULL) {
			uart_isr_callback(uart_isr_arg, c);
		}
#endif
		fifolen--;
	}

	gdbstub_savedRegs.reason = 0xff; //mark as user break reason
	gdbReadCommandWithFrame(frame);
}

static void ATTR_GDBINIT install_uart_hdlr() {
	ETS_UART_INTR_DISABLE();
	ETS_UART_INTR_ATTACH(gdbstub_uart_hdlr, NULL);

	configure_uart();

	WRITE_PERI_REG(UART_CONF1(0),
		((16 & UART_RXFIFO_FULL_THRHD) << UART_RXFIFO_FULL_THRHD_S) |
		((0x02 & UART_RX_TOUT_THRHD) << UART_RX_TOUT_THRHD_S) |
		UART_RX_TOUT_EN);

	WRITE_PERI_REG(UART_INT_CLR(0), 0xffff);
	SET_PERI_REG_MASK(UART_INT_ENA(0), UART_RXFIFO_FULL_INT_ENA | UART_RXFIFO_TOUT_INT_ENA);
	ETS_UART_INTR_ENABLE();
}

void ATTR_GDBINIT gdbstub_set_uart_isr_callback(void (*func)(void*, uint8_t), void* arg) {
	ETS_UART_INTR_DISABLE();
	uart_isr_callback = func;
	uart_isr_arg = arg;
	ETS_UART_INTR_ENABLE();
}

#endif

#endif



//gdbstub initialization routine.
void ATTR_GDBINIT gdbstub_init() {
#if GDBSTUB_REDIRECT_CONSOLE_OUTPUT
	os_install_putc1(gdbstub_semihost_putchar1);
#endif
#if GDBSTUB_CTRLC_BREAK
	install_uart_hdlr();
#else
	configure_uart();
#endif
#if GDBSTUB_BREAK_ON_EXCEPTION
	install_exceptions();
#endif
	gdbstub_init_debug_entry();
#if GDBSTUB_BREAK_ON_INIT
	gdbstub_do_break();
#endif
}

bool ATTR_GDBEXTERNFN gdb_present() {
	return true;
}

void ATTR_GDBFN gdb_do_break() { gdbstub_do_break(); }
void ATTR_GDBINIT gdb_init() __attribute__((alias("gdbstub_init")));
