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:
@@ -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)
|
||||
|
||||
@@ -1,56 +1,154 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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[] = {
|
||||
"<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[] =
|
||||
{
|
||||
{"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}};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Reference in New Issue
Block a user