arm_debug: OpenOCD-style debug entry + warm-up read

Make the system-speed memory read reliable within a halt:
- debug entry mirrors OpenOCD's arm7_9_debug_entry: change_to_arm when
  the core halted in Thumb, then read all 16 core registers. That fixed
  STMIA+NOP+NOP+16 sequence flushes the firmware out of the pipeline and
  leaves a deterministic state for both Thumb and ARM halts.
- warm-up read: the first system-speed read after debug entry normalizes
  the sys-speed pipeline but its own result is unreliable, so do one
  throwaway read block and discard it. Every read after it is consistent
  and correct (analogous to the FTDI stale-first-read).

Within one clean halt, reads now come back correct (no misalignment).
Repeated halt/read cycles without a power-cycle still degrade (the read
clobbers r0..r14, so a later re-halt/resume is messy) - the intended
flow is power-on -> one halt -> dump.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 20:09:39 +02:00
parent 2c16a66beb
commit cdbeea7b61

View File

@@ -553,21 +553,43 @@ int arm_debug_mem_read(jtag_core *jc, const jtag_target *t,
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;
/* Normalize the core to a known ARM pipeline state regardless of the
* halt state. In Thumb, change_to_arm (17 clocked instructions)
* switches to ARM and flushes the firmware out of the pipeline; the
* read alignment is tuned for that. In ARM, run the same NUMBER of
* clocked NOPs so the read sees the same pipeline phase (skipping it
* left the firmware's arbitrary pipeline and the read misaligned). */
c1_init(&c1, jc);
if (status & DBG_STATUS_ITBIT) {
if (change_to_arm(&c1) < 0) return -1;
} else {
int k;
for (k = 0; k < 17; k++)
if (c1_xfer(&c1, ARM_NOP, 0, NULL) < 0) return -1;
/* Debug entry, mirroring OpenOCD's arm7_9_debug_entry to leave a
* deterministic pipeline regardless of halt state: switch Thumb->ARM
* if needed, then read all 16 core registers. That STMIA+NOP+NOP+16
* sequence flushes the firmware out of the pipeline and ends in the
* same known state for both the Thumb and ARM paths, so the first
* system-speed read reliably re-enters debug. */
{
uint32_t scratch[16];
c1_init(&c1, jc);
if (status & DBG_STATUS_ITBIT)
if (change_to_arm(&c1) < 0) return -1;
memset(scratch, 0, sizeof(scratch));
if (read_core_regs(&c1, 0, 0xffff, scratch) < 0) return -1;
c1_end(&c1);
}
/* WARM-UP: the first system-speed read after debug entry normalizes
* the sys-speed pipeline but its own result is unreliable. Do one
* throwaway read block and discard it; every read after it is
* consistent and correct. (Like the FTDI stale-first-read, but for
* the ARM debug pipeline.) */
{
uint32_t scratch[16];
r0 = (uint32_t)base;
c1_init(&c1, jc);
if (write_core_regs(&c1, 0, 0x1, &r0) < 0) return -1;
if (load_word_regs(&c1, 0x7ffe) < 0) return -1; /* r1..r14 */
c1_end(&c1);
if (execute_sys_speed(jc) < 0) return -1;
if (quiet_chain_select(jc, SC_DEBUG) < 0) return -1;
if (quiet_latch_chain1(jc, ARM_NOP) < 0) return -1;
quiet_exit(jc);
memset(scratch, 0, sizeof(scratch));
c1_init(&c1, jc);
if (read_core_regs(&c1, 0, 0x7ffe, scratch) < 0) return -1;
c1_end(&c1);
}
c1_end(&c1);
r0 = (uint32_t)base;