aboutsummaryrefslogtreecommitdiff
path: root/common/kgdb.c
diff options
context:
space:
mode:
Diffstat (limited to 'common/kgdb.c')
-rw-r--r--common/kgdb.c580
1 files changed, 580 insertions, 0 deletions
diff --git a/common/kgdb.c b/common/kgdb.c
new file mode 100644
index 000000000..b563094d5
--- /dev/null
+++ b/common/kgdb.c
@@ -0,0 +1,580 @@
+/* taken from arch/ppc/kernel/ppc-stub.c */
+
+/****************************************************************************
+
+ THIS SOFTWARE IS NOT COPYRIGHTED
+
+ HP offers the following for use in the public domain. HP makes no
+ warranty with regard to the software or its performance and the
+ user accepts the software "AS IS" with all faults.
+
+ HP DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD
+ TO THIS SOFTWARE INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+****************************************************************************/
+
+/****************************************************************************
+ * Header: remcom.c,v 1.34 91/03/09 12:29:49 glenne Exp $
+ *
+ * Module name: remcom.c $
+ * Revision: 1.34 $
+ * Date: 91/03/09 12:29:49 $
+ * Contributor: Lake Stevens Instrument Division$
+ *
+ * Description: low level support for gdb debugger. $
+ *
+ * Considerations: only works on target hardware $
+ *
+ * Written by: Glenn Engel $
+ * ModuleState: Experimental $
+ *
+ * NOTES: See Below $
+ *
+ * Modified for SPARC by Stu Grossman, Cygnus Support.
+ *
+ * This code has been extensively tested on the Fujitsu SPARClite demo board.
+ *
+ * To enable debugger support, two things need to happen. One, a
+ * call to set_debug_traps() is necessary in order to allow any breakpoints
+ * or error conditions to be properly intercepted and reported to gdb.
+ * Two, a breakpoint needs to be generated to begin communication. This
+ * is most easily accomplished by a call to breakpoint(). Breakpoint()
+ * simulates a breakpoint by executing a trap #1.
+ *
+ *************
+ *
+ * The following gdb commands are supported:
+ *
+ * command function Return value
+ *
+ * g return the value of the CPU registers hex data or ENN
+ * G set the value of the CPU registers OK or ENN
+ * qOffsets Get section offsets. Reply is Text=xxx;Data=yyy;Bss=zzz
+ *
+ * mAA..AA,LLLL Read LLLL bytes at address AA..AA hex data or ENN
+ * MAA..AA,LLLL: Write LLLL bytes at address AA.AA OK or ENN
+ *
+ * c Resume at current address SNN ( signal NN)
+ * cAA..AA Continue at address AA..AA SNN
+ *
+ * s Step one instruction SNN
+ * sAA..AA Step one instruction from AA..AA SNN
+ *
+ * k kill
+ *
+ * ? What was the last sigval ? SNN (signal NN)
+ *
+ * bBB..BB Set baud rate to BB..BB OK or BNN, then sets
+ * baud rate
+ *
+ * All commands and responses are sent with a packet which includes a
+ * checksum. A packet consists of
+ *
+ * $<packet info>#<checksum>.
+ *
+ * where
+ * <packet info> :: <characters representing the command or response>
+ * <checksum> :: <two hex digits computed as modulo 256 sum of <packetinfo>>
+ *
+ * When a packet is received, it is first acknowledged with either '+' or '-'.
+ * '+' indicates a successful transfer. '-' indicates a failed transfer.
+ *
+ * Example:
+ *
+ * Host: Reply:
+ * $m0,10#2a +$00010203040506070809101112131415#42
+ *
+ ****************************************************************************/
+
+#include <common.h>
+
+#include <kgdb.h>
+#include <command.h>
+
+#if (CONFIG_COMMANDS & CFG_CMD_KGDB)
+
+#undef KGDB_DEBUG
+
+/*
+ * BUFMAX defines the maximum number of characters in inbound/outbound buffers
+ */
+#define BUFMAX 1024
+static char remcomInBuffer[BUFMAX];
+static char remcomOutBuffer[BUFMAX];
+static char remcomRegBuffer[BUFMAX];
+
+static int initialized = 0;
+static int kgdb_active = 0, first_entry = 1;
+static struct pt_regs entry_regs;
+static u_int error_jmp_buf[BUFMAX/2];
+static int longjmp_on_fault = 0;
+#ifdef KGDB_DEBUG
+static int kdebug = 1;
+#endif
+
+static const char hexchars[]="0123456789abcdef";
+
+/* Convert ch from a hex digit to an int */
+static int
+hex(unsigned char ch)
+{
+ if (ch >= 'a' && ch <= 'f')
+ return ch-'a'+10;
+ if (ch >= '0' && ch <= '9')
+ return ch-'0';
+ if (ch >= 'A' && ch <= 'F')
+ return ch-'A'+10;
+ return -1;
+}
+
+/* Convert the memory pointed to by mem into hex, placing result in buf.
+ * Return a pointer to the last char put in buf (null).
+ */
+static unsigned char *
+mem2hex(char *mem, char *buf, int count)
+{
+ unsigned char ch;
+
+ longjmp_on_fault = 1;
+ while (count-- > 0) {
+ ch = *mem++;
+ *buf++ = hexchars[ch >> 4];
+ *buf++ = hexchars[ch & 0xf];
+ }
+ *buf = 0;
+ longjmp_on_fault = 0;
+ return buf;
+}
+
+/* convert the hex array pointed to by buf into binary to be placed in mem
+ * return a pointer to the character AFTER the last byte fetched from buf.
+*/
+static char *
+hex2mem(char *buf, char *mem, int count)
+{
+ int i, hexValue;
+ unsigned char ch;
+ char *mem_start = mem;
+
+ longjmp_on_fault = 1;
+ for (i=0; i<count; i++) {
+ if ((hexValue = hex(*buf++)) < 0)
+ kgdb_error(KGDBERR_NOTHEXDIG);
+ ch = hexValue << 4;
+ if ((hexValue = hex(*buf++)) < 0)
+ kgdb_error(KGDBERR_NOTHEXDIG);
+ ch |= hexValue;
+ *mem++ = ch;
+ }
+ kgdb_flush_cache_range((void *)mem_start, (void *)(mem - 1));
+ longjmp_on_fault = 0;
+
+ return buf;
+}
+
+/*
+ * While we find nice hex chars, build an int.
+ * Return number of chars processed.
+ */
+static int
+hexToInt(char **ptr, int *intValue)
+{
+ int numChars = 0;
+ int hexValue;
+
+ *intValue = 0;
+
+ longjmp_on_fault = 1;
+ while (**ptr) {
+ hexValue = hex(**ptr);
+ if (hexValue < 0)
+ break;
+
+ *intValue = (*intValue << 4) | hexValue;
+ numChars ++;
+
+ (*ptr)++;
+ }
+ longjmp_on_fault = 0;
+
+ return (numChars);
+}
+
+/* scan for the sequence $<data>#<checksum> */
+static void
+getpacket(char *buffer)
+{
+ unsigned char checksum;
+ unsigned char xmitcsum;
+ int i;
+ int count;
+ unsigned char ch;
+
+ do {
+ /* wait around for the start character, ignore all other
+ * characters */
+ while ((ch = (getDebugChar() & 0x7f)) != '$') {
+#ifdef KGDB_DEBUG
+ if (kdebug)
+ putc(ch);
+#endif
+ ;
+ }
+
+ checksum = 0;
+ xmitcsum = -1;
+
+ count = 0;
+
+ /* now, read until a # or end of buffer is found */
+ while (count < BUFMAX) {
+ ch = getDebugChar() & 0x7f;
+ if (ch == '#')
+ break;
+ checksum = checksum + ch;
+ buffer[count] = ch;
+ count = count + 1;
+ }
+
+ if (count >= BUFMAX)
+ continue;
+
+ buffer[count] = 0;
+
+ if (ch == '#') {
+ xmitcsum = hex(getDebugChar() & 0x7f) << 4;
+ xmitcsum |= hex(getDebugChar() & 0x7f);
+ if (checksum != xmitcsum)
+ putDebugChar('-'); /* failed checksum */
+ else {
+ putDebugChar('+'); /* successful transfer */
+ /* if a sequence char is present, reply the ID */
+ if (buffer[2] == ':') {
+ putDebugChar(buffer[0]);
+ putDebugChar(buffer[1]);
+ /* remove sequence chars from buffer */
+ count = strlen(buffer);
+ for (i=3; i <= count; i++)
+ buffer[i-3] = buffer[i];
+ }
+ }
+ }
+ } while (checksum != xmitcsum);
+}
+
+/* send the packet in buffer. */
+static void
+putpacket(unsigned char *buffer)
+{
+ unsigned char checksum;
+ int count;
+ unsigned char ch, recv;
+
+ /* $<packet info>#<checksum>. */
+ do {
+ putDebugChar('$');
+ checksum = 0;
+ count = 0;
+
+ while ((ch = buffer[count])) {
+ putDebugChar(ch);
+ checksum += ch;
+ count += 1;
+ }
+
+ putDebugChar('#');
+ putDebugChar(hexchars[checksum >> 4]);
+ putDebugChar(hexchars[checksum & 0xf]);
+ recv = getDebugChar();
+ } while ((recv & 0x7f) != '+');
+}
+
+/*
+ * This function does all command processing for interfacing to gdb.
+ */
+static int
+handle_exception (struct pt_regs *regs)
+{
+ int addr;
+ int length;
+ char *ptr;
+ kgdb_data kd;
+ int i;
+
+ if (!initialized) {
+ printf("kgdb: exception before kgdb is initialized! huh?\n");
+ return (0);
+ }
+
+ /* probably should check which exception occured as well */
+ if (longjmp_on_fault) {
+ longjmp_on_fault = 0;
+ kgdb_longjmp((long*)error_jmp_buf, KGDBERR_MEMFAULT);
+ panic("kgdb longjump failed!\n");
+ }
+
+ if (kgdb_active) {
+ printf("kgdb: unexpected exception from within kgdb\n");
+ return (0);
+ }
+ kgdb_active = 1;
+
+ kgdb_interruptible(0);
+
+ printf("kgdb: handle_exception; trap [0x%x]\n", kgdb_trap(regs));
+
+ if (kgdb_setjmp((long*)error_jmp_buf) != 0)
+ panic("kgdb: error or fault in entry init!\n");
+
+ kgdb_enter(regs, &kd);
+
+ if (first_entry) {
+ /*
+ * the first time we enter kgdb, we save the processor
+ * state so that we can return to the monitor if the
+ * remote end quits gdb (or at least, tells us to quit
+ * with the 'k' packet)
+ */
+ entry_regs = *regs;
+ first_entry = 0;
+ }
+
+ ptr = remcomOutBuffer;
+
+ *ptr++ = 'T';
+
+ *ptr++ = hexchars[kd.sigval >> 4];
+ *ptr++ = hexchars[kd.sigval & 0xf];
+
+ for (i = 0; i < kd.nregs; i++) {
+ kgdb_reg *rp = &kd.regs[i];
+
+ *ptr++ = hexchars[rp->num >> 4];
+ *ptr++ = hexchars[rp->num & 0xf];
+ *ptr++ = ':';
+ ptr = mem2hex((char *)&rp->val, ptr, 4);
+ *ptr++ = ';';
+ }
+
+ *ptr = 0;
+
+#ifdef KGDB_DEBUG
+ if (kdebug)
+ printf("kgdb: remcomOutBuffer: %s\n", remcomOutBuffer);
+#endif
+
+ putpacket(remcomOutBuffer);
+
+ while (1) {
+ volatile int errnum;
+
+ remcomOutBuffer[0] = 0;
+
+ getpacket(remcomInBuffer);
+ ptr = &remcomInBuffer[1];
+
+#ifdef KGDB_DEBUG
+ if (kdebug)
+ printf("kgdb: remcomInBuffer: %s\n", remcomInBuffer);
+#endif
+
+ errnum = kgdb_setjmp((long*)error_jmp_buf);
+
+ if (errnum == 0) switch (remcomInBuffer[0]) {
+
+ case '?': /* report most recent signal */
+ remcomOutBuffer[0] = 'S';
+ remcomOutBuffer[1] = hexchars[kd.sigval >> 4];
+ remcomOutBuffer[2] = hexchars[kd.sigval & 0xf];
+ remcomOutBuffer[3] = 0;
+ break;
+
+#ifdef KGDB_DEBUG
+ case 'd':
+ /* toggle debug flag */
+ kdebug ^= 1;
+ break;
+#endif
+
+ case 'g': /* return the value of the CPU registers. */
+ length = kgdb_getregs(regs, remcomRegBuffer, BUFMAX);
+ mem2hex(remcomRegBuffer, remcomOutBuffer, length);
+ break;
+
+ case 'G': /* set the value of the CPU registers */
+ length = strlen(ptr);
+ if ((length & 1) != 0) kgdb_error(KGDBERR_BADPARAMS);
+ hex2mem(ptr, remcomRegBuffer, length/2);
+ kgdb_putregs(regs, remcomRegBuffer, length/2);
+ strcpy(remcomOutBuffer,"OK");
+ break;
+
+ case 'm': /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */
+ /* Try to read %x,%x. */
+
+ if (hexToInt(&ptr, &addr)
+ && *ptr++ == ','
+ && hexToInt(&ptr, &length)) {
+ mem2hex((char *)addr, remcomOutBuffer, length);
+ } else {
+ kgdb_error(KGDBERR_BADPARAMS);
+ }
+ break;
+
+ case 'M': /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */
+ /* Try to read '%x,%x:'. */
+
+ if (hexToInt(&ptr, &addr)
+ && *ptr++ == ','
+ && hexToInt(&ptr, &length)
+ && *ptr++ == ':') {
+ hex2mem(ptr, (char *)addr, length);
+ strcpy(remcomOutBuffer, "OK");
+ } else {
+ kgdb_error(KGDBERR_BADPARAMS);
+ }
+ break;
+
+
+ case 'k': /* kill the program, actually return to monitor */
+ kd.extype = KGDBEXIT_KILL;
+ *regs = entry_regs;
+ first_entry = 1;
+ goto doexit;
+
+ case 'C': /* CSS continue with signal SS */
+ *ptr = '\0'; /* ignore the signal number for now */
+ /* fall through */
+
+ case 'c': /* cAA..AA Continue; address AA..AA optional */
+ /* try to read optional parameter, pc unchanged if no parm */
+ kd.extype = KGDBEXIT_CONTINUE;
+
+ if (hexToInt(&ptr, &addr)) {
+ kd.exaddr = addr;
+ kd.extype |= KGDBEXIT_WITHADDR;
+ }
+
+ goto doexit;
+
+ case 'S': /* SSS single step with signal SS */
+ *ptr = '\0'; /* ignore the signal number for now */
+ /* fall through */
+
+ case 's':
+ kd.extype = KGDBEXIT_SINGLE;
+
+ if (hexToInt(&ptr, &addr)) {
+ kd.exaddr = addr;
+ kd.extype |= KGDBEXIT_WITHADDR;
+ }
+
+ doexit:
+/* Need to flush the instruction cache here, as we may have deposited a
+ * breakpoint, and the icache probably has no way of knowing that a data ref to
+ * some location may have changed something that is in the instruction cache.
+ */
+ kgdb_flush_cache_all();
+ kgdb_exit(regs, &kd);
+ kgdb_active = 0;
+ kgdb_interruptible(1);
+ return (1);
+
+ case 'r': /* Reset (if user process..exit ???)*/
+ panic("kgdb reset.");
+ break;
+
+ case 'P': /* Pr=v set reg r to value v (r and v are hex) */
+ if (hexToInt(&ptr, &addr)
+ && *ptr++ == '='
+ && ((length = strlen(ptr)) & 1) == 0) {
+ hex2mem(ptr, remcomRegBuffer, length/2);
+ kgdb_putreg(regs, addr,
+ remcomRegBuffer, length/2);
+ strcpy(remcomOutBuffer,"OK");
+ } else {
+ kgdb_error(KGDBERR_BADPARAMS);
+ }
+ break;
+ } /* switch */
+
+ if (errnum != 0)
+ sprintf(remcomOutBuffer, "E%02d", errnum);
+
+#ifdef KGDB_DEBUG
+ if (kdebug)
+ printf("kgdb: remcomOutBuffer: %s\n", remcomOutBuffer);
+#endif
+
+ /* reply to the request */
+ putpacket(remcomOutBuffer);
+
+ } /* while(1) */
+}
+
+/*
+ * kgdb_init must be called *after* the
+ * monitor is relocated into ram
+ */
+void
+kgdb_init(void)
+{
+ kgdb_serial_init();
+ debugger_exception_handler = handle_exception;
+ initialized = 1;
+
+ putDebugStr("kgdb ready\n");
+ puts("ready\n");
+}
+
+void
+kgdb_error(int errnum)
+{
+ longjmp_on_fault = 0;
+ kgdb_longjmp((long*)error_jmp_buf, errnum);
+ panic("kgdb_error: longjmp failed!\n");
+}
+
+/* Output string in GDB O-packet format if GDB has connected. If nothing
+ output, returns 0 (caller must then handle output). */
+int
+kgdb_output_string (const char* s, unsigned int count)
+{
+ char buffer[512];
+
+ count = (count <= (sizeof(buffer) / 2 - 2))
+ ? count : (sizeof(buffer) / 2 - 2);
+
+ buffer[0] = 'O';
+ mem2hex ((char *)s, &buffer[1], count);
+ putpacket(buffer);
+
+ return 1;
+}
+
+void
+breakpoint(void)
+{
+ if (!initialized) {
+ printf("breakpoint() called b4 kgdb init\n");
+ return;
+ }
+
+ kgdb_breakpoint(0, 0);
+}
+
+int
+do_kgdb(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
+{
+ printf("Entering KGDB mode via exception handler...\n\n");
+ kgdb_breakpoint(argc - 1, argv + 1);
+ printf("\nReturned from KGDB mode\n");
+ return 0;
+}
+
+#else
+
+int kgdb_not_configured = 1;
+
+#endif /* CFG_CMD_KGDB */