989 lines
21 KiB
C
989 lines
21 KiB
C
/*
|
|
* Copyright (c) 2021 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <kernel_internal.h>
|
|
#include <zephyr/toolchain.h>
|
|
#include <zephyr/debug/gdbstub.h>
|
|
|
|
#include <xtensa_asm2_context.h>
|
|
#include <xtensa/corebits.h>
|
|
|
|
static bool not_first_break;
|
|
|
|
extern struct gdb_ctx xtensa_gdb_ctx;
|
|
|
|
/*
|
|
* Special register number (from specreg.h).
|
|
*
|
|
* These should be the same across different Xtensa SoCs.
|
|
*/
|
|
enum {
|
|
LBEG = 0,
|
|
LEND = 1,
|
|
LCOUNT = 2,
|
|
SAR = 3,
|
|
SCOMPARE1 = 12,
|
|
WINDOWBASE = 72,
|
|
WINDOWSTART = 73,
|
|
IBREAKENABLE = 96,
|
|
MEMCTL = 97,
|
|
ATOMCTL = 99,
|
|
IBREAKA0 = 128,
|
|
IBREAKA1 = 129,
|
|
CONFIGID0 = 176,
|
|
EPC_1 = 177,
|
|
EPC_2 = 178,
|
|
EPC_3 = 179,
|
|
EPC_4 = 180,
|
|
EPC_5 = 181,
|
|
EPC_6 = 182,
|
|
EPC_7 = 183,
|
|
DEPC = 192,
|
|
EPS_2 = 194,
|
|
EPS_3 = 195,
|
|
EPS_4 = 196,
|
|
EPS_5 = 197,
|
|
EPS_6 = 198,
|
|
EPS_7 = 199,
|
|
CONFIGID1 = 208,
|
|
EXCSAVE_1 = 209,
|
|
EXCSAVE_2 = 210,
|
|
EXCSAVE_3 = 211,
|
|
EXCSAVE_4 = 212,
|
|
EXCSAVE_5 = 213,
|
|
EXCSAVE_6 = 214,
|
|
EXCSAVE_7 = 215,
|
|
CPENABLE = 224,
|
|
INTERRUPT = 226,
|
|
INTENABLE = 228,
|
|
PS = 230,
|
|
THREADPTR = 231,
|
|
EXCCAUSE = 232,
|
|
DEBUGCAUSE = 233,
|
|
CCOUNT = 234,
|
|
PRID = 235,
|
|
ICOUNT = 236,
|
|
ICOUNTLEVEL = 237,
|
|
EXCVADDR = 238,
|
|
CCOMPARE_0 = 240,
|
|
CCOMPARE_1 = 241,
|
|
CCOMPARE_2 = 242,
|
|
MISC_REG_0 = 244,
|
|
MISC_REG_1 = 245,
|
|
MISC_REG_2 = 246,
|
|
MISC_REG_3 = 247,
|
|
};
|
|
|
|
#define get_one_sreg(regnum_p) ({ \
|
|
unsigned int retval; \
|
|
__asm__ volatile( \
|
|
"rsr %[retval], %[regnum]\n\t" \
|
|
: [retval] "=r" (retval) \
|
|
: [regnum] "i" (regnum_p)); \
|
|
retval; \
|
|
})
|
|
|
|
#define set_one_sreg(regnum_p, regval) { \
|
|
__asm__ volatile( \
|
|
"wsr %[val], %[regnum]\n\t" \
|
|
:: \
|
|
[val] "r" (regval), \
|
|
[regnum] "i" (regnum_p)); \
|
|
}
|
|
|
|
/**
|
|
* Read one special register.
|
|
*
|
|
* @param ctx GDB context
|
|
* @param reg Register descriptor
|
|
*/
|
|
static void read_sreg(struct gdb_ctx *ctx, struct xtensa_register *reg)
|
|
{
|
|
uint8_t regno;
|
|
uint32_t val;
|
|
bool has_val = true;
|
|
|
|
if (!gdb_xtensa_is_special_reg(reg)) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Special registers have 0x300 added to the register number
|
|
* in the register descriptor. So need to extract the actual
|
|
* special register number recognized by architecture,
|
|
* which is 0-255.
|
|
*/
|
|
regno = reg->regno & 0xFF;
|
|
|
|
/*
|
|
* Each special register has to be done separately
|
|
* as the register number in RSR/WSR needs to be
|
|
* hard-coded at compile time.
|
|
*/
|
|
switch (regno) {
|
|
case SAR:
|
|
val = get_one_sreg(SAR);
|
|
break;
|
|
case PS:
|
|
val = get_one_sreg(PS);
|
|
break;
|
|
case MEMCTL:
|
|
val = get_one_sreg(MEMCTL);
|
|
break;
|
|
case ATOMCTL:
|
|
val = get_one_sreg(ATOMCTL);
|
|
break;
|
|
case CONFIGID0:
|
|
val = get_one_sreg(CONFIGID0);
|
|
break;
|
|
case CONFIGID1:
|
|
val = get_one_sreg(CONFIGID1);
|
|
break;
|
|
case DEBUGCAUSE:
|
|
val = get_one_sreg(DEBUGCAUSE);
|
|
break;
|
|
case EXCCAUSE:
|
|
val = get_one_sreg(EXCCAUSE);
|
|
break;
|
|
case DEPC:
|
|
val = get_one_sreg(DEPC);
|
|
break;
|
|
case EPC_1:
|
|
val = get_one_sreg(EPC_1);
|
|
break;
|
|
case EXCSAVE_1:
|
|
val = get_one_sreg(EXCSAVE_1);
|
|
break;
|
|
case EXCVADDR:
|
|
val = get_one_sreg(EXCVADDR);
|
|
break;
|
|
#if XCHAL_HAVE_LOOPS
|
|
case LBEG:
|
|
val = get_one_sreg(LBEG);
|
|
break;
|
|
case LEND:
|
|
val = get_one_sreg(LEND);
|
|
break;
|
|
case LCOUNT:
|
|
val = get_one_sreg(LCOUNT);
|
|
break;
|
|
#endif
|
|
#if XCHAL_HAVE_S32C1I
|
|
case SCOMPARE1:
|
|
val = get_one_sreg(SCOMPARE1);
|
|
break;
|
|
#endif
|
|
#if XCHAL_HAVE_WINDOWED
|
|
case WINDOWBASE:
|
|
val = get_one_sreg(WINDOWBASE);
|
|
break;
|
|
case WINDOWSTART:
|
|
val = get_one_sreg(WINDOWSTART);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_INTLEVELS > 0
|
|
case EPS_2:
|
|
val = get_one_sreg(EPS_2);
|
|
break;
|
|
case EPC_2:
|
|
val = get_one_sreg(EPC_2);
|
|
break;
|
|
case EXCSAVE_2:
|
|
val = get_one_sreg(EXCSAVE_2);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_INTLEVELS > 1
|
|
case EPS_3:
|
|
val = get_one_sreg(EPS_3);
|
|
break;
|
|
case EPC_3:
|
|
val = get_one_sreg(EPC_3);
|
|
break;
|
|
case EXCSAVE_3:
|
|
val = get_one_sreg(EXCSAVE_3);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_INTLEVELS > 2
|
|
case EPC_4:
|
|
val = get_one_sreg(EPC_4);
|
|
break;
|
|
case EPS_4:
|
|
val = get_one_sreg(EPS_4);
|
|
break;
|
|
case EXCSAVE_4:
|
|
val = get_one_sreg(EXCSAVE_4);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_INTLEVELS > 3
|
|
case EPC_5:
|
|
val = get_one_sreg(EPC_5);
|
|
break;
|
|
case EPS_5:
|
|
val = get_one_sreg(EPS_5);
|
|
break;
|
|
case EXCSAVE_5:
|
|
val = get_one_sreg(EXCSAVE_5);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_INTLEVELS > 4
|
|
case EPC_6:
|
|
val = get_one_sreg(EPC_6);
|
|
break;
|
|
case EPS_6:
|
|
val = get_one_sreg(EPS_6);
|
|
break;
|
|
case EXCSAVE_6:
|
|
val = get_one_sreg(EXCSAVE_6);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_INTLEVELS > 5
|
|
case EPC_7:
|
|
val = get_one_sreg(EPC_7);
|
|
break;
|
|
case EPS_7:
|
|
val = get_one_sreg(EPS_7);
|
|
break;
|
|
case EXCSAVE_7:
|
|
val = get_one_sreg(EXCSAVE_7);
|
|
break;
|
|
#endif
|
|
#if XCHAL_HAVE_CP
|
|
case CPENABLE:
|
|
val = get_one_sreg(CPENABLE);
|
|
break;
|
|
#endif
|
|
#if XCHAL_HAVE_INTERRUPTS
|
|
case INTERRUPT:
|
|
val = get_one_sreg(INTERRUPT);
|
|
break;
|
|
case INTENABLE:
|
|
val = get_one_sreg(INTENABLE);
|
|
break;
|
|
#endif
|
|
#if XCHAL_HAVE_THREADPTR
|
|
case THREADPTR:
|
|
val = get_one_sreg(THREADPTR);
|
|
break;
|
|
#endif
|
|
#if XCHAL_HAVE_CCOUNT
|
|
case CCOUNT:
|
|
val = get_one_sreg(CCOUNT);
|
|
break;
|
|
#endif
|
|
#if XCHAL_HAVE_PRID
|
|
case PRID:
|
|
val = get_one_sreg(PRID);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_TIMERS > 0
|
|
case CCOMPARE_0:
|
|
val = get_one_sreg(CCOMPARE_0);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_TIMERS > 1
|
|
case CCOMPARE_1:
|
|
val = get_one_sreg(CCOMPARE_1);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_TIMERS > 2
|
|
case CCOMPARE_2:
|
|
val = get_one_sreg(CCOMPARE_2);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_MISC_REGS > 0
|
|
case MISC_REG_0:
|
|
val = get_one_sreg(MISC_REG_0);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_MISC_REGS > 1
|
|
case MISC_REG_1:
|
|
val = get_one_sreg(MISC_REG_1);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_MISC_REGS > 2
|
|
case MISC_REG_2:
|
|
val = get_one_sreg(MISC_REG_2);
|
|
break;
|
|
#endif
|
|
#if XCHAL_NUM_MISC_REGS > 3
|
|
case MISC_REG_3:
|
|
val = get_one_sreg(MISC_REG_3);
|
|
break;
|
|
#endif
|
|
default:
|
|
has_val = false;
|
|
break;
|
|
}
|
|
|
|
if (has_val) {
|
|
reg->val = val;
|
|
reg->seqno = ctx->seqno;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Translate exception into GDB exception reason.
|
|
*
|
|
* @param reason Reason for exception
|
|
*/
|
|
static unsigned int get_gdb_exception_reason(unsigned int reason)
|
|
{
|
|
unsigned int exception;
|
|
|
|
switch (reason) {
|
|
case EXCCAUSE_ILLEGAL:
|
|
/* illegal instruction */
|
|
exception = GDB_EXCEPTION_INVALID_INSTRUCTION;
|
|
break;
|
|
case EXCCAUSE_INSTR_ERROR:
|
|
/* instr fetch error */
|
|
exception = GDB_EXCEPTION_MEMORY_FAULT;
|
|
break;
|
|
case EXCCAUSE_LOAD_STORE_ERROR:
|
|
/* load/store error */
|
|
exception = GDB_EXCEPTION_MEMORY_FAULT;
|
|
break;
|
|
case EXCCAUSE_DIVIDE_BY_ZERO:
|
|
/* divide by zero */
|
|
exception = GDB_EXCEPTION_DIVIDE_ERROR;
|
|
break;
|
|
case EXCCAUSE_UNALIGNED:
|
|
/* load/store alignment */
|
|
exception = GDB_EXCEPTION_MEMORY_FAULT;
|
|
break;
|
|
case EXCCAUSE_INSTR_DATA_ERROR:
|
|
/* instr PIF data error */
|
|
exception = GDB_EXCEPTION_MEMORY_FAULT;
|
|
break;
|
|
case EXCCAUSE_LOAD_STORE_DATA_ERROR:
|
|
/* load/store PIF data error */
|
|
exception = GDB_EXCEPTION_MEMORY_FAULT;
|
|
break;
|
|
case EXCCAUSE_INSTR_ADDR_ERROR:
|
|
/* instr PIF addr error */
|
|
exception = GDB_EXCEPTION_MEMORY_FAULT;
|
|
break;
|
|
case EXCCAUSE_LOAD_STORE_ADDR_ERROR:
|
|
/* load/store PIF addr error */
|
|
exception = GDB_EXCEPTION_MEMORY_FAULT;
|
|
break;
|
|
case EXCCAUSE_INSTR_PROHIBITED:
|
|
/* inst fetch prohibited */
|
|
exception = GDB_EXCEPTION_INVALID_MEMORY;
|
|
break;
|
|
case EXCCAUSE_LOAD_STORE_RING:
|
|
/* load/store privilege */
|
|
exception = GDB_EXCEPTION_INVALID_MEMORY;
|
|
break;
|
|
case EXCCAUSE_LOAD_PROHIBITED:
|
|
/* load prohibited */
|
|
exception = GDB_EXCEPTION_INVALID_MEMORY;
|
|
break;
|
|
case EXCCAUSE_STORE_PROHIBITED:
|
|
/* store prohibited */
|
|
exception = GDB_EXCEPTION_INVALID_MEMORY;
|
|
break;
|
|
case EXCCAUSE_CP0_DISABLED:
|
|
__fallthrough;
|
|
case EXCCAUSE_CP1_DISABLED:
|
|
__fallthrough;
|
|
case EXCCAUSE_CP2_DISABLED:
|
|
__fallthrough;
|
|
case EXCCAUSE_CP3_DISABLED:
|
|
__fallthrough;
|
|
case EXCCAUSE_CP4_DISABLED:
|
|
__fallthrough;
|
|
case EXCCAUSE_CP5_DISABLED:
|
|
__fallthrough;
|
|
case EXCCAUSE_CP6_DISABLED:
|
|
__fallthrough;
|
|
case EXCCAUSE_CP7_DISABLED:
|
|
/* coprocessor disabled */
|
|
exception = GDB_EXCEPTION_INVALID_INSTRUCTION;
|
|
break;
|
|
default:
|
|
exception = GDB_EXCEPTION_MEMORY_FAULT;
|
|
break;
|
|
}
|
|
|
|
return exception;
|
|
}
|
|
|
|
/**
|
|
* Copy debug information from stack into GDB context.
|
|
*
|
|
* This copies the information stored in the stack into the GDB
|
|
* context for the thread being debugged.
|
|
*
|
|
* @param ctx GDB context
|
|
* @param stack Pointer to the stack frame
|
|
*/
|
|
static void copy_to_ctx(struct gdb_ctx *ctx, const struct arch_esf *stack)
|
|
{
|
|
struct xtensa_register *reg;
|
|
int idx, num_laddr_regs;
|
|
|
|
uint32_t *bsa = *(int **)stack;
|
|
|
|
if ((int *)bsa - stack > 4) {
|
|
num_laddr_regs = 8;
|
|
} else if ((int *)bsa - stack > 8) {
|
|
num_laddr_regs = 12;
|
|
} else if ((int *)bsa - stack > 12) {
|
|
num_laddr_regs = 16;
|
|
} else {
|
|
num_laddr_regs = 4;
|
|
}
|
|
|
|
/* Get logical address registers A0 - A<num_laddr_regs> from stack */
|
|
for (idx = 0; idx < num_laddr_regs; idx++) {
|
|
reg = &xtensa_gdb_ctx.regs[xtensa_gdb_ctx.a0_idx + idx];
|
|
|
|
if (reg->regno == SOC_GDB_REGNO_A1) {
|
|
/* A1 is calculated */
|
|
reg->val = POINTER_TO_UINT(
|
|
((char *)bsa) + BASE_SAVE_AREA_SIZE);
|
|
reg->seqno = ctx->seqno;
|
|
} else {
|
|
reg->val = bsa[reg->stack_offset / 4];
|
|
reg->seqno = ctx->seqno;
|
|
}
|
|
}
|
|
|
|
/* For registers other than logical address registers */
|
|
for (idx = 0; idx < xtensa_gdb_ctx.num_regs; idx++) {
|
|
reg = &xtensa_gdb_ctx.regs[idx];
|
|
|
|
if (gdb_xtensa_is_logical_addr_reg(reg)) {
|
|
/* Logical address registers are handled above */
|
|
continue;
|
|
} else if (reg->stack_offset != 0) {
|
|
/* For those registers stashed in stack */
|
|
reg->val = bsa[reg->stack_offset / 4];
|
|
reg->seqno = ctx->seqno;
|
|
} else if (gdb_xtensa_is_special_reg(reg)) {
|
|
read_sreg(ctx, reg);
|
|
}
|
|
}
|
|
|
|
#if XCHAL_HAVE_WINDOWED
|
|
uint8_t a0_idx, ar_idx, wb_start;
|
|
|
|
wb_start = (uint8_t)xtensa_gdb_ctx.regs[xtensa_gdb_ctx.wb_idx].val;
|
|
|
|
/*
|
|
* Copied the logical registers A0-A15 to physical registers (AR*)
|
|
* according to WINDOWBASE.
|
|
*/
|
|
for (idx = 0; idx < num_laddr_regs; idx++) {
|
|
/* Index to register description array for A */
|
|
a0_idx = xtensa_gdb_ctx.a0_idx + idx;
|
|
|
|
/* Find the start of window (== WINDOWBASE * 4) */
|
|
ar_idx = wb_start * 4;
|
|
/* Which logical register we are working on... */
|
|
ar_idx += idx;
|
|
/* Wrap around A64 (or A32) -> A0 */
|
|
ar_idx %= XCHAL_NUM_AREGS;
|
|
/* Index to register description array for AR */
|
|
ar_idx += xtensa_gdb_ctx.ar_idx;
|
|
|
|
xtensa_gdb_ctx.regs[ar_idx].val = xtensa_gdb_ctx.regs[a0_idx].val;
|
|
xtensa_gdb_ctx.regs[ar_idx].seqno = xtensa_gdb_ctx.regs[a0_idx].seqno;
|
|
}
|
|
#endif
|
|
|
|
/* Disable stepping */
|
|
set_one_sreg(ICOUNT, 0);
|
|
set_one_sreg(ICOUNTLEVEL, 0);
|
|
__asm__ volatile("isync");
|
|
}
|
|
|
|
/**
|
|
* Restore debug information from stack into GDB context.
|
|
*
|
|
* This copies the information stored the GDB context back into
|
|
* the stack. So that the thread being debugged has new values
|
|
* after context switch from GDB stub back to the thread.
|
|
*
|
|
* @param ctx GDB context
|
|
* @param stack Pointer to the stack frame
|
|
*/
|
|
static void restore_from_ctx(struct gdb_ctx *ctx, const struct arch_esf *stack)
|
|
{
|
|
struct xtensa_register *reg;
|
|
int idx, num_laddr_regs;
|
|
|
|
_xtensa_irq_bsa_t *bsa = (void *)*(int **)stack;
|
|
|
|
if ((int *)bsa - stack > 4) {
|
|
num_laddr_regs = 8;
|
|
} else if ((int *)bsa - stack > 8) {
|
|
num_laddr_regs = 12;
|
|
} else if ((int *)bsa - stack > 12) {
|
|
num_laddr_regs = 16;
|
|
} else {
|
|
num_laddr_regs = 4;
|
|
}
|
|
|
|
/*
|
|
* Note that we don't need to copy AR* back to A* for
|
|
* windowed registers. GDB manipulates A0-A15 directly
|
|
* without going through AR*.
|
|
*/
|
|
|
|
/*
|
|
* Push values of logical address registers A0 - A<num_laddr_regs>
|
|
* back to stack.
|
|
*/
|
|
for (idx = 0; idx < num_laddr_regs; idx++) {
|
|
reg = &xtensa_gdb_ctx.regs[xtensa_gdb_ctx.a0_idx + idx];
|
|
|
|
if (reg->regno == SOC_GDB_REGNO_A1) {
|
|
/* Shouldn't be changing stack pointer */
|
|
continue;
|
|
} else {
|
|
bsa[reg->stack_offset / 4] = reg->val;
|
|
}
|
|
}
|
|
|
|
for (idx = 0; idx < xtensa_gdb_ctx.num_regs; idx++) {
|
|
reg = &xtensa_gdb_ctx.regs[idx];
|
|
|
|
if (gdb_xtensa_is_logical_addr_reg(reg)) {
|
|
/* Logical address registers are handled above */
|
|
continue;
|
|
} else if (reg->stack_offset != 0) {
|
|
/* For those registers stashed in stack */
|
|
bsa[reg->stack_offset / 4] = reg->val;
|
|
} else if (gdb_xtensa_is_special_reg(reg)) {
|
|
/*
|
|
* Currently not writing back any special
|
|
* registers.
|
|
*/
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!not_first_break) {
|
|
/*
|
|
* Need to go past the BREAK.N instruction (16-bit)
|
|
* in arch_gdb_init(). Or else the SoC will simply
|
|
* go back to execute the BREAK.N instruction,
|
|
* which raises debug interrupt, and we will be
|
|
* stuck in an infinite loop.
|
|
*/
|
|
bsa->pc += 2;
|
|
not_first_break = true;
|
|
}
|
|
}
|
|
|
|
void arch_gdb_continue(void)
|
|
{
|
|
/*
|
|
* No need to do anything. Simply let the GDB stub main
|
|
* loop to return from debug interrupt for code to
|
|
* continue running.
|
|
*/
|
|
}
|
|
|
|
void arch_gdb_step(void)
|
|
{
|
|
set_one_sreg(ICOUNT, 0xFFFFFFFEU);
|
|
set_one_sreg(ICOUNTLEVEL, XCHAL_DEBUGLEVEL);
|
|
__asm__ volatile("isync");
|
|
}
|
|
|
|
/**
|
|
* Convert a register value into hex string.
|
|
*
|
|
* Note that this assumes the output buffer always has enough
|
|
* space.
|
|
*
|
|
* @param reg Xtensa register
|
|
* @param hex Pointer to output buffer
|
|
* @return Number of bytes written to output buffer
|
|
*/
|
|
static size_t reg2hex(const struct xtensa_register *reg, char *hex)
|
|
{
|
|
uint8_t *bin = (uint8_t *)®->val;
|
|
size_t binlen = reg->byte_size;
|
|
|
|
for (size_t i = 0; i < binlen; i++) {
|
|
if (hex2char(bin[i] >> 4, &hex[2 * i]) < 0) {
|
|
return 0;
|
|
}
|
|
if (hex2char(bin[i] & 0xf, &hex[2 * i + 1]) < 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 2 * binlen;
|
|
}
|
|
|
|
size_t arch_gdb_reg_readall(struct gdb_ctx *ctx, uint8_t *buf, size_t buflen)
|
|
{
|
|
struct xtensa_register *reg;
|
|
int idx;
|
|
uint8_t *output;
|
|
size_t ret;
|
|
|
|
if (buflen < SOC_GDB_GPKT_HEX_SIZE) {
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Fill with 'x' to mark them as available since most registers
|
|
* are not available in the stack.
|
|
*/
|
|
memset(buf, 'x', SOC_GDB_GPKT_HEX_SIZE);
|
|
|
|
ret = 0;
|
|
for (idx = 0; idx < ctx->num_regs; idx++) {
|
|
reg = &ctx->regs[idx];
|
|
|
|
if (reg->seqno != ctx->seqno) {
|
|
/*
|
|
* Register struct has stale value from
|
|
* previous debug interrupt. Don't
|
|
* send it out.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if ((reg->gpkt_offset < 0) ||
|
|
(reg->gpkt_offset >= SOC_GDB_GPKT_BIN_SIZE)) {
|
|
/*
|
|
* Register is not in G-packet, or
|
|
* beyond maximum size of G-packet.
|
|
*
|
|
* xtensa-config.c may specify G-packet
|
|
* offset beyond what GDB expects, so
|
|
* need to make sure we won't write beyond
|
|
* the buffer.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
/* Two hex characters per byte */
|
|
output = &buf[reg->gpkt_offset * 2];
|
|
|
|
if (reg2hex(reg, output) == 0) {
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
ret = SOC_GDB_GPKT_HEX_SIZE;
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
size_t arch_gdb_reg_writeall(struct gdb_ctx *ctx, uint8_t *hex, size_t hexlen)
|
|
{
|
|
/*
|
|
* GDB on Xtensa does not seem to use G-packet to write register
|
|
* values. So we can skip this function.
|
|
*/
|
|
|
|
ARG_UNUSED(ctx);
|
|
ARG_UNUSED(hex);
|
|
ARG_UNUSED(hexlen);
|
|
|
|
return 0;
|
|
}
|
|
|
|
size_t arch_gdb_reg_readone(struct gdb_ctx *ctx, uint8_t *buf, size_t buflen,
|
|
uint32_t regno)
|
|
{
|
|
struct xtensa_register *reg;
|
|
int idx;
|
|
size_t ret;
|
|
|
|
ret = 0;
|
|
for (idx = 0; idx < ctx->num_regs; idx++) {
|
|
reg = &ctx->regs[idx];
|
|
|
|
/*
|
|
* GDB sends the G-packet index as register number
|
|
* instead of the actual Xtensa register number.
|
|
*/
|
|
if (reg->idx == regno) {
|
|
if (reg->seqno != ctx->seqno) {
|
|
/*
|
|
* Register value has stale value from
|
|
* previous debug interrupt. Report
|
|
* register value as unavailable.
|
|
*
|
|
* Don't report error here, or else GDB
|
|
* may stop the debug session.
|
|
*/
|
|
|
|
if (buflen < 2) {
|
|
/* Output buffer cannot hold 'xx' */
|
|
goto out;
|
|
}
|
|
|
|
buf[0] = 'x';
|
|
buf[1] = 'x';
|
|
ret = 2;
|
|
goto out;
|
|
}
|
|
|
|
/* Make sure output buffer is large enough */
|
|
if (buflen < (reg->byte_size * 2)) {
|
|
goto out;
|
|
}
|
|
|
|
ret = reg2hex(reg, buf);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
size_t arch_gdb_reg_writeone(struct gdb_ctx *ctx, uint8_t *hex, size_t hexlen,
|
|
uint32_t regno)
|
|
{
|
|
struct xtensa_register *reg = NULL;
|
|
int idx;
|
|
size_t ret;
|
|
|
|
ret = 0;
|
|
for (idx = 0; idx < ctx->num_regs; idx++) {
|
|
reg = &ctx->regs[idx];
|
|
|
|
/*
|
|
* Remember GDB sends index number instead of
|
|
* actual register number (as defined in Xtensa
|
|
* architecture).
|
|
*/
|
|
if (reg->idx != regno) {
|
|
continue;
|
|
}
|
|
|
|
if (hexlen < (reg->byte_size * 2)) {
|
|
/* Not enough hex digits to fill the register */
|
|
goto out;
|
|
}
|
|
|
|
/* Register value is now up-to-date */
|
|
reg->seqno = ctx->seqno;
|
|
|
|
/* Convert from hexadecimal into binary */
|
|
ret = hex2bin(hex, hexlen,
|
|
(uint8_t *)®->val, reg->byte_size);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int arch_gdb_add_breakpoint(struct gdb_ctx *ctx, uint8_t type,
|
|
uintptr_t addr, uint32_t kind)
|
|
{
|
|
int ret, idx;
|
|
uint32_t ibreakenable;
|
|
|
|
switch (type) {
|
|
case 1:
|
|
/* Hardware breakpoint */
|
|
ibreakenable = get_one_sreg(IBREAKENABLE);
|
|
for (idx = 0; idx < MAX(XCHAL_NUM_IBREAK, 2); idx++) {
|
|
/* Find an empty IBREAK slot */
|
|
if ((ibreakenable & BIT(idx)) == 0) {
|
|
/* Set breakpoint address */
|
|
|
|
if (idx == 0) {
|
|
set_one_sreg(IBREAKA0, addr);
|
|
} else if (idx == 1) {
|
|
set_one_sreg(IBREAKA1, addr);
|
|
} else {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Enable the breakpoint */
|
|
ibreakenable |= BIT(idx);
|
|
set_one_sreg(IBREAKENABLE, ibreakenable);
|
|
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
/* Cannot find an empty slot, return error */
|
|
ret = -1;
|
|
break;
|
|
case 0:
|
|
/*
|
|
* Software breakpoint is to replace the instruction at
|
|
* target address with BREAK or BREAK.N. GDB, by default,
|
|
* does this by using memory write packets to replace
|
|
* instructions. So there is no need to implement
|
|
* software breakpoint here.
|
|
*/
|
|
__fallthrough;
|
|
default:
|
|
/* Breakpoint type not supported */
|
|
ret = -2;
|
|
break;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
int arch_gdb_remove_breakpoint(struct gdb_ctx *ctx, uint8_t type,
|
|
uintptr_t addr, uint32_t kind)
|
|
{
|
|
int ret, idx;
|
|
uint32_t ibreakenable, ibreak;
|
|
|
|
switch (type) {
|
|
case 1:
|
|
/* Hardware breakpoint */
|
|
ibreakenable = get_one_sreg(IBREAKENABLE);
|
|
for (idx = 0; idx < MAX(XCHAL_NUM_IBREAK, 2); idx++) {
|
|
/* Find an active IBREAK slot and compare address */
|
|
if ((ibreakenable & BIT(idx)) == BIT(idx)) {
|
|
if (idx == 0) {
|
|
ibreak = get_one_sreg(IBREAKA0);
|
|
} else if (idx == 1) {
|
|
ibreak = get_one_sreg(IBREAKA1);
|
|
} else {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (ibreak == addr) {
|
|
/* Clear breakpoint address */
|
|
if (idx == 0) {
|
|
set_one_sreg(IBREAKA0, 0U);
|
|
} else if (idx == 1) {
|
|
set_one_sreg(IBREAKA1, 0U);
|
|
} else {
|
|
ret = -1;
|
|
goto out;
|
|
}
|
|
|
|
/* Disable the breakpoint */
|
|
ibreakenable &= ~BIT(idx);
|
|
set_one_sreg(IBREAKENABLE,
|
|
ibreakenable);
|
|
|
|
ret = 0;
|
|
goto out;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Cannot find matching breakpoint address,
|
|
* return error.
|
|
*/
|
|
ret = -1;
|
|
|
|
break;
|
|
case 0:
|
|
/*
|
|
* Software breakpoint is to replace the instruction at
|
|
* target address with BREAK or BREAK.N. GDB, by default,
|
|
* does this by using memory write packets to restore
|
|
* instructions. So there is no need to implement
|
|
* software breakpoint here.
|
|
*/
|
|
__fallthrough;
|
|
default:
|
|
/* Breakpoint type not supported */
|
|
ret = -2;
|
|
break;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
void z_gdb_isr(struct arch_esf *esf)
|
|
{
|
|
uint32_t reg;
|
|
|
|
reg = get_one_sreg(DEBUGCAUSE);
|
|
if (reg != 0) {
|
|
/* Manual breaking */
|
|
xtensa_gdb_ctx.exception = GDB_EXCEPTION_BREAKPOINT;
|
|
} else {
|
|
/* Actual exception */
|
|
reg = get_one_sreg(EXCCAUSE);
|
|
xtensa_gdb_ctx.exception = get_gdb_exception_reason(reg);
|
|
}
|
|
|
|
xtensa_gdb_ctx.seqno++;
|
|
|
|
/* Copy registers into GDB context */
|
|
copy_to_ctx(&xtensa_gdb_ctx, esf);
|
|
|
|
z_gdb_main_loop(&xtensa_gdb_ctx);
|
|
|
|
/* Restore registers from GDB context */
|
|
restore_from_ctx(&xtensa_gdb_ctx, esf);
|
|
}
|
|
|
|
void arch_gdb_init(void)
|
|
{
|
|
int idx;
|
|
|
|
/*
|
|
* Find out the starting index in the register
|
|
* description array of certain registers.
|
|
*/
|
|
for (idx = 0; idx < xtensa_gdb_ctx.num_regs; idx++) {
|
|
switch (xtensa_gdb_ctx.regs[idx].regno) {
|
|
case 0x0000:
|
|
/* A0: 0x0000 */
|
|
xtensa_gdb_ctx.a0_idx = idx;
|
|
break;
|
|
case XTREG_GRP_ADDR:
|
|
/* AR0: 0x0100 */
|
|
xtensa_gdb_ctx.ar_idx = idx;
|
|
break;
|
|
case (XTREG_GRP_SPECIAL + WINDOWBASE):
|
|
/* WINDOWBASE (Special Register) */
|
|
xtensa_gdb_ctx.wb_idx = idx;
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
}
|
|
|
|
/*
|
|
* The interrupt enable bits for higher level interrupts
|
|
* (level 2+) sit just after the level-1 interrupts.
|
|
* The need to do a minus 2 is simply that the first bit
|
|
* after level-1 interrupts is for level-2 interrupt.
|
|
* So need to do an offset by subtraction.
|
|
*/
|
|
xtensa_irq_enable(XCHAL_NUM_EXTINTERRUPTS + XCHAL_DEBUGLEVEL - 2);
|
|
|
|
/*
|
|
* Break and go into the GDB stub.
|
|
* The underscore in front is to avoid toolchain
|
|
* converting BREAK.N into BREAK which is bigger.
|
|
* This is needed as the GDB stub will need to change
|
|
* the program counter past this instruction to
|
|
* continue working. Or else SoC would repeatedly
|
|
* raise debug exception on this instruction and
|
|
* won't go forward.
|
|
*/
|
|
__asm__ volatile ("_break.n 0");
|
|
}
|