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

@@ -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;
}