From aecaebdaf1f0684b41fd4c85df6d28ac14961fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Sun, 24 May 2026 16:34:08 +0200 Subject: [PATCH] ftdi+arm_debug: honor last-bit TMS; ARM7 EmbeddedICE halt/resume MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FTDI MPSSE xfer ignored TMS on data bits, so bscan_set_ir never latched the IR — the bscan exit needs the last bit to clock Shift->Exit1 so the following Update latches. It only ever worked on the Digilent driver. Now the final TMS-flagged bit is clocked through the TMS pin (carrying TDI/TDO), so bscan_set_ir/bscan_shift_dr reach Exit1->Update correctly. Implement ARM7TDMI EmbeddedICE access (SCAN_N + INTEST, 38-bit scan chain 2 register R/W with pipelined read) and halt (force DBGRQ, poll DBGACK) / resume (clear DBGRQ + RESTART). New cpu_halt / cpu_resume commands; arm_debug links bscan. Validated on an LPC2103 over the ARM-USB-OCD: set_ir(IDCODE) reads 0x4F1F0F0F, EmbeddedICE registers round-trip, cpu_halt -> DBGACK, cpu_resume releases the core. Co-Authored-By: Claude Opus 4.7 --- src/modules/arm_debug/CMakeLists.txt | 3 + src/modules/arm_debug/arm_debug.c | 156 ++++++++++++++---- src/modules/drivers/ftdi_jtag/ftdi_jtag_drv.c | 43 ++++- src/modules/script/script.c | 59 +++++++ 4 files changed, 225 insertions(+), 36 deletions(-) diff --git a/src/modules/arm_debug/CMakeLists.txt b/src/modules/arm_debug/CMakeLists.txt index 482ab5a..ca33b20 100644 --- a/src/modules/arm_debug/CMakeLists.txt +++ b/src/modules/arm_debug/CMakeLists.txt @@ -3,3 +3,6 @@ file(GLOB_RECURSE ALL_SOURCES "*.c") include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) add_library(arm_debug ${ALL_SOURCES}) + +# arm_debug drives the EmbeddedICE scan chains via the bscan TAP primitives. +target_link_libraries(arm_debug PUBLIC bscan) diff --git a/src/modules/arm_debug/arm_debug.c b/src/modules/arm_debug/arm_debug.c index b850281..bd3d40a 100644 --- a/src/modules/arm_debug/arm_debug.c +++ b/src/modules/arm_debug/arm_debug.c @@ -1,56 +1,154 @@ #include +#include +#include +#include "bscan/bscan.h" #include "arm_debug.h" -/* Not implemented yet — every entry point reports failure for now. The - * real work — EmbeddedICE scan chains, halt/resume, memory access over - * the bscan_* primitives, and a per-MCU RAM flash loader — slots in - * behind these signatures without touching callers. */ +/* + * ARM7TDMI debug over JTAG (EmbeddedICE), built on the bscan_* TAP + * primitives. Incremental bring-up: + * - done: EmbeddedICE register access, halt (force DBGRQ) / resume + * - todo: memory read/write (debug-speed instruction injection) and + * the arm_flash backend. + */ + +/* ARM7TDMI public JTAG instructions (IR length 4). */ +#define ARM7_IR_LEN 4 +#define IR_SCAN_N 0x2 +#define IR_INTEST 0xC +#define IR_RESTART 0x4 + +/* EmbeddedICE scan chain (#2) and register addresses. */ +#define EICE_SCANCHAIN 2 +#define EICE_DBG_CTRL 0x00 +#define EICE_DBG_STATUS 0x01 + +#define DBG_STATUS_DBGACK (1u << 0) + +/* One EmbeddedICE scan-chain-2 access: 38 bits LSB-first = + * data[0..31] | address[32..36] | read/write[37] (1 = write). On a read, + * the captured data belongs to the *previously* addressed register. */ +static int eice_scan(jtag_core *jc, int addr, int rw, uint32_t data, uint32_t *out) +{ + uint8_t buf[5], cap[5]; + int i, r; + + memset(buf, 0, sizeof(buf)); + for (i = 0; i < 32; i++) + if (data & (1u << i)) buf[i >> 3] |= (uint8_t)(1u << (i & 7)); + for (i = 0; i < 5; i++) + if (addr & (1 << i)) { int b = 32 + i; buf[b >> 3] |= (uint8_t)(1u << (b & 7)); } + if (rw) buf[37 >> 3] |= (uint8_t)(1u << (37 & 7)); + + r = bscan_shift_dr(jc, buf, out ? cap : NULL, 38); + if (r < 0) return -1; + + if (out) { + uint32_t v = 0; + for (i = 0; i < 32; i++) + if (cap[i >> 3] & (1u << (i & 7))) v |= (1u << i); + *out = v; + } + return 0; +} + +/* Select the EmbeddedICE scan chain (#2) and enter INTEST. */ +static int eice_select(jtag_core *jc) +{ + uint8_t sc = EICE_SCANCHAIN; + if (bscan_set_ir(jc, IR_SCAN_N, ARM7_IR_LEN) < 0) return -1; + if (bscan_shift_dr(jc, &sc, NULL, ARM7_IR_LEN) < 0) return -1; + if (bscan_set_ir(jc, IR_INTEST, ARM7_IR_LEN) < 0) return -1; + return 0; +} + +static int eice_write(jtag_core *jc, int addr, uint32_t val) +{ + return eice_scan(jc, addr, 1, val, NULL); +} + +/* Read an EmbeddedICE register (two scans: request, then capture). */ +static int eice_read(jtag_core *jc, int addr, uint32_t *val) +{ + if (eice_scan(jc, addr, 0, 0, NULL) < 0) return -1; /* request */ + if (eice_scan(jc, addr, 0, 0, val) < 0) return -1; /* capture */ + return 0; +} int arm_debug_halt(jtag_core *jc, const jtag_target *t) { - (void)jc; (void)t; - fprintf(stderr, "arm_debug: halt not implemented yet\n"); - return -1; + uint32_t status = 0; + (void)t; + + bscan_tap_reset(jc); + if (eice_select(jc) < 0) + return -1; + + /* Debug Control bit 1 = DBGRQ -> core enters debug at the next + * instruction boundary; poll DBGACK (it isn't instantaneous). */ + if (eice_write(jc, EICE_DBG_CTRL, 0x2) < 0) + return -1; + + { + int tries; + for (tries = 0; tries < 100; tries++) { + bscan_idle_cycles(jc, 64); + if (eice_read(jc, EICE_DBG_STATUS, &status) < 0) + return -1; + if (status & DBG_STATUS_DBGACK) + return 0; + } + } + + fprintf(stderr, "arm_debug: halt requested but no DBGACK (status 0x%08x)\n", status); + return -1; } int arm_debug_resume(jtag_core *jc, const jtag_target *t) { - (void)jc; (void)t; - fprintf(stderr, "arm_debug: resume not implemented yet\n"); - return -1; + (void)t; + if (eice_select(jc) < 0) + return -1; + /* Clear DBGRQ, then RESTART exits debug state. */ + if (eice_write(jc, EICE_DBG_CTRL, 0x0) < 0) + return -1; + if (bscan_set_ir(jc, IR_RESTART, ARM7_IR_LEN) < 0) + return -1; + bscan_idle_cycles(jc, 16); + return 0; } int arm_debug_mem_read(jtag_core *jc, const jtag_target *t, unsigned long addr, void *buf, unsigned long len) { - (void)jc; (void)t; (void)addr; (void)buf; (void)len; - fprintf(stderr, "arm_debug: mem_read not implemented yet\n"); - return -1; + (void)jc; (void)t; (void)addr; (void)buf; (void)len; + fprintf(stderr, "arm_debug: mem_read not implemented yet\n"); + return -1; } int arm_debug_mem_write(jtag_core *jc, const jtag_target *t, unsigned long addr, const void *buf, unsigned long len) { - (void)jc; (void)t; (void)addr; (void)buf; (void)len; - fprintf(stderr, "arm_debug: mem_write not implemented yet\n"); - return -1; + (void)jc; (void)t; (void)addr; (void)buf; (void)len; + fprintf(stderr, "arm_debug: mem_write not implemented yet\n"); + return -1; } int arm_flash_program(jtag_core *jc, const jtag_target *t, const char *file, arm_log_fn log, void *user) { - char msg[256]; - (void)jc; (void)file; - if (log) { - snprintf(msg, sizeof(msg), - "arm_flash: backend not implemented yet. " - "Target '%s' debug=%d ram=0x%lX+0x%lX flash=0x%lX+0x%lX.", - t ? t->name : "?", - t ? (int)t->cpu.debug : 0, - t ? t->cpu.ram_base : 0UL, t ? t->cpu.ram_size : 0UL, - t ? t->cpu.flash_base : 0UL, t ? t->cpu.flash_size : 0UL); - log(user, 1, msg); - } - return -1; + char msg[256]; + (void)jc; (void)file; + if (log) { + snprintf(msg, sizeof(msg), + "arm_flash: backend not implemented yet. " + "Target '%s' debug=%d ram=0x%lX+0x%lX flash=0x%lX+0x%lX.", + t ? t->name : "?", + t ? (int)t->cpu.debug : 0, + t ? t->cpu.ram_base : 0UL, t ? t->cpu.ram_size : 0UL, + t ? t->cpu.flash_base : 0UL, t ? t->cpu.flash_size : 0UL); + log(user, 1, msg); + } + return -1; } diff --git a/src/modules/drivers/ftdi_jtag/ftdi_jtag_drv.c b/src/modules/drivers/ftdi_jtag/ftdi_jtag_drv.c index 40c7999..2142a42 100644 --- a/src/modules/drivers/ftdi_jtag/ftdi_jtag_drv.c +++ b/src/modules/drivers/ftdi_jtag/ftdi_jtag_drv.c @@ -406,6 +406,9 @@ int drv_FTDI_TDOTDI_xfer(jtag_core * jc, unsigned char * str_out, unsigned char int nbtosend; unsigned char opcode, data; + int body; + int last_tms; + (void)jc; rd_bit_index = 0; wr_bit_index = 0; @@ -413,11 +416,18 @@ int drv_FTDI_TDOTDI_xfer(jtag_core * jc, unsigned char * str_out, unsigned char if (!size) return 0; + /* The bscan_* primitives mark TMS on the LAST bit to leave Shift-DR/IR + * (-> Exit1, so the following Update latches). Honour it: shift the + * body via TDI, then clock the final bit through TMS (carrying its TDI + * and TDO). Without this the IR never latches. */ + last_tms = (str_out[size - 1] & JTAG_STR_TMS) ? 1 : 0; + body = last_tms ? size - 1 : size; + memset(ftdi_out_buf, 0, 16); memset(ftdi_in_buf, 0, 16); /* First bit, if it carries TMS (entering a state): bit-mode TMS shift. */ - if (str_out[wr_bit_index] & JTAG_STR_TMS) { + if (body > 0 && (str_out[wr_bit_index] & JTAG_STR_TMS)) { if (str_in) opcode = (OP_WR_TMS | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR | OP_RD_TDO); else @@ -440,11 +450,8 @@ int drv_FTDI_TDOTDI_xfer(jtag_core * jc, unsigned char * str_out, unsigned char } } - if (wr_bit_index >= size) - return 0; - /* Whole bytes via byte-mode TDI shift. */ - rounded_size = (size - wr_bit_index) & ~(0x7); + rounded_size = (body - wr_bit_index) & ~(0x7); if (rounded_size) { if (str_in) opcode = (OP_WR_TDI | OP_LSB_FIRST | OP_FEDGE_WR | OP_RD_TDO); @@ -482,14 +489,14 @@ int drv_FTDI_TDOTDI_xfer(jtag_core * jc, unsigned char * str_out, unsigned char } /* Trailing bits via bit-mode TDI shift. */ - while (wr_bit_index < size) { + while (wr_bit_index < body) { if (str_in) opcode = (OP_WR_TDI | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR | OP_RD_TDO); else opcode = (OP_WR_TDI | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR); nbtosend = 0; - bitscnt = (size - wr_bit_index); + bitscnt = (body - wr_bit_index); if (bitscnt > 8) bitscnt = 8; ftdi_out_buf[nbtosend++] = opcode; @@ -518,6 +525,28 @@ int drv_FTDI_TDOTDI_xfer(jtag_core * jc, unsigned char * str_out, unsigned char } } + /* Final bit carrying TMS=1: clock it through the TMS pin (Shift -> + * Exit1) with its TDI on bit 7, capturing TDO. This lets the following + * Update latch the IR/DR. */ + if (last_tms) { + opcode = str_in ? (OP_WR_TMS | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR | OP_RD_TDO) + : (OP_WR_TMS | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR); + nbtosend = 0; + ftdi_out_buf[nbtosend++] = opcode; + ftdi_out_buf[nbtosend++] = 0x00; /* 1 bit */ + data = 0x01; /* TMS=1 (bit 0) */ + if (str_out[body] & JTAG_STR_DOUT) data |= 0x80; /* TDI on bit 7 */ + ftdi_out_buf[nbtosend++] = data; + if (opcode & OP_RD_TDO) ftdi_out_buf[nbtosend++] = CMD_SEND_IMMEDIATE; + + if (ft_write(ftdi_out_buf, nbtosend) < 0) return -1; + + if (opcode & OP_RD_TDO) { + if (ft_read(ftdi_in_buf, 1) < 0) return -1; + str_in[rd_bit_index++] = (ftdi_in_buf[0] & 0x80) ? JTAG_STR_DOUT : 0x00; + } + } + return 0; } diff --git a/src/modules/script/script.c b/src/modules/script/script.c index 4bfb409..b0919c3 100644 --- a/src/modules/script/script.c +++ b/src/modules/script/script.c @@ -42,6 +42,7 @@ #include "bscan/bscan.h" #include "svf/svf.h" #include "program/program.h" +#include "arm_debug/arm_debug.h" #include "spi_flash/spi_flash.h" #include "env.h" @@ -3586,6 +3587,62 @@ static int cmd_program(script_ctx *ctx, char *line) return JTAG_CORE_NO_ERROR; } +const char *cmd_cpu_halt_help[] = { + "(int)", + "Halt the ARM CPU at chain device over JTAG debug (EmbeddedICE,", + "ARM7/9): forces DBGRQ and checks DBGACK. Run jtag_scan first.", + "" +}; +static int cmd_cpu_halt(script_ctx *ctx, char *line) +{ + jtag_core *jc; + char dev_s[32]; + int dev; + const jtag_target *t; + + jc = (jtag_core *)ctx->app_ctx; + if (get_param(ctx, line, 1, dev_s) <= 0) { + ctx->script_printf(ctx, MSG_ERROR, "Usage: cpu_halt \n"); + return JTAG_CORE_BAD_PARAMETER; + } + dev = (int)strtol(dev_s, NULL, 0); + t = target_lookup_by_idcode(jtagcore_get_dev_id(jc, dev)); + if (arm_debug_halt(jc, t) < 0) { + ctx->script_printf(ctx, MSG_ERROR, "cpu_halt: no DBGACK (debug not entered)\n"); + return JTAG_CORE_ACCESS_ERROR; + } + ctx->script_printf(ctx, MSG_INFO_0, "CPU halted (DBGACK).\n"); + return JTAG_CORE_NO_ERROR; +} + +const char *cmd_cpu_resume_help[] = { + "(int)", + "Release the ARM CPU at chain device from debug (clear DBGRQ +", + "RESTART).", + "" +}; +static int cmd_cpu_resume(script_ctx *ctx, char *line) +{ + jtag_core *jc; + char dev_s[32]; + int dev; + const jtag_target *t; + + jc = (jtag_core *)ctx->app_ctx; + if (get_param(ctx, line, 1, dev_s) <= 0) { + ctx->script_printf(ctx, MSG_ERROR, "Usage: cpu_resume \n"); + return JTAG_CORE_BAD_PARAMETER; + } + dev = (int)strtol(dev_s, NULL, 0); + t = target_lookup_by_idcode(jtagcore_get_dev_id(jc, dev)); + if (arm_debug_resume(jc, t) < 0) { + ctx->script_printf(ctx, MSG_ERROR, "cpu_resume failed\n"); + return JTAG_CORE_ACCESS_ERROR; + } + ctx->script_printf(ctx, MSG_INFO_0, "CPU resumed.\n"); + return JTAG_CORE_NO_ERROR; +} + cmd_list script_commands_list[] = { {"print", cmd_print, cmd_print_help}, @@ -3643,6 +3700,8 @@ cmd_list script_commands_list[] = {"flash_verify", cmd_flash_verify, cmd_flash_verify_help}, {"svf_play", cmd_svf_play, cmd_svf_play_help}, {"program", cmd_program, cmd_program_help}, + {"cpu_halt", cmd_cpu_halt, cmd_cpu_halt_help}, + {"cpu_resume", cmd_cpu_resume, cmd_cpu_resume_help}, {0, 0}}; ///////////////////////////////////////////////////////////////////////////////