| // SPDX-License-Identifier: GPL-2.0 | |
| /* MN10300 GDB stub | |
| * | |
| * Originally written by Glenn Engel, Lake Stevens Instrument Division | |
| * | |
| * Contributed by HP Systems | |
| * | |
| * Modified for SPARC by Stu Grossman, Cygnus Support. | |
| * | |
| * Modified for Linux/MIPS (and MIPS in general) by Andreas Busse | |
| * Send complaints, suggestions etc. to <andy@waldorf-gmbh.de> | |
| * | |
| * Copyright (C) 1995 Andreas Busse | |
| * | |
| * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. | |
| * Modified for Linux/mn10300 by David Howells <dhowells@redhat.com> | |
| */ | |
| /* | |
| * 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 BREAK instruction. | |
| * | |
| * | |
| * 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 | |
| * | |
| * 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 | |
| * | |
| * | |
| * ============== | |
| * MORE EXAMPLES: | |
| * ============== | |
| * | |
| * For reference -- the following are the steps that one | |
| * company took (RidgeRun Inc) to get remote gdb debugging | |
| * going. In this scenario the host machine was a PC and the | |
| * target platform was a Galileo EVB64120A MIPS evaluation | |
| * board. | |
| * | |
| * Step 1: | |
| * First download gdb-5.0.tar.gz from the internet. | |
| * and then build/install the package. | |
| * | |
| * Example: | |
| * $ tar zxf gdb-5.0.tar.gz | |
| * $ cd gdb-5.0 | |
| * $ ./configure --target=am33_2.0-linux-gnu | |
| * $ make | |
| * $ install | |
| * am33_2.0-linux-gnu-gdb | |
| * | |
| * Step 2: | |
| * Configure linux for remote debugging and build it. | |
| * | |
| * Example: | |
| * $ cd ~/linux | |
| * $ make menuconfig <go to "Kernel Hacking" and turn on remote debugging> | |
| * $ make dep; make vmlinux | |
| * | |
| * Step 3: | |
| * Download the kernel to the remote target and start | |
| * the kernel running. It will promptly halt and wait | |
| * for the host gdb session to connect. It does this | |
| * since the "Kernel Hacking" option has defined | |
| * CONFIG_REMOTE_DEBUG which in turn enables your calls | |
| * to: | |
| * set_debug_traps(); | |
| * breakpoint(); | |
| * | |
| * Step 4: | |
| * Start the gdb session on the host. | |
| * | |
| * Example: | |
| * $ am33_2.0-linux-gnu-gdb vmlinux | |
| * (gdb) set remotebaud 115200 | |
| * (gdb) target remote /dev/ttyS1 | |
| * ...at this point you are connected to | |
| * the remote target and can use gdb | |
| * in the normal fasion. Setting | |
| * breakpoints, single stepping, | |
| * printing variables, etc. | |
| * | |
| */ | |
| #include <linux/string.h> | |
| #include <linux/kernel.h> | |
| #include <linux/signal.h> | |
| #include <linux/sched.h> | |
| #include <linux/mm.h> | |
| #include <linux/console.h> | |
| #include <linux/init.h> | |
| #include <linux/bug.h> | |
| #include <asm/pgtable.h> | |
| #include <asm/gdb-stub.h> | |
| #include <asm/exceptions.h> | |
| #include <asm/debugger.h> | |
| #include <asm/serial-regs.h> | |
| #include <asm/busctl-regs.h> | |
| #include <unit/leds.h> | |
| #include <unit/serial.h> | |
| /* define to use F7F7 rather than FF which is subverted by JTAG debugger */ | |
| #undef GDBSTUB_USE_F7F7_AS_BREAKPOINT | |
| /* | |
| * BUFMAX defines the maximum number of characters in inbound/outbound buffers | |
| * at least NUMREGBYTES*2 are needed for register packets | |
| */ | |
| #define BUFMAX 2048 | |
| static const char gdbstub_banner[] = | |
| "Linux/MN10300 GDB Stub (c) RedHat 2007\n"; | |
| u8 gdbstub_rx_buffer[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); | |
| u32 gdbstub_rx_inp; | |
| u32 gdbstub_rx_outp; | |
| u8 gdbstub_busy; | |
| u8 gdbstub_rx_overflow; | |
| u8 gdbstub_rx_unget; | |
| static u8 gdbstub_flush_caches; | |
| static char input_buffer[BUFMAX]; | |
| static char output_buffer[BUFMAX]; | |
| static char trans_buffer[BUFMAX]; | |
| struct gdbstub_bkpt { | |
| u8 *addr; /* address of breakpoint */ | |
| u8 len; /* size of breakpoint */ | |
| u8 origbytes[7]; /* original bytes */ | |
| }; | |
| static struct gdbstub_bkpt gdbstub_bkpts[256]; | |
| /* | |
| * local prototypes | |
| */ | |
| static void getpacket(char *buffer); | |
| static int putpacket(char *buffer); | |
| static int computeSignal(enum exception_code excep); | |
| static int hex(unsigned char ch); | |
| static int hexToInt(char **ptr, int *intValue); | |
| static unsigned char *mem2hex(const void *mem, char *buf, int count, | |
| int may_fault); | |
| static const char *hex2mem(const char *buf, void *_mem, int count, | |
| int may_fault); | |
| /* | |
| * 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; | |
| } | |
| #ifdef CONFIG_GDBSTUB_DEBUGGING | |
| void debug_to_serial(const char *p, int n) | |
| { | |
| __debug_to_serial(p, n); | |
| /* gdbstub_console_write(NULL, p, n); */ | |
| } | |
| void gdbstub_printk(const char *fmt, ...) | |
| { | |
| va_list args; | |
| int len; | |
| /* Emit the output into the temporary buffer */ | |
| va_start(args, fmt); | |
| len = vsnprintf(trans_buffer, sizeof(trans_buffer), fmt, args); | |
| va_end(args); | |
| debug_to_serial(trans_buffer, len); | |
| } | |
| #endif | |
| static inline char *gdbstub_strcpy(char *dst, const char *src) | |
| { | |
| int loop = 0; | |
| while ((dst[loop] = src[loop])) | |
| loop++; | |
| return dst; | |
| } | |
| /* | |
| * scan for the sequence $<data>#<checksum> | |
| */ | |
| static void getpacket(char *buffer) | |
| { | |
| unsigned char checksum; | |
| unsigned char xmitcsum; | |
| unsigned char ch; | |
| int count, i, ret, error; | |
| for (;;) { | |
| /* | |
| * wait around for the start character, | |
| * ignore all other characters | |
| */ | |
| do { | |
| gdbstub_io_rx_char(&ch, 0); | |
| } while (ch != '$'); | |
| checksum = 0; | |
| xmitcsum = -1; | |
| count = 0; | |
| error = 0; | |
| /* | |
| * now, read until a # or end of buffer is found | |
| */ | |
| while (count < BUFMAX) { | |
| ret = gdbstub_io_rx_char(&ch, 0); | |
| if (ret < 0) | |
| error = ret; | |
| if (ch == '#') | |
| break; | |
| checksum += ch; | |
| buffer[count] = ch; | |
| count++; | |
| } | |
| if (error == -EIO) { | |
| gdbstub_proto("### GDB Rx Error - Skipping packet" | |
| " ###\n"); | |
| gdbstub_proto("### GDB Tx NAK\n"); | |
| gdbstub_io_tx_char('-'); | |
| continue; | |
| } | |
| if (count >= BUFMAX || error) | |
| continue; | |
| buffer[count] = 0; | |
| /* read the checksum */ | |
| ret = gdbstub_io_rx_char(&ch, 0); | |
| if (ret < 0) | |
| error = ret; | |
| xmitcsum = hex(ch) << 4; | |
| ret = gdbstub_io_rx_char(&ch, 0); | |
| if (ret < 0) | |
| error = ret; | |
| xmitcsum |= hex(ch); | |
| if (error) { | |
| if (error == -EIO) | |
| gdbstub_io("### GDB Rx Error -" | |
| " Skipping packet\n"); | |
| gdbstub_io("### GDB Tx NAK\n"); | |
| gdbstub_io_tx_char('-'); | |
| continue; | |
| } | |
| /* check the checksum */ | |
| if (checksum != xmitcsum) { | |
| gdbstub_io("### GDB Tx NAK\n"); | |
| gdbstub_io_tx_char('-'); /* failed checksum */ | |
| continue; | |
| } | |
| gdbstub_proto("### GDB Rx '$%s#%02x' ###\n", buffer, checksum); | |
| gdbstub_io("### GDB Tx ACK\n"); | |
| gdbstub_io_tx_char('+'); /* successful transfer */ | |
| /* | |
| * if a sequence char is present, | |
| * reply the sequence ID | |
| */ | |
| if (buffer[2] == ':') { | |
| gdbstub_io_tx_char(buffer[0]); | |
| gdbstub_io_tx_char(buffer[1]); | |
| /* | |
| * remove sequence chars from buffer | |
| */ | |
| count = 0; | |
| while (buffer[count]) | |
| count++; | |
| for (i = 3; i <= count; i++) | |
| buffer[i - 3] = buffer[i]; | |
| } | |
| break; | |
| } | |
| } | |
| /* | |
| * send the packet in buffer. | |
| * - return 0 if successfully ACK'd | |
| * - return 1 if abandoned due to new incoming packet | |
| */ | |
| static int putpacket(char *buffer) | |
| { | |
| unsigned char checksum; | |
| unsigned char ch; | |
| int count; | |
| /* | |
| * $<packet info>#<checksum>. | |
| */ | |
| gdbstub_proto("### GDB Tx $'%s'#?? ###\n", buffer); | |
| do { | |
| gdbstub_io_tx_char('$'); | |
| checksum = 0; | |
| count = 0; | |
| while ((ch = buffer[count]) != 0) { | |
| gdbstub_io_tx_char(ch); | |
| checksum += ch; | |
| count += 1; | |
| } | |
| gdbstub_io_tx_char('#'); | |
| gdbstub_io_tx_char(hex_asc_hi(checksum)); | |
| gdbstub_io_tx_char(hex_asc_lo(checksum)); | |
| } while (gdbstub_io_rx_char(&ch, 0), | |
| ch == '-' && (gdbstub_io("### GDB Rx NAK\n"), 0), | |
| ch != '-' && ch != '+' && | |
| (gdbstub_io("### GDB Rx ??? %02x\n", ch), 0), | |
| ch != '+' && ch != '$'); | |
| if (ch == '+') { | |
| gdbstub_io("### GDB Rx ACK\n"); | |
| return 0; | |
| } | |
| gdbstub_io("### GDB Tx Abandoned\n"); | |
| gdbstub_rx_unget = ch; | |
| return 1; | |
| } | |
| /* | |
| * 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; | |
| while (**ptr) { | |
| hexValue = hex(**ptr); | |
| if (hexValue < 0) | |
| break; | |
| *intValue = (*intValue << 4) | hexValue; | |
| numChars++; | |
| (*ptr)++; | |
| } | |
| return (numChars); | |
| } | |
| #ifdef CONFIG_GDBSTUB_ALLOW_SINGLE_STEP | |
| /* | |
| * We single-step by setting breakpoints. When an exception | |
| * is handled, we need to restore the instructions hoisted | |
| * when the breakpoints were set. | |
| * | |
| * This is where we save the original instructions. | |
| */ | |
| static struct gdb_bp_save { | |
| u8 *addr; | |
| u8 opcode[2]; | |
| } step_bp[2]; | |
| static const unsigned char gdbstub_insn_sizes[256] = | |
| { | |
| /* 1 2 3 4 5 6 7 8 9 a b c d e f */ | |
| 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, 1, 3, 3, 3, /* 0 */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 1 */ | |
| 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 3, 3, /* 2 */ | |
| 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, /* 3 */ | |
| 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 2, 2, /* 4 */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, /* 5 */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ | |
| 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 8 */ | |
| 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* 9 */ | |
| 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* a */ | |
| 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 2, /* b */ | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 2, /* c */ | |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ | |
| 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ | |
| 0, 2, 2, 2, 2, 2, 2, 4, 0, 3, 0, 4, 0, 6, 7, 1 /* f */ | |
| }; | |
| static int __gdbstub_mark_bp(u8 *addr, int ix) | |
| { | |
| /* vmalloc area */ | |
| if (((u8 *) VMALLOC_START <= addr) && (addr < (u8 *) VMALLOC_END)) | |
| goto okay; | |
| /* SRAM, SDRAM */ | |
| if (((u8 *) 0x80000000UL <= addr) && (addr < (u8 *) 0xa0000000UL)) | |
| goto okay; | |
| return 0; | |
| okay: | |
| if (gdbstub_read_byte(addr + 0, &step_bp[ix].opcode[0]) < 0 || | |
| gdbstub_read_byte(addr + 1, &step_bp[ix].opcode[1]) < 0) | |
| return 0; | |
| step_bp[ix].addr = addr; | |
| return 1; | |
| } | |
| static inline void __gdbstub_restore_bp(void) | |
| { | |
| #ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT | |
| if (step_bp[0].addr) { | |
| gdbstub_write_byte(step_bp[0].opcode[0], step_bp[0].addr + 0); | |
| gdbstub_write_byte(step_bp[0].opcode[1], step_bp[0].addr + 1); | |
| } | |
| if (step_bp[1].addr) { | |
| gdbstub_write_byte(step_bp[1].opcode[0], step_bp[1].addr + 0); | |
| gdbstub_write_byte(step_bp[1].opcode[1], step_bp[1].addr + 1); | |
| } | |
| #else | |
| if (step_bp[0].addr) | |
| gdbstub_write_byte(step_bp[0].opcode[0], step_bp[0].addr + 0); | |
| if (step_bp[1].addr) | |
| gdbstub_write_byte(step_bp[1].opcode[0], step_bp[1].addr + 0); | |
| #endif | |
| gdbstub_flush_caches = 1; | |
| step_bp[0].addr = NULL; | |
| step_bp[0].opcode[0] = 0; | |
| step_bp[0].opcode[1] = 0; | |
| step_bp[1].addr = NULL; | |
| step_bp[1].opcode[0] = 0; | |
| step_bp[1].opcode[1] = 0; | |
| } | |
| /* | |
| * emulate single stepping by means of breakpoint instructions | |
| */ | |
| static int gdbstub_single_step(struct pt_regs *regs) | |
| { | |
| unsigned size; | |
| uint32_t x; | |
| uint8_t cur, *pc, *sp; | |
| step_bp[0].addr = NULL; | |
| step_bp[0].opcode[0] = 0; | |
| step_bp[0].opcode[1] = 0; | |
| step_bp[1].addr = NULL; | |
| step_bp[1].opcode[0] = 0; | |
| step_bp[1].opcode[1] = 0; | |
| x = 0; | |
| pc = (u8 *) regs->pc; | |
| sp = (u8 *) (regs + 1); | |
| if (gdbstub_read_byte(pc, &cur) < 0) | |
| return -EFAULT; | |
| gdbstub_bkpt("Single Step from %p { %02x }\n", pc, cur); | |
| gdbstub_flush_caches = 1; | |
| size = gdbstub_insn_sizes[cur]; | |
| if (size > 0) { | |
| if (!__gdbstub_mark_bp(pc + size, 0)) | |
| goto fault; | |
| } else { | |
| switch (cur) { | |
| /* Bxx (d8,PC) */ | |
| case 0xc0 ... 0xca: | |
| if (gdbstub_read_byte(pc + 1, (u8 *) &x) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp(pc + 2, 0)) | |
| goto fault; | |
| if ((x < 0 || x > 2) && | |
| !__gdbstub_mark_bp(pc + (s8) x, 1)) | |
| goto fault; | |
| break; | |
| /* LXX (d8,PC) */ | |
| case 0xd0 ... 0xda: | |
| if (!__gdbstub_mark_bp(pc + 1, 0)) | |
| goto fault; | |
| if (regs->pc != regs->lar && | |
| !__gdbstub_mark_bp((u8 *) regs->lar, 1)) | |
| goto fault; | |
| break; | |
| /* SETLB - loads the next for bytes into the LIR | |
| * register */ | |
| case 0xdb: | |
| if (!__gdbstub_mark_bp(pc + 1, 0)) | |
| goto fault; | |
| break; | |
| /* JMP (d16,PC) or CALL (d16,PC) */ | |
| case 0xcc: | |
| case 0xcd: | |
| if (gdbstub_read_byte(pc + 1, ((u8 *) &x) + 0) < 0 || | |
| gdbstub_read_byte(pc + 2, ((u8 *) &x) + 1) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp(pc + (s16) x, 0)) | |
| goto fault; | |
| break; | |
| /* JMP (d32,PC) or CALL (d32,PC) */ | |
| case 0xdc: | |
| case 0xdd: | |
| if (gdbstub_read_byte(pc + 1, ((u8 *) &x) + 0) < 0 || | |
| gdbstub_read_byte(pc + 2, ((u8 *) &x) + 1) < 0 || | |
| gdbstub_read_byte(pc + 3, ((u8 *) &x) + 2) < 0 || | |
| gdbstub_read_byte(pc + 4, ((u8 *) &x) + 3) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp(pc + (s32) x, 0)) | |
| goto fault; | |
| break; | |
| /* RETF */ | |
| case 0xde: | |
| if (!__gdbstub_mark_bp((u8 *) regs->mdr, 0)) | |
| goto fault; | |
| break; | |
| /* RET */ | |
| case 0xdf: | |
| if (gdbstub_read_byte(pc + 2, (u8 *) &x) < 0) | |
| goto fault; | |
| sp += (s8)x; | |
| if (gdbstub_read_byte(sp + 0, ((u8 *) &x) + 0) < 0 || | |
| gdbstub_read_byte(sp + 1, ((u8 *) &x) + 1) < 0 || | |
| gdbstub_read_byte(sp + 2, ((u8 *) &x) + 2) < 0 || | |
| gdbstub_read_byte(sp + 3, ((u8 *) &x) + 3) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp((u8 *) x, 0)) | |
| goto fault; | |
| break; | |
| case 0xf0: | |
| if (gdbstub_read_byte(pc + 1, &cur) < 0) | |
| goto fault; | |
| if (cur >= 0xf0 && cur <= 0xf7) { | |
| /* JMP (An) / CALLS (An) */ | |
| switch (cur & 3) { | |
| case 0: x = regs->a0; break; | |
| case 1: x = regs->a1; break; | |
| case 2: x = regs->a2; break; | |
| case 3: x = regs->a3; break; | |
| } | |
| if (!__gdbstub_mark_bp((u8 *) x, 0)) | |
| goto fault; | |
| } else if (cur == 0xfc) { | |
| /* RETS */ | |
| if (gdbstub_read_byte( | |
| sp + 0, ((u8 *) &x) + 0) < 0 || | |
| gdbstub_read_byte( | |
| sp + 1, ((u8 *) &x) + 1) < 0 || | |
| gdbstub_read_byte( | |
| sp + 2, ((u8 *) &x) + 2) < 0 || | |
| gdbstub_read_byte( | |
| sp + 3, ((u8 *) &x) + 3) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp((u8 *) x, 0)) | |
| goto fault; | |
| } else if (cur == 0xfd) { | |
| /* RTI */ | |
| if (gdbstub_read_byte( | |
| sp + 4, ((u8 *) &x) + 0) < 0 || | |
| gdbstub_read_byte( | |
| sp + 5, ((u8 *) &x) + 1) < 0 || | |
| gdbstub_read_byte( | |
| sp + 6, ((u8 *) &x) + 2) < 0 || | |
| gdbstub_read_byte( | |
| sp + 7, ((u8 *) &x) + 3) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp((u8 *) x, 0)) | |
| goto fault; | |
| } else { | |
| if (!__gdbstub_mark_bp(pc + 2, 0)) | |
| goto fault; | |
| } | |
| break; | |
| /* potential 3-byte conditional branches */ | |
| case 0xf8: | |
| if (gdbstub_read_byte(pc + 1, &cur) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp(pc + 3, 0)) | |
| goto fault; | |
| if (cur >= 0xe8 && cur <= 0xeb) { | |
| if (gdbstub_read_byte( | |
| pc + 2, ((u8 *) &x) + 0) < 0) | |
| goto fault; | |
| if ((x < 0 || x > 3) && | |
| !__gdbstub_mark_bp(pc + (s8) x, 1)) | |
| goto fault; | |
| } | |
| break; | |
| case 0xfa: | |
| if (gdbstub_read_byte(pc + 1, &cur) < 0) | |
| goto fault; | |
| if (cur == 0xff) { | |
| /* CALLS (d16,PC) */ | |
| if (gdbstub_read_byte( | |
| pc + 2, ((u8 *) &x) + 0) < 0 || | |
| gdbstub_read_byte( | |
| pc + 3, ((u8 *) &x) + 1) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp(pc + (s16) x, 0)) | |
| goto fault; | |
| } else { | |
| if (!__gdbstub_mark_bp(pc + 4, 0)) | |
| goto fault; | |
| } | |
| break; | |
| case 0xfc: | |
| if (gdbstub_read_byte(pc + 1, &cur) < 0) | |
| goto fault; | |
| if (cur == 0xff) { | |
| /* CALLS (d32,PC) */ | |
| if (gdbstub_read_byte( | |
| pc + 2, ((u8 *) &x) + 0) < 0 || | |
| gdbstub_read_byte( | |
| pc + 3, ((u8 *) &x) + 1) < 0 || | |
| gdbstub_read_byte( | |
| pc + 4, ((u8 *) &x) + 2) < 0 || | |
| gdbstub_read_byte( | |
| pc + 5, ((u8 *) &x) + 3) < 0) | |
| goto fault; | |
| if (!__gdbstub_mark_bp( | |
| pc + (s32) x, 0)) | |
| goto fault; | |
| } else { | |
| if (!__gdbstub_mark_bp( | |
| pc + 6, 0)) | |
| goto fault; | |
| } | |
| break; | |
| } | |
| } | |
| gdbstub_bkpt("Step: %02x at %p; %02x at %p\n", | |
| step_bp[0].opcode[0], step_bp[0].addr, | |
| step_bp[1].opcode[0], step_bp[1].addr); | |
| if (step_bp[0].addr) { | |
| #ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT | |
| if (gdbstub_write_byte(0xF7, step_bp[0].addr + 0) < 0 || | |
| gdbstub_write_byte(0xF7, step_bp[0].addr + 1) < 0) | |
| goto fault; | |
| #else | |
| if (gdbstub_write_byte(0xFF, step_bp[0].addr + 0) < 0) | |
| goto fault; | |
| #endif | |
| } | |
| if (step_bp[1].addr) { | |
| #ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT | |
| if (gdbstub_write_byte(0xF7, step_bp[1].addr + 0) < 0 || | |
| gdbstub_write_byte(0xF7, step_bp[1].addr + 1) < 0) | |
| goto fault; | |
| #else | |
| if (gdbstub_write_byte(0xFF, step_bp[1].addr + 0) < 0) | |
| goto fault; | |
| #endif | |
| } | |
| return 0; | |
| fault: | |
| /* uh-oh - silly address alert, try and restore things */ | |
| __gdbstub_restore_bp(); | |
| return -EFAULT; | |
| } | |
| #endif /* CONFIG_GDBSTUB_ALLOW_SINGLE_STEP */ | |
| #ifdef CONFIG_GDBSTUB_CONSOLE | |
| void gdbstub_console_write(struct console *con, const char *p, unsigned n) | |
| { | |
| static const char gdbstub_cr[] = { 0x0d }; | |
| char outbuf[26]; | |
| int qty; | |
| u8 busy; | |
| busy = gdbstub_busy; | |
| gdbstub_busy = 1; | |
| outbuf[0] = 'O'; | |
| while (n > 0) { | |
| qty = 1; | |
| while (n > 0 && qty < 20) { | |
| mem2hex(p, outbuf + qty, 2, 0); | |
| qty += 2; | |
| if (*p == 0x0a) { | |
| mem2hex(gdbstub_cr, outbuf + qty, 2, 0); | |
| qty += 2; | |
| } | |
| p++; | |
| n--; | |
| } | |
| outbuf[qty] = 0; | |
| putpacket(outbuf); | |
| } | |
| gdbstub_busy = busy; | |
| } | |
| static kdev_t gdbstub_console_dev(struct console *con) | |
| { | |
| return MKDEV(1, 3); /* /dev/null */ | |
| } | |
| static struct console gdbstub_console = { | |
| .name = "gdb", | |
| .write = gdbstub_console_write, | |
| .device = gdbstub_console_dev, | |
| .flags = CON_PRINTBUFFER, | |
| .index = -1, | |
| }; | |
| #endif | |
| /* | |
| * Convert the memory pointed to by mem into hex, placing result in buf. | |
| * - if successful, return a pointer to the last char put in buf (NUL) | |
| * - in case of mem fault, return NULL | |
| * may_fault is non-zero if we are reading from arbitrary memory, but is | |
| * currently not used. | |
| */ | |
| static | |
| unsigned char *mem2hex(const void *_mem, char *buf, int count, int may_fault) | |
| { | |
| const u8 *mem = _mem; | |
| u8 ch[4]; | |
| if ((u32) mem & 1 && count >= 1) { | |
| if (gdbstub_read_byte(mem, ch) != 0) | |
| return 0; | |
| buf = hex_byte_pack(buf, ch[0]); | |
| mem++; | |
| count--; | |
| } | |
| if ((u32) mem & 3 && count >= 2) { | |
| if (gdbstub_read_word(mem, ch) != 0) | |
| return 0; | |
| buf = hex_byte_pack(buf, ch[0]); | |
| buf = hex_byte_pack(buf, ch[1]); | |
| mem += 2; | |
| count -= 2; | |
| } | |
| while (count >= 4) { | |
| if (gdbstub_read_dword(mem, ch) != 0) | |
| return 0; | |
| buf = hex_byte_pack(buf, ch[0]); | |
| buf = hex_byte_pack(buf, ch[1]); | |
| buf = hex_byte_pack(buf, ch[2]); | |
| buf = hex_byte_pack(buf, ch[3]); | |
| mem += 4; | |
| count -= 4; | |
| } | |
| if (count >= 2) { | |
| if (gdbstub_read_word(mem, ch) != 0) | |
| return 0; | |
| buf = hex_byte_pack(buf, ch[0]); | |
| buf = hex_byte_pack(buf, ch[1]); | |
| mem += 2; | |
| count -= 2; | |
| } | |
| if (count >= 1) { | |
| if (gdbstub_read_byte(mem, ch) != 0) | |
| return 0; | |
| buf = hex_byte_pack(buf, ch[0]); | |
| } | |
| *buf = 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 written | |
| * may_fault is non-zero if we are reading from arbitrary memory, but is | |
| * currently not used. | |
| */ | |
| static | |
| const char *hex2mem(const char *buf, void *_mem, int count, int may_fault) | |
| { | |
| u8 *mem = _mem; | |
| union { | |
| u32 val; | |
| u8 b[4]; | |
| } ch; | |
| if ((u32) mem & 1 && count >= 1) { | |
| ch.b[0] = hex(*buf++) << 4; | |
| ch.b[0] |= hex(*buf++); | |
| if (gdbstub_write_byte(ch.val, mem) != 0) | |
| return 0; | |
| mem++; | |
| count--; | |
| } | |
| if ((u32) mem & 3 && count >= 2) { | |
| ch.b[0] = hex(*buf++) << 4; | |
| ch.b[0] |= hex(*buf++); | |
| ch.b[1] = hex(*buf++) << 4; | |
| ch.b[1] |= hex(*buf++); | |
| if (gdbstub_write_word(ch.val, mem) != 0) | |
| return 0; | |
| mem += 2; | |
| count -= 2; | |
| } | |
| while (count >= 4) { | |
| ch.b[0] = hex(*buf++) << 4; | |
| ch.b[0] |= hex(*buf++); | |
| ch.b[1] = hex(*buf++) << 4; | |
| ch.b[1] |= hex(*buf++); | |
| ch.b[2] = hex(*buf++) << 4; | |
| ch.b[2] |= hex(*buf++); | |
| ch.b[3] = hex(*buf++) << 4; | |
| ch.b[3] |= hex(*buf++); | |
| if (gdbstub_write_dword(ch.val, mem) != 0) | |
| return 0; | |
| mem += 4; | |
| count -= 4; | |
| } | |
| if (count >= 2) { | |
| ch.b[0] = hex(*buf++) << 4; | |
| ch.b[0] |= hex(*buf++); | |
| ch.b[1] = hex(*buf++) << 4; | |
| ch.b[1] |= hex(*buf++); | |
| if (gdbstub_write_word(ch.val, mem) != 0) | |
| return 0; | |
| mem += 2; | |
| count -= 2; | |
| } | |
| if (count >= 1) { | |
| ch.b[0] = hex(*buf++) << 4; | |
| ch.b[0] |= hex(*buf++); | |
| if (gdbstub_write_byte(ch.val, mem) != 0) | |
| return 0; | |
| } | |
| return buf; | |
| } | |
| /* | |
| * This table contains the mapping between MN10300 exception codes, and | |
| * signals, which are primarily what GDB understands. It also indicates | |
| * which hardware traps we need to commandeer when initializing the stub. | |
| */ | |
| static const struct excep_to_sig_map { | |
| enum exception_code excep; /* MN10300 exception code */ | |
| unsigned char signo; /* Signal that we map this into */ | |
| } excep_to_sig_map[] = { | |
| { EXCEP_ITLBMISS, SIGSEGV }, | |
| { EXCEP_DTLBMISS, SIGSEGV }, | |
| { EXCEP_TRAP, SIGTRAP }, | |
| { EXCEP_ISTEP, SIGTRAP }, | |
| { EXCEP_IBREAK, SIGTRAP }, | |
| { EXCEP_OBREAK, SIGTRAP }, | |
| { EXCEP_UNIMPINS, SIGILL }, | |
| { EXCEP_UNIMPEXINS, SIGILL }, | |
| { EXCEP_MEMERR, SIGSEGV }, | |
| { EXCEP_MISALIGN, SIGSEGV }, | |
| { EXCEP_BUSERROR, SIGBUS }, | |
| { EXCEP_ILLINSACC, SIGSEGV }, | |
| { EXCEP_ILLDATACC, SIGSEGV }, | |
| { EXCEP_IOINSACC, SIGSEGV }, | |
| { EXCEP_PRIVINSACC, SIGSEGV }, | |
| { EXCEP_PRIVDATACC, SIGSEGV }, | |
| { EXCEP_FPU_DISABLED, SIGFPE }, | |
| { EXCEP_FPU_UNIMPINS, SIGFPE }, | |
| { EXCEP_FPU_OPERATION, SIGFPE }, | |
| { EXCEP_WDT, SIGALRM }, | |
| { EXCEP_NMI, SIGQUIT }, | |
| { EXCEP_IRQ_LEVEL0, SIGINT }, | |
| { EXCEP_IRQ_LEVEL1, SIGINT }, | |
| { EXCEP_IRQ_LEVEL2, SIGINT }, | |
| { EXCEP_IRQ_LEVEL3, SIGINT }, | |
| { EXCEP_IRQ_LEVEL4, SIGINT }, | |
| { EXCEP_IRQ_LEVEL5, SIGINT }, | |
| { EXCEP_IRQ_LEVEL6, SIGINT }, | |
| { 0, 0} | |
| }; | |
| /* | |
| * convert the MN10300 exception code into a UNIX signal number | |
| */ | |
| static int computeSignal(enum exception_code excep) | |
| { | |
| const struct excep_to_sig_map *map; | |
| for (map = excep_to_sig_map; map->signo; map++) | |
| if (map->excep == excep) | |
| return map->signo; | |
| return SIGHUP; /* default for things we don't know about */ | |
| } | |
| static u32 gdbstub_fpcr, gdbstub_fpufs_array[32]; | |
| /* | |
| * | |
| */ | |
| static void gdbstub_store_fpu(void) | |
| { | |
| #ifdef CONFIG_FPU | |
| asm volatile( | |
| "or %2,epsw\n" | |
| #ifdef CONFIG_MN10300_PROC_MN103E010 | |
| "nop\n" | |
| "nop\n" | |
| #endif | |
| "mov %1, a1\n" | |
| "fmov fs0, (a1+)\n" | |
| "fmov fs1, (a1+)\n" | |
| "fmov fs2, (a1+)\n" | |
| "fmov fs3, (a1+)\n" | |
| "fmov fs4, (a1+)\n" | |
| "fmov fs5, (a1+)\n" | |
| "fmov fs6, (a1+)\n" | |
| "fmov fs7, (a1+)\n" | |
| "fmov fs8, (a1+)\n" | |
| "fmov fs9, (a1+)\n" | |
| "fmov fs10, (a1+)\n" | |
| "fmov fs11, (a1+)\n" | |
| "fmov fs12, (a1+)\n" | |
| "fmov fs13, (a1+)\n" | |
| "fmov fs14, (a1+)\n" | |
| "fmov fs15, (a1+)\n" | |
| "fmov fs16, (a1+)\n" | |
| "fmov fs17, (a1+)\n" | |
| "fmov fs18, (a1+)\n" | |
| "fmov fs19, (a1+)\n" | |
| "fmov fs20, (a1+)\n" | |
| "fmov fs21, (a1+)\n" | |
| "fmov fs22, (a1+)\n" | |
| "fmov fs23, (a1+)\n" | |
| "fmov fs24, (a1+)\n" | |
| "fmov fs25, (a1+)\n" | |
| "fmov fs26, (a1+)\n" | |
| "fmov fs27, (a1+)\n" | |
| "fmov fs28, (a1+)\n" | |
| "fmov fs29, (a1+)\n" | |
| "fmov fs30, (a1+)\n" | |
| "fmov fs31, (a1+)\n" | |
| "fmov fpcr, %0\n" | |
| : "=d"(gdbstub_fpcr) | |
| : "g" (&gdbstub_fpufs_array), "i"(EPSW_FE) | |
| : "a1" | |
| ); | |
| #endif | |
| } | |
| /* | |
| * | |
| */ | |
| static void gdbstub_load_fpu(void) | |
| { | |
| #ifdef CONFIG_FPU | |
| asm volatile( | |
| "or %1,epsw\n" | |
| #ifdef CONFIG_MN10300_PROC_MN103E010 | |
| "nop\n" | |
| "nop\n" | |
| #endif | |
| "mov %0, a1\n" | |
| "fmov (a1+), fs0\n" | |
| "fmov (a1+), fs1\n" | |
| "fmov (a1+), fs2\n" | |
| "fmov (a1+), fs3\n" | |
| "fmov (a1+), fs4\n" | |
| "fmov (a1+), fs5\n" | |
| "fmov (a1+), fs6\n" | |
| "fmov (a1+), fs7\n" | |
| "fmov (a1+), fs8\n" | |
| "fmov (a1+), fs9\n" | |
| "fmov (a1+), fs10\n" | |
| "fmov (a1+), fs11\n" | |
| "fmov (a1+), fs12\n" | |
| "fmov (a1+), fs13\n" | |
| "fmov (a1+), fs14\n" | |
| "fmov (a1+), fs15\n" | |
| "fmov (a1+), fs16\n" | |
| "fmov (a1+), fs17\n" | |
| "fmov (a1+), fs18\n" | |
| "fmov (a1+), fs19\n" | |
| "fmov (a1+), fs20\n" | |
| "fmov (a1+), fs21\n" | |
| "fmov (a1+), fs22\n" | |
| "fmov (a1+), fs23\n" | |
| "fmov (a1+), fs24\n" | |
| "fmov (a1+), fs25\n" | |
| "fmov (a1+), fs26\n" | |
| "fmov (a1+), fs27\n" | |
| "fmov (a1+), fs28\n" | |
| "fmov (a1+), fs29\n" | |
| "fmov (a1+), fs30\n" | |
| "fmov (a1+), fs31\n" | |
| "fmov %2, fpcr\n" | |
| : | |
| : "g" (&gdbstub_fpufs_array), "i"(EPSW_FE), "d"(gdbstub_fpcr) | |
| : "a1" | |
| ); | |
| #endif | |
| } | |
| /* | |
| * set a software breakpoint | |
| */ | |
| int gdbstub_set_breakpoint(u8 *addr, int len) | |
| { | |
| int bkpt, loop, xloop; | |
| #ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT | |
| len = (len + 1) & ~1; | |
| #endif | |
| gdbstub_bkpt("setbkpt(%p,%d)\n", addr, len); | |
| for (bkpt = 255; bkpt >= 0; bkpt--) | |
| if (!gdbstub_bkpts[bkpt].addr) | |
| break; | |
| if (bkpt < 0) | |
| return -ENOSPC; | |
| for (loop = 0; loop < len; loop++) | |
| if (gdbstub_read_byte(&addr[loop], | |
| &gdbstub_bkpts[bkpt].origbytes[loop] | |
| ) < 0) | |
| return -EFAULT; | |
| gdbstub_flush_caches = 1; | |
| #ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT | |
| for (loop = 0; loop < len; loop++) | |
| if (gdbstub_write_byte(0xF7, &addr[loop]) < 0) | |
| goto restore; | |
| #else | |
| for (loop = 0; loop < len; loop++) | |
| if (gdbstub_write_byte(0xFF, &addr[loop]) < 0) | |
| goto restore; | |
| #endif | |
| gdbstub_bkpts[bkpt].addr = addr; | |
| gdbstub_bkpts[bkpt].len = len; | |
| gdbstub_bkpt("Set BKPT[%02x]: %p-%p {%02x%02x%02x%02x%02x%02x%02x}\n", | |
| bkpt, | |
| gdbstub_bkpts[bkpt].addr, | |
| gdbstub_bkpts[bkpt].addr + gdbstub_bkpts[bkpt].len - 1, | |
| gdbstub_bkpts[bkpt].origbytes[0], | |
| gdbstub_bkpts[bkpt].origbytes[1], | |
| gdbstub_bkpts[bkpt].origbytes[2], | |
| gdbstub_bkpts[bkpt].origbytes[3], | |
| gdbstub_bkpts[bkpt].origbytes[4], | |
| gdbstub_bkpts[bkpt].origbytes[5], | |
| gdbstub_bkpts[bkpt].origbytes[6] | |
| ); | |
| return 0; | |
| restore: | |
| for (xloop = 0; xloop < loop; xloop++) | |
| gdbstub_write_byte(gdbstub_bkpts[bkpt].origbytes[xloop], | |
| addr + xloop); | |
| return -EFAULT; | |
| } | |
| /* | |
| * clear a software breakpoint | |
| */ | |
| int gdbstub_clear_breakpoint(u8 *addr, int len) | |
| { | |
| int bkpt, loop; | |
| #ifdef GDBSTUB_USE_F7F7_AS_BREAKPOINT | |
| len = (len + 1) & ~1; | |
| #endif | |
| gdbstub_bkpt("clearbkpt(%p,%d)\n", addr, len); | |
| for (bkpt = 255; bkpt >= 0; bkpt--) | |
| if (gdbstub_bkpts[bkpt].addr == addr && | |
| gdbstub_bkpts[bkpt].len == len) | |
| break; | |
| if (bkpt < 0) | |
| return -ENOENT; | |
| gdbstub_bkpts[bkpt].addr = NULL; | |
| gdbstub_flush_caches = 1; | |
| for (loop = 0; loop < len; loop++) | |
| if (gdbstub_write_byte(gdbstub_bkpts[bkpt].origbytes[loop], | |
| addr + loop) < 0) | |
| return -EFAULT; | |
| return 0; | |
| } | |
| /* | |
| * This function does all command processing for interfacing to gdb | |
| * - returns 0 if the exception should be skipped, -ERROR otherwise. | |
| */ | |
| static int gdbstub(struct pt_regs *regs, enum exception_code excep) | |
| { | |
| unsigned long *stack; | |
| unsigned long epsw, mdr; | |
| uint32_t zero, ssp; | |
| uint8_t broke; | |
| char *ptr; | |
| int sigval; | |
| int addr; | |
| int length; | |
| int loop; | |
| if (excep == EXCEP_FPU_DISABLED) | |
| return -ENOTSUPP; | |
| gdbstub_flush_caches = 0; | |
| mn10300_set_gdbleds(1); | |
| asm volatile("mov mdr,%0" : "=d"(mdr)); | |
| local_save_flags(epsw); | |
| arch_local_change_intr_mask_level( | |
| NUM2EPSW_IM(CONFIG_DEBUGGER_IRQ_LEVEL + 1)); | |
| gdbstub_store_fpu(); | |
| #ifdef CONFIG_GDBSTUB_IMMEDIATE | |
| /* skip the initial pause loop */ | |
| if (regs->pc == (unsigned long) __gdbstub_pause) | |
| regs->pc = (unsigned long) start_kernel; | |
| #endif | |
| /* if we were single stepping, restore the opcodes hoisted for the | |
| * breakpoint[s] */ | |
| broke = 0; | |
| #ifdef CONFIG_GDBSTUB_ALLOW_SINGLE_STEP | |
| if ((step_bp[0].addr && step_bp[0].addr == (u8 *) regs->pc) || | |
| (step_bp[1].addr && step_bp[1].addr == (u8 *) regs->pc)) | |
| broke = 1; | |
| __gdbstub_restore_bp(); | |
| #endif | |
| if (gdbstub_rx_unget) { | |
| sigval = SIGINT; | |
| if (gdbstub_rx_unget != 3) | |
| goto packet_waiting; | |
| gdbstub_rx_unget = 0; | |
| } | |
| stack = (unsigned long *) regs->sp; | |
| sigval = broke ? SIGTRAP : computeSignal(excep); | |
| /* send information about a BUG() */ | |
| if (!user_mode(regs) && excep == EXCEP_SYSCALL15) { | |
| const struct bug_entry *bug; | |
| bug = find_bug(regs->pc); | |
| if (bug) | |
| goto found_bug; | |
| length = snprintf(trans_buffer, sizeof(trans_buffer), | |
| "BUG() at address %lx\n", regs->pc); | |
| goto send_bug_pkt; | |
| found_bug: | |
| length = snprintf(trans_buffer, sizeof(trans_buffer), | |
| "BUG() at address %lx (%s:%d)\n", | |
| regs->pc, bug->file, bug->line); | |
| send_bug_pkt: | |
| ptr = output_buffer; | |
| *ptr++ = 'O'; | |
| ptr = mem2hex(trans_buffer, ptr, length, 0); | |
| *ptr = 0; | |
| putpacket(output_buffer); | |
| regs->pc -= 2; | |
| sigval = SIGABRT; | |
| } else if (regs->pc == (unsigned long) __gdbstub_bug_trap) { | |
| regs->pc = regs->mdr; | |
| sigval = SIGABRT; | |
| } | |
| /* | |
| * send a message to the debugger's user saying what happened if it may | |
| * not be clear cut (we can't map exceptions onto signals properly) | |
| */ | |
| if (sigval != SIGINT && sigval != SIGTRAP && sigval != SIGILL) { | |
| static const char title[] = "Excep ", tbcberr[] = "BCBERR "; | |
| static const char crlf[] = "\r\n"; | |
| char hx; | |
| u32 bcberr = BCBERR; | |
| ptr = output_buffer; | |
| *ptr++ = 'O'; | |
| ptr = mem2hex(title, ptr, sizeof(title) - 1, 0); | |
| hx = hex_asc_hi(excep >> 8); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_lo(excep >> 8); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_hi(excep); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_lo(excep); | |
| ptr = hex_byte_pack(ptr, hx); | |
| ptr = mem2hex(crlf, ptr, sizeof(crlf) - 1, 0); | |
| *ptr = 0; | |
| putpacket(output_buffer); /* send it off... */ | |
| /* BCBERR */ | |
| ptr = output_buffer; | |
| *ptr++ = 'O'; | |
| ptr = mem2hex(tbcberr, ptr, sizeof(tbcberr) - 1, 0); | |
| hx = hex_asc_hi(bcberr >> 24); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_lo(bcberr >> 24); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_hi(bcberr >> 16); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_lo(bcberr >> 16); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_hi(bcberr >> 8); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_lo(bcberr >> 8); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_hi(bcberr); | |
| ptr = hex_byte_pack(ptr, hx); | |
| hx = hex_asc_lo(bcberr); | |
| ptr = hex_byte_pack(ptr, hx); | |
| ptr = mem2hex(crlf, ptr, sizeof(crlf) - 1, 0); | |
| *ptr = 0; | |
| putpacket(output_buffer); /* send it off... */ | |
| } | |
| /* | |
| * tell the debugger that an exception has occurred | |
| */ | |
| ptr = output_buffer; | |
| /* | |
| * Send trap type (converted to signal) | |
| */ | |
| *ptr++ = 'T'; | |
| ptr = hex_byte_pack(ptr, sigval); | |
| /* | |
| * Send Error PC | |
| */ | |
| ptr = hex_byte_pack(ptr, GDB_REGID_PC); | |
| *ptr++ = ':'; | |
| ptr = mem2hex(®s->pc, ptr, 4, 0); | |
| *ptr++ = ';'; | |
| /* | |
| * Send frame pointer | |
| */ | |
| ptr = hex_byte_pack(ptr, GDB_REGID_FP); | |
| *ptr++ = ':'; | |
| ptr = mem2hex(®s->a3, ptr, 4, 0); | |
| *ptr++ = ';'; | |
| /* | |
| * Send stack pointer | |
| */ | |
| ssp = (unsigned long) (regs + 1); | |
| ptr = hex_byte_pack(ptr, GDB_REGID_SP); | |
| *ptr++ = ':'; | |
| ptr = mem2hex(&ssp, ptr, 4, 0); | |
| *ptr++ = ';'; | |
| *ptr++ = 0; | |
| putpacket(output_buffer); /* send it off... */ | |
| packet_waiting: | |
| /* | |
| * Wait for input from remote GDB | |
| */ | |
| while (1) { | |
| output_buffer[0] = 0; | |
| getpacket(input_buffer); | |
| switch (input_buffer[0]) { | |
| /* request repeat of last signal number */ | |
| case '?': | |
| output_buffer[0] = 'S'; | |
| output_buffer[1] = hex_asc_hi(sigval); | |
| output_buffer[2] = hex_asc_lo(sigval); | |
| output_buffer[3] = 0; | |
| break; | |
| case 'd': | |
| /* toggle debug flag */ | |
| break; | |
| /* | |
| * Return the value of the CPU registers | |
| */ | |
| case 'g': | |
| zero = 0; | |
| ssp = (u32) (regs + 1); | |
| ptr = output_buffer; | |
| ptr = mem2hex(®s->d0, ptr, 4, 0); | |
| ptr = mem2hex(®s->d1, ptr, 4, 0); | |
| ptr = mem2hex(®s->d2, ptr, 4, 0); | |
| ptr = mem2hex(®s->d3, ptr, 4, 0); | |
| ptr = mem2hex(®s->a0, ptr, 4, 0); | |
| ptr = mem2hex(®s->a1, ptr, 4, 0); | |
| ptr = mem2hex(®s->a2, ptr, 4, 0); | |
| ptr = mem2hex(®s->a3, ptr, 4, 0); | |
| ptr = mem2hex(&ssp, ptr, 4, 0); /* 8 */ | |
| ptr = mem2hex(®s->pc, ptr, 4, 0); | |
| ptr = mem2hex(®s->mdr, ptr, 4, 0); | |
| ptr = mem2hex(®s->epsw, ptr, 4, 0); | |
| ptr = mem2hex(®s->lir, ptr, 4, 0); | |
| ptr = mem2hex(®s->lar, ptr, 4, 0); | |
| ptr = mem2hex(®s->mdrq, ptr, 4, 0); | |
| ptr = mem2hex(®s->e0, ptr, 4, 0); /* 15 */ | |
| ptr = mem2hex(®s->e1, ptr, 4, 0); | |
| ptr = mem2hex(®s->e2, ptr, 4, 0); | |
| ptr = mem2hex(®s->e3, ptr, 4, 0); | |
| ptr = mem2hex(®s->e4, ptr, 4, 0); | |
| ptr = mem2hex(®s->e5, ptr, 4, 0); | |
| ptr = mem2hex(®s->e6, ptr, 4, 0); | |
| ptr = mem2hex(®s->e7, ptr, 4, 0); | |
| ptr = mem2hex(&ssp, ptr, 4, 0); | |
| ptr = mem2hex(®s, ptr, 4, 0); | |
| ptr = mem2hex(®s->sp, ptr, 4, 0); | |
| ptr = mem2hex(®s->mcrh, ptr, 4, 0); /* 26 */ | |
| ptr = mem2hex(®s->mcrl, ptr, 4, 0); | |
| ptr = mem2hex(®s->mcvf, ptr, 4, 0); | |
| ptr = mem2hex(&gdbstub_fpcr, ptr, 4, 0); /* 29 - FPCR */ | |
| ptr = mem2hex(&zero, ptr, 4, 0); | |
| ptr = mem2hex(&zero, ptr, 4, 0); | |
| for (loop = 0; loop < 32; loop++) | |
| ptr = mem2hex(&gdbstub_fpufs_array[loop], | |
| ptr, 4, 0); /* 32 - FS0-31 */ | |
| break; | |
| /* | |
| * set the value of the CPU registers - return OK | |
| */ | |
| case 'G': | |
| { | |
| const char *ptr; | |
| ptr = &input_buffer[1]; | |
| ptr = hex2mem(ptr, ®s->d0, 4, 0); | |
| ptr = hex2mem(ptr, ®s->d1, 4, 0); | |
| ptr = hex2mem(ptr, ®s->d2, 4, 0); | |
| ptr = hex2mem(ptr, ®s->d3, 4, 0); | |
| ptr = hex2mem(ptr, ®s->a0, 4, 0); | |
| ptr = hex2mem(ptr, ®s->a1, 4, 0); | |
| ptr = hex2mem(ptr, ®s->a2, 4, 0); | |
| ptr = hex2mem(ptr, ®s->a3, 4, 0); | |
| ptr = hex2mem(ptr, &ssp, 4, 0); /* 8 */ | |
| ptr = hex2mem(ptr, ®s->pc, 4, 0); | |
| ptr = hex2mem(ptr, ®s->mdr, 4, 0); | |
| ptr = hex2mem(ptr, ®s->epsw, 4, 0); | |
| ptr = hex2mem(ptr, ®s->lir, 4, 0); | |
| ptr = hex2mem(ptr, ®s->lar, 4, 0); | |
| ptr = hex2mem(ptr, ®s->mdrq, 4, 0); | |
| ptr = hex2mem(ptr, ®s->e0, 4, 0); /* 15 */ | |
| ptr = hex2mem(ptr, ®s->e1, 4, 0); | |
| ptr = hex2mem(ptr, ®s->e2, 4, 0); | |
| ptr = hex2mem(ptr, ®s->e3, 4, 0); | |
| ptr = hex2mem(ptr, ®s->e4, 4, 0); | |
| ptr = hex2mem(ptr, ®s->e5, 4, 0); | |
| ptr = hex2mem(ptr, ®s->e6, 4, 0); | |
| ptr = hex2mem(ptr, ®s->e7, 4, 0); | |
| ptr = hex2mem(ptr, &ssp, 4, 0); | |
| ptr = hex2mem(ptr, &zero, 4, 0); | |
| ptr = hex2mem(ptr, ®s->sp, 4, 0); | |
| ptr = hex2mem(ptr, ®s->mcrh, 4, 0); /* 26 */ | |
| ptr = hex2mem(ptr, ®s->mcrl, 4, 0); | |
| ptr = hex2mem(ptr, ®s->mcvf, 4, 0); | |
| ptr = hex2mem(ptr, &zero, 4, 0); /* 29 - FPCR */ | |
| ptr = hex2mem(ptr, &zero, 4, 0); | |
| ptr = hex2mem(ptr, &zero, 4, 0); | |
| for (loop = 0; loop < 32; loop++) /* 32 - FS0-31 */ | |
| ptr = hex2mem(ptr, &zero, 4, 0); | |
| #if 0 | |
| /* | |
| * See if the stack pointer has moved. If so, then copy | |
| * the saved locals and ins to the new location. | |
| */ | |
| unsigned long *newsp = (unsigned long *) registers[SP]; | |
| if (sp != newsp) | |
| sp = memcpy(newsp, sp, 16 * 4); | |
| #endif | |
| gdbstub_strcpy(output_buffer, "OK"); | |
| } | |
| break; | |
| /* | |
| * mAA..AA,LLLL Read LLLL bytes at address AA..AA | |
| */ | |
| case 'm': | |
| ptr = &input_buffer[1]; | |
| if (hexToInt(&ptr, &addr) && | |
| *ptr++ == ',' && | |
| hexToInt(&ptr, &length) | |
| ) { | |
| if (mem2hex((char *) addr, output_buffer, | |
| length, 1)) | |
| break; | |
| gdbstub_strcpy(output_buffer, "E03"); | |
| } else { | |
| gdbstub_strcpy(output_buffer, "E01"); | |
| } | |
| break; | |
| /* | |
| * MAA..AA,LLLL: Write LLLL bytes at address AA.AA | |
| * return OK | |
| */ | |
| case 'M': | |
| ptr = &input_buffer[1]; | |
| if (hexToInt(&ptr, &addr) && | |
| *ptr++ == ',' && | |
| hexToInt(&ptr, &length) && | |
| *ptr++ == ':' | |
| ) { | |
| if (hex2mem(ptr, (char *) addr, length, 1)) | |
| gdbstub_strcpy(output_buffer, "OK"); | |
| else | |
| gdbstub_strcpy(output_buffer, "E03"); | |
| gdbstub_flush_caches = 1; | |
| } else { | |
| gdbstub_strcpy(output_buffer, "E02"); | |
| } | |
| break; | |
| /* | |
| * cAA..AA Continue at address AA..AA(optional) | |
| */ | |
| case 'c': | |
| /* try to read optional parameter, pc unchanged if no | |
| * parm */ | |
| ptr = &input_buffer[1]; | |
| if (hexToInt(&ptr, &addr)) | |
| regs->pc = addr; | |
| goto done; | |
| /* | |
| * kill the program | |
| */ | |
| case 'k' : | |
| goto done; /* just continue */ | |
| /* | |
| * Reset the whole machine (FIXME: system dependent) | |
| */ | |
| case 'r': | |
| break; | |
| /* | |
| * Step to next instruction | |
| */ | |
| case 's': | |
| /* Using the T flag doesn't seem to perform single | |
| * stepping (it seems to wind up being caught by the | |
| * JTAG unit), so we have to use breakpoints and | |
| * continue instead. | |
| */ | |
| #ifdef CONFIG_GDBSTUB_ALLOW_SINGLE_STEP | |
| if (gdbstub_single_step(regs) < 0) | |
| /* ignore any fault error for now */ | |
| gdbstub_printk("unable to set single-step" | |
| " bp\n"); | |
| goto done; | |
| #else | |
| gdbstub_strcpy(output_buffer, "E01"); | |
| break; | |
| #endif | |
| /* | |
| * Set baud rate (bBB) | |
| */ | |
| case 'b': | |
| do { | |
| int baudrate; | |
| ptr = &input_buffer[1]; | |
| if (!hexToInt(&ptr, &baudrate)) { | |
| gdbstub_strcpy(output_buffer, "B01"); | |
| break; | |
| } | |
| if (baudrate) { | |
| /* ACK before changing speed */ | |
| putpacket("OK"); | |
| gdbstub_io_set_baud(baudrate); | |
| } | |
| } while (0); | |
| break; | |
| /* | |
| * Set breakpoint | |
| */ | |
| case 'Z': | |
| ptr = &input_buffer[1]; | |
| if (!hexToInt(&ptr, &loop) || *ptr++ != ',' || | |
| !hexToInt(&ptr, &addr) || *ptr++ != ',' || | |
| !hexToInt(&ptr, &length) | |
| ) { | |
| gdbstub_strcpy(output_buffer, "E01"); | |
| break; | |
| } | |
| /* only support software breakpoints */ | |
| gdbstub_strcpy(output_buffer, "E03"); | |
| if (loop != 0 || | |
| length < 1 || | |
| length > 7 || | |
| (unsigned long) addr < 4096) | |
| break; | |
| if (gdbstub_set_breakpoint((u8 *) addr, length) < 0) | |
| break; | |
| gdbstub_strcpy(output_buffer, "OK"); | |
| break; | |
| /* | |
| * Clear breakpoint | |
| */ | |
| case 'z': | |
| ptr = &input_buffer[1]; | |
| if (!hexToInt(&ptr, &loop) || *ptr++ != ',' || | |
| !hexToInt(&ptr, &addr) || *ptr++ != ',' || | |
| !hexToInt(&ptr, &length) | |
| ) { | |
| gdbstub_strcpy(output_buffer, "E01"); | |
| break; | |
| } | |
| /* only support software breakpoints */ | |
| gdbstub_strcpy(output_buffer, "E03"); | |
| if (loop != 0 || | |
| length < 1 || | |
| length > 7 || | |
| (unsigned long) addr < 4096) | |
| break; | |
| if (gdbstub_clear_breakpoint((u8 *) addr, length) < 0) | |
| break; | |
| gdbstub_strcpy(output_buffer, "OK"); | |
| break; | |
| default: | |
| gdbstub_proto("### GDB Unsupported Cmd '%s'\n", | |
| input_buffer); | |
| break; | |
| } | |
| /* reply to the request */ | |
| putpacket(output_buffer); | |
| } | |
| done: | |
| /* | |
| * 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. | |
| * NB: We flush both caches, just to be sure... | |
| */ | |
| if (gdbstub_flush_caches) | |
| debugger_local_cache_flushinv(); | |
| gdbstub_load_fpu(); | |
| mn10300_set_gdbleds(0); | |
| if (excep == EXCEP_NMI) | |
| NMICR = NMICR_NMIF; | |
| touch_softlockup_watchdog(); | |
| local_irq_restore(epsw); | |
| return 0; | |
| } | |
| /* | |
| * Determine if we hit a debugger special breakpoint that needs skipping over | |
| * automatically. | |
| */ | |
| int at_debugger_breakpoint(struct pt_regs *regs) | |
| { | |
| return 0; | |
| } | |
| /* | |
| * handle event interception | |
| */ | |
| asmlinkage int debugger_intercept(enum exception_code excep, | |
| int signo, int si_code, struct pt_regs *regs) | |
| { | |
| static u8 notfirst = 1; | |
| int ret; | |
| if (gdbstub_busy) | |
| gdbstub_printk("--> gdbstub reentered itself\n"); | |
| gdbstub_busy = 1; | |
| if (notfirst) { | |
| unsigned long mdr; | |
| asm("mov mdr,%0" : "=d"(mdr)); | |
| gdbstub_entry( | |
| "--> debugger_intercept(%p,%04x) [MDR=%lx PC=%lx]\n", | |
| regs, excep, mdr, regs->pc); | |
| gdbstub_entry( | |
| "PC: %08lx EPSW: %08lx SSP: %08lx mode: %s\n", | |
| regs->pc, regs->epsw, (unsigned long) &ret, | |
| user_mode(regs) ? "User" : "Super"); | |
| gdbstub_entry( | |
| "d0: %08lx d1: %08lx d2: %08lx d3: %08lx\n", | |
| regs->d0, regs->d1, regs->d2, regs->d3); | |
| gdbstub_entry( | |
| "a0: %08lx a1: %08lx a2: %08lx a3: %08lx\n", | |
| regs->a0, regs->a1, regs->a2, regs->a3); | |
| gdbstub_entry( | |
| "e0: %08lx e1: %08lx e2: %08lx e3: %08lx\n", | |
| regs->e0, regs->e1, regs->e2, regs->e3); | |
| gdbstub_entry( | |
| "e4: %08lx e5: %08lx e6: %08lx e7: %08lx\n", | |
| regs->e4, regs->e5, regs->e6, regs->e7); | |
| gdbstub_entry( | |
| "lar: %08lx lir: %08lx mdr: %08lx usp: %08lx\n", | |
| regs->lar, regs->lir, regs->mdr, regs->sp); | |
| gdbstub_entry( | |
| "cvf: %08lx crl: %08lx crh: %08lx drq: %08lx\n", | |
| regs->mcvf, regs->mcrl, regs->mcrh, regs->mdrq); | |
| gdbstub_entry( | |
| "threadinfo=%p task=%p)\n", | |
| current_thread_info(), current); | |
| } else { | |
| notfirst = 1; | |
| } | |
| ret = gdbstub(regs, excep); | |
| gdbstub_entry("<-- debugger_intercept()\n"); | |
| gdbstub_busy = 0; | |
| return ret; | |
| } | |
| /* | |
| * handle the GDB stub itself causing an exception | |
| */ | |
| asmlinkage void gdbstub_exception(struct pt_regs *regs, | |
| enum exception_code excep) | |
| { | |
| unsigned long mdr; | |
| asm("mov mdr,%0" : "=d"(mdr)); | |
| gdbstub_entry("--> gdbstub exception({%p},%04x) [MDR=%lx]\n", | |
| regs, excep, mdr); | |
| while ((unsigned long) regs == 0xffffffff) {} | |
| /* handle guarded memory accesses where we know it might fault */ | |
| if (regs->pc == (unsigned) gdbstub_read_byte_guard) { | |
| regs->pc = (unsigned) gdbstub_read_byte_cont; | |
| goto fault; | |
| } | |
| if (regs->pc == (unsigned) gdbstub_read_word_guard) { | |
| regs->pc = (unsigned) gdbstub_read_word_cont; | |
| goto fault; | |
| } | |
| if (regs->pc == (unsigned) gdbstub_read_dword_guard) { | |
| regs->pc = (unsigned) gdbstub_read_dword_cont; | |
| goto fault; | |
| } | |
| if (regs->pc == (unsigned) gdbstub_write_byte_guard) { | |
| regs->pc = (unsigned) gdbstub_write_byte_cont; | |
| goto fault; | |
| } | |
| if (regs->pc == (unsigned) gdbstub_write_word_guard) { | |
| regs->pc = (unsigned) gdbstub_write_word_cont; | |
| goto fault; | |
| } | |
| if (regs->pc == (unsigned) gdbstub_write_dword_guard) { | |
| regs->pc = (unsigned) gdbstub_write_dword_cont; | |
| goto fault; | |
| } | |
| gdbstub_printk("\n### GDB stub caused an exception ###\n"); | |
| /* something went horribly wrong */ | |
| console_verbose(); | |
| show_registers(regs); | |
| panic("GDB Stub caused an unexpected exception - can't continue\n"); | |
| /* we caught an attempt by the stub to access silly memory */ | |
| fault: | |
| gdbstub_entry("<-- gdbstub exception() = EFAULT\n"); | |
| regs->d0 = -EFAULT; | |
| return; | |
| } | |
| /* | |
| * send an exit message to GDB | |
| */ | |
| void gdbstub_exit(int status) | |
| { | |
| unsigned char checksum; | |
| unsigned char ch; | |
| int count; | |
| gdbstub_busy = 1; | |
| output_buffer[0] = 'W'; | |
| output_buffer[1] = hex_asc_hi(status); | |
| output_buffer[2] = hex_asc_lo(status); | |
| output_buffer[3] = 0; | |
| gdbstub_io_tx_char('$'); | |
| checksum = 0; | |
| count = 0; | |
| while ((ch = output_buffer[count]) != 0) { | |
| gdbstub_io_tx_char(ch); | |
| checksum += ch; | |
| count += 1; | |
| } | |
| gdbstub_io_tx_char('#'); | |
| gdbstub_io_tx_char(hex_asc_hi(checksum)); | |
| gdbstub_io_tx_char(hex_asc_lo(checksum)); | |
| /* make sure the output is flushed, or else RedBoot might clobber it */ | |
| gdbstub_io_tx_flush(); | |
| gdbstub_busy = 0; | |
| } | |
| /* | |
| * initialise the GDB stub | |
| */ | |
| asmlinkage void __init gdbstub_init(void) | |
| { | |
| #ifdef CONFIG_GDBSTUB_IMMEDIATE | |
| unsigned char ch; | |
| int ret; | |
| #endif | |
| gdbstub_busy = 1; | |
| printk(KERN_INFO "%s", gdbstub_banner); | |
| gdbstub_io_init(); | |
| gdbstub_entry("--> gdbstub_init\n"); | |
| /* try to talk to GDB (or anyone insane enough to want to type GDB | |
| * protocol by hand) */ | |
| gdbstub_io("### GDB Tx ACK\n"); | |
| gdbstub_io_tx_char('+'); /* 'hello world' */ | |
| #ifdef CONFIG_GDBSTUB_IMMEDIATE | |
| gdbstub_printk("GDB Stub waiting for packet\n"); | |
| /* in case GDB is started before us, ACK any packets that are already | |
| * sitting there (presumably "$?#xx") | |
| */ | |
| do { gdbstub_io_rx_char(&ch, 0); } while (ch != '$'); | |
| do { gdbstub_io_rx_char(&ch, 0); } while (ch != '#'); | |
| /* eat first csum byte */ | |
| do { ret = gdbstub_io_rx_char(&ch, 0); } while (ret != 0); | |
| /* eat second csum byte */ | |
| do { ret = gdbstub_io_rx_char(&ch, 0); } while (ret != 0); | |
| gdbstub_io("### GDB Tx NAK\n"); | |
| gdbstub_io_tx_char('-'); /* NAK it */ | |
| #else | |
| printk("GDB Stub ready\n"); | |
| #endif | |
| gdbstub_busy = 0; | |
| gdbstub_entry("<-- gdbstub_init\n"); | |
| } | |
| /* | |
| * register the console at a more appropriate time | |
| */ | |
| #ifdef CONFIG_GDBSTUB_CONSOLE | |
| static int __init gdbstub_postinit(void) | |
| { | |
| printk(KERN_NOTICE "registering console\n"); | |
| register_console(&gdbstub_console); | |
| return 0; | |
| } | |
| __initcall(gdbstub_postinit); | |
| #endif | |
| /* | |
| * handle character reception on GDB serial port | |
| * - jump into the GDB stub if BREAK is detected on the serial line | |
| */ | |
| asmlinkage void gdbstub_rx_irq(struct pt_regs *regs, enum exception_code excep) | |
| { | |
| char ch; | |
| int ret; | |
| gdbstub_entry("--> gdbstub_rx_irq\n"); | |
| do { | |
| ret = gdbstub_io_rx_char(&ch, 1); | |
| if (ret != -EIO && ret != -EAGAIN) { | |
| if (ret != -EINTR) | |
| gdbstub_rx_unget = ch; | |
| gdbstub(regs, excep); | |
| } | |
| } while (ret != -EAGAIN); | |
| gdbstub_entry("<-- gdbstub_rx_irq\n"); | |
| } |