arm_debug: debug entry (DBGRQ-clear), Thumb->ARM, cpu_read (WIP)
Bring up ARM7TDMI debug toward reading CPU memory/flash over JTAG.
Validated on the LPC2103 (Olimex ARM-USB-OCD): halt holds DBGACK,
RESTART resumes, the Thumb->ARM switch clears ITBIT, and real register
data streams out of the STMIA injection.
- arm_debug:
- halt: after DBGACK, reprogram DBG_CTRL = DBGACK|INTDIS (deassert
DBGRQ) per OpenOCD's debug entry; without this, injected
instructions don't execute. Warn on Thumb (ITBIT).
- change_to_arm: switch a Thumb-state core to ARM (duplicated-halfword
Thumb opcodes), needed because the firmware may halt in either state.
- chain-1 instruction injection: c1_xfer/read_core_regs/
write_core_regs/load_word_regs + execute_sys_speed (RESTART, poll
DBGACK&SYSCOMP); arm_debug_mem_read does word-block system-speed LDM.
- script: cpu_read <dev> <addr> <len> <file> <bin|hex> command +
built-in Intel HEX writer (type 04/00/01 records).
WIP: c1_xfer (on bscan_shift_dr) is not yet cycle-exact (one debug clock
per access), so memory reads can be misaligned. Remaining work and the
diagnosis are in the arm7-debug-dclk-timing note.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8,9 +8,13 @@
|
|||||||
/*
|
/*
|
||||||
* ARM7TDMI debug over JTAG (EmbeddedICE), built on the bscan_* TAP
|
* ARM7TDMI debug over JTAG (EmbeddedICE), built on the bscan_* TAP
|
||||||
* primitives. Incremental bring-up:
|
* primitives. Incremental bring-up:
|
||||||
* - done: EmbeddedICE register access, halt (force DBGRQ) / resume
|
* - done: EmbeddedICE register access; halt (force DBGRQ, then debug
|
||||||
* - todo: memory read/write (debug-speed instruction injection) and
|
* entry = DBGACK|INTDIS) / resume (RESTART); Thumb->ARM switch;
|
||||||
* the arm_flash backend.
|
* instruction-injection register read/write + system-speed LDM.
|
||||||
|
* - WIP: the chain-1 access (c1_xfer) is not yet cycle-exact (one
|
||||||
|
* debug clock per access), so memory reads can be misaligned. See
|
||||||
|
* the arm7-debug-dclk-timing note.
|
||||||
|
* - todo: cycle-exact c1_xfer, memory write, the arm_flash backend.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* ARM7TDMI public JTAG instructions (IR length 4). */
|
/* ARM7TDMI public JTAG instructions (IR length 4). */
|
||||||
@@ -19,12 +23,52 @@
|
|||||||
#define IR_INTEST 0xC
|
#define IR_INTEST 0xC
|
||||||
#define IR_RESTART 0x4
|
#define IR_RESTART 0x4
|
||||||
|
|
||||||
/* EmbeddedICE scan chain (#2) and register addresses. */
|
/* Scan chains: #1 = debug (instruction/data bus), #2 = EmbeddedICE. */
|
||||||
#define EICE_SCANCHAIN 2
|
#define SC_DEBUG 1
|
||||||
|
#define SC_EICE 2
|
||||||
|
|
||||||
|
/* EmbeddedICE register addresses. */
|
||||||
#define EICE_DBG_CTRL 0x00
|
#define EICE_DBG_CTRL 0x00
|
||||||
#define EICE_DBG_STATUS 0x01
|
#define EICE_DBG_STATUS 0x01
|
||||||
|
|
||||||
#define DBG_STATUS_DBGACK (1u << 0)
|
/* EmbeddedICE Debug Control register bits (write). */
|
||||||
|
#define DBG_CTRL_DBGACK (1u << 0) /* force DBGACK */
|
||||||
|
#define DBG_CTRL_DBGRQ (1u << 1) /* request debug entry */
|
||||||
|
#define DBG_CTRL_INTDIS (1u << 2) /* disable interrupts in debug */
|
||||||
|
|
||||||
|
/* EmbeddedICE Debug Status register bits (read). */
|
||||||
|
#define DBG_STATUS_DBGACK (1u << 0)
|
||||||
|
#define DBG_STATUS_SYSCOMP (1u << 3)
|
||||||
|
#define DBG_STATUS_ITBIT (1u << 4) /* core was in Thumb state */
|
||||||
|
|
||||||
|
/* ARMv4 opcodes used for register/memory access via instruction
|
||||||
|
* injection (see ARM7TDMI TRM, debug chapter; mirrors OpenOCD). */
|
||||||
|
#define ARM_NOP 0xe1a08008u /* mov r8, r8 */
|
||||||
|
#define ARM_STMIA(rn, list, w) (0xe8800000u | ((unsigned)(w) << 21) | ((unsigned)(rn) << 16) | (unsigned)(list))
|
||||||
|
#define ARM_LDMIA(rn, list, w) (0xe8900000u | ((unsigned)(w) << 21) | ((unsigned)(rn) << 16) | (unsigned)(list))
|
||||||
|
|
||||||
|
/* Thumb opcodes (16-bit, duplicated into both halfwords as the debug
|
||||||
|
* data bus presents them) used only to switch a Thumb-state core to ARM
|
||||||
|
* state on debug entry. */
|
||||||
|
#define THUMB_DUP(op) ((unsigned)(op) | ((unsigned)(op) << 16))
|
||||||
|
#define ARM_T_NOP THUMB_DUP(0x46c0) /* mov r8, r8 */
|
||||||
|
#define ARM_T_STR(rd, rn) THUMB_DUP(0x6000 | (rd) | ((rn) << 3))
|
||||||
|
#define ARM_T_MOV(rd, rm) THUMB_DUP(0x4600 | ((rd) & 0x7) | (((rd) & 0x8) << 4) | \
|
||||||
|
(((rm) & 0x7) << 3) | (((rm) & 0x8) << 3))
|
||||||
|
#define ARM_T_LDR_PCREL(rd) THUMB_DUP(0x4800 | ((rd) << 8))
|
||||||
|
#define ARM_T_BX(rm) THUMB_DUP(0x4700 | ((rm) << 3))
|
||||||
|
|
||||||
|
/* Reverse the 32 bits of a word. Scan chain 1 shifts instructions and
|
||||||
|
* data with the bit order flipped (TRM); match OpenOCD's flip_u32. */
|
||||||
|
static uint32_t flip32(uint32_t v)
|
||||||
|
{
|
||||||
|
v = ((v & 0xFFFF0000u) >> 16) | ((v & 0x0000FFFFu) << 16);
|
||||||
|
v = ((v & 0xFF00FF00u) >> 8) | ((v & 0x00FF00FFu) << 8);
|
||||||
|
v = ((v & 0xF0F0F0F0u) >> 4) | ((v & 0x0F0F0F0Fu) << 4);
|
||||||
|
v = ((v & 0xCCCCCCCCu) >> 2) | ((v & 0x33333333u) << 2);
|
||||||
|
v = ((v & 0xAAAAAAAAu) >> 1) | ((v & 0x55555555u) << 1);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
/* One EmbeddedICE scan-chain-2 access: 38 bits LSB-first =
|
/* One EmbeddedICE scan-chain-2 access: 38 bits LSB-first =
|
||||||
* data[0..31] | address[32..36] | read/write[37] (1 = write). On a read,
|
* data[0..31] | address[32..36] | read/write[37] (1 = write). On a read,
|
||||||
@@ -53,16 +97,22 @@ static int eice_scan(jtag_core *jc, int addr, int rw, uint32_t data, uint32_t *o
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select the EmbeddedICE scan chain (#2) and enter INTEST. */
|
/* Select a scan chain via SCAN_N (4-bit register) and enter INTEST so
|
||||||
static int eice_select(jtag_core *jc)
|
* subsequent DR shifts hit that chain. */
|
||||||
|
static int chain_select(jtag_core *jc, int chain)
|
||||||
{
|
{
|
||||||
uint8_t sc = EICE_SCANCHAIN;
|
uint8_t sc = (uint8_t)chain;
|
||||||
if (bscan_set_ir(jc, IR_SCAN_N, ARM7_IR_LEN) < 0) return -1;
|
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_shift_dr(jc, &sc, NULL, ARM7_IR_LEN) < 0) return -1;
|
||||||
if (bscan_set_ir(jc, IR_INTEST, ARM7_IR_LEN) < 0) return -1;
|
if (bscan_set_ir(jc, IR_INTEST, ARM7_IR_LEN) < 0) return -1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int eice_select(jtag_core *jc)
|
||||||
|
{
|
||||||
|
return chain_select(jc, SC_EICE);
|
||||||
|
}
|
||||||
|
|
||||||
static int eice_write(jtag_core *jc, int addr, uint32_t val)
|
static int eice_write(jtag_core *jc, int addr, uint32_t val)
|
||||||
{
|
{
|
||||||
return eice_scan(jc, addr, 1, val, NULL);
|
return eice_scan(jc, addr, 1, val, NULL);
|
||||||
@@ -85,9 +135,9 @@ int arm_debug_halt(jtag_core *jc, const jtag_target *t)
|
|||||||
if (eice_select(jc) < 0)
|
if (eice_select(jc) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
/* Debug Control bit 1 = DBGRQ -> core enters debug at the next
|
/* DBGRQ -> core enters debug at the next instruction boundary;
|
||||||
* instruction boundary; poll DBGACK (it isn't instantaneous). */
|
* poll DBGACK (it isn't instantaneous). */
|
||||||
if (eice_write(jc, EICE_DBG_CTRL, 0x2) < 0)
|
if (eice_write(jc, EICE_DBG_CTRL, DBG_CTRL_DBGRQ) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -97,12 +147,24 @@ int arm_debug_halt(jtag_core *jc, const jtag_target *t)
|
|||||||
if (eice_read(jc, EICE_DBG_STATUS, &status) < 0)
|
if (eice_read(jc, EICE_DBG_STATUS, &status) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
if (status & DBG_STATUS_DBGACK)
|
if (status & DBG_STATUS_DBGACK)
|
||||||
return 0;
|
break;
|
||||||
|
}
|
||||||
|
if (!(status & DBG_STATUS_DBGACK)) {
|
||||||
|
fprintf(stderr, "arm_debug: halt requested but no DBGACK (status 0x%08x)\n", status);
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "arm_debug: halt requested but no DBGACK (status 0x%08x)\n", status);
|
/* Debug entry: force DBGACK, deassert DBGRQ (else the core keeps
|
||||||
return -1;
|
* re-requesting debug and injected instructions can't execute), and
|
||||||
|
* disable interrupts. Matches OpenOCD's arm7_9_debug_entry. */
|
||||||
|
if (eice_write(jc, EICE_DBG_CTRL, DBG_CTRL_DBGACK | DBG_CTRL_INTDIS) < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (status & DBG_STATUS_ITBIT)
|
||||||
|
fprintf(stderr, "arm_debug: warning - core halted in Thumb state; "
|
||||||
|
"ARM instruction injection will be wrong\n");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int arm_debug_resume(jtag_core *jc, const jtag_target *t)
|
int arm_debug_resume(jtag_core *jc, const jtag_target *t)
|
||||||
@@ -119,12 +181,237 @@ int arm_debug_resume(jtag_core *jc, const jtag_target *t)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scan-chain-1 (debug bus) access session. Mirrors OpenOCD's TAP usage:
|
||||||
|
* accesses are parked in Pause-DR so each injected instruction is clocked
|
||||||
|
* by exactly one Update-DR. Crucially we never dwell in Run-Test/Idle,
|
||||||
|
* which would generate extra debug clocks and desync the core's
|
||||||
|
* instruction pipeline (the bscan_* primitives all pass through Idle, so
|
||||||
|
* they can't be reused here). The Update for access N is emitted at the
|
||||||
|
* start of access N+1; c1_end() flushes the final pending Update.
|
||||||
|
* Captured data reflects the value the core drives on the bus when the
|
||||||
|
* access samples it at Capture-DR — the standard ARM7TDMI debug pipeline
|
||||||
|
* that the NOP padding in read/write_core_regs accounts for. */
|
||||||
|
typedef struct {
|
||||||
|
jtag_core *jc;
|
||||||
|
int started; /* a scan is currently parked in Pause-DR */
|
||||||
|
} c1_ctx;
|
||||||
|
|
||||||
|
static void c1_init(c1_ctx *c, jtag_core *jc) { c->jc = jc; c->started = 0; }
|
||||||
|
|
||||||
|
/* One chain-1 access: shift 33 bits = breakpoint[0] | flip32(instr)[1..32].
|
||||||
|
* sysspeed=1 marks the following instruction to run at system speed.
|
||||||
|
* capture != NULL reads back the 32-bit debug data bus.
|
||||||
|
* Parks in Pause-DR so each instruction is clocked by exactly ONE
|
||||||
|
* Update-DR (no Run-Test/Idle dwell, which would add debug clocks). The
|
||||||
|
* Update for access N is emitted at the start of access N+1; c1_end()
|
||||||
|
* flushes the final one. */
|
||||||
|
static int c1_xfer(c1_ctx *c, uint32_t instr, int sysspeed, uint32_t *capture)
|
||||||
|
{
|
||||||
|
uint8_t buf[5], cap[5];
|
||||||
|
uint32_t f = flip32(instr);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
if (sysspeed) buf[0] |= 1u; /* bit 0 = breakpoint/SYSSPEED */
|
||||||
|
for (i = 0; i < 32; i++) /* bits 1..32 = flip32(instr) */
|
||||||
|
if (f & (1u << i)) { int b = 1 + i; buf[b >> 3] |= (uint8_t)(1u << (b & 7)); }
|
||||||
|
|
||||||
|
/* Shift 33 bits (captures the bus at Capture-DR), then one explicit
|
||||||
|
* Run-Test/Idle clock to advance the core one debug step so the next
|
||||||
|
* access sees the next pipeline cycle. */
|
||||||
|
if (bscan_shift_dr(c->jc, buf, capture ? cap : NULL, 33) < 0)
|
||||||
|
return -1;
|
||||||
|
bscan_idle_cycles(c->jc, 1);
|
||||||
|
c->started = 1;
|
||||||
|
|
||||||
|
if (capture) {
|
||||||
|
uint32_t raw = 0;
|
||||||
|
for (i = 0; i < 32; i++) {
|
||||||
|
int b = 1 + i;
|
||||||
|
if (cap[b >> 3] & (1u << (b & 7))) raw |= (1u << i);
|
||||||
|
}
|
||||||
|
*capture = flip32(raw);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int c1_end(c1_ctx *c) { (void)c; return 0; }
|
||||||
|
|
||||||
|
/* Load core registers from the debug data bus (debug speed):
|
||||||
|
* LDMIA r<rn>, {regs} fed by the scanned-in values. */
|
||||||
|
static int write_core_regs(c1_ctx *c, int rn, uint32_t mask, const uint32_t *vals)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (c1_xfer(c, ARM_LDMIA(rn, mask & 0xffff, 0), 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_NOP, 0, NULL) < 0) return -1; /* DECODE */
|
||||||
|
if (c1_xfer(c, ARM_NOP, 0, NULL) < 0) return -1; /* EXECUTE 1 */
|
||||||
|
for (i = 0; i <= 15; i++)
|
||||||
|
if (mask & (1u << i))
|
||||||
|
if (c1_xfer(c, vals[i], 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_NOP, 0, NULL) < 0) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read core registers from the debug data bus (debug speed):
|
||||||
|
* STMIA r<rn>, {regs}; values appear from the 4th DCLK on. */
|
||||||
|
static int read_core_regs(c1_ctx *c, int rn, uint32_t mask, uint32_t *out)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
if (c1_xfer(c, ARM_STMIA(rn, mask & 0xffff, 0), 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_NOP, 0, NULL) < 0) return -1; /* DECODE */
|
||||||
|
if (c1_xfer(c, ARM_NOP, 0, NULL) < 0) return -1; /* EXECUTE 1 */
|
||||||
|
for (i = 0; i <= 15; i++)
|
||||||
|
if (mask & (1u << i))
|
||||||
|
if (c1_xfer(c, ARM_NOP, 0, &out[i]) < 0) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Queue a system-speed load-multiple from real memory into {regs}, with
|
||||||
|
* base writeback so r0 advances for the next block. The instruction
|
||||||
|
* preceding it carries the SYSSPEED bit. */
|
||||||
|
static int load_word_regs(c1_ctx *c, uint32_t mask)
|
||||||
|
{
|
||||||
|
if (c1_xfer(c, ARM_NOP, 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_NOP, 1, NULL) < 0) return -1; /* SYSSPEED marker */
|
||||||
|
if (c1_xfer(c, ARM_LDMIA(0, mask & 0xffff, 1), 0, NULL) < 0) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Switch a Thumb-state core to ARM state so the rest of the debug logic
|
||||||
|
* can use ARM instructions (mirrors OpenOCD's arm7tdmi_change_to_arm).
|
||||||
|
* Clobbers r0 and PC (fine for a read-then-power-cycle flow): loads r0
|
||||||
|
* with an even address and BX r0. Thumb instructions are injected as
|
||||||
|
* 16-bit opcodes duplicated into both halfwords. Assumes chain 1 +
|
||||||
|
* INTEST selected; the caller wraps it in a c1 session. */
|
||||||
|
static int change_to_arm(c1_ctx *c)
|
||||||
|
{
|
||||||
|
/* save r0 (STR r0,[r0]); value discarded */
|
||||||
|
if (c1_xfer(c, ARM_T_STR(0, 0), 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, 0, 0, NULL) < 0) return -1; /* data-in slot */
|
||||||
|
|
||||||
|
/* read pc (MOV r0,r15; STR r0,[r0]); value discarded */
|
||||||
|
if (c1_xfer(c, ARM_T_MOV(0, 15), 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_STR(0, 0), 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, 0, 0, NULL) < 0) return -1; /* data-in slot */
|
||||||
|
|
||||||
|
/* LDR r0,[PC,#0] with data 0 -> r0 = 0 (bits[1:0] cleared) */
|
||||||
|
if (c1_xfer(c, ARM_T_LDR_PCREL(0), 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, 0x0, 0, NULL) < 0) return -1; /* LDR data word */
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
|
||||||
|
/* BX r0 -> ARM state */
|
||||||
|
if (c1_xfer(c, ARM_T_BX(0), 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
if (c1_xfer(c, ARM_T_NOP, 0, NULL) < 0) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RESTART, then wait for the system-speed access to complete (DBGACK &
|
||||||
|
* SYSCOMP). Leaves the TAP on the EmbeddedICE chain. */
|
||||||
|
static int execute_sys_speed(jtag_core *jc)
|
||||||
|
{
|
||||||
|
uint32_t status = 0;
|
||||||
|
int tries;
|
||||||
|
|
||||||
|
if (bscan_set_ir(jc, IR_RESTART, ARM7_IR_LEN) < 0) return -1;
|
||||||
|
bscan_idle_cycles(jc, 32);
|
||||||
|
|
||||||
|
if (eice_select(jc) < 0) return -1;
|
||||||
|
for (tries = 0; tries < 100; tries++) {
|
||||||
|
if (eice_read(jc, EICE_DBG_STATUS, &status) < 0) return -1;
|
||||||
|
if ((status & DBG_STATUS_DBGACK) && (status & DBG_STATUS_SYSCOMP))
|
||||||
|
return 0;
|
||||||
|
bscan_idle_cycles(jc, 32);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "arm_debug: sys-speed access timed out (status 0x%08x)\n", status);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read memory by instruction injection. Reads word-aligned blocks
|
||||||
|
* covering [addr, addr+len) and copies the requested bytes out.
|
||||||
|
* Core registers r0..r14 are clobbered (acceptable for a read-then-
|
||||||
|
* power-cycle flow). The core must already be halted (DBGACK).
|
||||||
|
*
|
||||||
|
* WORK IN PROGRESS: the chain-1 instruction pipeline is not yet
|
||||||
|
* cycle-exact (see the arm7-debug-dclk-timing design note). Halt,
|
||||||
|
* Thumb->ARM, RESTART and the instruction sequences are in place and
|
||||||
|
* real register data streams out, but each access must clock the core
|
||||||
|
* exactly once and `c1_xfer` (built on bscan_shift_dr) does not do that
|
||||||
|
* deterministically yet, so the captured words can be misaligned. */
|
||||||
int arm_debug_mem_read(jtag_core *jc, const jtag_target *t,
|
int arm_debug_mem_read(jtag_core *jc, const jtag_target *t,
|
||||||
unsigned long addr, void *buf, unsigned long len)
|
unsigned long addr, void *buf, unsigned long len)
|
||||||
{
|
{
|
||||||
(void)jc; (void)t; (void)addr; (void)buf; (void)len;
|
unsigned long base = addr & ~3UL;
|
||||||
fprintf(stderr, "arm_debug: mem_read not implemented yet\n");
|
unsigned long end = addr + len;
|
||||||
return -1;
|
unsigned long total_words = (((end + 3) & ~3UL) - base) / 4;
|
||||||
|
unsigned long done = 0;
|
||||||
|
uint32_t r0, status = 0;
|
||||||
|
uint8_t *out = buf;
|
||||||
|
c1_ctx c1;
|
||||||
|
(void)t;
|
||||||
|
|
||||||
|
if (!buf || len == 0) return -1;
|
||||||
|
|
||||||
|
/* If the core halted in Thumb state, switch it to ARM. Do the EICE
|
||||||
|
* status read first (the chain switch clocks the halted core), then
|
||||||
|
* the switch in one continuous chain-1 session so no stray clocks
|
||||||
|
* land between change_to_arm and the first instruction. */
|
||||||
|
if (chain_select(jc, SC_EICE) < 0) return -1;
|
||||||
|
if (eice_read(jc, EICE_DBG_STATUS, &status) < 0) return -1;
|
||||||
|
if (chain_select(jc, SC_DEBUG) < 0) return -1;
|
||||||
|
if (status & DBG_STATUS_ITBIT) {
|
||||||
|
c1_init(&c1, jc);
|
||||||
|
if (change_to_arm(&c1) < 0) return -1;
|
||||||
|
c1_end(&c1);
|
||||||
|
}
|
||||||
|
|
||||||
|
r0 = (uint32_t)base;
|
||||||
|
|
||||||
|
while (done < total_words) {
|
||||||
|
uint32_t regs[16];
|
||||||
|
uint32_t reg_list;
|
||||||
|
unsigned long n = total_words - done;
|
||||||
|
unsigned long i;
|
||||||
|
if (n > 14) n = 14;
|
||||||
|
|
||||||
|
/* r1..rn, base (r0) excluded so it can be the autoincrement ptr. */
|
||||||
|
reg_list = (uint32_t)((0xffffu >> (15 - n)) & 0xfffe);
|
||||||
|
|
||||||
|
if (chain_select(jc, SC_DEBUG) < 0) return -1;
|
||||||
|
c1_init(&c1, jc);
|
||||||
|
if (done == 0) /* set r0 once; LDM writeback advances it */
|
||||||
|
if (write_core_regs(&c1, 0, 0x1, &r0) < 0) return -1;
|
||||||
|
if (load_word_regs(&c1, reg_list) < 0) return -1;
|
||||||
|
c1_end(&c1);
|
||||||
|
|
||||||
|
if (execute_sys_speed(jc) < 0) return -1;
|
||||||
|
|
||||||
|
/* execute_sys_speed left us on the EmbeddedICE chain. */
|
||||||
|
if (chain_select(jc, SC_DEBUG) < 0) return -1;
|
||||||
|
memset(regs, 0, sizeof(regs));
|
||||||
|
c1_init(&c1, jc);
|
||||||
|
if (read_core_regs(&c1, 0, reg_list, regs) < 0) return -1;
|
||||||
|
c1_end(&c1);
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++) {
|
||||||
|
unsigned long word_addr = base + (done + i) * 4;
|
||||||
|
uint32_t w = regs[1 + i];
|
||||||
|
int b;
|
||||||
|
for (b = 0; b < 4; b++) {
|
||||||
|
unsigned long byte_addr = word_addr + b;
|
||||||
|
if (byte_addr >= addr && byte_addr < end)
|
||||||
|
out[byte_addr - addr] = (uint8_t)(w >> (8 * b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
done += n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int arm_debug_mem_write(jtag_core *jc, const jtag_target *t,
|
int arm_debug_mem_write(jtag_core *jc, const jtag_target *t,
|
||||||
|
|||||||
@@ -3643,6 +3643,138 @@ static int cmd_cpu_resume(script_ctx *ctx, char *line)
|
|||||||
return JTAG_CORE_NO_ERROR;
|
return JTAG_CORE_NO_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Write a buffer as Intel HEX: type-00 data records (16 bytes each,
|
||||||
|
* never crossing a 64 KB boundary) preceded by a type-04 extended linear
|
||||||
|
* address record whenever the upper 16 bits of the address change, ended
|
||||||
|
* by the type-01 EOF record. */
|
||||||
|
static int write_intel_hex(const char *path, unsigned long base,
|
||||||
|
const uint8_t *data, unsigned long len)
|
||||||
|
{
|
||||||
|
FILE *f;
|
||||||
|
unsigned long i = 0;
|
||||||
|
unsigned int cur_upper = 0xFFFFFFFFu; /* force a leading ELA record */
|
||||||
|
|
||||||
|
f = fopen(path, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
unsigned long addr = base + i;
|
||||||
|
unsigned int upper = (unsigned int)((addr >> 16) & 0xFFFFu);
|
||||||
|
unsigned int chunk = (len - i > 16) ? 16 : (unsigned int)(len - i);
|
||||||
|
unsigned int to_boundary = 0x10000u - (unsigned int)(addr & 0xFFFFu);
|
||||||
|
unsigned int sum, j;
|
||||||
|
|
||||||
|
if (chunk > to_boundary) chunk = to_boundary;
|
||||||
|
|
||||||
|
if (upper != cur_upper) {
|
||||||
|
unsigned int s = 0x02 + 0x04 + ((upper >> 8) & 0xFF) + (upper & 0xFF);
|
||||||
|
fprintf(f, ":02000004%04X%02X\r\n", upper, (unsigned int)((0u - s) & 0xFF));
|
||||||
|
cur_upper = upper;
|
||||||
|
}
|
||||||
|
|
||||||
|
sum = chunk + ((addr >> 8) & 0xFF) + (addr & 0xFF); /* type 00 adds 0 */
|
||||||
|
fprintf(f, ":%02X%04X00", chunk, (unsigned int)(addr & 0xFFFFu));
|
||||||
|
for (j = 0; j < chunk; j++) {
|
||||||
|
fprintf(f, "%02X", data[i + j]);
|
||||||
|
sum += data[i + j];
|
||||||
|
}
|
||||||
|
fprintf(f, "%02X\r\n", (unsigned int)((0u - sum) & 0xFF));
|
||||||
|
i += chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(f, ":00000001FF\r\n");
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *cmd_cpu_read_help[] = {
|
||||||
|
"<dev>(int) <addr>(hex) <len>(hex) <file>(str) <format>(bin|hex)",
|
||||||
|
"Halt the ARM CPU at chain device <dev> and read <len> bytes from",
|
||||||
|
"memory at <addr> over JTAG debug (EmbeddedICE instruction injection),",
|
||||||
|
"writing <file> as raw binary (format 'bin') or Intel HEX ('hex').",
|
||||||
|
"Omit <file> for a console hex-dump. The CPU is left halted (use",
|
||||||
|
"cpu_resume or power-cycle). Run jtag_scan first. Core registers",
|
||||||
|
"r0..r14 are clobbered by the read.",
|
||||||
|
""
|
||||||
|
};
|
||||||
|
static int cmd_cpu_read(script_ctx *ctx, char *line)
|
||||||
|
{
|
||||||
|
jtag_core *jc;
|
||||||
|
char dev_s[32], addr_s[32], len_s[32], path[MAX_PATH + 1], fmt[16];
|
||||||
|
int dev, have_file;
|
||||||
|
unsigned long addr, len, i;
|
||||||
|
const jtag_target *t;
|
||||||
|
uint8_t *buf;
|
||||||
|
|
||||||
|
jc = (jtag_core *)ctx->app_ctx;
|
||||||
|
if (get_param(ctx, line, 1, dev_s) <= 0 ||
|
||||||
|
get_param(ctx, line, 2, addr_s) <= 0 ||
|
||||||
|
get_param(ctx, line, 3, len_s) <= 0) {
|
||||||
|
ctx->script_printf(ctx, MSG_ERROR,
|
||||||
|
"Usage: cpu_read <dev> <addr> <len> <file> <bin|hex>\n");
|
||||||
|
return JTAG_CORE_BAD_PARAMETER;
|
||||||
|
}
|
||||||
|
have_file = (get_param(ctx, line, 4, path) > 0);
|
||||||
|
if (have_file) {
|
||||||
|
if (get_param(ctx, line, 5, fmt) <= 0 ||
|
||||||
|
(strcmp(fmt, "bin") != 0 && strcmp(fmt, "hex") != 0)) {
|
||||||
|
ctx->script_printf(ctx, MSG_ERROR,
|
||||||
|
"cpu_read: a file needs a format: 'bin' (raw) or 'hex' (Intel HEX)\n");
|
||||||
|
return JTAG_CORE_BAD_PARAMETER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dev = (int)strtol(dev_s, NULL, 0);
|
||||||
|
addr = strtoul(addr_s, NULL, 0);
|
||||||
|
len = strtoul(len_s, NULL, 0);
|
||||||
|
if (len == 0) {
|
||||||
|
ctx->script_printf(ctx, MSG_ERROR, "cpu_read: length must be > 0\n");
|
||||||
|
return JTAG_CORE_BAD_PARAMETER;
|
||||||
|
}
|
||||||
|
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_read: halt failed (no DBGACK)\n");
|
||||||
|
return JTAG_CORE_ACCESS_ERROR;
|
||||||
|
}
|
||||||
|
buf = malloc(len);
|
||||||
|
if (!buf) return JTAG_CORE_MEM_ERROR;
|
||||||
|
|
||||||
|
if (arm_debug_mem_read(jc, t, addr, buf, len) < 0) {
|
||||||
|
ctx->script_printf(ctx, MSG_ERROR, "cpu_read: memory read failed\n");
|
||||||
|
free(buf);
|
||||||
|
return JTAG_CORE_ACCESS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (have_file) {
|
||||||
|
int ok;
|
||||||
|
if (strcmp(fmt, "hex") == 0) {
|
||||||
|
ok = (write_intel_hex(path, addr, buf, len) == 0);
|
||||||
|
} else {
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
ok = (f && fwrite(buf, 1, len, f) == len);
|
||||||
|
if (f) fclose(f);
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
ctx->script_printf(ctx, MSG_ERROR, "cpu_read: cannot write '%s'\n", path);
|
||||||
|
free(buf);
|
||||||
|
return JTAG_CORE_IO_ERROR;
|
||||||
|
}
|
||||||
|
ctx->script_printf(ctx, MSG_INFO_0,
|
||||||
|
"Read 0x%lX bytes from 0x%lX -> %s (%s)\n", len, addr, path, fmt);
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < len; i += 16) {
|
||||||
|
char hexs[16 * 3 + 1];
|
||||||
|
int p = 0;
|
||||||
|
unsigned long j;
|
||||||
|
for (j = i; j < i + 16 && j < len; j++)
|
||||||
|
p += snprintf(hexs + p, sizeof(hexs) - p, "%02X ", buf[j]);
|
||||||
|
ctx->script_printf(ctx, MSG_INFO_0, "%08lX %s\n", addr + i, hexs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
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},
|
||||||
@@ -3702,6 +3834,7 @@ cmd_list script_commands_list[] =
|
|||||||
{"program", cmd_program, cmd_program_help},
|
{"program", cmd_program, cmd_program_help},
|
||||||
{"cpu_halt", cmd_cpu_halt, cmd_cpu_halt_help},
|
{"cpu_halt", cmd_cpu_halt, cmd_cpu_halt_help},
|
||||||
{"cpu_resume", cmd_cpu_resume, cmd_cpu_resume_help},
|
{"cpu_resume", cmd_cpu_resume, cmd_cpu_resume_help},
|
||||||
|
{"cpu_read", cmd_cpu_read, cmd_cpu_read_help},
|
||||||
{0, 0}};
|
{0, 0}};
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
Reference in New Issue
Block a user