This is an automated email from Gerrit. Thomas Schmid ([email protected]) just uploaded a new patch set to Gerrit, which you can find at http://openocd.zylin.com/3348
-- gerrit commit fc79d765c5157bb8d0cd633a84695b29e36ad45b Author: Thomas Schmid <[email protected]> Date: Mon Feb 22 09:17:30 2016 -0700 xtensa, esp8266: Adding support for the xtensa target. Limited support for xtensa. See src/target/xtensa.c for limitations and details. Change-Id: Id25e6b61c87a8d24761fc2d1c6f593b00aa15b72 Signed-off-by: Thomas Schmid <[email protected]> diff --git a/src/target/Makefile.am b/src/target/Makefile.am index 1f4cbba..2d1bc0e 100644 --- a/src/target/Makefile.am +++ b/src/target/Makefile.am @@ -40,6 +40,7 @@ libtarget_la_SOURCES = \ dsp563xx.c \ dsp563xx_once.c \ dsp5680xx.c \ + xtensa.c \ hla_target.c TARGET_CORE_SRC = \ diff --git a/src/target/target.c b/src/target/target.c index 6df8d8b..1e6e7e1 100644 --- a/src/target/target.c +++ b/src/target/target.c @@ -105,6 +105,7 @@ extern struct target_type nds32_v3m_target; extern struct target_type or1k_target; extern struct target_type quark_x10xx_target; extern struct target_type quark_d20xx_target; +extern struct target_type xtensa_target; static struct target_type *target_types[] = { &arm7tdmi_target, @@ -135,6 +136,7 @@ static struct target_type *target_types[] = { &or1k_target, &quark_x10xx_target, &quark_d20xx_target, + &xtensa_target, NULL, }; diff --git a/src/target/xtensa.c b/src/target/xtensa.c new file mode 100644 index 0000000..20c06d0 --- /dev/null +++ b/src/target/xtensa.c @@ -0,0 +1,1596 @@ +/*************************************************************************** + * Copyright (C) 2015 by Angus Gratton * + * [email protected] * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +/* + * Xtensa arch target implementation. Very preliminary & restricted in scope: + * + * - Assumes OCD debug feature. + * - Other features/config modelled closely on lx106 (esp8266 SoC) at this time. + * - Assumes little endian target. + * - Assumes none of the following options configured: + * * "DIR Array option" + * * "Instruction cache option" (XCHAL_ICACHE_SIZE >0 in xtensa-config.h) + * * "MMU Option" (XCHAL_HAVE_MMU in xtensa-config.h) + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "target.h" +#include "target_type.h" +#include "register.h" +#include "assert.h" +#include "time_support.h" +#include "jim.h" +#include "command.h" + +#include "xtensa.h" +/******************************************************************* + * Xtensa Config Options * + *******************************************************************/ + +/* This is stuff which is currently hard-coded but ideally would + * eventually be per-target configured. + */ +#define XT_INS_NUM_BITS 24 +#define XT_DEBUGLEVEL 2 /* XCHAL_DEBUGLEVEL in xtensa-config.h */ +#define XT_NUM_BREAKPOINTS 1 +#define XT_NUM_WATCHPOINTS 1 + +static int xtensa_read_register(struct target *target, int idx, int force); + +static bool s_DisableInterruptsForStepping = false, s_FeedWatchdogDuringStops = false; + +/******************************************************************* + * Xtensa OCD TAP instructions * + *******************************************************************/ + +/* Xtensa OCD TAP instructions */ +enum xtensa_tap_ins_idx { + TAP_INS_ENABLE_OCD = 0, + TAP_INS_DEBUG_INT = 1, + TAP_INS_EXECUTE_DI = 2, + TAP_INS_LOAD_DI = 3, + TAP_INS_SCAN_DDR = 4, + TAP_INS_READ_DOSR = 5, + TAP_INS_SCAN_DCR = 6, + TAP_INS_LOAD_WDI = 7, +}; + +#define XTENSA_NUM_TAP_INS 8 + +static const struct xtensa_tap_instr tap_instrs[] = { + { TAP_INS_ENABLE_OCD, 0x11, 1, "EnableOCD" }, + { TAP_INS_DEBUG_INT, 0x12, 1, "DebugInt" }, + { TAP_INS_EXECUTE_DI, 0x15, 1, "ExecuteDI" }, + { TAP_INS_LOAD_DI, 0x16, XT_INS_NUM_BITS, "LoadDI" }, + { TAP_INS_SCAN_DDR, 0x17, 32, "ScanDDR" }, + { TAP_INS_READ_DOSR, 0x18, 8, "ReadDOSR" }, + { TAP_INS_SCAN_DCR, 0x19, 8, "ScanDCR" }, + { TAP_INS_LOAD_WDI, 0x1a, XT_INS_NUM_BITS, "LoadWDI" }, +}; +/* Static buffer that holds the tap instructions ready for sending to JTAG */ +static uint8_t tap_instr_buf[XTENSA_NUM_TAP_INS*4]; + +/* Debug Output Status Register (DOSR) fields */ +#define DOSR_NEXT_DI (1<<0) +#define DOSR_EXCEPTION (1<<1) +#define DOSR_IN_OCD_MODE (1<<2) +#define DOSR_DOD_READY (1<<3) + +/******************************************************************* + * Xtensa CPU registers * + *******************************************************************/ + +/* Xtensa register list currently taken from + ./overlays/xtensa_lx106/gdb/gdb/xtensa-config.c - probably entirely + overlay-specific :(. Maybe eventually should live in config file, + or least a mapping in config file from openocd's set of all + possible Xtensa registers to the register index used by gdb for the + current overlay (currently this is a 1:1 mapping to lx106 regs only)? +*/ +enum xtensa_reg_idx { + XT_REG_IDX_A0 = 0, + XT_REG_IDX_A1, + XT_REG_IDX_A2, + XT_REG_IDX_A3, + XT_REG_IDX_A4, + XT_REG_IDX_A5, + XT_REG_IDX_A6, + XT_REG_IDX_A7, + XT_REG_IDX_A8, + XT_REG_IDX_A9, + XT_REG_IDX_A10, + XT_REG_IDX_A11, + XT_REG_IDX_A12, + XT_REG_IDX_A13, + XT_REG_IDX_A14, + XT_REG_IDX_A15, + XT_REG_IDX_PC, + XT_REG_IDX_SAR, + XT_REG_IDX_LITBASE, + XT_REG_IDX_SR176, + XT_REG_IDX_SR208, + XT_REG_IDX_PS, + XT_REG_IDX_MMID, + XT_REG_IDX_IBREAKENABLE, + XT_REG_IDX_DDR, + XT_REG_IDX_IBREAKA0, + XT_REG_IDX_DBREAKA0, + XT_REG_IDX_DBREAKC0, + XT_REG_IDX_EPC1, + XT_REG_IDX_EPC2, + XT_REG_IDX_EPC3, + XT_REG_IDX_DEPC, + XT_REG_IDX_EPS2, + XT_REG_IDX_EPS3, + XT_REG_IDX_EXCSAVE1, + XT_REG_IDX_EXCSAVE2, + XT_REG_IDX_EXCSAVE3, + XT_REG_IDX_INTERRUPT, + XT_REG_IDX_INTSET, + XT_REG_IDX_INTCLEAR, + XT_REG_IDX_INTENABLE, + XT_REG_IDX_VECBASE, + XT_REG_IDX_EXCCAUSE, + XT_REG_IDX_DEBUGCAUSE, + XT_REG_IDX_CCOUNT, + XT_REG_IDX_PRID, + XT_REG_IDX_ICOUNT, + XT_REG_IDX_ICOUNTLEVEL, + XT_REG_IDX_EXCVADDR, + XT_REG_IDX_CCOMPARE0, +}; + +#define XT_NUM_REGS 50 + +static const struct xtensa_core_reg xt_regs[] = { + { XT_REG_IDX_A0, "a0", 0x00, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A1, "a1", 0x01, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A2, "a2", 0x02, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A3, "a3", 0x03, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A4, "a4", 0x04, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A5, "a5", 0x05, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A6, "a6", 0x06, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A7, "a7", 0x07, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A8, "a8", 0x08, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A9, "a9", 0x09, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A10, "a10", 0x0a, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A11, "a11", 0x0b, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A12, "a12", 0x0c, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A13, "a13", 0x0d, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A14, "a14", 0x0e, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_A15, "a15", 0x0f, XT_REG_GENERAL, 0 }, + { XT_REG_IDX_PC, "PC", 0xb0, XT_REG_ALIASED, 0 }, + { XT_REG_IDX_SAR, "SAR", 0x03, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_LITBASE, "LITBASE", 0x05, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_SR176, "SR176", 0xb0, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_SR208, "SR208", 0xd0, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_PS, "PS", 0xc0, XT_REG_ALIASED, 0 }, + { XT_REG_IDX_MMID, "MMID", 0x59, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_IBREAKENABLE, "IBREAKENABLE", 0x60, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_DDR, "DDR", 0x68, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_IBREAKA0, "IBREAKA0", 0x80, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_DBREAKA0, "DBREAKA0", 0x90, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_DBREAKC0, "DBREAKC0", 0xa0, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EPC1, "EPC1", 0xb1, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EPC2, "EPC2", 0xb2, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EPC3, "EPC3", 0xb3, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_DEPC, "DEPC", 0xc0, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EPS2, "EPS2", 0xc2, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EPS3, "EPS3", 0xc3, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EXCSAVE1, "EXCSAVE1", 0xd1, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EXCSAVE2, "EXCSAVE2", 0xd2, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EXCSAVE3, "EXCSAVE3", 0xd3, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_INTERRUPT, "INTERRUPT", 0xe2, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_INTSET, "INTSET", 0xe2, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_INTCLEAR, "INTCLEAR", 0xe3, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_INTENABLE, "INTENABLE", 0xe4, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_VECBASE, "VECBASE", 0xe7, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EXCCAUSE, "EXCCAUSE", 0xe8, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_DEBUGCAUSE, "DEBUGCAUSE", 0xe9, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_CCOUNT, "CCOUNT", 0xea, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_PRID, "PRID", 0xeb, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_ICOUNT, "ICOUNT", 0xec, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_ICOUNTLEVEL, "ICOUNTLEVEL", 0xed, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_EXCVADDR, "EXCVADDR", 0xee, XT_REG_SPECIAL, 0 }, + { XT_REG_IDX_CCOMPARE0, "CCOMPARE0", 0xf0, XT_REG_SPECIAL, 0 }, +}; + +/******************************************************************* + * Xtensa CPU instructions * + *******************************************************************/ + +#define _XT_INS_FORMAT_RSR(OPCODE,SR,T) (OPCODE \ + | ((SR & 0xFF) << 8) \ + | ((T & 0x0F) << 4)) + +#define _XT_INS_FORMAT_RRI8(OPCODE,R,S,T,IMM8) (OPCODE \ + | ((IMM8 & 0xFF) << 16) \ + | ((R & 0x0F) << 12 ) \ + | ((S & 0x0F) << 8 ) \ + | ((T & 0x0F) << 4 )) + +/* Special register number macro for DDR register. + * this gets used a lot so making a shortcut to it is + * useful. + */ +#define XT_SR_DDR (xt_regs[XT_REG_IDX_DDR].reg_num) + +/* Xtensa processor instruction opcodes +*/ +/* "Return From Debug Operation" to Normal */ +#define XT_INS_RFDO_0 0xf1e000 +/* "Return From Debug Operation" to OCD Run */ +#define XT_INS_RFDO_1 0xf1e100 + +#define XT_INS_ISYNC 0x002000 + +/* Load 32-bit Indirect from A(S)+4*IMM8 to A(T) */ +#define XT_INS_L32I(S,T,IMM8) _XT_INS_FORMAT_RRI8(0x002002,0,S,T,IMM8) +/* Load 16-bit Unsigned from A(S)+2*IMM8 to A(T) */ +#define XT_INS_L16UI(S,T,IMM8) _XT_INS_FORMAT_RRI8(0x001002,0,S,T,IMM8) +/* Load 8-bit Unsigned from A(S)+IMM8 to A(T) */ +#define XT_INS_L8UI(S,T,IMM8) _XT_INS_FORMAT_RRI8(0x000002,0,S,T,IMM8) + +/* Store 32-bit Indirect to A(S)+4*IMM8 from A(T) */ +#define XT_INS_S32I(S,T,IMM8) _XT_INS_FORMAT_RRI8(0x006002,0,S,T,IMM8) +/* Store 16-bit to A(S)+2*IMM8 from A(T) */ +#define XT_INS_S16I(S,T,IMM8) _XT_INS_FORMAT_RRI8(0x005002,0,S,T,IMM8) +/* Store 8-bit to A(S)+IMM8 from A(T) */ +#define XT_INS_S8I(S,T,IMM8) _XT_INS_FORMAT_RRI8(0x004002,0,S,T,IMM8) + +/* Read Special Register */ +#define XT_INS_RSR(SR,T) _XT_INS_FORMAT_RSR(0x030000,SR,T) +/* Write Special Register */ +#define XT_INS_WSR(SR,T) _XT_INS_FORMAT_RSR(0x130000,SR,T) +/* Swap Special Register */ +#define XT_INS_XSR(SR,T) _XT_INS_FORMAT_RSR(0x610000,SR,T) + +/* Forward declarations */ +static int xtensa_get_core_reg(struct reg *reg); +static int xtensa_set_core_reg(struct reg *reg, uint8_t *buf); +static int xtensa_save_context(struct target *target); +static int xtensa_restore_context(struct target *target); + +/* Add an Xtensa OCD TAP instruction to the JTAG queue */ +static int xtensa_tap_queue(struct target *target, int inst_idx, const uint8_t *data_out, uint8_t *data_in) +{ + const struct xtensa_tap_instr *ins; + static const uint8_t dummy_out[] = {0,0,0,0}; + + if ((inst_idx < 0 || inst_idx >= XTENSA_NUM_TAP_INS)) + return ERROR_COMMAND_SYNTAX_ERROR; + ins = &tap_instrs[inst_idx]; + + if(!data_out) + data_out = dummy_out; + + if (!target->tap) + return ERROR_FAIL; + + jtag_add_plain_ir_scan(target->tap->ir_length, tap_instr_buf+inst_idx*4, + NULL, TAP_IDLE); + jtag_add_plain_dr_scan(ins->data_len, data_out, data_in, TAP_IDLE); + + return ERROR_OK; +} + +/* Execute an Xtensa OCD TAP instruction immediately */ +static int xtensa_tap_exec(struct target *target, int inst_idx, uint32_t data_out, uint32_t *data_in) +{ + uint8_t out[4] = { 0 }, in[4] = { 0 }; + int res; + if(data_out) + buf_set_u32(out, 0, 32, data_out); + + res = xtensa_tap_queue(target, inst_idx, out, in); + if(res != ERROR_OK) + return res; + + res = jtag_execute_queue(); + if(res != ERROR_OK) { + LOG_ERROR("failed to scan tap instruction"); + return res; + } + + + if(data_in) { + static uint32_t last_dosr; + *data_in = buf_get_u32(in, 0, 32); + if(inst_idx != TAP_INS_READ_DOSR || *data_in != last_dosr) { + LOG_DEBUG("Executed %s, data_out=0x%" PRIx32 " data_in=0x%" PRIx32, + tap_instrs[inst_idx].name, data_out, *data_in); + if(inst_idx == TAP_INS_READ_DOSR) + last_dosr = *data_in; + } + } else { + LOG_DEBUG("Executed %s, data_out=0x%" PRIx32, + tap_instrs[inst_idx].name, data_out); + } + + return ERROR_OK; +} + +/* Set up a register we intend to use for scratch purposes */ +static int xtensa_setup_scratch_reg(struct target *target, int reg_idx) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + struct reg *reg_list = xtensa->core_cache->reg_list; + int res; + if (reg_idx < 0 || reg_idx >= XT_NUM_REGS) + return ERROR_COMMAND_SYNTAX_ERROR; + + if (!reg_list[reg_idx].valid) { + res = xtensa_get_core_reg(®_list[reg_idx]); + if (res != ERROR_OK) + return res; + } + reg_list[reg_idx].dirty = 1; + return ERROR_OK; +} + + +/* Queue an Xtensa CPU instruction via the TAP's LoadDI function */ +static inline int xtensa_tap_queue_cpu_inst(struct target *target, uint32_t inst) +{ + uint8_t inst_buf[4]; + buf_set_u32(inst_buf, 0, 32, inst); + return xtensa_tap_queue(target, TAP_INS_LOAD_DI, inst_buf, NULL); +} + + +/* Queue a load to a general register aX, via DDR */ +static inline int xtensa_tap_queue_load_general_reg(struct target *target, uint8_t general_reg_num, uint32_t value) +{ + uint8_t value_buf[4]; + int res; + buf_set_u32(value_buf, 0, 32, value); + res = xtensa_tap_queue(target, TAP_INS_SCAN_DDR, value_buf, NULL); + if(res != ERROR_OK) + return res; + return xtensa_tap_queue_cpu_inst(target, XT_INS_RSR(XT_SR_DDR, general_reg_num)); +} + + +/* Queue a write to an Xtensa special register, via the WSR instruction. + + This function does not go through the gdb-facing register cache. +*/ +static int xtensa_tap_queue_write_sr(struct target *target, enum xtensa_reg_idx idx, uint32_t value) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + struct reg *reg_list = xtensa->core_cache->reg_list; + struct reg *reg; + struct xtensa_core_reg *xt_reg; + int res; + + if(idx < 0 || idx >= XT_NUM_REGS) + return ERROR_COMMAND_SYNTAX_ERROR; + + reg = ®_list[idx]; + xt_reg = reg->arch_info; + + if(xt_reg->type != XT_REG_SPECIAL) + return ERROR_COMMAND_SYNTAX_ERROR; + + /* Use a0 as working register */ + xtensa_setup_scratch_reg(target, XT_REG_IDX_A0); + + /* Push new value into a0 via DDR */ + res = xtensa_tap_queue_load_general_reg(target, 0, value); + if(res != ERROR_OK) + return res; + /* load Special Register from a0 */ + res = xtensa_tap_queue_cpu_inst(target, XT_INS_WSR(xt_reg->reg_num, 0)); + + return res; +} + +static int xtensa_target_create(struct target *target, Jim_Interp *interp) +{ + struct xtensa_common *xtensa = calloc(1, sizeof(struct xtensa_common)); + + if (!xtensa) + return ERROR_COMMAND_SYNTAX_ERROR; + + target->arch_info = xtensa; + xtensa->tap = target->tap; + + xtensa->num_brps = XT_NUM_BREAKPOINTS; + xtensa->free_brps = XT_NUM_BREAKPOINTS; + xtensa->hw_brps = calloc(XT_NUM_BREAKPOINTS, sizeof(struct breakpoint *)); + + return ERROR_OK; +} + +static void xtensa_build_reg_cache(struct target *target); + +static int xtensa_init_target(struct command_context *cmd_ctx, struct target *target) +{ + int i; + LOG_DEBUG("%s", __func__); + struct xtensa_common *xtensa = target_to_xtensa(target); + + xtensa_build_reg_cache(target); + + xtensa->state = XT_NORMAL; // Assume normal state until we examine + + + /* pre-seed TAP instruction buffer with tap instruction opcodes */ + for(i = 0; i < XTENSA_NUM_TAP_INS; i++) + buf_set_u32(tap_instr_buf+4*i, 0, 32, tap_instrs[i].inst); + + return ERROR_OK; +} + +static void xtensa_feed_esp8266_watchdog(struct target *target); + +static int xtensa_poll(struct target *target) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + struct reg *reg; + uint32_t dosr; + int res; + + /* OCD guide 2.9.2 points out no reliable way to detect core reset. + + So, even though this ENABLE_OCD is nearly always a No-Op, we send it + on every poll just in case the target has reset and gone back to Running state + (in which case this moves it to OCD Run State. */ + res = xtensa_tap_queue(target, TAP_INS_ENABLE_OCD, NULL, NULL); + if(res != ERROR_OK) { + LOG_ERROR("Failed to queue EnableOCD instruction."); + return ERROR_FAIL; + } + + res = xtensa_tap_exec(target, TAP_INS_READ_DOSR, 0, &dosr); + if(res != ERROR_OK) { + LOG_ERROR("Failed to read DOSR. Not Xtensa OCD?"); + return ERROR_FAIL; + } + + if(dosr & (DOSR_IN_OCD_MODE)) { + if (target->state != TARGET_HALTED) + { + if (target->state != TARGET_UNKNOWN && (dosr & DOSR_EXCEPTION) == 0) + { + LOG_WARNING("%s: DOSR has set InOCDMode without the Exception flag. Unexpected. DOSR=0x%02x", + __func__, + dosr); + } + int state = target->state; + + xtensa->state = XT_OCD_HALT; + target->state = TARGET_HALTED; + register_cache_invalidate(xtensa->core_cache); + xtensa_save_context(target); + + if (state == TARGET_DEBUG_RUNNING) + { + target_call_event_callbacks(target, TARGET_EVENT_DEBUG_HALTED); + } + else + { + //target->debug_reason is checked in gdb_last_signal() that is invoked as a result of calling target_call_event_callbacks() below. + //Unless we specify it, GDB will get confused and report the stop to the user on its internal breakpoints. + uint32_t dbgcause = buf_get_u32(xtensa->core_cache->reg_list[XT_REG_IDX_DEBUGCAUSE].value, 0, 32); + if (dbgcause & 0x20) //Debug interrupt + target->debug_reason = DBG_REASON_DBGRQ; + else if (dbgcause & 0x01) //ICOUNT match + target->debug_reason = DBG_REASON_SINGLESTEP; + else + target->debug_reason = DBG_REASON_BREAKPOINT; + + target_call_event_callbacks(target, TARGET_EVENT_HALTED); + } + + LOG_DEBUG("target->state: %s", target_state_name(target)); + reg = &xtensa->core_cache->reg_list[XT_REG_IDX_PC]; + LOG_INFO("halted: PC: 0x%" PRIx32, buf_get_u32(reg->value, 0, 32)); + reg = &xtensa->core_cache->reg_list[XT_REG_IDX_DEBUGCAUSE]; + LOG_INFO("debug cause: 0x%" PRIx32, buf_get_u32(reg->value, 0, 32)); + } + else + { + if (s_FeedWatchdogDuringStops) + xtensa_feed_esp8266_watchdog(target); + } + } else if(target->state != TARGET_RUNNING && target->state != TARGET_DEBUG_RUNNING){ + xtensa->state = XT_OCD_RUN; + target->state = TARGET_RUNNING; + } + return ERROR_OK; +} + +static int xtensa_examine(struct target *target) +{ + int res = ERROR_OK; + size_t i; + + if (!target_was_examined(target)) { + target_set_examined(target); + + /* without IDCODE, there isn't actually a lot we can + do here apart from try to poll and check that the + TAP responds to it, ie has some known Xtensa + registers. */ + res = xtensa_poll(target); + if(res != ERROR_OK) { + LOG_ERROR("Failed to examine target."); + return ERROR_FAIL; + } + + if(target->state == TARGET_HALTED) { + LOG_DEBUG("Resetting breakpoint/watchpoint state..."); + xtensa_tap_queue_write_sr(target, XT_REG_IDX_IBREAKENABLE, 0); + for(i = 0; i < XT_NUM_WATCHPOINTS; i++) { + xtensa_tap_queue_write_sr(target, XT_REG_IDX_DBREAKA0+i*2, 0); + xtensa_tap_queue_write_sr(target, XT_REG_IDX_DBREAKC0+i*2, 0); + } + res = jtag_execute_queue(); + } else { + LOG_WARNING("Warning: Target not halted, breakpoint/watchpoint state may be unpredictable."); + } + } + + return res; +} + + +static int xtensa_halt(struct target *target) +{ + int res; + + if (target->state == TARGET_HALTED) { + LOG_DEBUG("target was already halted"); + return ERROR_OK; + } + + res = xtensa_tap_exec(target, TAP_INS_DEBUG_INT, 0, 0); + if(res != ERROR_OK) { + LOG_ERROR("Failed to issue DebugInt instruction. Can't halt."); + return ERROR_FAIL; + } + return ERROR_OK; +} + +static int xtensa_resume(struct target *target, + int current, + uint32_t address, + int handle_breakpoints, + int debug_execution) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + uint8_t buf[4]; + int res; + + LOG_DEBUG("%s current=%d address=%04" PRIx32, __func__, current, address); + + if (target->state != TARGET_HALTED) { + LOG_WARNING("%s: target not halted", __func__); + return ERROR_TARGET_NOT_HALTED; + } + + if(address && !current) { + buf_set_u32(buf, 0, 32, address); + xtensa_set_core_reg(&xtensa->core_cache->reg_list[XT_REG_IDX_PC], buf); + } + xtensa_restore_context(target); + register_cache_invalidate(xtensa->core_cache); + + res = xtensa_tap_queue_cpu_inst(target, XT_INS_ISYNC); + if (res != ERROR_OK) + return res; + + res = jtag_execute_queue(); + if (res != ERROR_OK) + return res; + + res = xtensa_tap_exec(target, TAP_INS_LOAD_DI, XT_INS_RFDO_1, 0); + if(res != ERROR_OK) { + LOG_ERROR("Failed to issue LoadDI instruction. Can't resume."); + return ERROR_FAIL; + } + + target->debug_reason = DBG_REASON_NOTHALTED; + if (!debug_execution) + target->state = TARGET_RUNNING; + else + target->state = TARGET_DEBUG_RUNNING; + res = target_call_event_callbacks(target, TARGET_EVENT_RESUMED); + + return res; +} + +static int xtensa_step(struct target *target, + int current, + uint32_t address, + int handle_breakpoints) +{ + int res; + int icountLevel = 1; + int originalIntenable = 0; + uint32_t dosr; + struct xtensa_common *xtensa = target_to_xtensa(target); + + static const uint32_t icount_val = -2; /* ICOUNT value to load for 1 step */ + if (target->state != TARGET_HALTED) { + LOG_WARNING("%s: target not halted", __func__); + return ERROR_TARGET_NOT_HALTED; + } + + LOG_DEBUG("%s current=%d address=%"PRIx32, __func__, current, address); + + /* Load debug level into ICOUNTLEVEL + + Originally had DEBUGLEVEL (ie 2) set here, not 1, but + seemed to result in occasionally stepping out into + inaccessible bits of ROM (low level interrupt handlers?) + and never quite recovering... One loop started at + 0x40000050. Re-attaching with ICOUNTLEVEL 1 caused this to + immediately step into an interrupt handler. + + ICOUNTLEVEL 1 still steps into interrupt handlers, but also + seems to recover. + + TODO: Experiment more, look into CPU exception nuances, + consider making this step level a configuration command. + */ + res = xtensa_read_register(target, XT_REG_IDX_PS, 0); + if (res == ERROR_OK) + { + int psValue = buf_get_u32(xtensa->core_cache->reg_list[XT_REG_IDX_PS].value, 0, 8); + if (psValue & 0x10) + { + //We are executing code in the exception mode. Setting ICOUNTLEVEL to 1 would step into the first instruction that gets executed after the exception handler is done. + //What we actually want is to step into the next instruction of the code we are debugging (i.e. an exception handler). Hence we ned to set ICOUNTLEVEL to 2 in order to count exception mode instructions as well. + icountLevel = 2; + } + } + + if (s_DisableInterruptsForStepping) + { + res = xtensa_read_register(target, XT_REG_IDX_INTENABLE, 0); + if (res != ERROR_OK) + LOG_ERROR("%s: Failed to read the INTENABLE register during single step", __func__); + else + { + originalIntenable = buf_get_u32(xtensa->core_cache->reg_list[XT_REG_IDX_INTENABLE].value, 0, 32); + if (originalIntenable) + { + buf_set_u32(xtensa->core_cache->reg_list[XT_REG_IDX_INTENABLE].value, 0, 32, 0); + xtensa->core_cache->reg_list[XT_REG_IDX_INTENABLE].dirty = 1; + } + } + } + + res = xtensa_tap_queue_write_sr(target, XT_REG_IDX_ICOUNTLEVEL, icountLevel); + if(res != ERROR_OK) + return res; + + /* load ICOUNT step count value */ + res = xtensa_tap_queue_write_sr(target, XT_REG_IDX_ICOUNT, icount_val); + if(res != ERROR_OK) + return res; + + res = xtensa_tap_queue_cpu_inst(target, XT_INS_ISYNC); + if (res != ERROR_OK) + return res; + + res = xtensa_tap_queue_cpu_inst(target, XT_INS_ISYNC); + if (res != ERROR_OK) + return res; + + res = jtag_execute_queue(); + if(res != ERROR_OK) + return res; + + /* Wait for everything to settle, seems necessary to avoid bad resumes */ + do { + res = xtensa_tap_exec(target, TAP_INS_READ_DOSR, 0, &dosr); + if(res != ERROR_OK) { + LOG_ERROR("Failed to read DOSR. Not Xtensa OCD?"); + return ERROR_FAIL; + } + } while(!(dosr & DOSR_IN_OCD_MODE) || (dosr & DOSR_EXCEPTION)); + + /* Now ICOUNT is set, we can resume as if we were going to run */ + res = xtensa_resume(target, current, address, 0, 0); + if(res != ERROR_OK) { + LOG_ERROR("%s: Failed to resume after setting up single step", __func__); + return res; + } + + /* Wait for stepping to complete */ + int64_t start = timeval_ms(); + while(target->state != TARGET_HALTED && timeval_ms() < start+500) { + res = target_poll(target); + if(res != ERROR_OK) + return res; + if(target->state != TARGET_HALTED) + usleep(50000); + } + if(target->state != TARGET_HALTED) { + xtensa_halt(target); + LOG_ERROR("%s: Timed out waiting for target to finish stepping.", __func__); + return ERROR_TARGET_TIMEOUT; + } + + if (originalIntenable) + { + buf_set_u32(xtensa->core_cache->reg_list[XT_REG_IDX_INTENABLE].value, 0, 32, originalIntenable); + xtensa->core_cache->reg_list[XT_REG_IDX_INTENABLE].dirty = 1; + } + + /* write ICOUNTLEVEL back to zero */ + res = xtensa_tap_queue_write_sr(target, XT_REG_IDX_ICOUNTLEVEL, 0); + if(res != ERROR_OK) + return res; + res = jtag_execute_queue(); + + return res; +} + + +static int xtensa_arch_state(struct target *target) +{ + LOG_DEBUG("%s", __func__); + return ERROR_OK; +} + +static int xtensa_assert_reset(struct target *target) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + enum reset_types jtag_reset_config = jtag_get_reset_config(); + + if (jtag_reset_config & RESET_HAS_SRST) { + /* default to asserting srst */ + if (jtag_reset_config & RESET_SRST_PULLS_TRST) + jtag_add_reset(1, 1); + else + jtag_add_reset(0, 1); + } + + target->state = TARGET_RESET; + jtag_add_sleep(5000); + + register_cache_invalidate(xtensa->core_cache); + + LOG_DEBUG("%s", __func__); + return ERROR_OK; +} + +static int xtensa_deassert_reset(struct target *target) +{ + int res; + + /* deassert reset lines */ + jtag_add_reset(0, 0); + + usleep(100000); + res = xtensa_poll(target); + if (res != ERROR_OK) + return res; + + if (target->reset_halt) { + /* TODO: work out if possible to halt on reset (I think "no" */ + res = target_halt(target); + if (res != ERROR_OK) { + LOG_ERROR("%s: failed to halt afte reset", __func__); + return res; + } + LOG_WARNING("%s: 'reset halt' is not supported for Xtensa. " + "Have halted some time after resetting (not the same thing!)", __func__); + } + + LOG_DEBUG("%s", __func__); + return ERROR_OK; +} + +static int xtensa_read_memory_inner(struct target *target, + uint32_t address, + uint32_t size, + uint32_t count, + uint8_t *buffer) +{ + int res; + uint32_t inst; + uint8_t imm8; + static const uint8_t zeroes[4] = {0}; + + /* Load DDR with base address, save to register a0 */ + /* Push base base address to a0 via DDR */ + res = xtensa_tap_queue_load_general_reg(target, 0, address); + if(res != ERROR_OK) + return res; + + for(imm8 = 0; imm8 < count; imm8++) { + /* determine the load instruction (based on size) */ + switch(size) { + case 4: + inst = XT_INS_L32I(0, 1, imm8); break; + case 2: + inst = XT_INS_L16UI(0, 1, imm8); break; + case 1: + inst = XT_INS_L8UI(0, 1, imm8); break; + default: + return ERROR_COMMAND_SYNTAX_ERROR; + } + /* queue the load instruction to the address register */ + res = xtensa_tap_queue_cpu_inst(target, inst); + if(res != ERROR_OK) + return res; + + /* queue the load instruction from the address register to DDR */ + res = xtensa_tap_queue_cpu_inst(target, XT_INS_WSR(XT_SR_DDR, 1)); + if(res != ERROR_OK) + return res; + + /* queue the read of DDR - note specific length to avoid buffer overrun */ + jtag_add_plain_ir_scan(target->tap->ir_length, + tap_instr_buf+TAP_INS_SCAN_DDR*4, + NULL, TAP_IDLE); + jtag_add_plain_dr_scan(8*size, zeroes, buffer+imm8*size, TAP_IDLE); + } + res = jtag_execute_queue(); + if(res != ERROR_OK) { + LOG_ERROR("%s: JTAG scan failed", __func__); + return res; + } + return ERROR_OK; +} + + +static int xtensa_read_memory(struct target *target, + uint32_t address, + uint32_t size, + uint32_t count, + uint8_t *buffer) +{ + int res; + + if (target->state != TARGET_HALTED) { + LOG_WARNING("target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + /* sanitize arguments */ + if (((size != 4) && (size != 2) && (size != 1)) || (count == 0) || !(buffer)) + return ERROR_COMMAND_SYNTAX_ERROR; + + if (((size == 4) && (address & 0x3u)) || ((size == 2) && (address & 0x1u))) + return ERROR_TARGET_UNALIGNED_ACCESS; + + /* Read & dirty a0 & a1 as we're going to use them as working regs */ + xtensa_setup_scratch_reg(target, XT_REG_IDX_A0); + xtensa_setup_scratch_reg(target, XT_REG_IDX_A1); + + /* NB: if we were supporting the ICACHE option, we would need + * to invalidate it here */ + + /* All the LxxI instructions support up to 255 offsets per + instruction, so we break each read up into blocks of at + most this size. + */ + while(count > 255) { + LOG_DEBUG("%s: splitting read from 0x%" PRIx32 " size %d count 255",__func__, + address,size); + res = xtensa_read_memory_inner(target, address, size, 255, buffer); + if(res != ERROR_OK) { + LOG_ERROR("%s: inner read failed at address 0x%" PRIx32, __func__, address); + return res; + } + count -= 255; + address += (255 * size); + buffer += (255 * size); + } + res = xtensa_read_memory_inner(target, address, size, count, buffer); + if(res != ERROR_OK) { + LOG_ERROR("%s: final read failed at address 0x%" PRIx32, __func__, address); + } + + return res; +} + +static int xtensa_write_memory_inner(struct target *target, + uint32_t address, + uint32_t size, + uint32_t count, + const uint8_t *buffer) +{ + int res; + uint32_t inst; + uint8_t imm8; + + /* Push base address to a0 via DDR */ + res = xtensa_tap_queue_load_general_reg(target, 0, address); + if(res != ERROR_OK) + return res; + + for(imm8 = 0; imm8 < count; imm8++) { + /* load next word from buffer into a1, via DDR */ + res = xtensa_tap_queue_load_general_reg(target, 1, + buf_get_u32(buffer+imm8*size, 0, 8*size)); + if(res != ERROR_OK) + return res; + + /* determine the store instruction (based on size) */ + switch(size) { + case 4: + inst = XT_INS_S32I(0, 1, imm8); break; + case 2: + inst = XT_INS_S16I(0, 1, imm8); break; + case 1: + inst = XT_INS_S8I(0, 1, imm8); break; + default: + return ERROR_COMMAND_SYNTAX_ERROR; + } + /* queue the store instruction to the address register */ + res = xtensa_tap_queue_cpu_inst(target, inst); + if(res != ERROR_OK) + return res; + } + res = jtag_execute_queue(); + if(res != ERROR_OK) { + LOG_ERROR("%s: JTAG scan failed", __func__); + return res; + } + + return ERROR_OK; +} + +static int xtensa_write_memory(struct target *target, + uint32_t address, + uint32_t size, + uint32_t count, + const uint8_t *buffer) +{ + /* NOTE FOR LATER: This function is almost identical to + xtensa_read_memory, and can possibly be converted into a common + wrapper function. The only problem is the 'const uint8_t + *buffer' rather than the non-const read function version... :(. + */ + int res; + + if (target->state != TARGET_HALTED) { + LOG_WARNING("target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + /* sanitize arguments */ + if (((size != 4) && (size != 2) && (size != 1)) || (count == 0) || !(buffer)) + return ERROR_COMMAND_SYNTAX_ERROR; + + if (((size == 4) && (address & 0x3u)) || ((size == 2) && (address & 0x1u))) + return ERROR_TARGET_UNALIGNED_ACCESS; + + /* Read & dirty a0 & a1 as we're going to use them as working regs */ + xtensa_setup_scratch_reg(target, XT_REG_IDX_A0); + xtensa_setup_scratch_reg(target, XT_REG_IDX_A1); + + /* All the LxxI instructions support up to 255 offsets per + instruction, so we break each read up into blocks of at + most this size. + */ + while(count > 255) { + LOG_DEBUG("%s: splitting read from 0x%" PRIx32 " size %d count 255",__func__, + address,size); + res = xtensa_write_memory_inner(target, address, size, 255, buffer); + if(res != ERROR_OK) { + LOG_ERROR("%s: inner write failed at address 0x%" PRIx32, __func__, address); + + return res; + } + count -= 255; + address += (255 * size); + buffer += (255 * size); + } + res = xtensa_write_memory_inner(target, address, size, count, buffer); + if(res != ERROR_OK) { + LOG_ERROR("%s: final write failed at address 0x%" PRIx32, __func__, address); + } + + /* NB: if we were supporting the ICACHE option, we would need + * to invalidate it here */ + + return res; +} + +static int xtensa_read_buffer(struct target *target, + uint32_t address, + uint32_t count, + uint8_t *buffer) +{ + uint8_t *aligned_buffer; + uint32_t aligned_address; + uint32_t aligned_count; + int res; + + /* In case we are reading IRAM/IROM, extend our read to be + * 32-bit aligned 32-bit reads */ + aligned_address = address & ~3; + aligned_count = ((address + count + 3) & ~3) - aligned_address; + + if (aligned_count != count) + aligned_buffer = malloc(aligned_count); + else + aligned_buffer = buffer; + + LOG_DEBUG("%s: aligned_address=0x%" PRIx32 " aligned_count=0x%" + PRIx32, __func__, aligned_address, aligned_count); + + res = xtensa_read_memory(target, aligned_address, + 4, aligned_count/4, + aligned_buffer); + + if(aligned_count != count) { + if(res == ERROR_OK) { + memcpy(buffer, aligned_buffer + (address & 3), count); + } + free(aligned_buffer); + } + + return res; +} + +static int xtensa_write_buffer(struct target *target, + uint32_t address, + uint32_t count, + const uint8_t *buffer) +{ + uint8_t *aligned_buffer = 0; + uint32_t aligned_address; + uint32_t aligned_count; + int res; + + /* In case we are writing IRAM/IROM, extend our write to cover + * 32-bit aligned 32-bit writes */ + aligned_address = address & ~3; + aligned_count = ((address + count + 3) & ~3) - aligned_address; + + if (aligned_count != count) { + aligned_buffer = malloc(aligned_count); + // Fill in head word with what's currently in memory + res = xtensa_read_buffer(target, aligned_address, + 4, aligned_buffer); + if(res != ERROR_OK) + goto cleanup; + if(aligned_count > 4) { + // Fill in tail word with what's currently in memory + res = xtensa_read_buffer(target, + aligned_address+aligned_count-4, + 4, aligned_buffer+aligned_count-4); + if(res != ERROR_OK) + goto cleanup; + } + memcpy(aligned_buffer + (address & 3), buffer, count); + buffer = aligned_buffer; + } + + LOG_DEBUG("%s: aligned_address=0x%" PRIx32 " aligned_count=0x%" + PRIx32, __func__, aligned_address, aligned_count); + + res = xtensa_write_memory(target, aligned_address, + 4, aligned_count/4, + buffer); + + cleanup: + if(aligned_buffer) { + free(aligned_buffer); + } + + return res; +} + +static int xtensa_set_breakpoint(struct target *target, struct breakpoint *breakpoint) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + struct reg *reg_list = xtensa->core_cache->reg_list; + size_t slot; + int res; + + for(slot = 0; slot < xtensa->num_brps; slot++) { + if(xtensa->hw_brps[slot] == NULL || xtensa->hw_brps[slot] == breakpoint) + break; + } + assert(slot < xtensa->num_brps && "Breakpoint slot should always be available to set breakpoint"); + + /* Write IBREAKA[slot] and set bit #slot in IBREAKENABLE */ + enum xtensa_reg_idx bp_reg_idx = XT_REG_IDX_IBREAKA0+slot; + xtensa_tap_queue_write_sr(target, bp_reg_idx, breakpoint->address); + uint32_t ibe_val = buf_get_u32(reg_list[XT_REG_IDX_IBREAKENABLE].value, 0, 32); + ibe_val |= (1<<slot); + xtensa_tap_queue_write_sr(target, XT_REG_IDX_IBREAKENABLE, ibe_val); + + res = jtag_execute_queue(); + if(res != ERROR_OK) + return res; + + xtensa->hw_brps[slot] = breakpoint; + + /* invalidate register cache */ + reg_list[XT_REG_IDX_IBREAKENABLE].valid = 0; + reg_list[bp_reg_idx].valid = 0; + + return ERROR_OK; +} + +static const uint8_t s_3ByteBreakpoint[] = { 0x00, 0x40, 0x00 }; +static const uint8_t s_2ByteBreakpoint[] = { 0x2d, 0xf0 }; + +static int xtensa_add_breakpoint(struct target *target, struct breakpoint *breakpoint) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + + if (target->state != TARGET_HALTED) { + LOG_WARNING("target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (breakpoint->type == BKPT_SOFT) { + const uint8_t *pBreakpointInsn = NULL; + char tmpBuf[16]; + int res = xtensa_read_buffer(target, breakpoint->address, breakpoint->length, breakpoint->orig_instr); + if (res != ERROR_OK) + return res; + + if (breakpoint->length == sizeof(s_3ByteBreakpoint)) + pBreakpointInsn = s_3ByteBreakpoint; + else if (breakpoint->length == sizeof(s_2ByteBreakpoint)) + pBreakpointInsn = s_2ByteBreakpoint; + else + { + LOG_ERROR("Unexpected SW breakpoint size: %d", breakpoint->length); + res = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } + + res = xtensa_write_buffer(target, breakpoint->address, breakpoint->length, pBreakpointInsn); + if (res != ERROR_OK) + return res; + + res = xtensa_read_buffer(target, breakpoint->address, breakpoint->length, (uint8_t*) tmpBuf); + if (res == ERROR_OK && !memcmp(tmpBuf, pBreakpointInsn, breakpoint->length)) + return res; + + breakpoint->type = BKPT_HARD; + LOG_WARNING("Cannot set a software breakpoint at 0x%x. Trying a hardware one instead...", breakpoint->address); + } + + if (!xtensa->free_brps) { + LOG_ERROR("no free breakpoint available for hardware breakpoint"); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } + xtensa->free_brps--; + + return xtensa_set_breakpoint(target, breakpoint); +} + +static int xtensa_unset_breakpoint(struct target *target, struct breakpoint *breakpoint) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + struct reg *reg_list = xtensa->core_cache->reg_list; + size_t slot; + int res; + + for(slot = 0; slot < xtensa->num_brps; slot++) { + if(xtensa->hw_brps[slot] == breakpoint) + break; + } + assert(slot < xtensa->num_brps && "Breakpoint slot not found"); + + uint32_t ibe_val = buf_get_u32(reg_list[XT_REG_IDX_IBREAKENABLE].value, 0, 32); + ibe_val &= ~(1<<slot); + xtensa_tap_queue_write_sr(target, XT_REG_IDX_IBREAKENABLE, ibe_val); + + res = jtag_execute_queue(); + if(res != ERROR_OK) + return res; + + xtensa->hw_brps[slot] = NULL; + + /* invalidate register cache */ + reg_list[XT_REG_IDX_IBREAKENABLE].valid = 0; + return ERROR_OK; +} + +static int xtensa_remove_breakpoint(struct target *target, struct breakpoint *breakpoint) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + int res; + + if (target->state != TARGET_HALTED) { + LOG_WARNING("target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (breakpoint->type == BKPT_SOFT) + { + res = xtensa_write_buffer(target, breakpoint->address, breakpoint->length, breakpoint->orig_instr); + } + else + { + res = xtensa_unset_breakpoint(target, breakpoint); + if (res == ERROR_OK) { + xtensa->free_brps++; + assert(xtensa->free_brps <= xtensa->num_brps && "Free breakpoint count should always be less than max breakpoints"); + } + } + return res; +} + + +/* Read register value from target. This function goes via the gdb-facing register cache. */ +static int xtensa_read_register(struct target *target, int idx, int force) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + struct reg *reg_list = xtensa->core_cache->reg_list; + struct reg *reg; + struct xtensa_core_reg *xt_reg; + uint32_t reg_value; + uint8_t read_reg_buf[4]; + int res; + + if(idx < 0 || idx >= XT_NUM_REGS) + return ERROR_COMMAND_SYNTAX_ERROR; + + reg = ®_list[idx]; + xt_reg = reg->arch_info; + + LOG_DEBUG("%s %s valid=%d dirty=%d force=%d", __func__, reg->name, + reg->valid, reg->dirty, force); + + if((reg->valid && !force) || reg->dirty) + return ERROR_OK; /* Still OK */ + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + if(xt_reg->type == XT_REG_GENERAL) { + /* Access via WSR from general reg Ax to DDR */ + res = xtensa_tap_queue_cpu_inst(target, XT_INS_WSR(XT_SR_DDR,xt_reg->reg_num)); + if(res != ERROR_OK) + return res; + xtensa_tap_queue(target, TAP_INS_SCAN_DDR, NULL, read_reg_buf); + if(res != ERROR_OK) + return res; + } + else { + /* Otherwise, access is via a special register read via a0 */ + + /* Store a0 as being used as scratch reg */ + xtensa_setup_scratch_reg(target, XT_REG_IDX_A0); + + /* RSR to read special reg to a0, then WSR to DDR, then scan via OCD */ + res = xtensa_tap_queue_cpu_inst(target, XT_INS_RSR(xt_reg->reg_num, 0)); + if(res != ERROR_OK) + return res; + res = xtensa_tap_queue_cpu_inst(target, XT_INS_WSR(XT_SR_DDR, 0)); + res = xtensa_tap_queue(target, TAP_INS_SCAN_DDR, NULL, read_reg_buf); + if(res != ERROR_OK) + return res; + } + + res = jtag_execute_queue(); + if(res != ERROR_OK) + return res; + + reg_value = buf_get_u32(read_reg_buf, 0, 32); + buf_set_u32(reg->value, 0, 32, reg_value); + + LOG_DEBUG("%s: read %s type %d num %d value 0x%" PRIx32, __func__, xt_reg->name, + xt_reg->type, xt_reg->reg_num, reg_value); + + reg->valid = 1; + reg->dirty = 0; + return ERROR_OK; +} + +static int xtensa_write_register(struct target *target, enum xtensa_reg_idx idx) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + struct reg *reg_list = xtensa->core_cache->reg_list; + struct reg *reg; + struct xtensa_core_reg *xt_reg, *xt_alias; + uint32_t value; + int res, i; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + if(idx < 0 || idx >= XT_NUM_REGS) + return ERROR_COMMAND_SYNTAX_ERROR; + + reg = ®_list[idx]; + xt_reg = reg->arch_info; + + LOG_DEBUG("%s %s dirty=%d value=%04"PRIx32, __func__, reg->name, + reg->dirty, buf_get_u32(reg->value, 0, 32)); + + if(!reg->dirty) + return ERROR_OK; + + if(xt_reg->type == XT_REG_GENERAL) { + /* Load new register value to general register Ax via DDR */ + res = xtensa_tap_queue_load_general_reg(target, xt_reg->reg_num, + buf_get_u32(reg->value, 0, 32)); + if(res != ERROR_OK) + return res; + } + else { + /* Special register load */ + value = buf_get_u32(reg->value, 0, 32); + res = xtensa_tap_queue_write_sr(target, xt_reg->idx, value); + if(res != ERROR_OK) + return res; + } + + res = jtag_execute_queue(); + if(res != ERROR_OK) + return res; + + /* In case we just wrote to an aliased register, make sure to + invalidate any other register sharing the same special + register number */ + if(xt_reg->type == XT_REG_ALIASED || xt_reg->type==XT_REG_SPECIAL) { + for(i = 0; i < XT_NUM_REGS; i++) { + xt_alias = reg_list[i].arch_info; + if((xt_alias->type == XT_REG_ALIASED || xt_alias->type == XT_REG_SPECIAL) + && xt_alias->reg_num == xt_reg->reg_num) { + reg_list[i].valid = 0; + reg_list[i].dirty = 0; + } + } + } + + reg->valid = 1; + reg->dirty = 0; + + return ERROR_OK; +} + +static int xtensa_get_core_reg(struct reg *reg) +{ + struct xtensa_core_reg *xt_reg = reg->arch_info; + return xtensa_read_register(xt_reg->target, xt_reg->idx, 1); +} + +static int xtensa_set_core_reg(struct reg *reg, uint8_t *buf) +{ + struct xtensa_core_reg *xt_reg = reg->arch_info; + struct target *target = xt_reg->target; + uint32_t value = buf_get_u32(buf, 0, 32); + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + buf_set_u32(reg->value, 0, reg->size, value); + reg->dirty = 1; + reg->valid = 1; + return ERROR_OK; +} + +/* Save context from target */ +static int xtensa_save_context(struct target *target) +{ + int i; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + for(i = 0; i < XT_NUM_REGS; i++) + xtensa_read_register(target, i, 1); + return ERROR_OK; +} + +/* Restore context to target */ +static int xtensa_restore_context(struct target *target) +{ + int i; + + if (target->state != TARGET_HALTED) + return ERROR_TARGET_NOT_HALTED; + + /* Write back registers in reverse order, because SRs (higher + indices) can use general registers (lower indices) as + part of writeback, thereby invalidating them. + */ + for(i = XT_NUM_REGS-1; i >= 0; i--) { + xtensa_write_register(target, i); + } + return ERROR_OK; +} + + +static const struct reg_arch_type xtensa_reg_type = { + .get = xtensa_get_core_reg, + .set = xtensa_set_core_reg, +}; + +static void xtensa_build_reg_cache(struct target *target) +{ + struct xtensa_common *xtensa = target_to_xtensa(target); + struct reg_cache **cache_p = register_get_last_cache_p(&target->reg_cache); + struct reg_cache *cache = malloc(sizeof(struct reg_cache)); + struct reg *reg_list = calloc(XT_NUM_REGS, sizeof(struct reg)); + struct xtensa_core_reg *arch_info = malloc( + sizeof(struct xtensa_core_reg) * XT_NUM_REGS); + uint8_t i; + + cache->name = "Xtensa registers"; + cache->next = NULL; + cache->reg_list = reg_list; + cache->num_regs = XT_NUM_REGS; + (*cache_p)= cache; + xtensa->core_cache = cache; + + for(i = 0; i < XT_NUM_REGS; i++) { + assert(xt_regs[i].idx == i && "xt_regs[] entry idx field should match index in array"); + arch_info[i] = xt_regs[i]; + arch_info[i].target = target; + if(arch_info[i].type == XT_REG_ALIASED) { + /* NB: This is a constant at the moment, but will eventually be target-specific */ + arch_info[i].reg_num += XT_DEBUGLEVEL; + } + reg_list[i].name = arch_info[i].name; + reg_list[i].size = 32; + reg_list[i].value = calloc(1,4); + reg_list[i].dirty = 0; + reg_list[i].valid = 0; + reg_list[i].type = &xtensa_reg_type; + reg_list[i].arch_info = &arch_info[i]; + } +} + +static int xtensa_get_gdb_reg_list(struct target *target, + struct reg **reg_list[], + int *reg_list_size, + enum target_register_class reg_class) +{ + int i; + struct xtensa_common *xtensa = target_to_xtensa(target); + + *reg_list_size = XT_NUM_REGS; + *reg_list = malloc(sizeof(struct reg *) * (*reg_list_size)); + + if (!*reg_list) + return ERROR_COMMAND_SYNTAX_ERROR; + + for (i = 0; i < XT_NUM_REGS; i++) + (*reg_list)[i] = &xtensa->core_cache->reg_list[i]; + + return ERROR_OK; +} + + +static void xtensa_feed_esp8266_watchdog(struct target *target) +{ + uint64_t wdtval = 0; + uint32_t wdtctl = 0; + int r; + r = xtensa_read_memory(target, 0x3ff21048, 4, 2, (uint8_t *)&wdtval); + if (r != ERROR_OK) + { + LOG_ERROR("failed to read wdtval: error %d", r); + return; + } + + wdtval += 1600000; + r = xtensa_write_memory(target, 0x3ff210cc, 4, 2, (uint8_t *)&wdtval); + if (r != ERROR_OK) + { + LOG_ERROR("failed to read wdtovf: error %d", r); + return; + } + + r = xtensa_read_memory(target, 0x3ff210c8, 4, 1, (uint8_t *)&wdtctl); + if (r != ERROR_OK) + { + LOG_ERROR("failed to read wdtctl: error %d", r); + return; + } + wdtctl |= (1 << 31); + r = xtensa_write_memory(target, 0x3ff210c8, 4, 1, (uint8_t *)&wdtctl); + if (r != ERROR_OK) + { + LOG_ERROR("failed to read wdtctl: error %d", r); + return; + } +} + +COMMAND_HANDLER(xtensa_no_interrupts_during_steps) +{ + if (CMD_ARGC > 1) + return ERROR_COMMAND_SYNTAX_ERROR; + if (CMD_ARGC == 1) + COMMAND_PARSE_ENABLE(CMD_ARGV[0], s_DisableInterruptsForStepping); + + command_print(CMD_CTX, "Interrupt suppression during single-stepping is %s%s", (CMD_ARGC == 1) ? "now " : "", s_DisableInterruptsForStepping ? "enabled" : "disabled"); + + return ERROR_OK; +} + +COMMAND_HANDLER(esp8266_autofeed_watchdog) +{ + if (CMD_ARGC > 1) + return ERROR_COMMAND_SYNTAX_ERROR; + if (CMD_ARGC == 1) + COMMAND_PARSE_ENABLE(CMD_ARGV[0], s_FeedWatchdogDuringStops); + + command_print(CMD_CTX, "Watchdog feeding during stops is %s%s", (CMD_ARGC == 1) ? "now " : "", s_FeedWatchdogDuringStops ? "enabled" : "disabled"); + + return ERROR_OK; +} + + +struct command_registration xtensa_commands[] = { + { + .name = "xtensa_no_interrupts_during_steps", + .handler = xtensa_no_interrupts_during_steps, + .mode = COMMAND_ANY, + .usage = "[enable|disable]", + .help = "Specifies whether the INTENABLE register will be set to 0 during single-stepping, temporarily disabling interrupts", + }, + { + .name = "esp8266_autofeed_watchdog", + .handler = esp8266_autofeed_watchdog, + .mode = COMMAND_ANY, + .usage = "[enable|disable]", + .help = "Specifies whether OpenOCD will feed the ESP8266 software watchdog while the target is halted", + }, + COMMAND_REGISTRATION_DONE +}; + +/** Holds methods for Xtensa targets. */ +struct target_type xtensa_target = { + .name = "xtensa", + + .poll = xtensa_poll, + .arch_state = xtensa_arch_state, + + .halt = xtensa_halt, + .resume = xtensa_resume, + .step = xtensa_step, + + .assert_reset = xtensa_assert_reset, + .deassert_reset = xtensa_deassert_reset, + + .read_memory = xtensa_read_memory, + .write_memory = xtensa_write_memory, + + .read_buffer = xtensa_read_buffer, + .write_buffer = xtensa_write_buffer, + + .get_gdb_reg_list = xtensa_get_gdb_reg_list, + + .add_breakpoint = xtensa_add_breakpoint, + .remove_breakpoint = xtensa_remove_breakpoint, + /* + .add_watchpoint = xtensa_add_watchpoint, + .remove_watchpoint = xtensa_remove_watchpoint, + */ + + .target_create = xtensa_target_create, + .init_target = xtensa_init_target, + .examine = xtensa_examine, + + .commands = xtensa_commands, +}; diff --git a/src/target/xtensa.h b/src/target/xtensa.h new file mode 100644 index 0000000..30519f2 --- /dev/null +++ b/src/target/xtensa.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (C) 2015 by Angus Gratton * + * [email protected] * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#ifndef XTENSA_H +#define XTENSA_H + +#include <jtag/jtag.h> +#include "breakpoints.h" + +enum xtensa_state { + XT_NORMAL, + XT_OCD_RUN, + XT_OCD_HALT, +}; + +struct xtensa_common { + struct jtag_tap *tap; + enum xtensa_state state; + struct reg_cache *core_cache; + + uint32_t num_brps; /* Number of breakpoints available */ + uint32_t free_brps; /* Number of free breakpoints */ + struct breakpoint **hw_brps; +}; + +struct xtensa_tap_instr { + const uint8_t idx; + const uint8_t inst; + const uint8_t data_len; + const char *name; +}; + +static inline struct xtensa_common *target_to_xtensa(struct target *target) +{ + return (struct xtensa_common *)target->arch_info; +} + +enum xt_reg_t { + XT_REG_GENERAL = 0, + XT_REG_ALIASED = 1, + XT_REG_SPECIAL = 2, +}; + +struct xtensa_core_reg { + uint32_t idx; /* gdb server access index */ + const char *name; + uint8_t reg_num; /* ISA register num (meaning depends on register type) */ + enum xt_reg_t type; + struct target *target; +}; + +#endif /* XTENSA_H */ diff --git a/tcl/target/esp8266.cfg b/tcl/target/esp8266.cfg new file mode 100644 index 0000000..383016d --- /dev/null +++ b/tcl/target/esp8266.cfg @@ -0,0 +1,38 @@ +set _CHIPNAME esp8266 + +transport select jtag + +reset_config trst_and_srst + +adapter_khz 1000 + +jtag newtap $_CHIPNAME cpu -irlen 5 -ircapture 0x1 -irmask 0x1f + +set _TARGETNAME $_CHIPNAME.cpu +target create $_TARGETNAME xtensa -endian little -chain-position $_TARGETNAME + +# esp8266 seems to have a quirk where the JTAG hardware doesn't work +# at all for ~20ms after RST is released. We do a custom reset to +# avoid JTAG layer errors +proc init_reset {mode} { + # assert both resets (SRST/TRST not a clear division on esp8266 anyhow) + jtag_reset 1 1 + sleep 30 + jtag_reset 0 0 + + # wait for debug port to wake up + sleep 30 + + # validate scanchain + jtag arp_init +} + + +# Disable system watchdog when halted to avoid unexpected resets +$_TARGETNAME configure -event halted { + stop_wdt +} + +proc stop_wdt { } { + mww 0x60000900 0 +} -- ------------------------------------------------------------------------------ Site24x7 APM Insight: Get Deep Visibility into Application Performance APM + Mobile APM + RUM: Monitor 3 App instances at just $35/Month Monitor end-to-end web transactions and take corrective actions now Troubleshoot faster and improve end-user experience. Signup Now! http://pubads.g.doubleclick.net/gampad/clk?id=272487151&iu=/4140 _______________________________________________ OpenOCD-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openocd-devel
