/* Contains the code to interface with a remote GDB */

/*
** Copyright 2002, Manuel J. Petit. All rights reserved.
** Distributed under the terms of the NewOS License.
*/
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <vm.h>
#include <smp.h>
#include <debug.h>
#include <gdb.h>
#include <arch/dbg_console.h>


enum { INIT= 0, CMDREAD, CKSUM1, CKSUM2, WAITACK, QUIT, GDBSTATES };


static char cmd[512];
static int  cmd_ptr;
static int  checksum;

static char reply[512];

static char safe_mem[512];


/*
 * utility functions
 */
static
int
htons(short value)
{
	return ((value>>8)&0xff) | ((value&0xff)<<8);
}

static
int
htonl(int value)
{
	return htons((value>>16)&0xffff) | (htons(value&0xffff)<<16);
}

static
int
parse_nibble(int input)
{
	int nibble= 0xff;

	if((input>= '0') && (input<= '9')) {
		nibble= input-'0';
	}
	if((input>= 'A') && (input<= 'F')) {
		nibble= 0x0a+(input-'A');
	}
	if((input>= 'a') && (input<= 'f')) {
		nibble= 0x0a+(input-'a');
	}

	return nibble;
}



/*
 * GDB protocol ACK & NAK & Reply
 *
 */
static
void
gdb_ack(void)
{
	dbg_putch('+');
}

static
void
gdb_nak(void)
{
	dbg_putch('-');
}

static
void
gdb_resend_reply(void)
{
	dbg_puts(reply);
}

static
void
gdb_reply(char const *fmt, ...)
{
	int i;
	int len;
	int sum;
	va_list args;

	va_start(args, fmt);
	reply[0]= '$';
	vsprintf(reply+1, fmt, args);
	va_end(args);

	len= strlen(reply);
	sum= 0;
	for(i= 1; i< len; i++) {
		sum+= reply[i];
	}
	sum%= 256;

	sprintf(reply+len, "#%02x", sum);

	gdb_resend_reply();
}

static
void
gdb_regreply(int const *regs, int numregs)
{
	int i;
	int len;
	int sum;

	reply[0]= '$';
	for(i= 0; i< numregs; i++) {
		sprintf(reply+1+8*i, "%08x", htonl(regs[i]));
	}

	len= strlen(reply);
	sum= 0;
	for(i= 1; i< len; i++) {
		sum+= reply[i];
	}
	sum%= 256;

	sprintf(reply+len, "#%02x", sum);

	gdb_resend_reply();
}

static
void
gdb_memreply(char const *bytes, int numbytes)
{
	int i;
	int len;
	int sum;

	reply[0]= '$';
	for(i= 0; i< numbytes; i++) {
		sprintf(reply+1+2*i, "%02x", (unsigned char)bytes[i]);
	}

	len= strlen(reply);
	sum= 0;
	for(i= 1; i< len; i++) {
		sum+= reply[i];
	}
	sum%= 256;

	sprintf(reply+len, "#%02x", sum);

	gdb_resend_reply();
}



/*
 * checksum verification
 */
static
int
gdb_verify_checksum(void)
{
	int i;
	int len;
	int sum;

	len= strlen(cmd);
	sum= 0;
	for(i= 0; i< len; i++) {
		sum+= cmd[i];
	}
	sum%= 256;

	return (sum==checksum)?1:0;
}


/*
 * command parsing an dispatching
 */
static
int
gdb_parse_command(void)
{
//	int retval;

	if(!gdb_verify_checksum()) {
		gdb_nak();
		return INIT;
	} else {
		gdb_ack();
	}

	switch(cmd[0]) {

		case 'H':
			{
				/*
				 * Command H (actually Hct) is used to select
				 * the current thread (-1 meaning all threads)
				 * We just fake we recognize the the command
				 * and send an 'OK' response.
				 */
				gdb_reply("OK");
			} break;

		case 'q':
			{
//				extern unsigned _start;
				extern unsigned __data_start;
				extern unsigned __bss_start;

				/*
				 * There are several q commands:
				 *
				 *     qXXXX        Request info about XXXX.
				 *     QXXXX=yyyy   Set value of XXXX to yyyy.
				 *     qOffsets     Get segment offsets
				 *
				 * Currently we only support the 'qOffsets'
				 * form.
				 *
				 * *Note* that we actually have to lie,
				 * At first thought looks like we should
				 * return '_start', '__data_start' &
				 * '__bss_start', however gdb gets
				 * confused because the kernel link script
				 * pre-links at 0x80000000. To keep gdb
				 * gdb happy we just substract that amount.
				 */
				if(strcmp(cmd+1, "Offsets")== 0) {
					gdb_reply(
						"Text=%x;Data=%x;Bss=%x",
						0,
						((unsigned)(&__data_start))-0x80000000,
						((unsigned)(&__bss_start))-0x80000000
					);
				} else {
					gdb_reply("ENS");
				}
			} break;

		case '?':
			{
				/*
				 * command '?' is used for retrieving the signal
				 * that stopped the program. Fully implemeting
				 * this command requires help from the debugger,
				 * by now we just fake a SIGKILL
				 */
				gdb_reply("S09");	/* SIGKILL = 9 */
			} break;

		case 'g':
			{
				int cpu;

				/*
				 * command 'g' is used for reading the register
				 * file. Faked by now.
				 *
				 * For x86 the register order is:
				 *
				 *    eax, ebx, ecx, edx,
				 *    esp, ebp, esi, edi,
				 *    eip, eflags,
				 *    cs, ss, ds, es
				 *
				 * Note that even thought the segment descriptors
				 * are actually 16 bits wide, gdb requires them
				 * as 32 bit integers. Note also that for some
				 * reason (unknown to me) gdb wants the register
				 * dump in *big endian* format.
				 */
				cpu= smp_get_current_cpu();
				gdb_regreply(dbg_register_file[cpu], 14);
			} break;

		case 'm':
			{
				char *ptr;
				unsigned address;
				unsigned len;

				/*
				 * The 'm' command has the form mAAA,LLL
				 * where AAA is the address and LLL is the
				 * number of bytes.
				 */
				ptr= cmd+1;
				address= 0;
				len= 0;
				while(ptr && *ptr && (*ptr!= ',')) {
					address<<= 4;
					address+= parse_nibble(*ptr);
					ptr+= 1;
				}
				if(*ptr== ',') {
					ptr+= 1;
				}
				while(ptr && *ptr) {
					len<<= 4;
					len+= parse_nibble(*ptr);
					ptr+= 1;
				}

				if(len> 128) {
					len= 128;
				}

				/*
				 * We cannot directly access the requested memory
				 * for gdb may be trying to access an stray pointer
				 * We copy the memory to a safe buffer using
				 * the bulletproof user_memcpy().
				 */
				if(user_memcpy(safe_mem, (char*)address, len)< 0) {
					gdb_reply("E02");
				} else {
					gdb_memreply(safe_mem, len);
				}
			} break;

		case 'k':
			{
				/*
				 * Command 'k' actual semantics is 'kill the damn thing'.
				 * However gdb sends that command when you disconnect
				 * from a debug session. I guess that 'kill' for the
				 * kernel would map to reboot... however that's a
				 * a very mean thing to do, instead we just quit
				 * the gdb state machine and fallback to the regular
				 * kernel debugger command prompt.
				 */
				return QUIT;
			} break;

		default:
			{
				gdb_reply("E01");
			} break;
	}

	return WAITACK;
}



/*
 * GDB protocol state machine
 */
static
int
gdb_init_handler(int input)
{
	switch(input) {
		case '$':
			memset(cmd, 0, sizeof(cmd));
			cmd_ptr= 0;
			return CMDREAD;
		default:
#if 0
			gdb_nak();
#else
			/*
			 * looks to me like we should send
			 * a NAK here but it kinda works
			 * better if we just gobble all
			 * junk chars silently
			 */
#endif
			return INIT;
	}
}

static
int
gdb_cmdread_handler(int input)
{
	switch(input) {
		case '#':
			return CKSUM1;
		default:
			cmd[cmd_ptr]= input;
			cmd_ptr+= 1;
			return CMDREAD;
	}
}

static
int
gdb_cksum1_handler(int input)
{
	int nibble= parse_nibble(input);

	if(nibble== 0xff) {
#if 0
		gdb_nak();
		return INIT;
#else
		/*
		 * looks to me like we should send
		 * a NAK here but it kinda works
		 * better if we just gobble all
		 * junk chars silently
		 */
#endif
	}

	checksum= nibble<< 4;

	return CKSUM2;
}

static
int
gdb_cksum2_handler(int input)
{
	int nibble= parse_nibble(input);

	if(nibble== 0xff) {
#if 0
		gdb_nak();
		return INIT;
#else
		/*
		 * looks to me like we should send
		 * a NAK here but it kinda works
		 * better if we just gobble all
		 * junk chars silently
		 */
#endif
	}

	checksum+= nibble;

	return gdb_parse_command();
}

static
int
gdb_waitack_handler(int input)
{
	switch(input) {
		case '+':
			return INIT;
		case '-':
			gdb_resend_reply();
			return WAITACK;
		default:
			/*
			 * looks like gdb and us are out of synch,
			 * send a NAK and retry from INIT state.
			 */
			gdb_nak();
			return INIT;
	}
}

static
int
gdb_quit_handler(int input)
{
	(void)(input);

	/*
	 * actually we should never be here
	 */
	return QUIT;
}

static int (*dispatch_table[GDBSTATES])(int)=
{
	&gdb_init_handler,
	&gdb_cmdread_handler,
	&gdb_cksum1_handler,
	&gdb_cksum2_handler,
	&gdb_waitack_handler,
	&gdb_quit_handler
};

static
int
gdb_state_dispatch(int curr, int input)
{
	if(curr< INIT) {
		return QUIT;
	}
	if(curr>= GDBSTATES) {
		return QUIT;
	}

	return dispatch_table[curr](input);
}

static
int
gdb_state_machine(void)
{
	int state= INIT;
	int c;

	while(state!= QUIT) {
		c= arch_dbg_con_read();
		state= gdb_state_dispatch(state, c);
	}

	return 0;
}

int
cmd_gdb(int argc, char **argv)
{
	(void)(argc);
	(void)(argv);

	return gdb_state_machine();
}
