/*
* 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 <bsp/linker-symbols.h>
#include "rtems-debugger-target.h"
#include "rtems-debugger-threads.h"
#if TARGET_DEBUG
#include <rtems/bspIo.h>
#endif
/*
* ARM Variant controls.
*/
#if defined(__ARM_ARCH_7A__) || \
defined(__ARM_ARCH_7R__)
#define ARM_CP15 1
#endif
#if (defined(__ARM_ARCH_7M__) || \
defined(__ARM_ARCH_7EM__))
#define ARM_THUMB_ONLY 1
#else
#define ARM_THUMB_ONLY 0
#endif
#if defined(ARM_MULTILIB_ARCH_V4)
#define ARM_PSR_HAS_INT_MASK 1
#define ARM_PSR_HAS_THUMB 1
#else
#define ARM_PSR_HAS_INT_MASK 0
#define ARM_PSR_HAS_THUMB 0
#endif
#if ARM_CP15
#include <libcpu/arm-cp15.h>
#endif
/**
* If thumb build of code switch the asm to thumb as required.
*
* If the variant only supports thumb insturctions disable the support.
*/
#define ARM_SWITCH_REG uint32_t arm_switch_reg
#define ARM_SWITCH_REG_ASM [arm_switch_reg] "=&r" (arm_switch_reg)
#if !ARM_THUMB_ONLY && defined(__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"
#else
#define ASM_ARM_MODE
#define ASM_THUMB_MODE
#endif
/*
* Hack to work around ARMv7-M not having a the T and I bits in the PSR.
*
* This needs to be fixed when real support for this ARM variant is added.
*/
#if !defined(ARM_PSR_I)
#define ARM_PSR_I 0
#endif
#if !defined(ARM_PSR_T)
#define ARM_PSR_T 0
#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, 0xe0, 0x20, 0xe1 };
#endif
/**
* Target lock.
*/
RTEMS_INTERRUPT_LOCK_DEFINE(static, target_lock, "target_lock")
/**
* 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_REG; \
asm volatile( \
ASM_ARM_MODE \
ARM_CP14_INSTR(mcr, val, _CRn, _CRm, _opc2) \
ASM_THUMB_MODE \
: ARM_SWITCH_REG_ASM \
: [val] "r" (_val)); \
} while (0)
#define ARM_CP14_READ(_val, _CRn, _CRm, _opc2) \
do { \
ARM_SWITCH_REG; \
asm volatile( \
ASM_ARM_MODE \
ARM_CP14_INSTR(mrc, val, _CRn, _CRm, _opc2) \
ASM_THUMB_MODE \
: ARM_SWITCH_REG_ASM, \
[val] "=&r" (_val)); \
} 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);
}
/**
* 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_ASM \
: [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) \
"bic r1, r1, %[psr_i]\n" /* clear irq mask, debug checks */ \
"msr cpsr, r1\n" /* restore the state with irq mask clear */ \
ASM_THUMB_MODE \
: ARM_SWITCH_REG_ASM, \
[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_ASM \
: [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
if ((arm_cp15_get_instruction_fault_status() & 0x1f) == 0x02)
frame->vector = 2;
else
frame->vector = 3;
#else
frame->vector = 3;
#endif
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();
}
#if ARM_CP15
/**
* The init value for the text section.
*/
static uint32_t text_section_flags;
static void
rtems_debugger_target_set_vectors(void)
{
void* text_begin;
void* text_end;
text_begin = &bsp_section_text_begin[0];
text_end = &bsp_section_text_end[0];
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);
}
#else
static void
rtems_debugger_target_set_vectors(void)
{
/*
* Dummy, please add support for your ARM variant.
*/
void* ui = target_exception_undefined_instruction;
void* sc = target_exception_supervisor_call;
void* pa = target_exception_prefetch_abort;
void* da = target_exception_data_abort;
(void) ui;
(void) sc;
(void) pa;
(void) da;
}
#endif
int
rtems_debugger_target_enable(void)
{
rtems_interrupt_lock_context lock_context;
debug_session_active = true;
rtems_interrupt_lock_acquire(&target_lock, &lock_context);
arm_debug_break_unload();
arm_debug_break_clear();
rtems_debugger_target_set_vectors();
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 ARM_PSR_HAS_INT_MASK
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;
#endif
break;
}
}
}
return 0;
}
int
rtems_debugger_target_exception_to_signal(CPU_Exception_frame* frame)
{
int sig = RTEMS_DEBUGGER_SIGNAL_HUP;
#if defined(ARM_EXCEPTION_RESET)
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;
}
#endif
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;
}