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}/..)
|
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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}};
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
Reference in New Issue
Block a user