#include <generated/csr.h>
#include <stdio.h>
#include <stdarg.h>

void isr(void);

#ifdef __or1k__

#include <base/uart.h>

#define EXTERNAL_IRQ 0x8

static void emerg_printf(const char *fmt, ...)
{
	char buf[512];
	va_list args;
	va_start(args, fmt);
	vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);

	char *p = buf;
	while(*p) {
		while(uart_txfull_read());
		uart_rxtx_write(*p++);
	}
}

static char emerg_getc(void)
{
	while(uart_rxempty_read());
	char c = uart_rxtx_read();
	uart_ev_pending_write(UART_EV_RX);
	return c;
}

static const char hex[] = "0123456789abcdef";

static void gdb_send(const char *txbuf)
{
	unsigned char cksum = 0;
	const char *p = txbuf;
	while(*p) cksum += *p++;
	emerg_printf("+$%s#%c%c", txbuf, hex[cksum >> 4], hex[cksum & 0xf]);
}

static void gdb_recv(char *rxbuf, size_t size)
{
	size_t pos = (size_t)-1;
	for(;;) {
		char c = emerg_getc();
		if(c == '$')
			pos = 0;
		else if(c == '#')
			return;
		else if(pos < size - 1) {
			rxbuf[pos++] = c;
			rxbuf[pos] = 0;
		}
	}
}

static void gdb_stub(unsigned long pc, unsigned long sr,
                     unsigned long r1, unsigned long *regs)
{
	gdb_send("S05");

	char buf[385];
	for(;;) {
		gdb_recv(buf, sizeof(buf));

		switch(buf[0]) {
			case '?': {
				snprintf(buf, sizeof(buf), "S05");
				break;
			}

			case 'g': {
				snprintf(buf, sizeof(buf),
				         "%08x%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx"
				         "%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx%08lx"
				         "%08lx%08lx%08lx",
				         0,        r1,       regs[2],  regs[3],  regs[4],  regs[5],  regs[6],  regs[7],
				         regs[8],  regs[9],  regs[10], regs[11], regs[12], regs[13], regs[14], regs[15],
				         regs[16], regs[17], regs[18], regs[19], regs[20], regs[21], regs[22], regs[23],
				         regs[24], regs[25], regs[26], regs[27], regs[28], regs[29], regs[30], regs[31],
				         pc-4,     pc,       sr);
				break;
			}

			case 'm': {
				unsigned long addr, len;
				char *endptr = &buf[0];
				addr  = strtoul(endptr + 1, &endptr, 16);
				len   = strtoul(endptr + 1, &endptr, 16);
				unsigned char *ptr = (unsigned char *)addr;
				if(len > sizeof(buf) / 2) len = sizeof(buf) / 2;
				for(size_t i = 0; i < len; i++) {
					buf[i*2  ] = hex[ptr[i] >> 4];
					buf[i*2+1] = hex[ptr[i] & 15];
					buf[i*2+2] = 0;
				}
				break;
			}

			case 'p': {
				unsigned long reg, value;
				char *endptr = &buf[0];
				reg   = strtoul(endptr + 1, &endptr, 16);
				if(reg == 0)
					value = 0;
				else if(reg == 1)
					value = r1;
				else if(reg >= 2 && reg <= 31)
					value = regs[reg];
				else if(reg == 33)
					value = pc;
				else if(reg == 34)
					value = sr;
				else {
					snprintf(buf, sizeof(buf), "E01");
					break;
				}
				snprintf(buf, sizeof(buf), "%08lx", value);
				break;
			}

			case 'P': {
				unsigned long reg, value;
				char *endptr = &buf[0];
				reg   = strtoul(endptr + 1, &endptr, 16);
				value = strtoul(endptr + 1, &endptr, 16);
				if(reg == 0)
					/* ignore */;
				else if(reg == 1)
					r1 = value;
				else if(reg >= 2 && reg <= 31)
					regs[reg] = value;
				else if(reg == 33)
					pc = value;
				else if(reg == 34)
					sr = value;
				else {
					snprintf(buf, sizeof(buf), "E01");
					break;
				}
				snprintf(buf, sizeof(buf), "OK");
				break;
			}

			case 'c': {
				if(buf[1] != '\0') {
					snprintf(buf, sizeof(buf), "E01");
					break;
				}
				return;
			}

			default:
				break;
		}

		do {
			gdb_send(buf);
		} while(emerg_getc() == '-');
	}
}

void exception_handler(unsigned long vect, unsigned long *regs,
                       unsigned long pc, unsigned long ea, unsigned long sr);
void exception_handler(unsigned long vect, unsigned long *regs,
                       unsigned long pc, unsigned long ea, unsigned long sr)
{
	if(vect == EXTERNAL_IRQ) {
		isr();
	} else {
		emerg_printf("\n *** Unhandled exception %d *** \n", vect);
		emerg_printf("   pc  %08x sr  %08x ea  %08x\n",
		             pc, sr, ea);
		unsigned long r1 = (unsigned long)regs + 4*32;
		regs -= 2;
		emerg_printf("   r0  %08x r1  %08x r2  %08x r3  %08x\n",
		             0, r1, regs[2], regs[3]);
		emerg_printf("   r4  %08x r5  %08x r6  %08x r7  %08x\n",
		             regs[4], regs[5], regs[6], regs[7]);
		emerg_printf("   r8  %08x r9  %08x r10 %08x r11 %08x\n",
		             regs[8], regs[9], regs[10], regs[11]);
		emerg_printf("   r12 %08x r13 %08x r14 %08x r15 %08x\n",
		             regs[12], regs[13], regs[14], regs[15]);
		emerg_printf("   r16 %08x r17 %08x r18 %08x r19 %08x\n",
		             regs[16], regs[17], regs[18], regs[19]);
		emerg_printf("   r20 %08x r21 %08x r22 %08x r23 %08x\n",
		             regs[20], regs[21], regs[22], regs[23]);
		emerg_printf("   r24 %08x r25 %08x r26 %08x r27 %08x\n",
		             regs[24], regs[25], regs[26], regs[27]);
		emerg_printf("   r28 %08x r29 %08x r30 %08x r31 %08x\n",
		             regs[28], regs[29], regs[30], regs[31]);
		emerg_printf(" stack:\n");
		unsigned long *sp = (unsigned long *)r1;
		for(unsigned long spoff = 0; spoff < 16; spoff += 4) {
			emerg_printf("   %08x:", &sp[spoff]);
			for(unsigned long spoff2 = 0; spoff2 < 4; spoff2++) {
				emerg_printf(" %08x", sp[spoff + spoff2]);
			}
			emerg_printf("\n");
		}
		emerg_printf(" waiting for gdb... ");
		gdb_stub(pc, sr, r1, regs);
	}
}
#endif
