]> www.infradead.org Git - users/borneoa/openocd-next.git/commitdiff
flash/nor/rp2040: Add RISC-V ROM algorithm batch call support
authorLuke Wren <wren6991@gmail.com>
Wed, 14 Jul 2021 15:36:13 +0000 (16:36 +0100)
committerTomas Vanek <vanekt@fbl.cz>
Fri, 25 Apr 2025 09:25:57 +0000 (09:25 +0000)
And add support for A1 ROM table.

TV: cortex_m smp change removed.
Fixed style problems.
'uint' replaced by unsigned int

Change-Id: Iff2710fa0734dc7074d8d490d8fae43dc27c0c2a
Signed-off-by: Tomas Vanek <vanekt@fbl.cz>
Signed-off-by: Luke Wren <wren6991@gmail.com>
Reviewed-on: https://review.openocd.org/c/openocd/+/8441
Tested-by: jenkins
src/flash/nor/rp2040.c

index c53b54754a852e90c1b2ec175b62ffc66a4dad1c..1e582b433a6214a2b8f51933d209e4e0e70756ec 100644 (file)
 #include "spi.h"
 #include <target/cortex_m.h>
 
-/* NOTE THAT THIS CODE REQUIRES FLASH ROUTINES in BOOTROM WITH FUNCTION TABLE PTR AT 0x00000010
-   Your gdbinit should load the bootrom.elf if appropriate */
-
 /* this is 'M' 'u', 1 (version) */
 #define BOOTROM_RP2040_MAGIC 0x01754d
 /* this is 'M' 'u', 2 (version) */
 #define BOOTROM_RP2350_MAGIC 0x02754d
 #define BOOTROM_MAGIC_ADDR 0x00000010
 
-#define RT_ARM_FUNC 0x1
-
-/* Call a ROM function via the debug trampoline
-   Up to four arguments passed in r0...r3 as per ABI
-   Function address is passed in r7
-   the trampoline is needed because OpenOCD "algorithm" code insists on sw breakpoints. */
-
 #define MAKE_TAG(a, b) (((b)<<8) | a)
-#define FUNC_DEBUG_TRAMPOLINE       MAKE_TAG('D', 'T')
-#define FUNC_DEBUG_TRAMPOLINE_END   MAKE_TAG('D', 'E')
-#define FUNC_FLASH_EXIT_XIP         MAKE_TAG('E', 'X')
-#define FUNC_CONNECT_INTERNAL_FLASH MAKE_TAG('I', 'F')
-#define FUNC_FLASH_RANGE_ERASE      MAKE_TAG('R', 'E')
-#define FUNC_FLASH_RANGE_PROGRAM    MAKE_TAG('R', 'P')
-#define FUNC_FLASH_FLUSH_CACHE      MAKE_TAG('F', 'C')
-#define FUNC_FLASH_ENTER_CMD_XIP    MAKE_TAG('C', 'X')
-#define FUNC_BOOTROM_STATE_RESET    MAKE_TAG('S', 'R')
+#define FUNC_FLASH_EXIT_XIP            MAKE_TAG('E', 'X')
+#define FUNC_CONNECT_INTERNAL_FLASH    MAKE_TAG('I', 'F')
+#define FUNC_FLASH_RANGE_ERASE         MAKE_TAG('R', 'E')
+#define FUNC_FLASH_RANGE_PROGRAM       MAKE_TAG('R', 'P')
+#define FUNC_FLASH_FLUSH_CACHE         MAKE_TAG('F', 'C')
+#define FUNC_FLASH_ENTER_CMD_XIP       MAKE_TAG('C', 'X')
+#define FUNC_BOOTROM_STATE_RESET       MAKE_TAG('S', 'R')
+#define FUNC_FLASH_RESET_ADDRESS_TRANS MAKE_TAG('R', 'A')
+
+/* ROM table flags for RP2350 A1 ROM onwards */
+#define RT_FLAG_FUNC_RISCV      0x01
+#define RT_FLAG_FUNC_ARM_SEC    0x04
+#define RT_FLAG_FUNC_ARM_NONSEC 0x10
+#define RT_FLAG_DATA            0x40
 
 // these form a bit set
 #define BOOTROM_STATE_RESET_CURRENT_CORE 0x01
 #define ACCESSCTRL_CFGRESET_OFFSET 0x40060008u
 #define ACCESSCTRL_WRITE_PASSWORD  0xacce0000u
 
-// Calling bootrom functions requires the redundancy coprocessor (RCP) to be
-// initialised. Usually this is done first thing by the bootrom, but the
-// debugger may skip this, e.g. by resetting the cores and then running a
-// NO_FLASH binary, or by reset-halting the cores before flash programming.
+#define RP2XXX_MAX_ALGO_STACK_USAGE 1024
+#define RP2XXX_MAX_RAM_ALGO_SIZE 1024
+
+// Calling bootrom functions on Arm RP2350 requires the redundancy
+// coprocessor (RCP) to be initialised. Usually this is done first thing by
+// the bootrom, but the debugger may skip this, e.g. by resetting the cores
+// and then running a NO_FLASH binary, or by reset-halting the cores before
+// flash programming.
 //
 // The first case can be handled by a stub in the binary itself to initialise
 // the RCP with dummy values if the bootrom has not already initialised it.
 // requires the debugger itself to initialise the RCP, using this stub code:
 
 static const int rcp_init_code_bkpt_offset = 24;
-static const uint16_t rcp_init_code[] = {
+static const uint8_t rcp_init_code[] = {
        // Just enable the RCP which is fine if it already was (we assume no other
        // co-processors are enabled at this point to save space)
-       0x4806,         // ldr r0, = PPB_BASE + M33_CPACR_OFFSET
-       0xf45f, 0x4140, // movs r1, #M33_CPACR_CP7_BITS
-       0x6001,         // str r1, [r0]
+       0x06, 0x48,             // ldr r0, = PPB_BASE + M33_CPACR_OFFSET
+       0x5f, 0xf4, 0x40, 0x41, // movs r1, #M33_CPACR_CP7_BITS
+       0x01, 0x60,             // str r1, [r0]
        // Only initialize canary seeds if they haven't been (as to do so twice is a fault)
-       0xee30, 0xf710, // mrc p7, #1, r15, c0, c0, #0
-       0xd404,         // bmi 1f
+       0x30, 0xee, 0x10, 0xf7, // mrc p7, #1, r15, c0, c0, #0
+       0x04, 0xd4,             // bmi 1f
        // Todo should we use something random here and pass it into the algorithm?
-       0xec40, 0x0780, // mcrr p7, #8, r0, r0, c0
-       0xec40, 0x0781, // mcrr p7, #8, r0, r0, c1
+       0x40, 0xec, 0x80, 0x07, // mcrr p7, #8, r0, r0, c0
+       0x40, 0xec, 0x81, 0x07, // mcrr p7, #8, r0, r0, c1
        // Let other core know
-       0xbf40,         // sev
+       0x40, 0xbf,             // sev
        // 1:
-       0xbe00,         // bkpt (end of algorithm)
-       0x0000,         // pad
-       0xed88, 0xe000  // PPB_BASE + M33_CPACR_OFFSET
+       0x00, 0xbe,             // bkpt (end of algorithm)
+       0x00, 0x00,             // pad
+       0x88, 0xed, 0x00, 0xe0  // PPB_BASE + M33_CPACR_OFFSET
 };
 
+// An algorithm stub that can be concatenated with a null-terminated list of
+// (PC, SP, r0-r3) records to perform a batch of ROM calls under a single
+// OpenOCD algorithm call, to save on algorithm overhead:
+#define ROM_CALL_BATCH_ALGO_SIZE_BYTES 32
+static const int rp2xxx_rom_call_batch_algo_bkpt_offset = ROM_CALL_BATCH_ALGO_SIZE_BYTES - 2;
+static const uint8_t rp2xxx_rom_call_batch_algo_armv6m[ROM_CALL_BATCH_ALGO_SIZE_BYTES] = {
+// <_start>:
+       0x07, 0xa7,             // add r7, pc, #28 ; (adr r7, 20 <_args>)
+// <_do_next>:
+       0x10, 0xcf,             // ldmia   r7!, {r4}
+       0x00, 0x2c,             // cmp r4, #0
+       0x0a, 0xd0,             // beq.n   1e <_done>
+       0x20, 0xcf,             // ldmia   r7!, {r5}
+       0xad, 0x46,             // mov sp, r5
+       0x0f, 0xcf,             // ldmia   r7!, {r0, r1, r2, r3}
+       0xa0, 0x47,             // blx r4
+       0xf7, 0xe7,             // b.n 2 <_do_next>
+       0xc0, 0x46,             // nop
+       0xc0, 0x46,             // nop
+       0xc0, 0x46,             // nop
+       0xc0, 0x46,             // nop
+       0xc0, 0x46,             // nop
+       0xc0, 0x46,             // nop
+// <_done>:
+       0x00, 0xbe,             // bkpt    0x0000
+// <_args>:
+};
+
+// The same as rom_call_batch_algo_armv6m, but clearing stack limits before setting stack:
+static const uint8_t rp2xxx_rom_call_batch_algo_armv8m[ROM_CALL_BATCH_ALGO_SIZE_BYTES] = {
+// <_start>:
+       0x07, 0xa7,             // add   r7, pc, #28 ; (adr r7, 20 <_args>)
+       0x00, 0x20,             // movs  r0, #0
+       0x80, 0xf3, 0x0a, 0x88, // msr   MSPLIM, r0
+       0x80, 0xf3, 0x0b, 0x88, // msr   PSPLIM, r0
+// <_do_next>:
+       0x10, 0xcf,             // ldmia r7!, {r4}
+       0x00, 0x2c,             // cmp   r4, #0
+       0x05, 0xd0,             // beq.n 1e <_done>
+       0x20, 0xcf,             // ldmia r7!, {r5}
+       0xad, 0x46,             // mov   sp, r5
+       0x0f, 0xcf,             // ldmia r7!, {r0, r1, r2, r3}
+       0xa0, 0x47,             // blx   r4
+       0xf7, 0xe7,             // b.n   c <_do_next>
+       0xc0, 0x46,             // nop
+// <_done>:
+       0x00, 0xbe,             // bkpt  0x0000
+// <_args>:
+};
+
+// The same as rom_call_batch_algo_armv6m, but placing arguments in a0-a3 on RISC-V:
+static const uint8_t rp2xxx_rom_call_batch_algo_riscv[ROM_CALL_BATCH_ALGO_SIZE_BYTES] = {
+// <_start>:
+       0x97, 0x04, 0x00, 0x00, // auipc s1,0
+       0x93, 0x84, 0x04, 0x02, // add   s1,s1,32 # 20 <_args>
+// <_do_next>:
+       0x98, 0x40,             // lw    a4,0(s1)
+       0x11, 0xcb,             // beqz  a4,1e <_done>
+       0x03, 0xa1, 0x44, 0x00, // lw    sp,4(s1)
+       0x88, 0x44,             // lw    a0,8(s1)
+       0xcc, 0x44,             // lw    a1,12(s1)
+       0x90, 0x48,             // lw    a2,16(s1)
+       0xd4, 0x48,             // lw    a3,20(s1)
+       0xe1, 0x04,             // add   s1,s1,24
+       0x02, 0x97,             // jalr  a4
+       0xf5, 0xb7,             // j     8 <_do_next>
+// <_done>:
+       0x02, 0x90,             // ebreak
+// <_args>:
+};
+
+typedef struct rp2xxx_rom_call_batch_record {
+       uint32_t pc;
+       uint32_t sp;
+       uint32_t args[4];
+} rp2xxx_rom_call_batch_record_t;
+
 struct rp2040_flash_bank {
        /* flag indicating successful flash probe */
        bool probed;
        /* stack used by Boot ROM calls */
        struct working_area *stack;
+       /* static code scratchpad used for RAM algorithms -- allocated in advance
+          so that higher-level calls can just grab all remaining workarea: */
+       struct working_area *ram_algo_space;
        /* function jump table populated by rp2040_flash_probe() */
-       uint16_t jump_debug_trampoline;
-       uint16_t jump_debug_trampoline_end;
        uint16_t jump_flash_exit_xip;
        uint16_t jump_connect_internal_flash;
        uint16_t jump_flash_range_erase;
        uint16_t jump_flash_range_program;
        uint16_t jump_flush_cache;
+       uint16_t jump_flash_reset_address_trans;
        uint16_t jump_enter_cmd_xip;
        uint16_t jump_bootrom_reset_state;
 };
 
-static uint32_t rp2040_lookup_symbol(struct target *target, uint32_t tag, uint16_t *symbol)
+#ifndef LOG_ROM_SYMBOL_DEBUG
+#define LOG_ROM_SYMBOL_DEBUG LOG_DEBUG
+#endif
+
+static int rp2040_lookup_rom_symbol(struct target *target, uint16_t tag, uint16_t flags, uint16_t *symbol_out)
 {
-       uint32_t magic, magic_addr;
-       bool found_rp2040_magic, found_rp2350_magic;
-       magic_addr = BOOTROM_MAGIC_ADDR;
-       int err = target_read_u32(target, BOOTROM_MAGIC_ADDR, &magic);
+       LOG_ROM_SYMBOL_DEBUG("Looking up ROM symbol '%c%c' in RP2040 table", tag & 0xff, (tag >> 8) & 0xff);
+       if (flags != RT_FLAG_FUNC_ARM_SEC && flags != RT_FLAG_DATA) {
+               /* Note RT flags do not exist on RP2040, so just sanity check that we
+                  are asked for a type of thing that actually exists in the ROM table */
+               LOG_ERROR("Only data and Secure Arm functions can be looked up in RP2040 ROM table");
+               return ERROR_FAIL;
+       }
+
+       uint16_t ptr_to_entry;
+       const unsigned int offset_magic_to_table_ptr = flags == RT_FLAG_DATA ? 6 : 4;
+       int err = target_read_u16(target, BOOTROM_MAGIC_ADDR + offset_magic_to_table_ptr, &ptr_to_entry);
        if (err != ERROR_OK)
                return err;
 
-       magic &= 0xffffff; /* ignore bootrom version */
-
-       found_rp2040_magic = magic == BOOTROM_RP2040_MAGIC;
-       found_rp2350_magic = magic == BOOTROM_RP2350_MAGIC;
+       uint16_t entry_tag;
+       do {
+               err = target_read_u16(target, ptr_to_entry, &entry_tag);
+               if (err != ERROR_OK)
+                       return err;
+               if (entry_tag == tag) {
+                       /* 16 bit symbol is next */
+                       err = target_read_u16(target, ptr_to_entry + 2, symbol_out);
+                       if (err != ERROR_OK)
+                               return err;
+                       LOG_ROM_SYMBOL_DEBUG(" -> found: 0x%04x", *symbol_out);
+                       return ERROR_OK;
+               }
+               ptr_to_entry += 4;
+       } while (entry_tag);
+       *symbol_out = 0;
+       return ERROR_FAIL;
+}
 
-       if (!(found_rp2040_magic || found_rp2350_magic)) {
-               LOG_ERROR("RP2040/RP2350 BOOT ROM not found");
-               return ERROR_FAIL;
+static int rp2350_a0_lookup_symbol(struct target *target, uint16_t tag, uint16_t flags, uint16_t *symbol_out)
+{
+       LOG_ROM_SYMBOL_DEBUG("Looking up ROM symbol '%c%c' in RP2350 A0 table", tag & 0xff, (tag >> 8) & 0xff);
+
+       /* RP2350 A0 table format is the same as RP2040 except with 16 bits of
+          flags after each 16-bit pointer. We ignore the flags, as each symbol
+          only has one datum associated with it. */
+
+       uint32_t magic_ptr = BOOTROM_MAGIC_ADDR;
+       if (flags == RT_FLAG_FUNC_RISCV) {
+               /* RP2350 A0 used split function tables for Arm/RISC-V -- not used on
+                  any other device or any other version of this device. There is a
+                  well-known RISC-V table at the top of ROM, matching the well-known
+                  Arm table at the bottom of ROM. */
+               magic_ptr = 0x7decu;
+       } else if (flags != RT_FLAG_FUNC_ARM_SEC) {
+               LOG_WARNING("Ignoring non-default flags for RP2350 A0 lookup, hope you like Secure Arm functions");
        }
 
-       /* dereference the table pointer */
-       uint16_t table_entry;
-       err = target_read_u16(target, magic_addr + 4, &table_entry);
+       uint16_t ptr_to_entry;
+       const unsigned int offset_magic_to_table_ptr = 4;
+       int err = target_read_u16(target, magic_ptr + offset_magic_to_table_ptr, &ptr_to_entry);
        if (err != ERROR_OK)
                return err;
 
        uint16_t entry_tag;
        do {
-               err = target_read_u16(target, table_entry, &entry_tag);
+               err = target_read_u16(target, ptr_to_entry, &entry_tag);
                if (err != ERROR_OK)
                        return err;
                if (entry_tag == tag) {
-                       if (found_rp2350_magic) {
-                               uint16_t flags;
-                               /* flags are next */
-                               err = target_read_u16(target, table_entry + 4, &flags);
-                               if (err != ERROR_OK)
-                                       return err;
-                               //
-                               if (flags & RT_ARM_FUNC) {
-                                       /* 16 bit symbol */
-                                       return target_read_u16(target, table_entry + 2, symbol);
-                               }
-                       } else {
-                               /* 16 bit symbol is next */
-                               return target_read_u16(target, table_entry + 2, symbol);
-                       }
+                       err = target_read_u16(target, ptr_to_entry + 2, symbol_out);
+                       if (err != ERROR_OK)
+                               return err;
+                       LOG_ROM_SYMBOL_DEBUG(" -> found: 0x%04x", *symbol_out);
+                       return ERROR_OK;
                }
-               table_entry += found_rp2350_magic ? 6 : 4;
+               ptr_to_entry += 6;
        } while (entry_tag);
+       *symbol_out = 0;
        return ERROR_FAIL;
 }
 
-static int rp2040_call_rom_func(struct target *target, struct rp2040_flash_bank *priv,
-               uint16_t func_offset, uint32_t argdata[], unsigned int n_args)
+static int rp2350_lookup_rom_symbol(struct target *target, uint16_t tag, uint16_t flags, uint16_t *symbol_out)
 {
-       char *regnames[4] = { "r0", "r1", "r2", "r3" };
+       LOG_ROM_SYMBOL_DEBUG("Looking up ROM symbol '%c%c' in RP2350 A1 table", tag & 0xff, (tag >> 8) & 0xff);
+       uint32_t ptr_to_entry;
+       int err = target_read_u32(target, BOOTROM_MAGIC_ADDR + 4, &ptr_to_entry);
+       if (err != ERROR_OK)
+               return err;
 
-       assert(n_args <= ARRAY_SIZE(regnames)); /* only allow register arguments */
+       /* On RP2350 A1, Each entry has a flag bitmap identifying the type of its
+          contents. The entry contains one halfword of data for each set flag
+          bit. There may be both Arm and RISC-V entries under the same tag, or
+          separate Arm Secure/NonSecure entries (or all three, why not). */
 
-       if (!priv->stack) {
-               LOG_ERROR("no stack for flash programming code");
+       while (true) {
+               uint16_t entry_tag, entry_flags;
+
+               err = target_read_u16(target, ptr_to_entry, &entry_tag);
+               if (err != ERROR_OK)
+                       return err;
+               if (entry_tag == 0) {
+                       *symbol_out = 0;
+                       return ERROR_FAIL;
+               }
+               ptr_to_entry += 2;
+
+               err = target_read_u16(target, ptr_to_entry, &entry_flags);
+               if (err != ERROR_OK)
+                       return err;
+               ptr_to_entry += 2;
+
+               uint16_t matching_flags = flags & entry_flags;
+
+               if (tag == entry_tag && matching_flags != 0) {
+                       /* This is our entry, seek to the correct data item and return it. */
+                       bool is_riscv_func = matching_flags & RT_FLAG_FUNC_RISCV;
+                       while (!(matching_flags & 1)) {
+                               if (entry_flags & 1)
+                                       ptr_to_entry += 2;
+
+                               matching_flags >>= 1;
+                               entry_flags >>= 1;
+                       }
+                       if (is_riscv_func) {
+                               /* For RISC-V, the table entry itself is the entry point -- trick
+                                  to make shared function implementations smaller */
+                               *symbol_out = ptr_to_entry;
+                               return ERROR_OK;
+                       }
+                       err = target_read_u16(target, ptr_to_entry, symbol_out);
+                       if (err != ERROR_OK)
+                               return err;
+                       LOG_ROM_SYMBOL_DEBUG(" -> found: 0x%04x", *symbol_out);
+                       return ERROR_OK;
+               }
+               /* Skip past this entry */
+               while (entry_flags) {
+                       if (entry_flags & 1)
+                               ptr_to_entry += 2;
+
+                       entry_flags >>= 1;
+               }
+       }
+}
+
+static int rp2xxx_lookup_rom_symbol(struct target *target, uint16_t tag, uint16_t flags, uint16_t *symbol_out)
+{
+       uint32_t magic;
+       int err = target_read_u32(target, BOOTROM_MAGIC_ADDR, &magic);
+       if (err != ERROR_OK)
+               return err;
+
+       /* Ignore version */
+       magic &= 0xffffff;
+
+       if (magic == BOOTROM_RP2350_MAGIC) {
+               /* Distinguish old-style RP2350 ROM table (A0, and earlier A1 builds)
+                  based on position of table -- a high address means it is shared with
+                  RISC-V, i.e. new-style. */
+               uint32_t table_ptr;
+               err = target_read_u32(target, BOOTROM_MAGIC_ADDR + 4, &table_ptr);
+               if (err != ERROR_OK)
+                       return err;
+               if (table_ptr < 0x7c00)
+                       return rp2350_a0_lookup_symbol(target, tag, flags, symbol_out);
+               else
+                       return rp2350_lookup_rom_symbol(target, tag, flags, symbol_out);
+
+       } else if (magic == BOOTROM_RP2040_MAGIC) {
+               return rp2040_lookup_rom_symbol(target, tag, flags, symbol_out);
+       }
+       LOG_ERROR("RP2040/RP2350 BOOT ROM not found");
+       return ERROR_FAIL;
+}
+
+static int rp2xxx_populate_rom_pointer_cache(struct target *target, struct rp2040_flash_bank *priv)
+{
+       const uint16_t symtype_func = is_arm(target_to_arm(target))
+                                                                        ? RT_FLAG_FUNC_ARM_SEC : RT_FLAG_FUNC_RISCV;
+       int err;
+       err = rp2xxx_lookup_rom_symbol(target, FUNC_FLASH_EXIT_XIP,
+                                                                  symtype_func, &priv->jump_flash_exit_xip);
+       if (err != ERROR_OK) {
+               LOG_ERROR("Function FUNC_FLASH_EXIT_XIP not found in RP2xxx ROM.");
+               return err;
+       }
+
+       err = rp2xxx_lookup_rom_symbol(target, FUNC_CONNECT_INTERNAL_FLASH,
+                                                                  symtype_func, &priv->jump_connect_internal_flash);
+       if (err != ERROR_OK) {
+               LOG_ERROR("Function FUNC_CONNECT_INTERNAL_FLASH not found in RP2xxx ROM.");
+               return err;
+       }
+
+       err = rp2xxx_lookup_rom_symbol(target, FUNC_FLASH_RANGE_ERASE, symtype_func, &priv->jump_flash_range_erase);
+       if (err != ERROR_OK) {
+               LOG_ERROR("Function FUNC_FLASH_RANGE_ERASE not found in RP2xxx ROM.");
+               return err;
+       }
+
+       err = rp2xxx_lookup_rom_symbol(target, FUNC_FLASH_RANGE_PROGRAM, symtype_func, &priv->jump_flash_range_program);
+       if (err != ERROR_OK) {
+               LOG_ERROR("Function FUNC_FLASH_RANGE_PROGRAM not found in RP2xxx ROM.");
+               return err;
+       }
+
+       err = rp2xxx_lookup_rom_symbol(target, FUNC_FLASH_FLUSH_CACHE, symtype_func, &priv->jump_flush_cache);
+       if (err != ERROR_OK) {
+               LOG_ERROR("Function FUNC_FLASH_FLUSH_CACHE not found in RP2xxx ROM.");
+               return err;
+       }
+
+       err = rp2xxx_lookup_rom_symbol(target, FUNC_FLASH_ENTER_CMD_XIP, symtype_func, &priv->jump_enter_cmd_xip);
+       if (err != ERROR_OK) {
+               LOG_ERROR("Function FUNC_FLASH_ENTER_CMD_XIP not found in RP2xxx ROM.");
+               return err;
+       }
+
+       // From this point are optional functions which do not exist on e.g. RP2040
+       // or pre-production RP2350 ROM versions:
+
+       err = rp2xxx_lookup_rom_symbol(target, FUNC_BOOTROM_STATE_RESET, symtype_func, &priv->jump_bootrom_reset_state);
+       if (err != ERROR_OK) {
+               priv->jump_bootrom_reset_state = 0;
+               LOG_WARNING("Function FUNC_BOOTROM_STATE_RESET not found in RP2xxx ROM. (probably an RP2040 or an RP2350 A0)");
+       }
+
+       err = rp2xxx_lookup_rom_symbol(target, FUNC_FLASH_RESET_ADDRESS_TRANS,
+                                                                  symtype_func, &priv->jump_flash_reset_address_trans);
+       if (err != ERROR_OK) {
+               priv->jump_flash_reset_address_trans = 0;
+               LOG_WARNING("Function FUNC_FLASH_RESET_ADDRESS_TRANS not found in RP2xxx ROM. (probably an RP2040 or an RP2350 A0)");
+       }
+       return ERROR_OK;
+}
+
+// Call a list of PC + SP + r0-r3 function call tuples with a single OpenOCD
+// algorithm invocation, to amortise the algorithm overhead over multiple calls:
+static int rp2xxx_call_rom_func_batch(struct target *target, struct rp2040_flash_bank *priv,
+       rp2xxx_rom_call_batch_record_t *calls, unsigned int n_calls)
+{
+       // Note +1 is for the null terminator
+       unsigned int batch_words = 1 + (ROM_CALL_BATCH_ALGO_SIZE_BYTES +
+                                                       n_calls * sizeof(rp2xxx_rom_call_batch_record_t)
+                                                  ) / sizeof(uint32_t);
+
+       if (!priv->ram_algo_space) {
+               LOG_ERROR("No RAM code space allocated for ROM call");
                return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
        }
-       target_addr_t stacktop = priv->stack->address + priv->stack->size;
+       if (priv->ram_algo_space->size < batch_words * sizeof(uint32_t)) {
+               LOG_ERROR("RAM code space too small for call batch size of %u\n", n_calls);
+               return ERROR_BUF_TOO_SMALL;
+       }
 
-       LOG_DEBUG("Calling ROM func @0x%" PRIx16 " with %d arguments", func_offset, n_args);
+       LOG_DEBUG("Calling batch of %u ROM functions:", n_calls);
+       for (unsigned int i = 0; i < n_calls; ++i) {
+               LOG_DEBUG("  func @ %" PRIx32, calls[i].pc);
+               LOG_DEBUG("    sp = %" PRIx32, calls[i].sp);
+               for (int j = 0; j < 4; ++j)
+                       LOG_DEBUG("    a%d = %" PRIx32, j, calls[i].args[j]);
+       }
        LOG_DEBUG("Calling on core \"%s\"", target->cmd_name);
 
-       struct reg_param args[ARRAY_SIZE(regnames) + 12];
-       struct armv7m_algorithm alg_info;
+       if (n_calls <= 0) {
+               LOG_DEBUG("Returning early from call of 0 ROM functions");
+               return ERROR_OK;
+       }
 
-       for (unsigned int i = 0; i < n_args; ++i) {
-               init_reg_param(&args[i], regnames[i], 32, PARAM_OUT);
-               buf_set_u32(args[i].value, 0, 32, argdata[i]);
-       }
-       /* Pass function pointer in r7 */
-       unsigned int extra_args = 0;
-       init_reg_param(&args[n_args + extra_args], "r7", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, func_offset);
-       /* Set stack pointer, have seen the caching get confused by the aliases of sp so
-          take the shotgun approach*/
-       init_reg_param(&args[n_args + extra_args], "msp_s", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, stacktop);
-       init_reg_param(&args[n_args + extra_args], "msp_ns", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, stacktop);
-       init_reg_param(&args[n_args + extra_args], "psp_s", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, stacktop);
-       init_reg_param(&args[n_args + extra_args], "psp_ns", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, stacktop);
-       init_reg_param(&args[n_args + extra_args], "msp", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, stacktop);
-       init_reg_param(&args[n_args + extra_args], "psp", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, stacktop);
-       init_reg_param(&args[n_args + extra_args], "sp", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, stacktop);
-       /* Clear stack pointer limits, as they may be above the algorithm stack */
-       init_reg_param(&args[n_args + extra_args], "msplim_s", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, 0);
-       init_reg_param(&args[n_args + extra_args], "psplim_s", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, 0);
-       init_reg_param(&args[n_args + extra_args], "msplim_ns", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, 0);
-       init_reg_param(&args[n_args + extra_args], "psplim_ns", 32, PARAM_OUT);
-       buf_set_u32(args[n_args + extra_args++].value, 0, 32, 0);
-
-       for (unsigned int i = 0; i < n_args + extra_args; ++i)
-               LOG_DEBUG("Set %s = 0x%" PRIx32, args[i].reg_name, buf_get_u32(args[i].value, 0, 32));
+       const uint8_t *algo_code;
+       if (is_arm(target_to_arm(target))) {
+               if (target_to_arm(target)->arch == ARM_ARCH_V8M) {
+                       LOG_DEBUG("Using algo: rp2xxx_rom_call_batch_algo_armv8m");
+                       algo_code = rp2xxx_rom_call_batch_algo_armv8m;
+               } else {
+                       LOG_DEBUG("Using algo: rp2xxx_rom_call_batch_algo_armv6m");
+                       algo_code = rp2xxx_rom_call_batch_algo_armv6m;
+               }
+       } else {
+               LOG_DEBUG("Using algo: rp2xxx_rom_call_batch_algo_riscv");
+               algo_code = rp2xxx_rom_call_batch_algo_riscv;
+       }
 
-       /* Actually call the function */
-       alg_info.common_magic = ARMV7M_COMMON_MAGIC;
-       alg_info.core_mode = ARM_MODE_THREAD;
-       int err = target_run_algorithm(
-               target,
-               0, NULL,          /* No memory arguments */
-               n_args + extra_args, args, /* User arguments + r7 + SPs */
-               priv->jump_debug_trampoline, priv->jump_debug_trampoline_end,
-               3000, /* 3s timeout */
-               &alg_info
+       int err = target_write_buffer(target,
+               priv->ram_algo_space->address,
+               ROM_CALL_BATCH_ALGO_SIZE_BYTES,
+               algo_code
        );
-       for (unsigned int i = 0; i < n_args + extra_args; ++i)
-               destroy_reg_param(&args[i]);
-       if (err != ERROR_OK)
-               LOG_ERROR("Failed to invoke ROM function @0x%" PRIx16 "\n", func_offset);
-       return err;
+       if (err != ERROR_OK) {
+               LOG_ERROR("Failed to write ROM batch algorithm to RAM code space\n");
+               return err;
+       }
+       err = target_write_buffer(target,
+               priv->ram_algo_space->address + ROM_CALL_BATCH_ALGO_SIZE_BYTES,
+               n_calls * sizeof(rp2xxx_rom_call_batch_record_t),
+               (const uint8_t *)calls
+       );
+       if (err != ERROR_OK) {
+               LOG_ERROR("Failed to write ROM batch records to RAM code space\n");
+               return err;
+       }
+       err = target_write_u32(target,
+               priv->ram_algo_space->address + (batch_words - 1) * sizeof(uint32_t),
+               0
+       );
+       if (err != ERROR_OK) {
+               LOG_ERROR("Failed to write null terminator for ROM batch records\n");
+               return err;
+       }
+
+       // Call into the ROM batch algorithm -- this will in turn call each ROM
+       // call specified by the batch records.
+       target_addr_t algo_start_addr = priv->ram_algo_space->address;
+       target_addr_t algo_end_addr = priv->ram_algo_space->address + rp2xxx_rom_call_batch_algo_bkpt_offset;
+       unsigned int algo_timeout_ms = 3000;
+       if (is_arm(target_to_arm(target))) {
+               struct armv7m_algorithm alg_info;
+               alg_info.common_magic = ARMV7M_COMMON_MAGIC;
+               alg_info.core_mode = ARM_MODE_THREAD;
+               err = target_run_algorithm(target,
+                       0, NULL,          /* No memory arguments */
+                       0, NULL,          /* No register arguments */
+                       algo_start_addr, algo_end_addr,
+                       algo_timeout_ms,
+                       &alg_info
+               );
+       } else {
+               // Presumed RISC-V -- there is no RISCV_COMMON_MAGIC on older OpenOCD
+               err = target_run_algorithm(target,
+                       0, NULL,          /* No memory arguments */
+                       0, NULL,          /* No register arguments */
+                       algo_start_addr, algo_end_addr,
+                       algo_timeout_ms,
+                       NULL              /* Currently no RISC-V-specific algorithm info */
+               );
+       }
+       if (err != ERROR_OK) {
+               LOG_ERROR("Failed to call ROM function batch\n");
+               /* This case is hit when loading new ROM images on FPGA, but can also be
+                  hit on real hardware if you swap two devices with different ROM
+                  versions without restarting OpenOCD: */
+               LOG_INFO("Repopulating ROM address cache after failed ROM call");
+               /* We ignore the error because we have already failed, this is just
+                  recovery for the next attempt. */
+               (void)rp2xxx_populate_rom_pointer_cache(target, priv);
+               return err;
+       }
+       return ERROR_OK;
 }
 
-static int rp2350_init_core(struct target *target, struct rp2040_flash_bank *priv)
+// Call a single ROM function, using the default algorithm stack.
+static int rp2xxx_call_rom_func(struct target *target, struct rp2040_flash_bank *priv,
+               uint16_t func_offset, uint32_t argdata[], unsigned int n_args)
 {
+       assert(n_args <= 4); /* only allow register arguments -- capped at just 4 on Arm */
+
        if (!priv->stack) {
                LOG_ERROR("no stack for flash programming code");
                return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
        }
+       target_addr_t stacktop = priv->stack->address + priv->stack->size;
 
-       struct armv7m_algorithm alg_info;
+       rp2xxx_rom_call_batch_record_t call = {
+               .pc = func_offset,
+               .sp = stacktop
+       };
+       for (unsigned int i = 0; i < n_args; ++i)
+               call.args[i] = argdata[i];
 
-       // copy rcp_init code onto stack, as we don't actually need any stack during the call
-       if (priv->stack->size < sizeof(rcp_init_code)) {
-               LOG_ERROR("Working area too small for rcp_init");
-               return ERROR_BUF_TOO_SMALL;
-       }
+       return rp2xxx_call_rom_func_batch(target, priv, &call, 1);
+}
 
-       // Attempt to reset ACCESSCTRL before running RCP init, in case Secure
-       // access to SRAM has been blocked. (Also ROM, QMI regs are needed later)
+static int rp2350_init_accessctrl(struct target *target)
+{
+       // Attempt to reset ACCESSCTRL, in case Secure access to SRAM has been
+       // blocked, which will stop us from loading/running algorithms such as RCP
+       // init. (Also ROM, QMI regs are needed later)
        uint32_t accessctrl_lock_reg;
        if (target_read_u32(target, ACCESSCTRL_LOCK_OFFSET, &accessctrl_lock_reg) != ERROR_OK) {
                LOG_ERROR("Failed to read ACCESSCTRL lock register");
@@ -254,30 +567,59 @@ static int rp2350_init_core(struct target *target, struct rp2040_flash_bank *pri
                LOG_ERROR("ACCESSCTRL is locked, so can't reset permissions. Following steps might fail.\n");
        } else {
                LOG_DEBUG("Reset ACCESSCTRL permissions via CFGRESET\n");
-               target_write_u32(target, ACCESSCTRL_CFGRESET_OFFSET, ACCESSCTRL_WRITE_PASSWORD | 1u);
+               return target_write_u32(target, ACCESSCTRL_CFGRESET_OFFSET, ACCESSCTRL_WRITE_PASSWORD | 1u);
+       }
+       return ERROR_OK;
+}
+
+static int rp2350_init_arm_core0(struct target *target, struct rp2040_flash_bank *priv)
+{
+       // Flash algorithms (and the RCP init stub called by this function) must
+       // run in the Secure state, so flip the state now before attempting to
+       // execute any code on the core.
+       uint32_t dscsr;
+       (void)target_read_u32(target, DCB_DSCSR, &dscsr);
+       LOG_DEBUG("DSCSR:  %08x\n", dscsr);
+       if (!(dscsr & DSCSR_CDS)) {
+               LOG_DEBUG("Setting Current Domain Secure in DSCSR\n");
+               (void)target_write_u32(target, DCB_DSCSR, (dscsr & ~DSCSR_CDSKEY) | DSCSR_CDS);
+               (void)target_read_u32(target, DCB_DSCSR, &dscsr);
+               LOG_DEBUG("DSCSR*: %08x\n", dscsr);
+       }
+
+       if (!priv->stack) {
+               LOG_ERROR("No stack for flash programming code");
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       }
+
+       if (!priv->ram_algo_space || priv->ram_algo_space->size < sizeof(rcp_init_code)) {
+               LOG_ERROR("No algorithm space for rcp_init code");
+               return ERROR_BUF_TOO_SMALL;
        }
 
        int err = target_write_memory(target,
-               priv->stack->address,
+               priv->ram_algo_space->address,
                1,
                sizeof(rcp_init_code),
-               (const uint8_t *)rcp_init_code
+               rcp_init_code
        );
        if (err != ERROR_OK) {
                LOG_ERROR("Failed to load rcp_init algorithm into RAM\n");
                return ERROR_FAIL;
        }
 
-       LOG_DEBUG("Calling rcp_init core \"%s\" code at 0x%" PRIx16 "\n", target->cmd_name, (uint32_t)priv->stack->address);
+       LOG_DEBUG("Calling rcp_init on core \"%s\", code at 0x%" PRIx32 "\n",
+                         target->cmd_name, (uint32_t)priv->ram_algo_space->address);
 
        /* Actually call the function */
+       struct armv7m_algorithm alg_info;
        alg_info.common_magic = ARMV7M_COMMON_MAGIC;
        alg_info.core_mode = ARM_MODE_THREAD;
        err = target_run_algorithm(target,
                        0, NULL,          /* No memory arguments */
                        0, NULL,          /* No register arguments */
-                       priv->stack->address,
-                       priv->stack->address + rcp_init_code_bkpt_offset,
+                       priv->ram_algo_space->address,
+                       priv->ram_algo_space->address + rcp_init_code_bkpt_offset,
                        1000, /* 1s timeout */
                        &alg_info
        );
@@ -286,80 +628,75 @@ static int rp2350_init_core(struct target *target, struct rp2040_flash_bank *pri
                return err;
        }
 
-       uint32_t reset_args[1] = {
-                       BOOTROM_STATE_RESET_CURRENT_CORE
-       };
-       if (!priv->jump_bootrom_reset_state) {
-               LOG_WARNING("RP2350 flash: no bootrom_reset_method\n");
-       } else {
-               err = rp2040_call_rom_func(target, priv, priv->jump_bootrom_reset_state, reset_args, ARRAY_SIZE(reset_args));
-               if (err != ERROR_OK) {
-                       LOG_ERROR("RP2040 flash: failed to call reset core state");
-                       return err;
-               }
-       }
-
        return err;
 }
 
-static int setup_for_rom_call(struct flash_bank *bank)
+static int setup_for_raw_flash_cmd(struct flash_bank *bank)
 {
        struct rp2040_flash_bank *priv = bank->driver_priv;
-
        struct target *target = bank->target;
 
        int err = ERROR_OK;
+
        if (!priv->stack) {
                /* target_alloc_working_area always allocates multiples of 4 bytes, so no worry about alignment */
-               const int STACK_SIZE = 256;
-               target_alloc_working_area(bank->target, STACK_SIZE, &priv->stack);
+               err = target_alloc_working_area(bank->target, RP2XXX_MAX_ALGO_STACK_USAGE, &priv->stack);
                if (err != ERROR_OK) {
-                       LOG_ERROR("Could not allocate stack for flash programming code");
+                       LOG_ERROR("Could not allocate stack for flash programming code -- insufficient space");
                        return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
                }
        }
 
-       // Flash algorithms must run in Secure state
-       uint32_t dscsr;
-       (void)target_read_u32(target, DCB_DSCSR, &dscsr);
-       LOG_DEBUG("DSCSR:  %08x\n", dscsr);
-       if (!(dscsr & DSCSR_CDS)) {
-               LOG_DEBUG("Setting Current Domain Secure in DSCSR\n");
-               (void)target_write_u32(target, DCB_DSCSR, (dscsr & ~DSCSR_CDSKEY) | DSCSR_CDS);
-               (void)target_read_u32(target, DCB_DSCSR, &dscsr);
-               LOG_DEBUG("DSCSR*: %08x\n", dscsr);
+       if (!priv->ram_algo_space) {
+               err = target_alloc_working_area(bank->target, RP2XXX_MAX_RAM_ALGO_SIZE, &priv->ram_algo_space);
+               if (err != ERROR_OK) {
+                       LOG_ERROR("Could not allocate RAM code space for ROM calls -- insufficient space");
+                       return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+               }
        }
 
-       // hacky RP2350 check
-       if (target_to_arm(target)->arch == ARM_ARCH_V8M) {
-               err = rp2350_init_core(target, priv);
+       // hacky RP2350 check -- either RISC-V or v8-M
+       if (is_arm(target_to_arm(target)) ? target_to_arm(target)->arch == ARM_ARCH_V8M : true) {
+               err = rp2350_init_accessctrl(target);
                if (err != ERROR_OK) {
-                       LOG_ERROR("RP2350 flash: failed to init core");
+                       LOG_ERROR("Failed to init ACCESSCTRL before ROM call");
                        return err;
                }
-       }
-       return err;
-}
-
-static int setup_for_raw_flash_cmd(struct flash_bank *bank)
-{
-       struct rp2040_flash_bank *priv = bank->driver_priv;
-       int err = setup_for_rom_call(bank);
-       err = rp2040_call_rom_func(bank->target, priv, priv->jump_connect_internal_flash, NULL, 0);
-       if (err != ERROR_OK) {
-               LOG_ERROR("RP2040 flash: failed to setup for rom call");
-               return err;
-       }
-
-       LOG_DEBUG("Connecting internal flash");
-       err = rp2040_call_rom_func(bank->target, priv, priv->jump_connect_internal_flash, NULL, 0);
-       if (err != ERROR_OK) {
-               LOG_ERROR("RP2040 flash: failed to connect internal flash");
-               return err;
+               if (is_arm(target_to_arm(target))) {
+                       err = rp2350_init_arm_core0(target, priv);
+                       if (err != ERROR_OK) {
+                               LOG_ERROR("Failed to init Arm core 0 before ROM call");
+                               return err;
+                       }
+               }
+               uint32_t reset_args[1] = {
+                               BOOTROM_STATE_RESET_CURRENT_CORE
+               };
+               if (!priv->jump_bootrom_reset_state) {
+                       LOG_WARNING("RP2350 flash: no bootrom_reset_method\n");
+               } else {
+                       LOG_DEBUG("Clearing core 0 ROM state");
+                       err = rp2xxx_call_rom_func(target, priv, priv->jump_bootrom_reset_state,
+                                                                          reset_args, ARRAY_SIZE(reset_args));
+                       if (err != ERROR_OK) {
+                               LOG_ERROR("RP2350 flash: failed to call reset core state");
+                               return err;
+                       }
+               }
        }
 
-       LOG_DEBUG("Kicking flash out of XIP mode");
-       err = rp2040_call_rom_func(bank->target, priv, priv->jump_flash_exit_xip, NULL, 0);
+       LOG_DEBUG("Connecting flash IOs and issuing XIP exit sequence to flash");
+       rp2xxx_rom_call_batch_record_t calls[2] = {
+               {
+                       .pc = priv->jump_connect_internal_flash,
+                       .sp = priv->stack->address + priv->stack->size
+               },
+               {
+                       .pc = priv->jump_flash_exit_xip,
+                       .sp = priv->stack->address + priv->stack->size
+               }
+       };
+       err = rp2xxx_call_rom_func_batch(bank->target, priv, calls, 2);
        if (err != ERROR_OK) {
                LOG_ERROR("RP2040 flash: failed to exit flash XIP mode");
                return err;
@@ -368,6 +705,25 @@ static int setup_for_raw_flash_cmd(struct flash_bank *bank)
        return ERROR_OK;
 }
 
+static void cleanup_after_raw_flash_cmd(struct flash_bank *bank)
+{
+       /* OpenOCD is prone to trashing work-area allocations on target state
+          transitions, which leaves us with stale work area pointers in our
+          driver state. Best to clean up our allocations manually after
+          completing each flash call, so we know to make fresh ones next time. */
+       LOG_DEBUG("Cleaning up after flash operations");
+       struct rp2040_flash_bank *priv = bank->driver_priv;
+       struct target *target = bank->target;
+       if (priv->stack) {
+               target_free_working_area(target, priv->stack);
+               priv->stack = 0;
+       }
+       if (priv->ram_algo_space) {
+               target_free_working_area(target, priv->ram_algo_space);
+               priv->ram_algo_space = 0;
+       }
+}
+
 static int rp2040_flash_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count)
 {
        LOG_DEBUG("Writing %d bytes starting at 0x%" PRIx32, count, offset);
@@ -378,13 +734,14 @@ static int rp2040_flash_write(struct flash_bank *bank, const uint8_t *buffer, ui
 
        int err = setup_for_raw_flash_cmd(bank);
        if (err != ERROR_OK)
-               return err;
+               goto cleanup_and_return;
 
        // Allocate as much memory as possible, rounded down to a whole number of flash pages
        const unsigned int chunk_size = target_get_working_area_avail(target) & ~0xffu;
        if (chunk_size == 0 || target_alloc_working_area(target, chunk_size, &bounce) != ERROR_OK) {
                LOG_ERROR("Could not allocate bounce buffer for flash programming. Can't continue");
-               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+               err = ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+               goto cleanup_and_return;
        }
 
        LOG_DEBUG("Allocated flash bounce buffer @" TARGET_ADDR_FMT, bounce->address);
@@ -402,7 +759,7 @@ static int rp2040_flash_write(struct flash_bank *bank, const uint8_t *buffer, ui
                        bounce->address, /* data */
                        write_size /* count */
                };
-               err = rp2040_call_rom_func(target, priv, priv->jump_flash_range_program, args, ARRAY_SIZE(args));
+               err = rp2xxx_call_rom_func(target, priv, priv->jump_flash_range_program, args, ARRAY_SIZE(args));
                if (err != ERROR_OK) {
                        LOG_ERROR("Failed to invoke flash programming code on target");
                        break;
@@ -415,20 +772,43 @@ static int rp2040_flash_write(struct flash_bank *bank, const uint8_t *buffer, ui
        target_free_working_area(target, bounce);
 
        if (err != ERROR_OK)
-               return err;
+               goto cleanup_and_return;
+
+       // Flash is successfully programmed. We can now do a bit of poking to make
+       // the new flash contents visible to us via memory-mapped (XIP) interface
+       // in the 0x1... memory region.
+       //
+       // Note on RP2350 it's not *required* to call flash_enter_cmd_xip, since
+       // the ROM leaves flash XIPable by default in between direct-mode
+       // accesses, but there's no harm in calling it anyway.
 
-       /* Flash is successfully programmed. We can now do a bit of poking to make the flash
-          contents visible to us via memory-mapped (XIP) interface in the 0x1... memory region */
        LOG_DEBUG("Flushing flash cache after write behind");
-       err = rp2040_call_rom_func(bank->target, priv, priv->jump_flush_cache, NULL, 0);
+       err = rp2xxx_call_rom_func(bank->target, priv, priv->jump_flush_cache, NULL, 0);
+
+       rp2xxx_rom_call_batch_record_t finishing_calls[3] = {
+               {
+                       .pc = priv->jump_flush_cache,
+                       .sp = priv->stack->address + priv->stack->size
+               },
+               {
+                       .pc = priv->jump_enter_cmd_xip,
+                       .sp = priv->stack->address + priv->stack->size
+               },
+               {
+                       .pc = priv->jump_flash_reset_address_trans,
+                       .sp = priv->stack->address + priv->stack->size
+               }
+       };
+       // Note the last function does not exist on older devices:
+       int num_finishing_calls = priv->jump_flash_reset_address_trans ? 3 : 2;
+
+       err = rp2xxx_call_rom_func_batch(target, priv, finishing_calls, num_finishing_calls);
        if (err != ERROR_OK) {
                LOG_ERROR("RP2040 write: failed to flush flash cache");
-               return err;
+               goto cleanup_and_return;
        }
-       LOG_DEBUG("Configuring SSI for execute-in-place");
-       err = rp2040_call_rom_func(bank->target, priv, priv->jump_enter_cmd_xip, NULL, 0);
-       if (err != ERROR_OK)
-               LOG_ERROR("RP2040 write: failed to flush flash cache");
+cleanup_and_return:
+       cleanup_after_raw_flash_cmd(bank);
        return err;
 }
 
@@ -441,7 +821,7 @@ static int rp2040_flash_erase(struct flash_bank *bank, unsigned int first, unsig
 
        int err = setup_for_raw_flash_cmd(bank);
        if (err != ERROR_OK)
-               return err;
+               goto cleanup_and_return;
 
        LOG_DEBUG("Remote call flash_range_erase");
 
@@ -452,18 +832,13 @@ static int rp2040_flash_erase(struct flash_bank *bank, unsigned int first, unsig
                0xd8   /* block_cmd */
        };
 
-       /*
-       The RP2040 Boot ROM provides a _flash_range_erase() API call documented in Section 2.8.3.1.3:
-       https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
-       and the particular source code for said Boot ROM function can be found here:
-       https://github.com/raspberrypi/pico-bootrom/blob/master/bootrom/program_flash_generic.c
-
-       In theory, the function algorithm provides for erasing both a smaller "sector" (4096 bytes) and
-       an optional larger "block" (size and command provided in args).  OpenOCD's spi.c only uses "block" sizes.
-       */
-
-       err = rp2040_call_rom_func(bank->target, priv, priv->jump_flash_range_erase, args, ARRAY_SIZE(args));
+       /* This ROM function will use the optimal mixture of 4k 20h and 64k D8h
+          erases, without over-erase, as long as you just tell OpenOCD that your
+          flash is made up of 4k sectors instead of letting it try to guess. */
+       err = rp2xxx_call_rom_func(bank->target, priv, priv->jump_flash_range_erase, args, ARRAY_SIZE(args));
 
+cleanup_and_return:
+       cleanup_after_raw_flash_cmd(bank);
        return err;
 }
 
@@ -475,63 +850,9 @@ static int rp2040_flash_probe(struct flash_bank *bank)
        struct rp2040_flash_bank *priv = bank->driver_priv;
        struct target *target = bank->target;
 
-       int err = rp2040_lookup_symbol(target, FUNC_DEBUG_TRAMPOLINE, &priv->jump_debug_trampoline);
-       if (err != ERROR_OK) {
-               LOG_ERROR("Debug trampoline not found in RP2040 ROM.");
-               return err;
-       }
-       priv->jump_debug_trampoline &= ~1u; /* mask off thumb bit */
-
-       err = rp2040_lookup_symbol(target, FUNC_DEBUG_TRAMPOLINE_END, &priv->jump_debug_trampoline_end);
-       if (err != ERROR_OK) {
-               LOG_ERROR("Debug trampoline end not found in RP2040 ROM.");
-               return err;
-       }
-       priv->jump_debug_trampoline_end &= ~1u; /* mask off thumb bit */
-
-       err = rp2040_lookup_symbol(target, FUNC_FLASH_EXIT_XIP, &priv->jump_flash_exit_xip);
-       if (err != ERROR_OK) {
-               LOG_ERROR("Function FUNC_FLASH_EXIT_XIP not found in RP2040 ROM.");
-               return err;
-       }
-
-       err = rp2040_lookup_symbol(target, FUNC_CONNECT_INTERNAL_FLASH, &priv->jump_connect_internal_flash);
-       if (err != ERROR_OK) {
-               LOG_ERROR("Function FUNC_CONNECT_INTERNAL_FLASH not found in RP2040 ROM.");
-               return err;
-       }
-
-       err = rp2040_lookup_symbol(target, FUNC_FLASH_RANGE_ERASE, &priv->jump_flash_range_erase);
-       if (err != ERROR_OK) {
-               LOG_ERROR("Function FUNC_FLASH_RANGE_ERASE not found in RP2040 ROM.");
-               return err;
-       }
-       LOG_WARNING("GOT FLASH ERASE AT %08x\n", (int)priv->jump_flash_range_erase);
-
-       err = rp2040_lookup_symbol(target, FUNC_FLASH_RANGE_PROGRAM, &priv->jump_flash_range_program);
-       if (err != ERROR_OK) {
-               LOG_ERROR("Function FUNC_FLASH_RANGE_PROGRAM not found in RP2040 ROM.");
-               return err;
-       }
-
-       err = rp2040_lookup_symbol(target, FUNC_FLASH_FLUSH_CACHE, &priv->jump_flush_cache);
-       if (err != ERROR_OK) {
-               LOG_ERROR("Function FUNC_FLASH_FLUSH_CACHE not found in RP2040 ROM.");
-               return err;
-       }
-
-       err = rp2040_lookup_symbol(target, FUNC_FLASH_ENTER_CMD_XIP, &priv->jump_enter_cmd_xip);
-       if (err != ERROR_OK) {
-               LOG_ERROR("Function FUNC_FLASH_ENTER_CMD_XIP not found in RP2040 ROM.");
+       int err = rp2xxx_populate_rom_pointer_cache(target, priv);
+       if (err != ERROR_OK)
                return err;
-       }
-
-       err = rp2040_lookup_symbol(target, FUNC_BOOTROM_STATE_RESET, &priv->jump_bootrom_reset_state);
-       if (err != ERROR_OK) {
-               priv->jump_bootrom_reset_state = 0;
-//        LOG_ERROR("Function FUNC_BOOTROM_STATE_RESET not found in RP2040 ROM.");
-//        return err;
-       }
 
        /* the Boot ROM flash_range_program() routine requires page alignment */
        bank->write_start_alignment = 256;
@@ -577,6 +898,7 @@ FLASH_BANK_COMMAND_HANDLER(rp2040_flash_bank_command)
 {
        struct rp2040_flash_bank *priv;
        priv = malloc(sizeof(struct rp2040_flash_bank));
+       memset(priv, 0, sizeof(struct rp2040_flash_bank));
        priv->probed = false;
 
        /* Set up driver_priv */