diff options
Diffstat (limited to 'cpukit/libdebugger/rtems-debugger-arm.c')
-rw-r--r-- | cpukit/libdebugger/rtems-debugger-arm.c | 1193 |
1 files changed, 1193 insertions, 0 deletions
diff --git a/cpukit/libdebugger/rtems-debugger-arm.c b/cpukit/libdebugger/rtems-debugger-arm.c new file mode 100644 index 0000000000..945dc9c2d9 --- /dev/null +++ b/cpukit/libdebugger/rtems-debugger-arm.c @@ -0,0 +1,1193 @@ +/* + * Copyright (c) 2016 Chris Johns <chrisj@rtems.org>. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define TARGET_DEBUG 0 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> + +#include <rtems.h> +#include <rtems/score/threadimpl.h> + +#include <libcpu/arm-cp15.h> +#include <bsp/linker-symbols.h> + +#include "rtems-debugger-target.h" +#include "rtems-debugger-threads.h" + +#if TARGET_DEBUG +#include <rtems/bspIo.h> +#endif + +/* + * The ARM has 2 interrupt bits. + */ +#define CPSR_IRQ_DISABLE 0x80 /* IIQ disabled when 1 */ +#define CPSR_FIQ_DISABLE 0x40 /* FIQ disabled when 1 */ +#define CPSR_INTS_MASK (CPSR_IRQ_DISABLE | CPSR_FIQ_DISABLE) + +/* + * Software breakpoint block size. + */ +#define RTEMS_DEBUGGER_SWBREAK_NUM 64 + +/* + * Number of registers. + */ +#define RTEMS_DEBUGGER_NUMREGS 26 + +/* + * Number of bytes per register. + */ +#define RTEMS_DEBUGGER_REGBYTES 4 + +/* + * Number of bytes of registers. + */ +#define RTEMS_DEBUGGER_NUMREGBYTES \ + (RTEMS_DEBUGGER_NUMREGS * RTEMS_DEBUGGER_REGBYTES) + +/* + * Debugger registers layout. + */ +#define REG_R0 0 +#define REG_R1 1 +#define REG_R2 2 +#define REG_R3 3 +#define REG_R4 4 +#define REG_R5 5 +#define REG_R6 6 +#define REG_R7 7 +#define REG_R8 8 +#define REG_R9 9 +#define REG_R10 10 +#define REG_R11 11 +#define REG_R12 12 +#define REG_SP 13 +#define REG_LR 14 +#define REG_PC 15 +#define REG_CPSR 25 + +/** + * The various status registers. + */ +#if defined(ARM_MULTILIB_ARCH_V4) + #define FRAME_SR frame->register_cpsr +#elif defined(ARM_MULTILIB_ARCH_V7M) + #define FRAME_SR frame->register_xpsr +#else + #error ARM architecture is not supported. +#endif + +/** + * The breakpoint. + */ +#ifdef __thumb__ + static const uint8_t breakpoint[2] = { 0x55, 0xbe }; +#else + static const uint8_t breakpoint[4] = { 0x75, 0xe0a, 0x20, 0xe1 }; +#endif + +/** + * Target lock. + */ +RTEMS_INTERRUPT_LOCK_DEFINE(static, target_lock, "target_lock") + +/** + * The init value for the text section. + */ +static uint32_t text_section_flags; + +/** + * Is a session active? + */ +static bool debug_session_active; + +/* + * ARM debug hardware. + */ +static int debug_version; +static int debug_revision;; +static int hw_breakpoints; +static int hw_watchpoints; + +/** + * Hardware break and watch points. + */ +typedef struct +{ + bool enabled; + bool loaded; + void* address; + size_t length; + CPU_Exception_frame* frame; + uint32_t control; + uint32_t value; +} arm_debug_hwbreak; + +#define ARM_HW_BREAKPOINT_MAX (16) +#define ARM_HW_WATCHPOINT_MAX (16) + +/* + * Types of break points. Only the 2 we use listed. + */ +#define ARM_HW_BP_UNLINKED_INSTR_MATCH (0x00) +#define ARM_HW_BP_UNLINKED_INSTR_MISMATCH (0x04) + +/* + * Privilege levels. + */ +#define ARM_HW_BP_PRIV_PL0_SUP_SYS (0x00) +#define ARM_HW_BP_PRIV_PL1_ONLY (0x01) +#define ARM_HW_BP_PRIV_PL0_ONLY (0x02) +#define ARM_HW_BP_PRIV_ALL_MODES (0x03) + +static arm_debug_hwbreak hw_breaks[ARM_HW_BREAKPOINT_MAX]; +//static arm_debug_hwbreak hw_watches[ARM_HW_WATCHPOINT_MAX]; + +#if TARGET_DEBUG +static void target_printk(const char* format, ...) RTEMS_PRINTFLIKE(1, 2); +static void +target_printk(const char* format, ...) +{ + va_list ap; + va_start(ap, format); + vprintk(format, ap); + va_end(ap); +} +static const char* +mode_label(int mode) +{ + switch (mode) { + case 0x10: + return "USR"; + case 0x11: + return "FIQ"; + case 0x12: + return "IRQ"; + case 0x13: + return "SVC"; + case 0x16: + return "MON"; + case 0x17: + return "ABT"; + case 0x1a: + return "HYP"; + case 0x1b: + return "UND"; + case 0x1f: + return "SYS"; + } + return "---"; +} +#else +#define target_printk(_fmt, ...) +#define mode_labels(_m) NULL +#endif + +/* + * Read and write a CP14 register. + * + * The software debug event registers are not easy to program because there are + * up to 32 registers and the instructions have to assembler for each of the 32 + * registers, you cannot program it. This means there is a switch table to do + * this. + */ +#define ARM_CP14_INSTR(_opc, _val, _CRn, _CRm, _opc2) \ + #_opc " p14, 0, %[" #_val "], c" #_CRn ", c" #_CRm ", " #_opc2 "\n" + +#define ARM_CP14_WRITE(_val, _CRn, _CRm, _opc2) \ + do { \ + ARM_SWITCH_REGISTERS; \ + asm volatile( \ + ARM_SWITCH_TO_ARM \ + ARM_CP14_INSTR(mcr, val, _CRn, _CRm, _opc2) \ + ARM_SWITCH_BACK \ + : ARM_SWITCH_OUTPUT : [val] "r" (_val)); \ + } while (0) + +#define ARM_CP14_READ(_val, _CRn, _CRm, _opc2) \ + do { \ + ARM_SWITCH_REGISTERS; \ + asm volatile( \ + ARM_SWITCH_TO_ARM \ + ARM_CP14_INSTR(mrc, val, _CRn, _CRm, _opc2) \ + ARM_SWITCH_BACK \ + : [val] "=&r" (_val) ARM_SWITCH_ADDITIONAL_OUTPUT); \ + } while (0) + +static int +arm_debug_probe(rtems_debugger_target* target) +{ + #define ID_VALUE(_i, _h, _l) ((_i >> _l) & ((1 << ((_h - _l) + 1)) -1)) + uint32_t val; + const char const* vl = "[Invalid version]"; + const char const* labels[] = { + "ARMv6 [v6]", + "ARMv6 [v6.1]", + "ARMv7 [v7, all CP14 registers]", + "ARMv7 [v7, baseline CP14 registers]", + "ARMv7 [v7.1]" + }; + ARM_CP14_READ(val, 0, 0, 0); + debug_version = ID_VALUE(val, 19, 16); + if (debug_version < 1 || debug_version > 5) { + rtems_debugger_printf("rtems-db: arm debug: (v%d.%d) not supported\n", + debug_version, debug_revision); + errno = EIO; + return -1; + } + vl = labels[debug_version - 1]; + debug_revision = ID_VALUE(val, 3, 0); + hw_breakpoints = ID_VALUE(val, 27, 24); + hw_watchpoints = ID_VALUE(val, 31, 28); + rtems_debugger_printf("rtems-db: arm debug: (v%d.%d) %s breakpoints:%d watchpoints:%d\n", + debug_version, debug_revision, vl, + hw_breakpoints, hw_watchpoints); + ARM_CP14_READ(val, 0, 1, 0); + if ((val & (1 << 15)) == 0) { + switch (debug_version) { + case 1: + case 2: + ARM_CP14_WRITE(val | (1 << 15), 0, 1, 0); + break; + case 3: + case 4: + case 5: + default: + ARM_CP14_WRITE(val | (1 << 15), 0, 2, 2); + break; + } + ARM_CP14_READ(val, 0, 1, 0); + if ((val & (1 << 15)) == 0) { + rtems_debugger_printf("rtems-db: arm debug: cannot enter monitor mode\n"); + errno = EIO; + return -1; + } + } + return 0; +} + +static inline void +arm_debug_break_setup(arm_debug_hwbreak* bp, + uint32_t address, + uint32_t type, + uint32_t byte_address_select, + uint32_t privilege) +{ + bp->control = (((type & 0xf) << 20) | + ((byte_address_select & 0xf) << 5) | + ((privilege & 0x3) << 1) | 1); + bp->value = (intptr_t) address; +} + +static void +arm_debug_break_write_control(int bp, uint32_t control) +{ + switch (bp) { + case 0: + ARM_CP14_WRITE(control, 0, 0, 5); + break; + case 1: + ARM_CP14_WRITE(control, 0, 1, 5); + break; + case 2: + ARM_CP14_WRITE(control, 0, 2, 5); + break; + case 3: + ARM_CP14_WRITE(control, 0, 3, 5); + break; + case 4: + ARM_CP14_WRITE(control, 0, 4, 5); + break; + case 5: + ARM_CP14_WRITE(control, 0, 5, 5); + break; + case 6: + ARM_CP14_WRITE(control, 0, 6, 5); + break; + case 7: + ARM_CP14_WRITE(control, 0, 7, 5); + break; + case 8: + ARM_CP14_WRITE(control, 0, 8, 5); + break; + case 9: + ARM_CP14_WRITE(control, 0, 9, 5); + break; + case 10: + ARM_CP14_WRITE(control, 0, 10, 5); + break; + case 11: + ARM_CP14_WRITE(control, 0, 11, 5); + break; + case 12: + ARM_CP14_WRITE(control, 0, 12, 5); + break; + case 13: + ARM_CP14_WRITE(control, 0, 13, 5); + break; + case 14: + ARM_CP14_WRITE(control, 0, 14, 5); + break; + case 15: + ARM_CP14_WRITE(control, 0, 15, 5); + break; + } +} + +static void +arm_debug_break_write_value(int bp, uint32_t value) +{ + switch (bp) { + case 0: + ARM_CP14_WRITE(value, 0, 0, 4); + break; + case 1: + ARM_CP14_WRITE(value, 0, 1, 4); + break; + case 2: + ARM_CP14_WRITE(value, 0, 2, 4); + break; + case 3: + ARM_CP14_WRITE(value, 0, 3, 4); + break; + case 4: + ARM_CP14_WRITE(value, 0, 4, 4); + break; + case 5: + ARM_CP14_WRITE(value, 0, 5, 4); + break; + case 6: + ARM_CP14_WRITE(value, 0, 6, 4); + break; + case 7: + ARM_CP14_WRITE(value, 0, 7, 4); + break; + case 8: + ARM_CP14_WRITE(value, 0, 8, 4); + break; + case 9: + ARM_CP14_WRITE(value, 0, 9, 4); + break; + case 10: + ARM_CP14_WRITE(value, 0, 10, 4); + break; + case 11: + ARM_CP14_WRITE(value, 0, 11, 4); + break; + case 12: + ARM_CP14_WRITE(value, 0, 12, 4); + break; + case 13: + ARM_CP14_WRITE(value, 0, 13, 4); + break; + case 14: + ARM_CP14_WRITE(value, 0, 14, 4); + break; + case 15: + ARM_CP14_WRITE(value, 0, 15, 4); + break; + } +} + +static void +arm_debug_break_clear(void) +{ + arm_debug_hwbreak* bp = &hw_breaks[0]; + int i; + for (i = 0; i < hw_breakpoints; ++i, ++bp) { + bp->enabled = false; + bp->loaded = false; + } +} + +static void +arm_debug_break_load(void) +{ + arm_debug_hwbreak* bp = &hw_breaks[0]; + int i; + for (i = 0; i < hw_breakpoints; ++i, ++bp) { + if (bp->enabled && !bp->loaded) { + bp->loaded = true; + target_printk("]]} hwbp: %i: v:%08lx c:%08lx l:%08x\n", + i, bp->value, bp->control, bp->length); + arm_debug_break_write_value(i, bp->value); + arm_debug_break_write_control(i, bp->control); + } + } +} + +static void +arm_debug_break_unload(void) +{ + arm_debug_hwbreak* bp = &hw_breaks[0]; + int i; + for (i = 0; i < hw_breakpoints; ++i, ++bp) { + bp->loaded = false; + arm_debug_break_write_control(i, 0); + } +} + +#if NOT_USED_BUT_KEEPING +static size_t +arm_debug_break_length(void* pc) +{ + arm_debug_hwbreak* bp = &hw_breaks[0]; + int i; + + for (i = 0; i < hw_breakpoints; ++i, ++bp) { + if (bp->enabled && bp->address == pc) { + return bp->length; + } + } + return sizeof(DB_UINT); +} +#endif + +int +rtems_debugger_target_configure(rtems_debugger_target* target) +{ + target->capabilities = (RTEMS_DEBUGGER_TARGET_CAP_SWBREAK); + target->reg_num = RTEMS_DEBUGGER_NUMREGS; + target->reg_size = sizeof(uint32_t); + target->breakpoint = &breakpoint[0]; + target->breakpoint_size = sizeof(breakpoint); + return arm_debug_probe(target); +} + +static void +target_exception(CPU_Exception_frame* frame) +{ +#if TARGET_DEBUG + uint32_t ifsr = arm_cp15_get_instruction_fault_status(); +#endif + + target_printk("[} frame = %08lx sig=%d vector=%x ifsr=%08lx pra=%08x\n", + (uint32_t) frame, + rtems_debugger_target_exception_to_signal(frame), + frame->vector, ifsr, (intptr_t) frame->register_pc); + + if ((FRAME_SR & (1 << 5)) == 0) + frame->register_pc = (void*) ((intptr_t) frame->register_pc - 8); + else + frame->register_pc = (void*) ((intptr_t) frame->register_pc - 4); + + target_printk("[} R0 = %08" PRIx32 " R1 = %08" PRIx32 \ + " R2 = %08" PRIx32 " R3 = %08" PRIx32 "\n", + frame->register_r0, frame->register_r1, + frame->register_r2, frame->register_r3); + target_printk("[} R4 = %08" PRIx32 " R5 = %08" PRIx32 \ + " R6 = %08" PRIx32 " R7 = %08" PRIx32 "\n", + frame->register_r4, frame->register_r5, + frame->register_r6, frame->register_r7); + target_printk("[} R8 = %08" PRIx32 " R9 = %08" PRIx32 \ + " R10 = %08" PRIx32 " R11 = %08" PRIx32 "\n", + frame->register_r8, frame->register_r9, + frame->register_r10, frame->register_r11); + target_printk("[} R12 = %08" PRIx32 " SP = %08" PRIx32 \ + " LR = %08" PRIxPTR " PC = %08" PRIxPTR "\n", \ + frame->register_r12, frame->register_sp, + (intptr_t) frame->register_lr, (intptr_t) frame->register_pc); + target_printk("[} CPSR = %08" PRIx32 " %c%c%c%c%c%c%c%c%c%c%c" \ + " GE:%" PRIx32 " IT:%02" PRIx32 " M:%" PRIx32 " %s\n", + FRAME_SR, + (FRAME_SR & (1 << 31)) != 0 ? 'N' : '-', + (FRAME_SR & (1 << 30)) != 0 ? 'Z' : '-', + (FRAME_SR & (1 << 29)) != 0 ? 'C' : '-', + (FRAME_SR & (1 << 28)) != 0 ? 'V' : '-', + (FRAME_SR & (1 << 27)) != 0 ? 'Q' : '-', + (FRAME_SR & (1 << 24)) != 0 ? 'J' : '-', + (FRAME_SR & (1 << 9)) != 0 ? 'E' : '-', + (FRAME_SR & (1 << 8)) != 0 ? 'A' : '-', + (FRAME_SR & (1 << 7)) != 0 ? 'I' : '-', + (FRAME_SR & (1 << 6)) != 0 ? 'F' : '-', + (FRAME_SR & (1 << 5)) != 0 ? 'T' : '-', + ((FRAME_SR >> (25 - 5)) & (0x3 << 5)) | ((FRAME_SR >> 10) & 0x1f), + (FRAME_SR >> 16) & 0xf, + FRAME_SR & 0x1f, mode_label(FRAME_SR & 0x1f)); + + arm_debug_break_clear(); + + if (!debug_session_active) + _ARM_Exception_default(frame); + + switch (rtems_debugger_target_exception(frame)) { + case rtems_debugger_target_exc_consumed: + default: + break; + case rtems_debugger_target_exc_step: + FRAME_SR |= CPSR_INTS_MASK; + break; + case rtems_debugger_target_exc_cascade: + target_printk("rtems-db: unhandled exception: cascading\n"); + _ARM_Exception_default(frame); + break; + } + + target_printk("[} resuming frame = %08lx PC = %08" PRIxPTR " CPSR = %08" PRIx32 "\n", + (uint32_t) frame, (intptr_t) frame->register_pc, FRAME_SR); +} + +/** + * If thumb build of code switch the asm to thumb as required. + */ +#ifdef __thumb__ + #define ASM_ARM_MODE ".align 2\nbx pc\n.arm\n" + #define ASM_THUMB_MODE "add %[arm_switch_reg], pc, #1\nbx %[arm_switch_reg]\n.thumb\n" + #define ARM_SWITCH_REG uint32_t arm_switch_reg +#else + #define ASM_ARM_MODE + #define ASM_THUMB_MODE + #define ARM_SWITCH_REG +#endif + +/** + * Exception stack frame size. + * + * The size is the exception stack frame plus the CPSR from the exception. We + * save the CPSR and restore it when we exit the exception. + */ +#define EXCEPTION_FRAME_SIZE (sizeof(CPU_Exception_frame) + sizeof(uint32_t)) + +/** + * Exception stack frame FPU offsets and sizes. + */ +#define EXCEPTION_FRAME_FPU_SIZE ARM_VFP_CONTEXT_SIZE +#define EXCEPTION_FRAME_FPU_OFFSET ARM_EXCEPTION_FRAME_VFP_CONTEXT_OFFSET + +/** + * Exception entry. + * + * We have switched from svc (or even user) to an exception mode. Save the + * current CPSR and create an exception frame on the exception's stack and then + * copy it to the thread's stack. Switch back to the thread's context and mode + * to handle the exception to avoid any stack checks thinking the stack is + * blown. This lets the thread be suspended. + * + * The entry is in two parts, the exception mode entry and the trhead mode + * entry. This lets us disable any hardware breakpoint support. We need to do + * this because it is enabled in PL0 mode. + * + * Note, the code currently assumes cp15 has been set up to match the + * instruction set being used. + */ +#define EXCEPTION_ENTRY_EXC() \ + __asm__ volatile( \ + ASM_ARM_MODE \ + "sub sp, %[frame_size]\n" /* alloc the frame and CPSR */ \ + "stm sp, {r0-r12}\n" /* store r0-r12 */ \ + "sub sp, #4\n" \ + "str lr, [sp]\n" /* save the link reg */ \ + ASM_THUMB_MODE \ + : [arm_switch_reg] "=&r" (arm_switch_reg) \ + : [frame_size] "i" (EXCEPTION_FRAME_SIZE) \ + : "memory") + +/* + * FPU entry. Conditionally D16 or D32 support. + */ +#ifdef ARM_MULTILIB_VFP +#ifdef ARM_MULTILIB_VFP_D32 +#define FPU_ENTRY_VFP_D32 \ + "vstmia r5!, {d16-d31}\n" +#else /* ARM_MULTILIB_VFP_D32 */ +#define FPU_ENTRY_VFP_D32 \ + "mov r3, #0\n" \ + "mov r4, #0\n" \ + "adds r6, r5, #128\n" \ + "3:\n" \ + "stmia r5!, {r3-r4}\n" \ + "cmp r5, r6\n" \ + "bne 3b\n" +#endif /* ARM_MULTILIB_VFP_D32 */ +#define EXCEPTION_ENTRY_FPU(frame_fpu_size) \ + "sub sp, %[frame_fpu_size]\n" /* size includes alignment size */ \ + "add r5, sp, #4\n" /* up to align down */ \ + "bic r5, r5, #7\n" /* align the FPU frame */ \ + "str r5, [r2]\n" /* store the FPU frame pointer */ \ + "vmrs r3, FPEXC\n" \ + "vmrs r4, FPSCR\n" \ + "stmia r5!, {r3-r4}\n" \ + "vstmia r5!, {d0-d15}\n" \ + FPU_ENTRY_VFP_D32 +#else /* ARM_MULTILIB_VFP */ +#define EXCEPTION_ENTRY_FPU(frame_fpu_size) +#endif /* ARM_MULTILIB_VFP */ + +#define EXCEPTION_ENTRY_THREAD(_frame) \ + __asm__ volatile( \ + ASM_ARM_MODE \ + "ldr lr, [sp]\n" /* recover the link reg */ \ + "add sp, #4\n" \ + "add r0, sp, %[r0_r12_size]\n" /* get the sp in the frame */ \ + "mrs r1, spsr\n" /* get the saved sr */ \ + "mov r6, r1\n" /* stash it for later */ \ + "bic r1, r1, %[psr_t]\n" /* clear thumb mode, not sure? */ \ + "orr r1, r1, %[psr_i]\n" /* mask irqs */ \ + "mrs r2, cpsr\n" /* get the current sr */ \ + "str r2, [sp, %[frame_cpsr]]\n" /* save for exc return */ \ + "msr cpsr, r1\n" /* switch to user mode */ \ + "mov r3, sp\n" /* get the stack pointer */ \ + "mov r4, lr\n" /* get the link reg */ \ + "msr cpsr, r2\n" /* back to exc mode */ \ + "mov r5, lr\n" /* get the PRA */ \ + "stm r0, {r3-r6}\n" /* save into the frame */ \ + "sub r4, r3, %[frame_size]\n" /* destination address */ \ + "mov r6, r4\n" /* save the frame */ \ + "sub r4, #1\n" /* one before the start */ \ + "add r3, #1\n" /* one past the end */ \ + "sub r5, sp, #1\n" /* source address */ \ + "1:\n" \ + "ldrb r0, [r5, #1]!\n" /* get a byte */ \ + "strb r0, [r4, #1]!\n" /* put the byte */ \ + "cmp r3, r4\n" /* the end? */ \ + "bne 1b\n" \ + "add sp, %[frame_size]\n" /* free the frame and CPSR */ \ + "mrs r1, spsr\n" /* get the thread's saved sr */ \ + "orr r2, r1, %[psr_i]\n" /* mask irqs */ \ + "msr cpsr, r2\n" /* switch back to the thread's context */ \ + "sub sp, %[frame_size]\n" /* alloc in the thread stack */ \ + "mov %[o_frame], sp\n" /* save the frame */ \ + "add r2, sp, %[o_frame_fpu]\n" /* get the FPU offset */ \ + "mov r3, #0\n" \ + "str r3, [r2]\n" /* clear the FPU frame pointer */ \ + EXCEPTION_ENTRY_FPU(frame_fpu_size) \ + "msr cpsr, r1\n" /* restore the irq mask */ \ + ASM_THUMB_MODE \ + : [arm_switch_reg] "=&r" (arm_switch_reg), \ + [o_frame] "=r" (_frame) \ + : [psr_t] "i" (ARM_PSR_T), \ + [psr_i] "i" (ARM_PSR_I), \ + [r0_r12_size] "i" (13 * sizeof(uint32_t)), \ + [frame_cpsr] "i" (EXCEPTION_FRAME_SIZE - sizeof(uint32_t)), \ + [frame_size] "i" (EXCEPTION_FRAME_SIZE), \ + [o_frame_fpu] "i" (EXCEPTION_FRAME_FPU_OFFSET), \ + [frame_fpu_size] "i" (EXCEPTION_FRAME_FPU_SIZE + 4) \ + : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "memory") + +/* + * FPU exit. Conditionally D16 or D32 support. + */ +#ifdef ARM_MULTILIB_VFP +#ifdef ARM_MULTILIB_VFP_D32 +#define FPU_EXIT_VFP_D32 \ + "vldmia r0, {d16-d31}\n" +#else /* ARM_MULTILIB_VFP_D32 */ +#define FPU_EXIT_VFP_D32 +#endif /* ARM_MULTILIB_VFP_D32 */ +#define EXCEPTION_EXIT_FPU(frame_fpu_size) \ + "ldmia r0!, {r1-r2}\n" \ + "vldmia r0!, {d0-d15}\n" \ + FPU_EXIT_VFP_D32 \ + "vmsr FPEXC, r1\n" \ + "vmsr FPSCR, r2\n" \ + "add sp, %[frame_fpu_size]\n" /* size includes alignment size */ +#else /* ARM_MULTILIB_VFP */ +#define EXCEPTION_EXIT_FPU(frame_fpu_size) +#endif /* ARM_MULTILIB_VFP */ + +/** + * Exception exit. + * + * The thread is to be resumed so we are still in the thread's mode. Copy the + * exception frame from the thread's stack back to the exception's stack and + * restore the thread's context before returning from the exception to the + * thread. + * + * Note, the code currently assumes cp15 has been set up to match the + * instruction set being used. + */ +#define EXCEPTION_EXIT_THREAD(_frame) \ + __asm__ volatile( \ + ASM_ARM_MODE \ + "mov r0, %[i_frame]\n" /* get the frame */ \ + "ldr r0, [r0, %[frame_fpu]]\n" /* recover FPU frame pointer */ \ + EXCEPTION_EXIT_FPU(frame_fpu_size) \ + "ldr r2, [sp, %[frame_cpsr]]\n" /* recover exc CPSR from thread */ \ + "mov r0, sp\n" /* get the thread frame pointer */ \ + "msr cpsr, r2\n" /* switch back to the exc's context */ \ + "add r3, sp, #1\n" /* get the end */ \ + "sub sp, %[frame_size]\n" /* alloc the frame */ \ + "sub r4, sp, #1\n" /* destination address */ \ + "sub r5, r0, #1\n" /* source address */ \ + "1:\n" \ + "ldrb r1, [r5, #1]!\n" /* get a byte */ \ + "strb r1, [r4, #1]!\n" /* put the byte */ \ + "cmp r3, r4\n" /* the end? */ \ + "bne 1b\n" \ + "add r1, r0, %[r0_r12_size]\n" /* get the sp in the frame */ \ + "ldm r1, {r3-r6}\n" /* recover from the frame */ \ + "orr r1, r6, %[psr_i]\n" /* get the thread's psr and mask irqs */ \ + "msr cpsr, r1\n" /* switch to user mode */ \ + "mov sp, r3\n" /* set the stack pointer */ \ + "mov lr, r4\n" /* set the link reg */ \ + "msr cpsr, r2\n" /* switch back to the exc's context */ \ + "msr spsr, r6\n" /* set the thread's CPSR */ \ + "sub sp, #4\n" \ + "mov lr, r5\n" /* get the PC */ \ + "str lr, [sp]\n" /* save the link reg */ \ + ASM_THUMB_MODE \ + : [arm_switch_reg] "=&r" (arm_switch_reg) \ + : [psr_i] "i" (ARM_PSR_I), \ + [r0_r12_size] "i" (13 * sizeof(uint32_t)), \ + [frame_cpsr] "i" (EXCEPTION_FRAME_SIZE - sizeof(uint32_t)), \ + [frame_size] "i" (EXCEPTION_FRAME_SIZE), \ + [frame_fpu] "i" (EXCEPTION_FRAME_FPU_OFFSET), \ + [frame_fpu_size] "i" (EXCEPTION_FRAME_FPU_SIZE + 4), \ + [i_frame] "r" (_frame) \ + : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "memory") + +#define EXCEPTION_EXIT_EXC() \ + __asm__ volatile( \ + ASM_ARM_MODE \ + "ldr lr, [sp]\n" /* recover the link reg */ \ + "add sp, #4\n" \ + "ldm sp, {r0-r12}\n" /* restore the trhead's context */ \ + "add sp, %[frame_size]\n" /* free the frame */ \ + "subs pc, lr, #0\n" /* return from the exc */ \ + : \ + : [frame_size] "i" (EXCEPTION_FRAME_SIZE) \ + : "memory") + + +static void __attribute__((naked)) +target_exception_undefined_instruction(void) +{ + CPU_Exception_frame* frame; + ARM_SWITCH_REG; + EXCEPTION_ENTRY_EXC(); + arm_debug_break_unload(); + EXCEPTION_ENTRY_THREAD(frame); + frame->vector = 1; + target_exception(frame); + EXCEPTION_EXIT_THREAD(frame); + arm_debug_break_load(); + EXCEPTION_EXIT_EXC(); +} + +static void __attribute__((naked)) +target_exception_supervisor_call(void) +{ + CPU_Exception_frame* frame; + ARM_SWITCH_REG; + /* + * The PC offset needs to be review so we move past a svc instruction. This + * can then used as a user breakpoint. The issue is this exception is used by + * the BKPT instruction in the prefetch abort handler to signal a TRAP. + */ + EXCEPTION_ENTRY_EXC(); + arm_debug_break_unload(); + EXCEPTION_ENTRY_THREAD(frame); + frame->vector = 2; + target_exception(frame); + EXCEPTION_EXIT_THREAD(frame); + arm_debug_break_load(); + EXCEPTION_EXIT_EXC(); +} + +static void __attribute__((naked)) +target_exception_prefetch_abort(void) +{ + CPU_Exception_frame* frame; + ARM_SWITCH_REG; + EXCEPTION_ENTRY_EXC(); + arm_debug_break_unload(); + EXCEPTION_ENTRY_THREAD(frame); + if ((arm_cp15_get_instruction_fault_status() & 0x1f) == 0x02) + frame->vector = 2; + else + frame->vector = 3; + target_exception(frame); + EXCEPTION_EXIT_THREAD(frame); + arm_debug_break_load(); + EXCEPTION_EXIT_EXC(); +} + +static void __attribute__((naked)) +target_exception_data_abort(void) +{ + CPU_Exception_frame* frame; + ARM_SWITCH_REG; + EXCEPTION_ENTRY_EXC(); + arm_debug_break_unload(); + EXCEPTION_ENTRY_THREAD(frame); + frame->vector = 4; + target_exception(frame); + EXCEPTION_EXIT_THREAD(frame); + arm_debug_break_load(); + EXCEPTION_EXIT_EXC(); +} + +int +rtems_debugger_target_enable(void) +{ + rtems_interrupt_lock_context lock_context; + void* text_begin; + void* text_end; + debug_session_active = true; + text_begin = &bsp_section_text_begin[0]; + text_end = &bsp_section_text_end[0]; + rtems_interrupt_lock_acquire(&target_lock, &lock_context); + arm_debug_break_unload(); + arm_debug_break_clear(); + text_section_flags = + arm_cp15_set_translation_table_entries(text_begin, + text_end, + ARMV7_MMU_DATA_READ_WRITE_CACHED); + arm_cp15_set_exception_handler(ARM_EXCEPTION_UNDEF, + target_exception_undefined_instruction); + arm_cp15_set_exception_handler(ARM_EXCEPTION_SWI, + target_exception_supervisor_call); + arm_cp15_set_exception_handler(ARM_EXCEPTION_PREF_ABORT, + target_exception_prefetch_abort); + arm_cp15_set_exception_handler(ARM_EXCEPTION_DATA_ABORT, + target_exception_data_abort); + rtems_interrupt_lock_release(&target_lock, &lock_context); + return 0; +} + +int +rtems_debugger_target_disable(void) +{ + rtems_interrupt_lock_context lock_context; +#if DOES_NOT_WORK + void* text_begin; + void* text_end; +#endif + rtems_interrupt_lock_acquire(&target_lock, &lock_context); + debug_session_active = false; +#if DOES_NOT_WORK + text_begin = &bsp_section_text_begin[0]; + text_end = &bsp_section_text_end[0]; + arm_cp15_set_translation_table_entries(text_begin, + text_end, + text_section_flags); +#endif + rtems_interrupt_lock_release(&target_lock, &lock_context); + return 0; +} + +int +rtems_debugger_target_read_regs(rtems_debugger_thread* thread) +{ + if (!rtems_debugger_thread_flag(thread, + RTEMS_DEBUGGER_THREAD_FLAG_REG_VALID)) { + static const uint32_t good_address = (uint32_t) &good_address; + uint32_t* regs = &thread->registers[0]; + int i; + + for (i = 0; i < RTEMS_DEBUGGER_NUMREGS; ++i) + regs[i] = (uint32_t) &good_address; + + if (thread->frame) { + CPU_Exception_frame* frame = thread->frame; + + /* + * Assume interrupts are not masked and if masked set them to the saved + * value. + */ + FRAME_SR &= ~CPSR_INTS_MASK; + + if (rtems_debugger_thread_flag(thread, + RTEMS_DEBUGGER_THREAD_FLAG_INTS_DISABLED)) { + FRAME_SR |= + (thread->flags >> RTEMS_DEBUGGER_THREAD_FLAG_TARGET_BASE) & CPSR_INTS_MASK; + thread->flags = ~RTEMS_DEBUGGER_THREAD_FLAG_INTS_DISABLED; + } + + regs[REG_R0] = frame->register_r0; + regs[REG_R1] = frame->register_r1; + regs[REG_R2] = frame->register_r2; + regs[REG_R3] = frame->register_r3; + regs[REG_R4] = frame->register_r4; + regs[REG_R5] = frame->register_r5; + regs[REG_R6] = frame->register_r6; + regs[REG_R7] = frame->register_r7; + regs[REG_R8] = frame->register_r8; + regs[REG_R9] = frame->register_r9; + regs[REG_R10] = frame->register_r10; + regs[REG_R11] = frame->register_r11; + regs[REG_R12] = frame->register_r12; + regs[REG_SP] = frame->register_sp; + regs[REG_LR] = (uint32_t) frame->register_lr; + regs[REG_PC] = (uint32_t) frame->register_pc; + regs[REG_CPSR] = FRAME_SR; + /* + * Get the signal from the frame. + */ + thread->signal = rtems_debugger_target_exception_to_signal(frame); + } + else { +#if defined(ARM_MULTILIB_ARCH_V4) + regs[REG_R4] = thread->tcb->Registers.register_r4; + regs[REG_R5] = thread->tcb->Registers.register_r5; + regs[REG_R6] = thread->tcb->Registers.register_r6; + regs[REG_R7] = thread->tcb->Registers.register_r7; + regs[REG_R8] = thread->tcb->Registers.register_r8; + regs[REG_R9] = thread->tcb->Registers.register_r9; + regs[REG_R10] = thread->tcb->Registers.register_r10; + regs[REG_R11] = thread->tcb->Registers.register_fp; + regs[REG_LR] = (intptr_t) thread->tcb->Registers.register_lr; + regs[REG_PC] = (intptr_t) thread->tcb->Registers.register_lr; + regs[REG_SP] = (intptr_t) thread->tcb->Registers.register_sp; +#elif defined(ARM_MULTILIB_ARCH_V7M) + regs[REG_R4] = thread->tcb->Registers.register_r4; + regs[REG_R5] = thread->tcb->Registers.register_r5; + regs[REG_R6] = thread->tcb->Registers.register_r6; + regs[REG_R7] = thread->tcb->Registers.register_r7; + regs[REG_R8] = thread->tcb->Registers.register_r8; + regs[REG_R9] = thread->tcb->Registers.register_r9; + regs[REG_R10] = thread->tcb->Registers.register_r10; + regs[REG_R11] = thread->tcb->Registers.register_r11; + regs[REG_LR] = (intptr_t) thread->tcb->Registers.register_lr; + regs[REG_PC] = (intptr_t) thread->tcb->Registers.register_lr; + regs[REG_SP] = (intptr_t) thread->tcb->Registers.register_sp; +#endif + /* + * Blocked threads have no signal. + */ + thread->signal = 0; + } + + thread->flags |= RTEMS_DEBUGGER_THREAD_FLAG_REG_VALID; + thread->flags &= ~RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY; + } + + return 0; +} + +int +rtems_debugger_target_write_regs(rtems_debugger_thread* thread) +{ + if (rtems_debugger_thread_flag(thread, + RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY)) { + uint32_t* regs = &thread->registers[0]; + + /* + * Only write to debugger controlled threads. Do not touch the registers + * for threads blocked in the context switcher. + */ + if (rtems_debugger_thread_flag(thread, + RTEMS_DEBUGGER_THREAD_FLAG_DEBUGGING)) { + CPU_Exception_frame* frame = thread->frame; + frame->register_r0 = regs[REG_R0]; + frame->register_r1 = regs[REG_R1]; + frame->register_r2 = regs[REG_R2]; + frame->register_r3 = regs[REG_R3]; + frame->register_r4 = regs[REG_R4]; + frame->register_r5 = regs[REG_R5]; + frame->register_r6 = regs[REG_R6]; + frame->register_r7 = regs[REG_R7]; + frame->register_r8 = regs[REG_R8]; + frame->register_r9 = regs[REG_R9]; + frame->register_r10 = regs[REG_R10]; + frame->register_r11 = regs[REG_R11]; + frame->register_r12 = regs[REG_R12]; + frame->register_sp = regs[REG_SP]; + frame->register_lr = (void*) regs[REG_LR]; + frame->register_pc = (void*) regs[REG_PC]; + FRAME_SR = regs[REG_CPSR]; + } + thread->flags &= ~RTEMS_DEBUGGER_THREAD_FLAG_REG_DIRTY; + } + return 0; +} + +DB_UINT +rtems_debugger_target_reg_pc(rtems_debugger_thread* thread) +{ + int r; + r = rtems_debugger_target_read_regs(thread); + if (r >= 0) { + uint32_t* regs = &thread->registers[0]; + return regs[REG_PC]; + } + return 0; +} + +DB_UINT +rtems_debugger_target_frame_pc(CPU_Exception_frame* frame) +{ + return (DB_UINT) frame->register_pc; +} + +DB_UINT +rtems_debugger_target_reg_sp(rtems_debugger_thread* thread) +{ + int r; + r = rtems_debugger_target_read_regs(thread); + if (r >= 0) { + uint32_t* regs = &thread->registers[0]; + return regs[REG_SP]; + } + return 0; +} + +DB_UINT +rtems_debugger_target_tcb_sp(rtems_debugger_thread* thread) +{ + return (DB_UINT) thread->tcb->Registers.register_sp; +} + +int +rtems_debugger_target_thread_stepping(rtems_debugger_thread* thread) +{ + if (rtems_debugger_thread_flag(thread, + (RTEMS_DEBUGGER_THREAD_FLAG_STEP | + RTEMS_DEBUGGER_THREAD_FLAG_STEPPING))) { + /* + * Single stepping and range stepping uses hardware debug breakpoint + * 0. This is reserved for single stepping. + */ + CPU_Exception_frame* frame = thread->frame; + arm_debug_hwbreak* bp = &hw_breaks[0]; + int i; + for (i = 0; i < hw_breakpoints; ++i, ++bp) { + if (!bp->enabled) { + const uint32_t addr = (intptr_t) frame->register_pc; + const bool thumb = (FRAME_SR & (1 << 5)) != 0 ? true : false; + uint32_t bas; + + bp->enabled = true; + bp->loaded = false; + bp->address = frame->register_pc; + bp->frame = frame; + bp->length = sizeof(uint32_t); + + if (thumb) { + uint16_t instr = *((uint16_t*) frame->register_pc); + switch (instr & 0xf800) { + case 0xe800: + case 0xf000: + case 0xf800: + break; + default: + bp->length = sizeof(uint16_t); + break; + } + } + + /* + * See table C3-2 Effect of byte address selection on Breakpoint + * generation and "Instruction address comparisoin programming + * examples. + */ + if (thumb) { + if ((addr & (1 << 1)) == 0) { + bas = 0x3; /* b0011 */ + } + else { + bas = 0xc; /* b1100 */ + } + } + else { + bas = 0xf; /* b1111 */ + } + + arm_debug_break_setup(bp, + addr & ~0x3, + ARM_HW_BP_UNLINKED_INSTR_MISMATCH, + bas, + ARM_HW_BP_PRIV_PL0_SUP_SYS); + + /* + * Save the interrupt state before stepping if set. + */ + if ((FRAME_SR & CPSR_INTS_MASK) != 0) { + uint32_t int_state; + int_state = + (frame->register_cpsr & CPSR_INTS_MASK) << RTEMS_DEBUGGER_THREAD_FLAG_TARGET_BASE; + thread->flags |= RTEMS_DEBUGGER_THREAD_FLAG_INTS_DISABLED | int_state; + } + /* + * Mask the interrupt when stepping. + */ + FRAME_SR |= CPSR_INTS_MASK; + break; + } + } + } + return 0; +} + +int +rtems_debugger_target_exception_to_signal(CPU_Exception_frame* frame) +{ + int sig = RTEMS_DEBUGGER_SIGNAL_HUP; + switch (frame->vector) { + case ARM_EXCEPTION_RESET: + case ARM_EXCEPTION_SWI: + sig = RTEMS_DEBUGGER_SIGNAL_TRAP; + break; + case ARM_EXCEPTION_UNDEF: + sig = RTEMS_DEBUGGER_SIGNAL_ILL; + break; + case ARM_EXCEPTION_FIQ: + sig = RTEMS_DEBUGGER_SIGNAL_FPE; + break; + case ARM_EXCEPTION_PREF_ABORT: + case ARM_EXCEPTION_DATA_ABORT: + sig = RTEMS_DEBUGGER_SIGNAL_SEGV; + break; + case ARM_EXCEPTION_RESERVED: + case ARM_EXCEPTION_IRQ: + sig = RTEMS_DEBUGGER_SIGNAL_BUS; + break; + default: + break; + } + return sig; +} + +int +rtems_debugger_target_hwbreak_control(rtems_debugger_target_watchpoint wp, + bool insert, + DB_UINT addr, + DB_UINT kind) +{ + /* + * To do. + */ + return 0; +} + +int +rtems_debugger_target_cache_sync(rtems_debugger_target_swbreak* swbreak) +{ + /* + * Flush the data cache and invalidate the instruction cache. + */ + rtems_cache_flush_multiple_data_lines(swbreak->address, + sizeof(breakpoint)); + rtems_cache_invalidate_multiple_instruction_lines(swbreak->address, + sizeof(breakpoint)); + return 0; +} |