ftdi+arm_debug: honor last-bit TMS; ARM7 EmbeddedICE halt/resume

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 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 16:34:08 +02:00
parent 39963fd6d8
commit aecaebdaf1
4 changed files with 225 additions and 36 deletions

View File

@@ -3,3 +3,6 @@ file(GLOB_RECURSE ALL_SOURCES "*.c")
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
add_library(arm_debug ${ALL_SOURCES}) 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)

View File

@@ -1,24 +1,122 @@
#include <stdio.h> #include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "bscan/bscan.h"
#include "arm_debug.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 * ARM7TDMI debug over JTAG (EmbeddedICE), built on the bscan_* TAP
* the bscan_* primitives, and a per-MCU RAM flash loader — slots in * primitives. Incremental bring-up:
* behind these signatures without touching callers. */ * - 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) int arm_debug_halt(jtag_core *jc, const jtag_target *t)
{ {
(void)jc; (void)t; uint32_t status = 0;
fprintf(stderr, "arm_debug: halt not implemented yet\n"); (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; return -1;
} }
int arm_debug_resume(jtag_core *jc, const jtag_target *t) int arm_debug_resume(jtag_core *jc, const jtag_target *t)
{ {
(void)jc; (void)t; (void)t;
fprintf(stderr, "arm_debug: resume not implemented yet\n"); if (eice_select(jc) < 0)
return -1; 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, int arm_debug_mem_read(jtag_core *jc, const jtag_target *t,

View File

@@ -406,6 +406,9 @@ int drv_FTDI_TDOTDI_xfer(jtag_core * jc, unsigned char * str_out, unsigned char
int nbtosend; int nbtosend;
unsigned char opcode, data; unsigned char opcode, data;
int body;
int last_tms;
(void)jc; (void)jc;
rd_bit_index = 0; rd_bit_index = 0;
wr_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) if (!size)
return 0; 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_out_buf, 0, 16);
memset(ftdi_in_buf, 0, 16); memset(ftdi_in_buf, 0, 16);
/* First bit, if it carries TMS (entering a state): bit-mode TMS shift. */ /* 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) if (str_in)
opcode = (OP_WR_TMS | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR | OP_RD_TDO); opcode = (OP_WR_TMS | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR | OP_RD_TDO);
else 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. */ /* 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 (rounded_size) {
if (str_in) if (str_in)
opcode = (OP_WR_TDI | OP_LSB_FIRST | OP_FEDGE_WR | OP_RD_TDO); 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. */ /* Trailing bits via bit-mode TDI shift. */
while (wr_bit_index < size) { while (wr_bit_index < body) {
if (str_in) if (str_in)
opcode = (OP_WR_TDI | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR | OP_RD_TDO); opcode = (OP_WR_TDI | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR | OP_RD_TDO);
else else
opcode = (OP_WR_TDI | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR); opcode = (OP_WR_TDI | OP_LSB_FIRST | OP_BIT_MODE | OP_FEDGE_WR);
nbtosend = 0; nbtosend = 0;
bitscnt = (size - wr_bit_index); bitscnt = (body - wr_bit_index);
if (bitscnt > 8) bitscnt = 8; if (bitscnt > 8) bitscnt = 8;
ftdi_out_buf[nbtosend++] = opcode; 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; return 0;
} }

View File

@@ -42,6 +42,7 @@
#include "bscan/bscan.h" #include "bscan/bscan.h"
#include "svf/svf.h" #include "svf/svf.h"
#include "program/program.h" #include "program/program.h"
#include "arm_debug/arm_debug.h"
#include "spi_flash/spi_flash.h" #include "spi_flash/spi_flash.h"
#include "env.h" #include "env.h"
@@ -3586,6 +3587,62 @@ static int cmd_program(script_ctx *ctx, char *line)
return JTAG_CORE_NO_ERROR; return JTAG_CORE_NO_ERROR;
} }
const char *cmd_cpu_halt_help[] = {
"<dev>(int)",
"Halt the ARM CPU at chain device <dev> 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 <dev>\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[] = {
"<dev>(int)",
"Release the ARM CPU at chain device <dev> 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 <dev>\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[] = cmd_list script_commands_list[] =
{ {
{"print", cmd_print, cmd_print_help}, {"print", cmd_print, cmd_print_help},
@@ -3643,6 +3700,8 @@ cmd_list script_commands_list[] =
{"flash_verify", cmd_flash_verify, cmd_flash_verify_help}, {"flash_verify", cmd_flash_verify, cmd_flash_verify_help},
{"svf_play", cmd_svf_play, cmd_svf_play_help}, {"svf_play", cmd_svf_play, cmd_svf_play_help},
{"program", cmd_program, cmd_program_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}}; {0, 0}};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////