zephyr/arch/xtensa/core/gdbstub.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 *)&reg->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 *)&reg->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");
}