xtensa: introduce support for GDB stub

This adds basic support for GDB stub on Xtensa. Note that
this only provides the common bits on the architecture side.
SoC support is also required to fully enable GDB stub on
each Xtensa SoC.

Signed-off-by: Daniel Leung <daniel.leung@intel.com>
This commit is contained in:
Daniel Leung 2021-10-26 12:58:21 -07:00 committed by Anas Nashif
commit dc34f6c84d
7 changed files with 1199 additions and 0 deletions

View file

@ -19,6 +19,8 @@ zephyr_library_sources_ifdef(CONFIG_THREAD_LOCAL_STORAGE tls.c)
zephyr_library_sources_ifdef(CONFIG_XTENSA_ENABLE_BACKTRACE xtensa_backtrace.c)
zephyr_library_sources_ifdef(CONFIG_XTENSA_ENABLE_BACKTRACE debug_helpers_asm.S)
zephyr_library_sources_ifdef(CONFIG_GDBSTUB gdbstub.c)
zephyr_library_include_directories(include)
add_subdirectory(startup)

989
arch/xtensa/core/gdbstub.c Normal file
View file

@ -0,0 +1,989 @@
/*
* Copyright (c) 2021 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel.h>
#include <kernel_internal.h>
#include <toolchain.h>
#include <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 z_arch_esf_t *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 z_arch_esf_t *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;
}
/*
* 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[BSA_PC_OFF / 4] += 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(z_arch_esf_t *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.
*/
z_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 repeartedly
* raise debug exception on this instruction and
* won't go forward.
*/
__asm__ volatile ("_break.n 0");
}

View file

@ -299,28 +299,44 @@ _handle_excint:
DEF_EXCINT 1, _handle_excint, xtensa_excint1_c
#if XCHAL_NMILEVEL >= 2
#if !(defined(CONFIG_GDBSTUB) && (XCHAL_DEBUGLEVEL == 2))
DEF_EXCINT 2, _handle_excint, xtensa_int2_c
#endif
#endif
#if XCHAL_NMILEVEL >= 3
#if !(defined(CONFIG_GDBSTUB) && (XCHAL_DEBUGLEVEL == 3))
DEF_EXCINT 3, _handle_excint, xtensa_int3_c
#endif
#endif
#if XCHAL_NMILEVEL >= 4
#if !(defined(CONFIG_GDBSTUB) && (XCHAL_DEBUGLEVEL == 4))
DEF_EXCINT 4, _handle_excint, xtensa_int4_c
#endif
#endif
#if XCHAL_NMILEVEL >= 5
#if !(defined(CONFIG_GDBSTUB) && (XCHAL_DEBUGLEVEL == 5))
DEF_EXCINT 5, _handle_excint, xtensa_int5_c
#endif
#endif
#if XCHAL_NMILEVEL >= 6
#if !(defined(CONFIG_GDBSTUB) && (XCHAL_DEBUGLEVEL == 6))
DEF_EXCINT 6, _handle_excint, xtensa_int6_c
#endif
#endif
#if XCHAL_NMILEVEL >= 7
#if !(defined(CONFIG_GDBSTUB) && (XCHAL_DEBUGLEVEL == 7))
DEF_EXCINT 7, _handle_excint, xtensa_int7_c
#endif
#endif
#if defined(CONFIG_GDBSTUB)
DEF_EXCINT XCHAL_DEBUGLEVEL, _handle_excint, xtensa_debugint_c
#endif
/* The user exception vector is defined here, as we need to handle
* MOVSP exceptions in assembly (the result has to be to unspill the

View file

@ -249,6 +249,17 @@ void *xtensa_excint1_c(int *interrupted_stack)
return z_get_next_switch_handle(interrupted_stack);
}
#if defined(CONFIG_GDBSTUB)
void *xtensa_debugint_c(int *interrupted_stack)
{
extern void z_gdb_isr(z_arch_esf_t *esf);
z_gdb_isr((void *)interrupted_stack);
return z_get_next_switch_handle(interrupted_stack);
}
#endif
int z_xtensa_irq_is_enabled(unsigned int irq)
{
uint32_t ie;

View file

@ -27,6 +27,7 @@
#include <arch/xtensa/irq.h>
#include <xtensa/config/core.h>
#include <arch/common/addr_types.h>
#include <arch/xtensa/gdbstub.h>
#ifdef CONFIG_KERNEL_COHERENCE
#define ARCH_STACK_PTR_ALIGN XCHAL_DCACHE_LINESIZE

View file

@ -0,0 +1,168 @@
/*
* Copyright (c) 2021 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#ifndef ZEPHYR_INCLUDE_ARCH_XTENSA_GDBSTUB_SYS_H_
#define ZEPHYR_INCLUDE_ARCH_XTENSA_GDBSTUB_SYS_H_
#ifdef CONFIG_GDBSTUB
#define XTREG_GRP_MASK 0x0F00
#define XTREG_GRP_GENERAL 0x0000
#define XTREG_GRP_ADDR 0x0100
#define XTREG_GRP_SPECIAL 0x0200
#define XTREG_GRP_USER 0x0300
/*
* Register description fot GDB stub.
*
* Values are based on gdb/gdb/xtensa-config.c in the Xtensa overlay,
* where registers are defined using XTREG() macro:
* XTREG(index,ofs,bsz,sz,al,tnum,flg,cp,ty,gr,name,fet,sto,mas,ct,x,y)
*
* Translation:
* idx : index
* regno : tnum
* 0x00xx : General Registers (A0 - A15, PC)
* 0x01xx : Address Registers (AR0 - AR31/AR63)
* 0x02xx : Special Registers (access via RSR/WSR)
* 0x03xx : User Registers (access via RUR/WUR)
* byte_size : sz
* gpkt_offset : ofs
*/
struct xtensa_register {
/* Register value */
uint32_t val;
/* GDB register index (for p/P packets) */
uint8_t idx;
/* Size of register */
uint8_t byte_size;
/* Xtensa register number */
uint16_t regno;
/* Offset of this register in GDB G-packet.
* -1 if register is not in G-packet.
*/
int16_t gpkt_offset;
/* Offset of saved register in stack frame.
* 0 if not saved in stack frame.
*/
int8_t stack_offset;
/* Sequence number */
uint8_t seqno;
/* Set 1 to if register should not be written
* to during debugging.
*/
uint8_t is_read_only:1;
};
/* Due to Xtensa SoCs being highly configurable,
* the register files between SoCs are not identical.
*
* This means generic registers can, sometimes, have
* different offsets from start of register files
* needed to communicate with GDB.
*
* Therefore, it is better to defer to the SoC layer
* for proper support for GDB.
*/
#include <gdbstub/soc.h>
struct gdb_ctx {
/* Exception reason */
unsigned int exception;
/* Register descriptions */
struct xtensa_register *regs;
/* Number of registers */
uint8_t num_regs;
/* Sequence number */
uint8_t seqno;
/* Index in register descriptions of A0 register */
uint8_t a0_idx;
/* Index in register descriptions of AR0 register */
uint8_t ar_idx;
/* Index in register descriptions of WINDOWBASE register */
uint8_t wb_idx;
};
/**
* Test if the register is a logical address register (A0 - A15).
*
* @retval true if register is A0 - A15
* @retval false if register is not A0 - A15
*/
static inline bool gdb_xtensa_is_logical_addr_reg(struct xtensa_register *reg)
{
if (reg->regno < 16) {
return true;
} else {
return false;
}
}
/**
* Test if the register is a address register (AR0 - AR31/AR63).
*
* @retval true if register is AR0 - AR31/AR63
* @retval false if not
*/
static inline bool gdb_xtensa_is_address_reg(struct xtensa_register *reg)
{
if ((reg->regno & XTREG_GRP_MASK) == XTREG_GRP_ADDR) {
return true;
} else {
return false;
}
}
/**
* Test if the register is a special register that needs to be
* accessed via RSR/WSR.
*
* @retval true if special register
* @retval false if not
*/
static inline bool gdb_xtensa_is_special_reg(struct xtensa_register *reg)
{
if ((reg->regno & XTREG_GRP_MASK) == XTREG_GRP_SPECIAL) {
return true;
} else {
return false;
}
}
/**
* Test if the register is a user register that needs to be
* accessed via RUR/WUR.
*
* @retval true if user register
* @retval false if not
*/
static inline bool gdb_xtensa_is_user_reg(struct xtensa_register *reg)
{
if ((reg->regno & XTREG_GRP_MASK) == XTREG_GRP_USER) {
return true;
} else {
return false;
}
}
#endif /* CONFIG_GDBSTUB */
#endif /* ZEPHYR_INCLUDE_ARCH_XTENSA_GDBSTUB_SYS_H_ */

View file

@ -834,4 +834,16 @@ int gdb_init(const struct device *arg)
return 0;
}
#ifdef CONFIG_XTENSA
/*
* Interrupt stacks are being setup during init and are not
* available before POST_KERNEL. Xtensa needs to trigger
* interrupts to get into GDB stub. So this can only be
* initialized in POST_KERNEL, or else the interrupt would not be
* using the correct interrupt stack and will result in
* double exception.
*/
SYS_INIT(gdb_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#else
SYS_INIT(gdb_init, PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
#endif