-
-
Save cr0sh/1920833b7b7ecd40f3c605f49dd32c84 to your computer and use it in GitHub Desktop.
Buildroot package patch for OpenOCD raspberrypi fork
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/configure.ac b/configure.ac | |
index a5c9c9276..a25477407 100644 | |
--- a/configure.ac | |
+++ b/configure.ac | |
@@ -112,6 +112,7 @@ m4_define([ADAPTER_OPT], [m4_translit(ADAPTER_ARG($1), [_], [-])]) | |
m4_define([USB1_ADAPTERS], | |
[[[ftdi], [MPSSE mode of FTDI based devices], [FTDI]], | |
+ [[picoprobe], [Raspberry Pi Pico Probe], [PICOPROBE]], | |
[[stlink], [ST-Link Programmer], [HLADAPTER_STLINK]], | |
[[ti_icdi], [TI ICDI JTAG Programmer], [HLADAPTER_ICDI]], | |
[[ulink], [Keil ULINK JTAG Programmer], [ULINK]], | |
diff --git a/contrib/60-openocd.rules b/contrib/60-openocd.rules | |
index e0864b827..57006a4c6 100644 | |
--- a/contrib/60-openocd.rules | |
+++ b/contrib/60-openocd.rules | |
@@ -175,4 +175,7 @@ ATTRS{idVendor}=="c251", ATTRS{idProduct}=="2750", MODE="660", GROUP="plugdev", | |
# CMSIS-DAP compatible adapters | |
ATTRS{product}=="*CMSIS-DAP*", MODE="660", GROUP="plugdev", TAG+="uaccess" | |
+# Raspberry Pi Picoprobe | |
+ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0004", MODE="660", GROUP="plugdev", TAG+="uaccess" | |
+ | |
LABEL="openocd_rules_end" | |
diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am | |
index 8c9b2b7ab..532670436 100644 | |
--- a/src/flash/nor/Makefile.am | |
+++ b/src/flash/nor/Makefile.am | |
@@ -52,6 +52,7 @@ NOR_DRIVERS = \ | |
%D%/psoc5lp.c \ | |
%D%/psoc6.c \ | |
%D%/renesas_rpchf.c \ | |
+ %D%/rp2040.c \ | |
%D%/sfdp.c \ | |
%D%/sh_qspi.c \ | |
%D%/sim3x.c \ | |
diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c | |
index 570861ec5..6eadc756b 100644 | |
--- a/src/flash/nor/drivers.c | |
+++ b/src/flash/nor/drivers.c | |
@@ -67,6 +67,7 @@ extern const struct flash_driver psoc5lp_eeprom_flash; | |
extern const struct flash_driver psoc5lp_nvl_flash; | |
extern const struct flash_driver psoc6_flash; | |
extern const struct flash_driver renesas_rpchf_flash; | |
+extern const struct flash_driver rp2040_flash; | |
extern const struct flash_driver sh_qspi_flash; | |
extern const struct flash_driver sim3x_flash; | |
extern const struct flash_driver stellaris_flash; | |
@@ -140,6 +141,7 @@ static const struct flash_driver * const flash_drivers[] = { | |
&psoc5lp_nvl_flash, | |
&psoc6_flash, | |
&renesas_rpchf_flash, | |
+ &rp2040_flash, | |
&sh_qspi_flash, | |
&sim3x_flash, | |
&stellaris_flash, | |
diff --git a/src/flash/nor/rp2040.c b/src/flash/nor/rp2040.c | |
new file mode 100644 | |
index 000000000..74f4ad568 | |
--- /dev/null | |
+++ b/src/flash/nor/rp2040.c | |
@@ -0,0 +1,420 @@ | |
+#ifdef HAVE_CONFIG_H | |
+#include "config.h" | |
+#endif | |
+ | |
+#include "imp.h" | |
+#include <helper/binarybuffer.h> | |
+#include <target/algorithm.h> | |
+#include <target/armv7m.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_MAGIC 0x01754d | |
+#define BOOTROM_MAGIC_ADDR 0x00000010 | |
+ | |
+// 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 | |
+// FIXME the trampoline is needed because OpenOCD "algorithm" code insists on sw breakpoints. | |
+ | |
+char *regnames[4] = { | |
+ "r0", "r1", "r2", "r3" | |
+}; // FIXME pretty sure this is defined elsewhere too | |
+ | |
+#define MAKE_TAG(a, b) (((b)<<8) | a) | |
+#define FUNC_FLASH_EXIT_XIP MAKE_TAG('E', 'X') | |
+#define FUNC_DEBUG_TRAMPOLINE MAKE_TAG('D', 'T') | |
+#define FUNC_DEBUG_TRAMPOLINE_END MAKE_TAG('D', 'E') | |
+#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') | |
+ | |
+static uint32_t rp2040_lookup_symbol(struct target *target, uint32_t tag, uint16_t *symbol) { | |
+ uint32_t magic; | |
+ int err = target_read_u32(target, BOOTROM_MAGIC_ADDR, &magic); | |
+ if (err != ERROR_OK) | |
+ return err; | |
+ | |
+ magic &= 0xffffff; // ignore bootrom version | |
+ if (magic != BOOTROM_MAGIC) { | |
+ if (!((magic ^ BOOTROM_MAGIC)&0xffff)) | |
+ LOG_ERROR("Incorrect RP2040 BOOT ROM version"); | |
+ else | |
+ LOG_ERROR("RP2040 BOOT ROM not found"); | |
+ return ERROR_FAIL; | |
+ } | |
+ | |
+ // dereference the table pointer | |
+ uint16_t table_entry; | |
+ err = target_read_u16(target, BOOTROM_MAGIC_ADDR + 4, &table_entry); | |
+ if (err != ERROR_OK) | |
+ return err; | |
+ | |
+ uint16_t entry_tag; | |
+ do { | |
+ err = target_read_u16(target, table_entry, &entry_tag); | |
+ if (err != ERROR_OK) | |
+ return err; | |
+ if (entry_tag == tag) { | |
+ // 16 bit symbol is next | |
+ return target_read_u16(target, table_entry + 2, symbol); | |
+ } | |
+ table_entry += 4; | |
+ } while (entry_tag); | |
+ return ERROR_FAIL; | |
+} | |
+ | |
+static int rp2040_call_rom_func(struct target *target, target_addr_t stack, uint32_t func_tag, uint32_t argdata[], int n_args) | |
+{ | |
+ const int max_args = 4; // only allow register arguments | |
+ if (n_args > max_args) | |
+ { | |
+ LOG_ERROR("Max of 4 arguments permitted when calling RP2040 ROM functions."); | |
+ return ERROR_FAIL; | |
+ } | |
+ uint16_t debug_trampoline; | |
+ int err = rp2040_lookup_symbol(target, FUNC_DEBUG_TRAMPOLINE, &debug_trampoline); | |
+ if (err != ERROR_OK) { | |
+ LOG_ERROR("Debug trampoline not found in RP2040 ROM."); | |
+ return err; | |
+ } | |
+ debug_trampoline &= ~1u; // mask off thumb bit | |
+ | |
+ uint16_t debug_trampoline_end; | |
+ err = rp2040_lookup_symbol(target, FUNC_DEBUG_TRAMPOLINE_END, &debug_trampoline_end); | |
+ if (err != ERROR_OK) { | |
+ LOG_ERROR("Debug trampoline end not found in RP2040 ROM."); | |
+ return err; | |
+ } | |
+ debug_trampoline_end &= ~1u; // mask off thumb bit | |
+ | |
+ LOG_DEBUG("Calling ROM func %c%c with %d arguments", (char)func_tag, (char)(func_tag>>8), n_args); | |
+ LOG_DEBUG("Calling on core \"%s\"", target->cmd_name); | |
+ | |
+ uint16_t func; | |
+ err = rp2040_lookup_symbol(target, func_tag, &func); | |
+ if (err != ERROR_OK) { | |
+ LOG_ERROR("Function %c%c not found in RP2040 ROM.", (char)func_tag, (char)(func_tag>>8)); | |
+ return err; | |
+ } | |
+ | |
+ struct reg_param args[max_args + 2]; | |
+ struct armv7m_algorithm alg_info; | |
+ | |
+ for (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 | |
+ init_reg_param(&args[n_args], "r7", 32, PARAM_OUT); | |
+ buf_set_u32(args[n_args].value, 0, 32, func); | |
+ init_reg_param(&args[n_args + 1], "sp", 32, PARAM_OUT); | |
+ buf_set_u32(args[n_args + 1].value, 0, 32, stack); | |
+ | |
+ | |
+ for (int i = 0; i < n_args + 2; ++i) | |
+ LOG_DEBUG("Set %s = %08x", args[i].reg_name, buf_get_u32(args[i].value, 0, 32)); | |
+ | |
+ // Actually call the function | |
+ alg_info.common_magic = ARMV7M_COMMON_MAGIC; | |
+ alg_info.core_mode = ARM_MODE_THREAD; | |
+ err = target_run_algorithm( | |
+ target, | |
+ 0, NULL, // No memory arguments | |
+ n_args + 1, args, // User arguments + r7 | |
+ debug_trampoline, debug_trampoline_end, | |
+ 3000, // 3s timeout | |
+ &alg_info | |
+ ); | |
+ for (int i = 0; i < n_args + 1; ++i) | |
+ destroy_reg_param(&args[i]); | |
+ if (err != ERROR_OK) | |
+ LOG_ERROR("Failed to invoke ROM function %c%c (@%08x)\n", (char)func_tag, (char)(func_tag>>8), func); | |
+ return err; | |
+ | |
+} | |
+ | |
+// ----------------------------------------------------------------------------- | |
+// Flash access code | |
+ | |
+// FIXME: support other flash geometries; where to get these consts from? | |
+// These are common values, and are accurate for W25X10CL, W25Q16JV and friends | |
+#define BLOCK_SIZE (1ul << 16) | |
+#define BLOCK_ERASE_CMD 0xd8 | |
+#define SECTOR_SIZE 4096 | |
+#define PAGE_SIZE 256 | |
+ | |
+struct rp2040_flash_bank { | |
+ int probed; | |
+ // Infrastructure for calling into ROM code: | |
+ struct working_area *stack; | |
+ target_addr_t stacktop; | |
+}; | |
+ | |
+static int rp2040_flash_exit_xip(struct flash_bank *bank) | |
+{ | |
+ struct rp2040_flash_bank *priv = bank->driver_priv; | |
+ int err = ERROR_OK; | |
+ | |
+ LOG_DEBUG("Connecting internal flash"); | |
+ err = rp2040_call_rom_func(bank->target, priv->stacktop, FUNC_CONNECT_INTERNAL_FLASH, NULL, 0); | |
+ if (err != ERROR_OK) | |
+ { | |
+ LOG_ERROR("RP2040 exit xip: failed to connect internal flash"); | |
+ return err; | |
+ } | |
+ | |
+ LOG_DEBUG("Kicking flash out of XIP mode"); | |
+ err = rp2040_call_rom_func(bank->target, priv->stacktop, FUNC_FLASH_EXIT_XIP, NULL, 0); | |
+ if (err != ERROR_OK) | |
+ { | |
+ LOG_ERROR("RP2040 exit xip: failed to exit flash XIP mode"); | |
+ return err; | |
+ } | |
+ | |
+ return err; | |
+} | |
+ | |
+static int rp2040_flash_enter_xip(struct flash_bank *bank) | |
+{ | |
+ struct rp2040_flash_bank *priv = bank->driver_priv; | |
+ | |
+ // Always flush before returning to execute-in-place, to invalidate stale cache contents. | |
+ // The flush call also restores regular hardware-controlled chip select following a rp2040_flash_exit_xip(). | |
+ LOG_DEBUG("Flushing flash cache after write behind"); | |
+ int err = rp2040_call_rom_func(bank->target, priv->stacktop, FUNC_FLASH_FLUSH_CACHE, NULL, 0); | |
+ if (err != ERROR_OK) | |
+ { | |
+ LOG_ERROR("RP2040 enter xip: failed to flush flash cache"); | |
+ return err; | |
+ } | |
+ | |
+ LOG_DEBUG("Configuring SSI for execute-in-place"); | |
+ err = rp2040_call_rom_func(bank->target, priv->stacktop, FUNC_FLASH_ENTER_CMD_XIP, NULL, 0); | |
+ if (err != ERROR_OK) | |
+ { | |
+ LOG_ERROR("RP2040 enter xip: failed to enter flash XIP mode"); | |
+ } | |
+ return err; | |
+} | |
+ | |
+static int rp2040_flash_write(struct flash_bank *bank, const uint8_t *buffer, uint32_t offset, uint32_t count) | |
+{ | |
+ LOG_INFO("Writing %d bytes starting at 0x%x", count, offset); | |
+ | |
+ // todo fixme hard coded chunk size | |
+ struct rp2040_flash_bank *priv = bank->driver_priv; | |
+ const unsigned int chunk_size = 16 * 1024; | |
+ struct target *target = bank->target; | |
+ struct working_area *bounce; | |
+ int err = ERROR_OK; | |
+ | |
+ if (offset % PAGE_SIZE) { | |
+ LOG_ERROR("RP2040 flash writes must be page-aligned (%d bytes). Can't continue", PAGE_SIZE); | |
+ return ERROR_TARGET_UNALIGNED_ACCESS; | |
+ } | |
+ | |
+ err = rp2040_flash_exit_xip(bank); | |
+ if (err != ERROR_OK) | |
+ { | |
+ return err; | |
+ } | |
+ | |
+ if (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; | |
+ } | |
+ | |
+ LOG_DEBUG("Allocated flash bounce buffer @%08x", (unsigned)bounce->address); | |
+ | |
+ while (count > 0) | |
+ { | |
+ uint32_t write_size = count > chunk_size ? chunk_size : count; | |
+ LOG_DEBUG("Writing %d bytes to offset %08x", write_size, offset); | |
+ err = target_write_buffer(target, bounce->address, write_size, buffer); | |
+ if (err != ERROR_OK) { | |
+ LOG_ERROR("Could not load data into target bounce buffer"); | |
+ break; | |
+ } | |
+ uint32_t args[3] = { | |
+ offset, | |
+ bounce->address, | |
+ write_size | |
+ }; | |
+ err = rp2040_call_rom_func(target, priv->stacktop, FUNC_FLASH_RANGE_PROGRAM, args, 3); | |
+ if (err != ERROR_OK) { | |
+ LOG_ERROR("Failed to invoke flash programming code on target"); | |
+ break; | |
+ } | |
+ | |
+ buffer += write_size; | |
+ offset += write_size; | |
+ count -= write_size; | |
+ } | |
+ target_free_working_area(target, bounce); | |
+ | |
+ if (err != ERROR_OK) | |
+ return err; | |
+ | |
+ // 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->stacktop, FUNC_FLASH_FLUSH_CACHE, NULL, 0); | |
+ if (err != ERROR_OK) | |
+ { | |
+ LOG_ERROR("RP2040 write: failed to flush flash cache"); | |
+ return err; | |
+ } | |
+ | |
+ err = rp2040_flash_enter_xip(bank); | |
+ | |
+ return err; | |
+} | |
+ | |
+static int rp2040_flash_erase(struct flash_bank *bank, unsigned int first, unsigned int last) | |
+{ | |
+ struct rp2040_flash_bank *priv = bank->driver_priv; | |
+ uint32_t start_addr = bank->sectors[first].offset; | |
+ uint32_t length = bank->sectors[last].offset + bank->sectors[last].size - start_addr; | |
+ int err = ERROR_OK; | |
+ | |
+ LOG_DEBUG("RP2040 erase %d bytes starting at 0x%08x", length, start_addr); | |
+ | |
+ err = rp2040_flash_exit_xip(bank); | |
+ if (err != ERROR_OK) | |
+ { | |
+ return err; | |
+ } | |
+ | |
+ LOG_DEBUG("Remote call flash_range_erase"); | |
+ | |
+ // Erase in naturally-aligned chunks of n sectors per call. Get speed of | |
+ // block erases, without timeout on large erase ranges: | |
+ const unsigned int erase_sectors_per_call = 4 * BLOCK_SIZE / SECTOR_SIZE; | |
+ unsigned int first_of_call, last_of_call; | |
+ for (first_of_call = first; first_of_call <= last; first_of_call = last_of_call + 1) | |
+ { | |
+ // Try to keep our erase calls block-aligned, to avoid degeneration to | |
+ // sector erase at start and end of each call (RP2040ch slower) | |
+ last_of_call = first_of_call + erase_sectors_per_call; | |
+ last_of_call -= (last_of_call % erase_sectors_per_call) + 1; | |
+ if (last_of_call > last) | |
+ last_of_call = last; | |
+ uint32_t args[4] = { | |
+ bank->sectors[first_of_call].offset, | |
+ bank->sectors[last_of_call].offset + bank->sectors[last_of_call].size - bank->sectors[first_of_call].offset, | |
+ BLOCK_SIZE, | |
+ BLOCK_ERASE_CMD | |
+ }; | |
+ err = rp2040_call_rom_func(bank->target, priv->stacktop, FUNC_FLASH_RANGE_ERASE, args, 4); | |
+ if (err != ERROR_OK) | |
+ { | |
+ LOG_ERROR("RP2040 erase: flash_range_erase failed"); | |
+ break; | |
+ } | |
+ } | |
+ | |
+ err = rp2040_flash_enter_xip(bank); | |
+ | |
+ return err; | |
+} | |
+ | |
+static int rp2040_flash_protect_check(struct flash_bank *bank) | |
+{ | |
+ LOG_WARNING("RP2040 Flash Protect Check (ignored)"); | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int rp2040_flash_protect(struct flash_bank *bank, int set, unsigned int first, unsigned int last) | |
+{ | |
+ LOG_WARNING("RP2040 Flash Protect (ignored)"); | |
+ return ERROR_OK; | |
+} | |
+ | |
+// ----------------------------------------------------------------------------- | |
+// Driver probing etc | |
+ | |
+static int rp2040_flash_probe(struct flash_bank *bank) | |
+{ | |
+ struct rp2040_flash_bank *flash_info = bank->driver_priv; | |
+ | |
+ bank->num_sectors = bank->size / SECTOR_SIZE; | |
+ LOG_INFO("RP2040 B0 Flash Probe: %d bytes @%08x, in %d sectors\n", bank->size, (uint32_t)bank->base, bank->num_sectors); | |
+ bank->sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors); | |
+ | |
+ for (unsigned int i = 0; i < bank->num_sectors; i++) { | |
+ bank->sectors[i].offset = i * SECTOR_SIZE; | |
+ bank->sectors[i].size = SECTOR_SIZE; | |
+ bank->sectors[i].is_erased = -1; | |
+ bank->sectors[i].is_protected = 1; | |
+ } | |
+ | |
+ // target_alloc_working_area always allocates RP2040ltiple of 4 bytes, so no worry about alignment | |
+ const int STACK_SIZE = 256; | |
+ if (target_alloc_working_area(bank->target, STACK_SIZE, &flash_info->stack) != ERROR_OK) { | |
+ LOG_ERROR("Could not allocate stack for flash programming code"); | |
+ return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; | |
+ } | |
+ flash_info->stacktop = flash_info->stack->address + flash_info->stack->size; | |
+ LOG_DEBUG("Allocated flash algorithm stack @%08x size %d bytes", | |
+ (uint32_t)flash_info->stack->address, | |
+ flash_info->stack->size); | |
+ | |
+ flash_info->probed = 1; | |
+ | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int rp2040_flash_auto_probe(struct flash_bank *bank) | |
+{ | |
+ struct rp2040_flash_bank *flash_info = bank->driver_priv; | |
+ | |
+ if (flash_info->probed) | |
+ return ERROR_OK; | |
+ | |
+ return rp2040_flash_probe(bank); | |
+} | |
+ | |
+static int rp2040_flash_info(struct flash_bank *bank, char *buf, int buf_size) | |
+{ | |
+ LOG_INFO("RP2040 Flash Info"); | |
+ return ERROR_OK; | |
+} | |
+ | |
+// ----------------------------------------------------------------------------- | |
+// Driver boilerplate | |
+ | |
+FLASH_BANK_COMMAND_HANDLER(rp2040_flash_bank_command) | |
+{ | |
+ LOG_INFO("RP2040 Flash Bank Command"); | |
+ struct rp2040_flash_bank *flash_info; | |
+ | |
+ flash_info = malloc(sizeof(struct rp2040_flash_bank)); | |
+ flash_info->probed = 0; | |
+ | |
+ // Set up bank info | |
+ bank->driver_priv = flash_info; | |
+ | |
+ return ERROR_OK; | |
+} | |
+ | |
+struct flash_driver rp2040_flash = { | |
+ .name = "rp2040_flash", | |
+ .commands = NULL, | |
+ .flash_bank_command = rp2040_flash_bank_command, | |
+ .erase = rp2040_flash_erase, | |
+ .protect = rp2040_flash_protect, | |
+ .write = rp2040_flash_write, | |
+ .read = default_flash_read, | |
+ .probe = rp2040_flash_probe, | |
+ .auto_probe = rp2040_flash_auto_probe, | |
+ .erase_check = default_flash_blank_check, | |
+ .protect_check = rp2040_flash_protect_check, | |
+ .info = rp2040_flash_info, | |
+ .free_driver_priv = default_flash_free_driver_priv // FIXME free working areas etc | |
+}; | |
diff --git a/src/helper/log.h b/src/helper/log.h | |
index f2ba0daa6..93b6cddce 100644 | |
--- a/src/helper/log.h | |
+++ b/src/helper/log.h | |
@@ -155,6 +155,10 @@ extern int debug_level; | |
/* ERROR_TIMEOUT is already taken by winerror.h. */ | |
#define ERROR_TIMEOUT_REACHED (-6) | |
#define ERROR_NOT_IMPLEMENTED (-7) | |
+/* | |
+ * there was a sticky error, but it has been cleared. safe to retry | |
+ */ | |
+#define ERROR_STICKY_CLEARED (-8) | |
#endif /* OPENOCD_HELPER_LOG_H */ | |
diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am | |
index f7a54b003..a11b76a58 100644 | |
--- a/src/jtag/drivers/Makefile.am | |
+++ b/src/jtag/drivers/Makefile.am | |
@@ -186,6 +186,9 @@ endif | |
if KITPROG | |
DRIVERFILES += %D%/kitprog.c | |
endif | |
+if PICOPROBE | |
+DRIVERFILES += %D%/picoprobe.c | |
+endif | |
if XDS110 | |
DRIVERFILES += %D%/xds110.c | |
endif | |
diff --git a/src/jtag/drivers/bcm2835gpio.c b/src/jtag/drivers/bcm2835gpio.c | |
index 40cb5aa0b..9116e131e 100644 | |
--- a/src/jtag/drivers/bcm2835gpio.c | |
+++ b/src/jtag/drivers/bcm2835gpio.c | |
@@ -115,10 +115,15 @@ static int bcm2835gpio_swd_write(int swclk, int swdio) | |
{ | |
uint32_t set = swclk << swclk_gpio | swdio << swdio_gpio; | |
uint32_t clear = !swclk << swclk_gpio | !swdio << swdio_gpio; | |
- | |
+ int temp; | |
+ | |
GPIO_SET = set; | |
GPIO_CLR = clear; | |
+ do { | |
+ temp = GPIO_LEV & ( 1 << swclk_gpio ); | |
+ } while ( temp != swclk << swclk_gpio ); | |
+ | |
for (unsigned int i = 0; i < jtag_delay; i++) | |
asm volatile (""); | |
diff --git a/src/jtag/drivers/bitbang.c b/src/jtag/drivers/bitbang.c | |
index df1d601b8..25d53c75a 100644 | |
--- a/src/jtag/drivers/bitbang.c | |
+++ b/src/jtag/drivers/bitbang.c | |
@@ -427,6 +427,8 @@ static int bitbang_swd_switch_seq(enum swd_special_seq seq) | |
switch (seq) { | |
case LINE_RESET: | |
+ LOG_DEBUG_IO("SWD line reset"); | |
+ bitbang_swd_exchange(false, (uint8_t *)swd_seq_line_reset, 0, swd_seq_line_reset_len); | |
LOG_DEBUG("SWD line reset"); | |
bitbang_swd_exchange(false, (uint8_t *)swd_seq_line_reset, 0, swd_seq_line_reset_len); | |
break; | |
@@ -438,6 +440,14 @@ static int bitbang_swd_switch_seq(enum swd_special_seq seq) | |
LOG_DEBUG("SWD-to-JTAG"); | |
bitbang_swd_exchange(false, (uint8_t *)swd_seq_swd_to_jtag, 0, swd_seq_swd_to_jtag_len); | |
break; | |
+ case DORMANT_TO_SWD: | |
+ LOG_DEBUG("DORMANT-to-SWD"); | |
+ bitbang_swd_exchange(false, (uint8_t *)swd_seq_dormant_to_swd, 0, swd_seq_dormant_to_swd_len); | |
+ break; | |
+ case SWD_TO_DORMANT: | |
+ LOG_DEBUG("SWD-to-DORMANT"); | |
+ bitbang_swd_exchange(false, (uint8_t *) swd_seq_swd_to_dormant, 0, swd_seq_swd_to_dormant_len); | |
+ break; | |
default: | |
LOG_ERROR("Sequence %d not supported", seq); | |
return ERROR_FAIL; | |
@@ -531,6 +541,11 @@ static void bitbang_swd_write_reg(uint8_t cmd, uint32_t value, uint32_t ap_delay | |
bitbang_interface->swdio_drive(false); | |
bitbang_swd_exchange(true, trn_ack_data_parity_trn, 0, 1 + 3 + 1); | |
+ if (0 == ((cmd ^ swd_cmd(false, false, DP_TARGETSEL)) & | |
+ (SWD_CMD_APnDP|SWD_CMD_RnW|SWD_CMD_A32))) { | |
+ /* Targetsel has no ack so force it */ | |
+ buf_set_u32(trn_ack_data_parity_trn, 1, 3, SWD_ACK_OK); | |
+ } | |
bitbang_interface->swdio_drive(true); | |
bitbang_swd_exchange(false, trn_ack_data_parity_trn, 1 + 3 + 1, 32 + 1); | |
diff --git a/src/jtag/drivers/cmsis_dap.c b/src/jtag/drivers/cmsis_dap.c | |
index 16480ae1e..f49eeb0d7 100644 | |
--- a/src/jtag/drivers/cmsis_dap.c | |
+++ b/src/jtag/drivers/cmsis_dap.c | |
@@ -136,6 +136,7 @@ static bool swd_mode; | |
/* CMSIS-DAP SWD Commands */ | |
#define CMD_DAP_SWD_CONFIGURE 0x13 | |
+#define CMD_DAP_SWD_SEQUENCE 0x1D | |
/* CMSIS-DAP JTAG Commands */ | |
#define CMD_DAP_JTAG_SEQ 0x14 | |
@@ -538,6 +539,47 @@ static int cmsis_dap_cmd_DAP_Delay(uint16_t delay_us) | |
} | |
#endif | |
+static int cmsis_dap_metacmd_targetsel(uint32_t instance_id) | |
+{ | |
+ int retval; | |
+ uint8_t *buffer = cmsis_dap_handle->packet_buffer; | |
+ const uint32_t SEQ_RD = 0x80, SEQ_WR = 0x00; | |
+ | |
+ /* SWD multi-drop requires a transfer ala CMD_DAP_TFER, | |
+ but with no expectation of an SWD ACK response. In | |
+ CMSIS-DAP v1.20 and v2.00, CMD_DAP_SWD_SEQUENCE was | |
+ added to allow this special sequence to be generated. | |
+ The purpose of this operation is to select the target | |
+ corresponding to the instance_id that is written */ | |
+ | |
+ int idx = 0; | |
+ buffer[idx++] = 0; /* report number */ | |
+ buffer[idx++] = CMD_DAP_SWD_SEQUENCE; | |
+ buffer[idx++] = 3; /* sequence count */ | |
+ | |
+ /* sequence 0: packet request for TARGETSEL */ | |
+ buffer[idx++] = SEQ_WR | 8; | |
+ buffer[idx++] = 0x99; | |
+ /* sequence 1: no expectation for target to ACK */ | |
+ buffer[idx++] = SEQ_RD | 5; | |
+ /* sequence 2: WDATA plus parity */ | |
+ buffer[idx++] = SEQ_WR | 33; | |
+ buffer[idx++] = (uint8_t)(instance_id >> 0); | |
+ buffer[idx++] = (uint8_t)(instance_id >> 8); | |
+ buffer[idx++] = (uint8_t)(instance_id >> 16); | |
+ buffer[idx++] = (uint8_t)(instance_id >> 24); | |
+ buffer[idx++] = parity_u32(instance_id); | |
+ | |
+ retval = cmsis_dap_xfer(cmsis_dap_handle, idx); | |
+ | |
+ if (retval != ERROR_OK || buffer[1] != DAP_OK) { | |
+ LOG_ERROR("CMSIS-DAP command CMD_SWD_Configure failed."); | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ } | |
+ | |
+ return ERROR_OK; | |
+} | |
+ | |
static void cmsis_dap_swd_write_from_queue(struct cmsis_dap *dap) | |
{ | |
uint8_t *buffer = dap->packet_buffer; | |
@@ -704,7 +746,9 @@ static int cmsis_dap_swd_run_queue(void) | |
static void cmsis_dap_swd_queue_cmd(uint8_t cmd, uint32_t *dst, uint32_t data) | |
{ | |
- if (pending_fifo[pending_fifo_put_idx].transfer_count == pending_queue_len) { | |
+ bool targetsel_cmd = swd_cmd(false, false, DP_TARGETSEL) == cmd; | |
+ | |
+ if ((pending_fifo[pending_fifo_put_idx].transfer_count == pending_queue_len) || targetsel_cmd) { | |
if (pending_fifo_block_count) | |
cmsis_dap_swd_read_process(cmsis_dap_handle, 0); | |
@@ -718,6 +762,11 @@ static void cmsis_dap_swd_queue_cmd(uint8_t cmd, uint32_t *dst, uint32_t data) | |
if (queued_retval != ERROR_OK) | |
return; | |
+ if (targetsel_cmd) { | |
+ cmsis_dap_metacmd_targetsel(data); | |
+ return; | |
+ } | |
+ | |
struct pending_request_block *block = &pending_fifo[pending_fifo_put_idx]; | |
struct pending_transfer_result *transfer = &(block->transfers[block->transfer_count]); | |
transfer->data = data; | |
@@ -849,6 +898,16 @@ static int cmsis_dap_swd_switch_seq(enum swd_special_seq seq) | |
s = swd_seq_swd_to_jtag; | |
s_len = swd_seq_swd_to_jtag_len; | |
break; | |
+ case DORMANT_TO_SWD: | |
+ LOG_DEBUG("DORMANT-to-SWD"); | |
+ s = swd_seq_dormant_to_swd; | |
+ s_len = swd_seq_dormant_to_swd_len; | |
+ break; | |
+ case SWD_TO_DORMANT: | |
+ LOG_DEBUG("SWD-to-DORMANT"); | |
+ s = swd_seq_swd_to_dormant; | |
+ s_len = swd_seq_swd_to_dormant_len; | |
+ break; | |
default: | |
LOG_ERROR("Sequence %d not supported", seq); | |
return ERROR_FAIL; | |
diff --git a/src/jtag/drivers/ftdi.c b/src/jtag/drivers/ftdi.c | |
index 9e47d3cad..0b31cc855 100644 | |
--- a/src/jtag/drivers/ftdi.c | |
+++ b/src/jtag/drivers/ftdi.c | |
@@ -1092,6 +1092,12 @@ static int ftdi_swd_run_queue(void) | |
} | |
for (size_t i = 0; i < swd_cmd_queue_length; i++) { | |
+ if (0 == ((swd_cmd_queue[i].cmd ^ swd_cmd(false, false, DP_TARGETSEL)) & | |
+ (SWD_CMD_APnDP|SWD_CMD_RnW|SWD_CMD_A32))) { | |
+ /* Targetsel has no ack so force it */ | |
+ buf_set_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1, 3, SWD_ACK_OK); | |
+ } | |
+ | |
int ack = buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1, 3); | |
LOG_DEBUG_IO("%s %s %s reg %X = %08"PRIx32, | |
@@ -1122,6 +1128,10 @@ static int ftdi_swd_run_queue(void) | |
} | |
skip: | |
+ /* Defensive cleanup - seems like a bad idea to have potentially stale pointers sticking around */ | |
+ for (size_t i = 0; i < swd_cmd_queue_length; i++) | |
+ swd_cmd_queue[i].dst = NULL; | |
+ | |
swd_cmd_queue_length = 0; | |
retval = queued_retval; | |
queued_retval = ERROR_OK; | |
@@ -1202,7 +1212,7 @@ static int ftdi_swd_switch_seq(enum swd_special_seq seq) | |
{ | |
switch (seq) { | |
case LINE_RESET: | |
- LOG_DEBUG("SWD line reset"); | |
+ LOG_DEBUG_IO("SWD line reset"); | |
ftdi_swd_swdio_en(true); | |
mpsse_clock_data_out(mpsse_ctx, swd_seq_line_reset, 0, swd_seq_line_reset_len, SWD_MODE); | |
break; | |
@@ -1216,6 +1226,16 @@ static int ftdi_swd_switch_seq(enum swd_special_seq seq) | |
ftdi_swd_swdio_en(true); | |
mpsse_clock_data_out(mpsse_ctx, swd_seq_swd_to_jtag, 0, swd_seq_swd_to_jtag_len, SWD_MODE); | |
break; | |
+ case DORMANT_TO_SWD: | |
+ LOG_DEBUG("DORMANT-to-SWD"); | |
+ ftdi_swd_swdio_en(true); | |
+ mpsse_clock_data_out(mpsse_ctx, swd_seq_dormant_to_swd, 0, swd_seq_dormant_to_swd_len, SWD_MODE); | |
+ break; | |
+ case SWD_TO_DORMANT: | |
+ LOG_DEBUG("SWD-to-DORMANT"); | |
+ ftdi_swd_swdio_en(true); | |
+ mpsse_clock_data_out(mpsse_ctx, swd_seq_swd_to_dormant, 0, swd_seq_swd_to_dormant_len, SWD_MODE); | |
+ break; | |
default: | |
LOG_ERROR("Sequence %d not supported", seq); | |
return ERROR_FAIL; | |
diff --git a/src/jtag/drivers/jlink.c b/src/jtag/drivers/jlink.c | |
index 15d252cfb..22ef72635 100644 | |
--- a/src/jtag/drivers/jlink.c | |
+++ b/src/jtag/drivers/jlink.c | |
@@ -2008,6 +2008,8 @@ struct pending_scan_result { | |
void *buffer; | |
/** Offset in the destination buffer */ | |
unsigned buffer_offset; | |
+ /** true if the command has nmo acknowledgement */ | |
+ bool no_ack; | |
}; | |
#define MAX_PENDING_SCAN_RESULTS 256 | |
@@ -2155,6 +2157,16 @@ static int jlink_swd_switch_seq(enum swd_special_seq seq) | |
s = swd_seq_swd_to_jtag; | |
s_len = swd_seq_swd_to_jtag_len; | |
break; | |
+ case DORMANT_TO_SWD: | |
+ LOG_DEBUG("DORMANT-to-SWD"); | |
+ s = swd_seq_dormant_to_swd; | |
+ s_len = swd_seq_dormant_to_swd_len; | |
+ break; | |
+ case SWD_TO_DORMANT: | |
+ LOG_DEBUG("SWD-to-DORMANT"); | |
+ s = swd_seq_swd_to_dormant; | |
+ s_len = swd_seq_swd_to_dormant_len; | |
+ break; | |
default: | |
LOG_ERROR("Sequence %d not supported.", seq); | |
return ERROR_FAIL; | |
@@ -2191,7 +2203,9 @@ static int jlink_swd_run_queue(void) | |
} | |
for (i = 0; i < pending_scan_results_length; i++) { | |
- int ack = buf_get_u32(tdo_buffer, pending_scan_results_buffer[i].first, 3); | |
+ int ack = pending_scan_results_buffer[i].no_ack ? | |
+ SWD_ACK_OK : | |
+ buf_get_u32(tdo_buffer, pending_scan_results_buffer[i].first, 3); | |
if (ack != SWD_ACK_OK) { | |
LOG_DEBUG("SWD ack not OK: %d %s", ack, | |
@@ -2255,6 +2269,9 @@ static void jlink_swd_queue_cmd(uint8_t cmd, uint32_t *dst, uint32_t data, uint3 | |
jlink_queue_data_out(data_parity_trn, 32 + 1); | |
} | |
+ pending_scan_results_buffer[pending_scan_results_length].no_ack = | |
+ (0 == ((cmd ^ swd_cmd(false, false, DP_TARGETSEL)) & | |
+ (SWD_CMD_APnDP|SWD_CMD_RnW|SWD_CMD_A32))); | |
pending_scan_results_length++; | |
diff --git a/src/jtag/drivers/picoprobe.c b/src/jtag/drivers/picoprobe.c | |
new file mode 100644 | |
index 000000000..fc73b1ee3 | |
--- /dev/null | |
+++ b/src/jtag/drivers/picoprobe.c | |
@@ -0,0 +1,595 @@ | |
+/*************************************************************************** | |
+ * Copyright (C) 2020 by Liam Fraser * | |
+ * liam@raspberrypi.com * | |
+ * * | |
+ * Based on: kitprog.c, ftdi.c, mpsse.c * | |
+ * * | |
+ * This program is free software; you can redistribute it and/or modify * | |
+ * it under the terms of the GNU General Public License as published by * | |
+ * the Free Software Foundation; either version 2 of the License, or * | |
+ * (at your option) any later version. * | |
+ * * | |
+ * This program is distributed in the hope that it will be useful, * | |
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
+ * GNU General Public License for more details. * | |
+ * * | |
+ * You should have received a copy of the GNU General Public License * | |
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. * | |
+ ***************************************************************************/ | |
+ | |
+#ifdef HAVE_CONFIG_H | |
+#include "config.h" | |
+#endif | |
+ | |
+#include <jtag/interface.h> | |
+#include <jtag/swd.h> | |
+#include <jtag/commands.h> | |
+#include <helper/command.h> | |
+ | |
+#include "libusb_helper.h" | |
+ | |
+#define VID 0x2E8A /* Raspberry Pi */ | |
+#define PID 0x0004 /* Picoprobe */ | |
+ | |
+#define BULK_EP_OUT 4 | |
+#define BULK_EP_IN 5 | |
+#define PICOPROBE_INTERFACE 2 | |
+ | |
+#define PICOPROBE_MAX_PACKET_LENGTH 512 | |
+#define LIBUSB_TIMEOUT 10000 | |
+ | |
+struct picoprobe { | |
+ struct libusb_device_handle *usb_handle; | |
+ uint8_t *packet_buffer; | |
+ int freq; | |
+}; | |
+ | |
+static struct swd_cmd_queue_entry { | |
+ uint8_t cmd; | |
+ uint32_t *dst; | |
+ uint8_t trn_ack_data_parity_trn[DIV_ROUND_UP(4 + 3 + 32 + 1 + 4, 8)]; | |
+} *swd_cmd_queue; | |
+static size_t swd_cmd_queue_length; | |
+static size_t swd_cmd_queue_alloced; | |
+static int queued_retval; | |
+ | |
+static struct picoprobe *picoprobe_handle; | |
+ | |
+static int picoprobe_init(void); | |
+static int picoprobe_quit(void); | |
+ | |
+enum PROBE_CMDS { | |
+ PROBE_INVALID = 0, /* Invalid */ | |
+ PROBE_WRITE_BITS = 1, /* Host wants us to write bits */ | |
+ PROBE_READ_BITS = 2, /* Host wants us to read bits */ | |
+ PROBE_SET_FREQ = 3, /* Set TCK freq */ | |
+ PROBE_RESET = 4 | |
+}; | |
+ | |
+struct __attribute__((__packed__)) probe_cmd_hdr { | |
+ uint8_t id; | |
+ uint8_t cmd; | |
+ uint32_t bits; | |
+}; | |
+ | |
+struct __attribute__((__packed__)) probe_pkt_hdr { | |
+ uint32_t total_packet_length; | |
+}; | |
+ | |
+/* Separate queue to swd_cmd_queue because we sometimes insert idle cycles not described | |
+ * there */ | |
+#define PICOPROBE_QUEUE_SIZE 64 | |
+static struct picoprobe_queue_entry { | |
+ uint8_t id; | |
+ uint8_t cmd; /* PROBE_CMDS */ | |
+ unsigned bits; | |
+ unsigned offset; | |
+ const uint8_t *buf; | |
+} *picoprobe_queue; | |
+static size_t picoprobe_queue_length; | |
+static size_t picoprobe_queue_alloced; | |
+ | |
+static inline unsigned packet_length(uint8_t *pkt) | |
+{ | |
+ return pkt - picoprobe_handle->packet_buffer; | |
+} | |
+ | |
+static int picoprobe_bulk_write(struct probe_pkt_hdr *pkt_hdr, uint8_t *pkt) | |
+{ | |
+ pkt_hdr->total_packet_length = packet_length(pkt); | |
+ assert(pkt_hdr->total_packet_length <= PICOPROBE_MAX_PACKET_LENGTH); | |
+ int ret = 0; | |
+ jtag_libusb_bulk_write(picoprobe_handle->usb_handle, | |
+ BULK_EP_OUT, (char *)picoprobe_handle->packet_buffer, packet_length(pkt), LIBUSB_TIMEOUT, | |
+ &ret); | |
+ if (ret < 0) | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int picoprobe_flush(void) | |
+{ | |
+ LOG_DEBUG_IO("Flush %d transactions", (int)picoprobe_queue_length); | |
+ int ret = ERROR_OK; | |
+ | |
+ struct probe_pkt_hdr *pkt_hdr = (struct probe_pkt_hdr *)picoprobe_handle->packet_buffer; | |
+ | |
+ /* Chain pending write and read commands together */ | |
+ uint8_t *pkt = picoprobe_handle->packet_buffer + sizeof(struct probe_pkt_hdr); | |
+ | |
+ unsigned total_reads = 0; | |
+ unsigned total_read_bytes = 0; | |
+ | |
+ for (unsigned i = 0; i < picoprobe_queue_length; i++) { | |
+ /* Copy header regardless of read or write */ | |
+ struct picoprobe_queue_entry *q = &picoprobe_queue[i]; | |
+ struct probe_cmd_hdr *hdr = (struct probe_cmd_hdr *)pkt; | |
+ if (q->id != i) { | |
+ LOG_ERROR("Wrong queue id. q->id %d != %d", q->id, i); | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ } | |
+ hdr->id = q->id; | |
+ hdr->cmd = q->cmd; | |
+ hdr->bits = q->bits; | |
+ pkt += sizeof(struct probe_cmd_hdr); | |
+ unsigned length_bytes = DIV_ROUND_UP(q->bits, 8); | |
+ | |
+ if (q->cmd == PROBE_WRITE_BITS) { | |
+ /* Copy the data to write into the packet buffer */ | |
+ if (q->buf) { | |
+ bit_copy(pkt, 0, q->buf, q->offset, q->bits); | |
+ } else { | |
+ /* Make sure the packet buffer is zerod to clock zeros */ | |
+ assert(q->offset == 0); | |
+ memset(pkt, 0, length_bytes); | |
+ } | |
+ pkt += length_bytes; | |
+ } else if (q->cmd == PROBE_READ_BITS) { | |
+ /* Nothing to do for a read as we have already copied the header | |
+ * Will process the data later in one go */ | |
+ total_reads++; | |
+ total_read_bytes += length_bytes; | |
+ } else { | |
+ /* Unexpected cmd to flush */ | |
+ return ERROR_FAIL; | |
+ } | |
+ } | |
+ | |
+ /* Send all read/write commands + write data */ | |
+ ret = picoprobe_bulk_write(pkt_hdr, pkt); | |
+ if (ret < 0) | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ | |
+ /* If no reads we can bail */ | |
+ if (total_reads == 0) { | |
+ picoprobe_queue_length = 0; | |
+ return ret; | |
+ } | |
+ | |
+ /* Now get any read responses */ | |
+ unsigned rx_pkt_len = sizeof(struct probe_pkt_hdr) + | |
+ (sizeof(struct probe_cmd_hdr) * total_reads) + | |
+ total_read_bytes; | |
+ jtag_libusb_bulk_read(picoprobe_handle->usb_handle, | |
+ BULK_EP_IN | LIBUSB_ENDPOINT_IN, (char *)picoprobe_handle->packet_buffer, | |
+ rx_pkt_len, LIBUSB_TIMEOUT, &ret); | |
+ | |
+ if (ret < 0) | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ | |
+ /* Now time to process the rx data */ | |
+ LOG_DEBUG_IO("Read %d bytes from probe", ret); | |
+ | |
+ /* If we didn't get length we expected */ | |
+ if ((int)rx_pkt_len != ret) | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ | |
+ struct probe_pkt_hdr *response_hdr = (struct probe_pkt_hdr *)picoprobe_handle->packet_buffer; | |
+ if (rx_pkt_len != response_hdr->total_packet_length) | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ | |
+ pkt = picoprobe_handle->packet_buffer + sizeof(struct probe_pkt_hdr); | |
+ | |
+ /* Now go through read responses */ | |
+ for (unsigned i = 0; i < total_reads; i++) { | |
+ struct probe_cmd_hdr *read_hdr = (struct probe_cmd_hdr *)pkt; | |
+ pkt += sizeof(struct probe_cmd_hdr); | |
+ unsigned read_bytes = DIV_ROUND_UP(read_hdr->bits, 8); | |
+ | |
+ if (read_hdr->cmd != PROBE_READ_BITS) | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ | |
+ uint8_t id = read_hdr->id; | |
+ struct picoprobe_queue_entry *q = &picoprobe_queue[id]; | |
+ assert(read_hdr->cmd == q->cmd); | |
+ assert(read_hdr->id == q->id); | |
+ assert(read_hdr->bits == q->bits); | |
+ LOG_DEBUG_IO("Processing read of %d bits", read_hdr->bits); | |
+ | |
+ /* Copy data back to swd cmd queue */ | |
+ memcpy((void *)q->buf, pkt, read_bytes); | |
+ pkt += read_bytes; | |
+ } | |
+ | |
+ unsigned processed_len = (pkt - picoprobe_handle->packet_buffer); | |
+ if (processed_len != rx_pkt_len) | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ | |
+ picoprobe_queue_length = 0; | |
+ | |
+ /* Keep gdb alive */ | |
+ keep_alive(); | |
+ | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int picoprobe_read_write_bits(const uint8_t *buf, unsigned offset, unsigned length, uint8_t cmd) | |
+{ | |
+ if (picoprobe_queue_length == picoprobe_queue_alloced) { | |
+ LOG_ERROR("Picoprobe queue full"); | |
+ return ERROR_BUF_TOO_SMALL; | |
+ } else { | |
+ LOG_DEBUG_IO("Picoprobe queue len %d -> %d", (int)picoprobe_queue_length, | |
+ (int)picoprobe_queue_length + 1); | |
+ } | |
+ | |
+ struct picoprobe_queue_entry *q = &picoprobe_queue[picoprobe_queue_length]; | |
+ q->id = picoprobe_queue_length++; | |
+ q->cmd = cmd; | |
+ q->bits = length; | |
+ q->offset = offset; | |
+ q->buf = buf; | |
+ | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int picoprobe_write_bits(const uint8_t *buf, unsigned offset, unsigned length) | |
+{ | |
+ LOG_DEBUG_IO("Write %d bits @ offset %d", length, offset); | |
+ return picoprobe_read_write_bits(buf, offset, length, PROBE_WRITE_BITS); | |
+} | |
+ | |
+static int picoprobe_read_bits(const uint8_t *buf, unsigned offset, unsigned length) | |
+{ | |
+ LOG_DEBUG_IO("Read %d bits @ offset %d", length, offset); | |
+ | |
+ if (picoprobe_queue_length == picoprobe_queue_alloced) | |
+ return ERROR_BUF_TOO_SMALL; | |
+ | |
+ return picoprobe_read_write_bits(buf, offset, length, PROBE_READ_BITS); | |
+} | |
+ | |
+static int picoprobe_swd_run_queue(void) | |
+{ | |
+ LOG_DEBUG_IO("Executing %zu queued transactions", swd_cmd_queue_length); | |
+ int retval; | |
+ | |
+ queued_retval = picoprobe_flush(); | |
+ | |
+ if (queued_retval != ERROR_OK) { | |
+ LOG_DEBUG_IO("Skipping due to previous errors: %d", queued_retval); | |
+ goto skip; | |
+ } | |
+ | |
+ for (size_t i = 0; i < swd_cmd_queue_length; i++) { | |
+ if (0 == ((swd_cmd_queue[i].cmd ^ swd_cmd(false, false, DP_TARGETSEL)) & | |
+ (SWD_CMD_APnDP|SWD_CMD_RnW|SWD_CMD_A32))) { | |
+ /* Targetsel has no ack so force it */ | |
+ buf_set_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1, 3, SWD_ACK_OK); | |
+ } | |
+ | |
+ | |
+ LOG_DEBUG_IO("trn_ack_data_parity_trn:"); | |
+ for (size_t y = 0; y < sizeof(swd_cmd_queue[i].trn_ack_data_parity_trn); y++) | |
+ LOG_DEBUG_IO("BYTE %d 0x%x", (int)y, swd_cmd_queue[i].trn_ack_data_parity_trn[y]); | |
+ | |
+ int ack = buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1, 3); | |
+ | |
+ LOG_DEBUG_IO("%s %s %s reg %X = %08"PRIx32, | |
+ ack == SWD_ACK_OK ? "OK" : ack == SWD_ACK_WAIT ? "WAIT" : ack == SWD_ACK_FAULT ? "FAULT" : "JUNK", | |
+ swd_cmd_queue[i].cmd & SWD_CMD_APnDP ? "AP" : "DP", | |
+ swd_cmd_queue[i].cmd & SWD_CMD_RnW ? "read" : "write", | |
+ (swd_cmd_queue[i].cmd & SWD_CMD_A32) >> 1, | |
+ buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, | |
+ 1 + 3 + (swd_cmd_queue[i].cmd & SWD_CMD_RnW ? 0 : 1), 32)); | |
+ | |
+ if (ack != SWD_ACK_OK) { | |
+ queued_retval = ack == SWD_ACK_WAIT ? ERROR_WAIT : ERROR_FAIL; | |
+ goto skip; | |
+ | |
+ } else if (swd_cmd_queue[i].cmd & SWD_CMD_RnW) { | |
+ uint32_t data = buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1 + 3, 32); | |
+ int parity = buf_get_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1 + 3 + 32, 1); | |
+ | |
+ if (parity != parity_u32(data)) { | |
+ LOG_ERROR("SWD Read data parity mismatch"); | |
+ queued_retval = ERROR_FAIL; | |
+ goto skip; | |
+ } | |
+ | |
+ if (swd_cmd_queue[i].dst != NULL) | |
+ *swd_cmd_queue[i].dst = data; | |
+ } | |
+ } | |
+ | |
+skip: | |
+ /* Defensive cleanup - seems like a bad idea to have potentially stale pointers sticking around */ | |
+ for (size_t i = 0; i < swd_cmd_queue_length; i++) | |
+ swd_cmd_queue[i].dst = NULL; | |
+ | |
+ swd_cmd_queue_length = 0; | |
+ retval = queued_retval; | |
+ queued_retval = ERROR_OK; | |
+ | |
+ return retval; | |
+} | |
+ | |
+static void picoprobe_swd_queue_cmd(uint8_t cmd, uint32_t *dst, uint32_t data, uint32_t ap_delay_clk) | |
+{ | |
+ if (swd_cmd_queue_length == swd_cmd_queue_alloced) | |
+ queued_retval = picoprobe_swd_run_queue(); | |
+ | |
+ if (queued_retval != ERROR_OK) | |
+ return; | |
+ | |
+ size_t i = swd_cmd_queue_length++; | |
+ swd_cmd_queue[i].cmd = cmd | SWD_CMD_START | SWD_CMD_PARK; | |
+ | |
+ picoprobe_write_bits(&swd_cmd_queue[i].cmd, 0, 8); | |
+ | |
+ if (swd_cmd_queue[i].cmd & SWD_CMD_RnW) { | |
+ /* Queue a read transaction */ | |
+ swd_cmd_queue[i].dst = dst; | |
+ | |
+ picoprobe_read_bits(swd_cmd_queue[i].trn_ack_data_parity_trn, | |
+ 0, 1 + 3 + 32 + 1 + 1); | |
+ } else { | |
+ /* Queue a write transaction */ | |
+ picoprobe_read_bits(swd_cmd_queue[i].trn_ack_data_parity_trn, | |
+ 0, 1 + 3 + 1); | |
+ | |
+ buf_set_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1 + 3 + 1, 32, data); | |
+ buf_set_u32(swd_cmd_queue[i].trn_ack_data_parity_trn, 1 + 3 + 1 + 32, 1, parity_u32(data)); | |
+ | |
+ picoprobe_write_bits(swd_cmd_queue[i].trn_ack_data_parity_trn, | |
+ 1 + 3 + 1, 32 + 1); | |
+ } | |
+ | |
+ /* Insert idle cycles after AP accesses to avoid WAIT */ | |
+ if (cmd & SWD_CMD_APnDP) { | |
+ if (ap_delay_clk == 0) | |
+ return; | |
+ LOG_DEBUG("Add %d idle cycles", ap_delay_clk); | |
+ picoprobe_write_bits(NULL, 0, ap_delay_clk); | |
+ } | |
+ | |
+} | |
+ | |
+static void picoprobe_swd_read_reg(uint8_t cmd, uint32_t *value, uint32_t ap_delay_clk) | |
+{ | |
+ assert(cmd & SWD_CMD_RnW); | |
+ picoprobe_swd_queue_cmd(cmd, value, 0, ap_delay_clk); | |
+} | |
+ | |
+static void picoprobe_swd_write_reg(uint8_t cmd, uint32_t value, uint32_t ap_delay_clk) | |
+{ | |
+ assert(!(cmd & SWD_CMD_RnW)); | |
+ picoprobe_swd_queue_cmd(cmd, NULL, value, ap_delay_clk); | |
+} | |
+ | |
+static int_least32_t picoprobe_set_frequency(int_least32_t hz) | |
+{ | |
+ int ret; | |
+ struct probe_pkt_hdr *pkt_hdr = (struct probe_pkt_hdr *)picoprobe_handle->packet_buffer; | |
+ | |
+ /* Assert this is a standalone command with nothing else queued */ | |
+ assert(picoprobe_queue_length == 0); | |
+ | |
+ /* Chain writes and read commands together */ | |
+ uint8_t *pkt = picoprobe_handle->packet_buffer + sizeof(struct probe_pkt_hdr); | |
+ struct probe_cmd_hdr *hdr = (struct probe_cmd_hdr *)pkt; | |
+ hdr->id = 0; | |
+ hdr->cmd = PROBE_SET_FREQ; | |
+ hdr->bits = hz / 1000; | |
+ pkt += sizeof(struct probe_cmd_hdr); | |
+ | |
+ /* Send all read/write commands + write data */ | |
+ ret = picoprobe_bulk_write(pkt_hdr, pkt); | |
+ if (ret < 0) | |
+ return ERROR_JTAG_DEVICE_ERROR; | |
+ | |
+ return hz; | |
+} | |
+ | |
+static int_least32_t picoprobe_speed(int_least32_t hz) | |
+{ | |
+ int ret = picoprobe_set_frequency(hz); | |
+ | |
+ if (ret < 0) | |
+ LOG_ERROR("Couldn't set picoprobe speed"); | |
+ else | |
+ picoprobe_handle->freq = ret; | |
+ | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int picoprobe_khz(int khz, int *jtag_speed) | |
+{ | |
+ *jtag_speed = khz * 1000; | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int picoprobe_speed_div(int speed, int *khz) | |
+{ | |
+ *khz = speed / 1000; | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int picoprobe_swd_init(void) | |
+{ | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int picoprobe_swd_switch_seq(enum swd_special_seq seq) | |
+{ | |
+ int ret = ERROR_OK; | |
+ | |
+ switch (seq) { | |
+ case LINE_RESET: | |
+ LOG_DEBUG_IO("SWD line reset"); | |
+ ret = picoprobe_write_bits(swd_seq_line_reset, 0, swd_seq_line_reset_len); | |
+ break; | |
+ case JTAG_TO_SWD: | |
+ LOG_DEBUG("JTAG-to-SWD"); | |
+ ret = picoprobe_write_bits(swd_seq_jtag_to_swd, 0, swd_seq_jtag_to_swd_len); | |
+ break; | |
+ case SWD_TO_JTAG: | |
+ LOG_DEBUG("SWD-to-JTAG"); | |
+ ret = picoprobe_write_bits(swd_seq_swd_to_jtag, 0, swd_seq_swd_to_jtag_len); | |
+ break; | |
+ case DORMANT_TO_SWD: | |
+ LOG_DEBUG("DORMANT-to-SWD"); | |
+ ret = picoprobe_write_bits(swd_seq_dormant_to_swd, 0, swd_seq_dormant_to_swd_len); | |
+ break; | |
+ case SWD_TO_DORMANT: | |
+ LOG_DEBUG("SWD-to-DORMANT"); | |
+ ret = picoprobe_write_bits(swd_seq_swd_to_dormant, 0, swd_seq_swd_to_dormant_len); | |
+ break; | |
+ default: | |
+ LOG_ERROR("Sequence %d not supported", seq); | |
+ return ERROR_FAIL; | |
+ } | |
+ | |
+ return ret; | |
+} | |
+ | |
+static int picoprobe_reset(int trst, int srst) | |
+{ | |
+ return ERROR_OK; | |
+} | |
+ | |
+static const struct swd_driver picoprobe_swd = { | |
+ .init = picoprobe_swd_init, | |
+ .switch_seq = picoprobe_swd_switch_seq, | |
+ .read_reg = picoprobe_swd_read_reg, | |
+ .write_reg = picoprobe_swd_write_reg, | |
+ .run = picoprobe_swd_run_queue, | |
+}; | |
+ | |
+const char *probe_serial_number = NULL; | |
+ | |
+static COMMAND_HELPER(handle_serialnum_args, const char **serialNumber) | |
+{ | |
+ if (CMD_ARGC != 1) { | |
+ LOG_ERROR("%s: need single argument with serial number", CMD_NAME); | |
+ *serialNumber = NULL; | |
+ return ERROR_COMMAND_SYNTAX_ERROR; | |
+ } else { | |
+ *serialNumber = CMD_ARGV[0]; | |
+ return ERROR_OK; | |
+ } | |
+} | |
+ | |
+COMMAND_HANDLER(handle_serialnum_command) | |
+{ | |
+ const char *serialNumber = NULL; | |
+ int retval = CALL_COMMAND_HANDLER(handle_serialnum_args, &serialNumber); | |
+ if (ERROR_OK == retval) { | |
+ probe_serial_number = malloc(strlen(serialNumber) + 1); | |
+ if (probe_serial_number) { | |
+ strcpy((char *) probe_serial_number, (char *) serialNumber); | |
+ command_print(CMD, "Using serial number : %s", serialNumber); | |
+ } | |
+ } | |
+ return retval; | |
+} | |
+ | |
+static const struct command_registration serialnum_command_handlers[] = { | |
+ { | |
+ .name = "pico_serialnum", | |
+ .mode = COMMAND_ANY, | |
+ .handler = handle_serialnum_command, | |
+ .help = "use picoprobe with this serial number", | |
+ .usage = "'serial number'", | |
+ }, | |
+ COMMAND_REGISTRATION_DONE | |
+}; | |
+static const char * const picoprobe_transports[] = { "swd", NULL }; | |
+ | |
+struct adapter_driver picoprobe_adapter_driver = { | |
+ .name = "picoprobe", | |
+ .commands = serialnum_command_handlers, | |
+ .transports = picoprobe_transports, | |
+ .swd_ops = &picoprobe_swd, | |
+ .init = picoprobe_init, | |
+ .quit = picoprobe_quit, | |
+ .reset = picoprobe_reset, | |
+ .speed = picoprobe_speed, | |
+ .speed_div = picoprobe_speed_div, | |
+ .khz = picoprobe_khz, | |
+}; | |
+ | |
+static int picoprobe_usb_open(void) | |
+{ | |
+ const uint16_t vids[] = { VID, 0 }; | |
+ const uint16_t pids[] = { PID, 0 }; | |
+ | |
+ if (jtag_libusb_open(vids, pids, probe_serial_number, | |
+ &picoprobe_handle->usb_handle, NULL) != ERROR_OK) { | |
+ LOG_ERROR("Failed to open or find the device"); | |
+ return ERROR_FAIL; | |
+ } | |
+ | |
+ if (libusb_claim_interface(picoprobe_handle->usb_handle, PICOPROBE_INTERFACE) != ERROR_OK) { | |
+ LOG_ERROR("Failed to claim picoprobe interface"); | |
+ return ERROR_FAIL; | |
+ } | |
+ | |
+ return ERROR_OK; | |
+} | |
+ | |
+static void picoprobe_usb_close(void) | |
+{ | |
+ jtag_libusb_close(picoprobe_handle->usb_handle); | |
+} | |
+ | |
+static int picoprobe_init(void) | |
+{ | |
+ picoprobe_handle = malloc(sizeof(struct picoprobe)); | |
+ if (picoprobe_handle == NULL) { | |
+ LOG_ERROR("Failed to allocate memory"); | |
+ return ERROR_FAIL; | |
+ } | |
+ | |
+ if (picoprobe_usb_open() != ERROR_OK) { | |
+ LOG_ERROR("Can't find a picoprobe device! Please check device connections and permissions."); | |
+ return ERROR_JTAG_INIT_FAILED; | |
+ } | |
+ | |
+ /* Allocate packet buffers and queues */ | |
+ picoprobe_handle->packet_buffer = malloc(PICOPROBE_MAX_PACKET_LENGTH); | |
+ if (picoprobe_handle->packet_buffer == NULL) { | |
+ LOG_ERROR("Failed to allocate memory for the packet buffer"); | |
+ return ERROR_FAIL; | |
+ } | |
+ | |
+ picoprobe_queue_alloced = PICOPROBE_QUEUE_SIZE; | |
+ picoprobe_queue_length = 0; | |
+ picoprobe_queue = malloc(picoprobe_queue_alloced * sizeof(*picoprobe_queue)); | |
+ if (picoprobe_queue == NULL) | |
+ return ERROR_FAIL; | |
+ | |
+ swd_cmd_queue_alloced = 10; | |
+ swd_cmd_queue = malloc(swd_cmd_queue_alloced * sizeof(*swd_cmd_queue)); | |
+ | |
+ return swd_cmd_queue != NULL ? ERROR_OK : ERROR_FAIL; | |
+} | |
+ | |
+ | |
+static int picoprobe_quit(void) | |
+{ | |
+ picoprobe_usb_close(); | |
+ return ERROR_OK; | |
+} | |
diff --git a/src/jtag/interfaces.c b/src/jtag/interfaces.c | |
index 061a78f9c..3e1b0d0d7 100644 | |
--- a/src/jtag/interfaces.c | |
+++ b/src/jtag/interfaces.c | |
@@ -134,6 +134,9 @@ extern struct adapter_driver aice_adapter_driver; | |
#if BUILD_BCM2835GPIO == 1 | |
extern struct adapter_driver bcm2835gpio_adapter_driver; | |
#endif | |
+#if BUILD_PICOPROBE == 1 | |
+extern struct adapter_driver picoprobe_adapter_driver; | |
+#endif | |
#if BUILD_CMSIS_DAP_USB == 1 || BUILD_CMSIS_DAP_HID == 1 | |
extern struct adapter_driver cmsis_dap_adapter_driver; | |
#endif | |
@@ -254,6 +257,9 @@ struct adapter_driver *adapter_drivers[] = { | |
#if BUILD_BCM2835GPIO == 1 | |
&bcm2835gpio_adapter_driver, | |
#endif | |
+#if BUILD_PICOPROBE == 1 | |
+ &picoprobe_adapter_driver, | |
+#endif | |
#if BUILD_CMSIS_DAP_USB == 1 || BUILD_CMSIS_DAP_HID == 1 | |
&cmsis_dap_adapter_driver, | |
#endif | |
diff --git a/src/jtag/jtag.h b/src/jtag/jtag.h | |
index 2fa580223..20669c3e4 100644 | |
--- a/src/jtag/jtag.h | |
+++ b/src/jtag/jtag.h | |
@@ -152,6 +152,10 @@ struct jtag_tap { | |
struct jtag_tap_event_action *event_action; | |
+ /* Value to use with DP_TARGETSEL in SWD multidrop mode, otherwise | |
+ * DP_TARGETSEL_INVALID*/ | |
+ uint32_t multidrop_targetsel; | |
+ | |
struct jtag_tap *next_tap; | |
/* private pointer to support none-jtag specific functions */ | |
void *priv; | |
diff --git a/src/jtag/swd.h b/src/jtag/swd.h | |
index 487cb85bf..e499128ca 100644 | |
--- a/src/jtag/swd.h | |
+++ b/src/jtag/swd.h | |
@@ -277,5 +277,8 @@ struct swd_driver { | |
}; | |
int swd_init_reset(struct command_context *cmd_ctx); | |
+void swd_add_reset(int req_srst); | |
+int swd_create_transport_session(struct adiv5_dap *dap, const struct dap_ops **dap_ops, | |
+ void **transport_private); | |
#endif /* OPENOCD_JTAG_SWD_H */ | |
diff --git a/src/jtag/tcl.c b/src/jtag/tcl.c | |
index 2fa162e56..5d14f950d 100644 | |
--- a/src/jtag/tcl.c | |
+++ b/src/jtag/tcl.c | |
@@ -475,6 +475,9 @@ static int jim_newtap_expected_id(Jim_Nvp *n, Jim_GetOptInfo *goi, | |
#define NTAP_OPT_DISABLED 4 | |
#define NTAP_OPT_EXPECTED_ID 5 | |
#define NTAP_OPT_VERSION 6 | |
+#define NTAP_OPT_DP_ID 7 | |
+#define NTAP_OPT_INSTANCE_ID 8 | |
+ | |
static int jim_newtap_ir_param(Jim_Nvp *n, Jim_GetOptInfo *goi, | |
struct jtag_tap *pTap) | |
@@ -522,6 +525,38 @@ static int jim_newtap_ir_param(Jim_Nvp *n, Jim_GetOptInfo *goi, | |
return JIM_OK; | |
} | |
+static int jim_newtap_md_param(Jim_Nvp *n, Jim_GetOptInfo *goi, struct jtag_tap *pTap) | |
+{ | |
+ jim_wide w; | |
+ int e = Jim_GetOpt_Wide(goi, &w); | |
+ if (e != JIM_OK) { | |
+ Jim_SetResultFormatted(goi->interp, | |
+ "option: %s bad parameter", n->name); | |
+ return e; | |
+ } | |
+ switch (n->value) { | |
+ case NTAP_OPT_INSTANCE_ID: | |
+ if (w < 0 || w > 15) { | |
+ LOG_ERROR("%s: invalid multidrop instance-id %d", | |
+ pTap->dotted_name, (int) w); | |
+ return JIM_ERR; | |
+ } | |
+ pTap->multidrop_targetsel = (pTap->multidrop_targetsel & DP_TARGETSEL_DPID_MASK) | | |
+ (w << DP_TARGETSEL_INSTANCEID_SHIFT); | |
+ break; | |
+ case NTAP_OPT_DP_ID: | |
+ if (w < 0 || w > DP_TARGETSEL_DPID_MASK) { | |
+ LOG_ERROR("%s: invalid multidrop target-id %d", | |
+ pTap->dotted_name, (int) w); | |
+ } | |
+ pTap->multidrop_targetsel = (pTap->multidrop_targetsel & DP_TARGETSEL_INSTANCEID_MASK) | w; | |
+ break; | |
+ default: | |
+ return JIM_ERR; | |
+ } | |
+ return JIM_OK; | |
+} | |
+ | |
static int jim_newtap_cmd(Jim_GetOptInfo *goi) | |
{ | |
struct jtag_tap *pTap; | |
@@ -537,6 +572,8 @@ static int jim_newtap_cmd(Jim_GetOptInfo *goi) | |
{ .name = "-disable", .value = NTAP_OPT_DISABLED }, | |
{ .name = "-expected-id", .value = NTAP_OPT_EXPECTED_ID }, | |
{ .name = "-ignore-version", .value = NTAP_OPT_VERSION }, | |
+ { .name = "-dp-id", .value = NTAP_OPT_DP_ID }, | |
+ { .name = "-instance-id", .value = NTAP_OPT_INSTANCE_ID }, | |
{ .name = NULL, .value = -1 }, | |
}; | |
@@ -567,12 +604,57 @@ static int jim_newtap_cmd(Jim_GetOptInfo *goi) | |
cp = malloc(x); | |
sprintf(cp, "%s.%s", pTap->chip, pTap->tapname); | |
pTap->dotted_name = cp; | |
+ pTap->multidrop_targetsel = DP_TARGETSEL_INVALID; | |
LOG_DEBUG("Creating New Tap, Chip: %s, Tap: %s, Dotted: %s, %d params", | |
pTap->chip, pTap->tapname, pTap->dotted_name, goi->argc); | |
if (!transport_is_jtag()) { | |
- /* SWD doesn't require any JTAG tap parameters */ | |
+ bool target_id_specified = false; | |
+ bool instance_id_specified = false; | |
+ while (goi->argc) { | |
+ e = Jim_GetOpt_Nvp(goi, opts, &n); | |
+ if (e != JIM_OK) { | |
+ Jim_GetOpt_NvpUnknown(goi, opts, 0); | |
+ free(cp); | |
+ free(pTap); | |
+ return e; | |
+ } | |
+ LOG_DEBUG("Processing option: %s", n->name); | |
+ switch (n->value) { | |
+ case NTAP_OPT_DP_ID: | |
+ target_id_specified = true; | |
+ e = jim_newtap_md_param(n, goi, pTap); | |
+ break; | |
+ case NTAP_OPT_INSTANCE_ID: | |
+ instance_id_specified = true; | |
+ e = jim_newtap_md_param(n, goi, pTap); | |
+ break; | |
+ /* SWD doesn't require any JTAG tap parameters; we allow (but ignore) them */ | |
+ case NTAP_OPT_EXPECTED_ID: | |
+ case NTAP_OPT_IRLEN: | |
+ case NTAP_OPT_IRMASK: | |
+ case NTAP_OPT_IRCAPTURE: | |
+ /* dummy read to ignore the next argument */ | |
+ Jim_GetOpt_Wide(goi, NULL); | |
+ break; | |
+ default: | |
+ e = JIM_OK; | |
+ break; | |
+ } /* switch (n->value) */ | |
+ if (JIM_OK != e) { | |
+ free(cp); | |
+ free(pTap); | |
+ return e; | |
+ } | |
+ } /* while (goi->argc) */ | |
+ | |
+ if (instance_id_specified != target_id_specified) { | |
+ LOG_ERROR("%s: -dp-id and -instance-id must both be specified", pTap->dotted_name); | |
+ free(cp); | |
+ free(pTap); | |
+ return JIM_ERR; | |
+ } | |
pTap->enabled = true; | |
jtag_tap_init(pTap); | |
return JIM_OK; | |
diff --git a/src/target/adi_v5_swd.c b/src/target/adi_v5_swd.c | |
index b25181e21..0f3f07ce2 100644 | |
--- a/src/target/adi_v5_swd.c | |
+++ b/src/target/adi_v5_swd.c | |
@@ -55,6 +55,37 @@ | |
static bool do_sync; | |
+enum queued_request_type { | |
+ dp_read, | |
+ dp_write, | |
+ ap_read, | |
+ ap_write, | |
+ ap_abort | |
+}; | |
+ | |
+struct queued_request { | |
+ enum queued_request_type type; | |
+ unsigned reg; | |
+ struct adiv5_ap *ap; | |
+ uint32_t write; | |
+ uint32_t *read; | |
+ uint8_t *ack; | |
+}; | |
+ | |
+struct swd_multidrop_session_state { | |
+ struct queued_request *request_queue; | |
+ int request_queue_size; | |
+ int queued_request_count; | |
+ bool active_on_wire; | |
+}; | |
+ | |
+static inline struct swd_multidrop_session_state *adiv5_dap_swd_multidrop_session_state(struct adiv5_dap *dap) | |
+{ | |
+ return (struct swd_multidrop_session_state *)adiv5_dap_swd_transport_private(dap); | |
+} | |
+ | |
+static int swd_multidrop_select_target(struct adiv5_dap *dap, uint32_t *dpidr, uint32_t *dlpidr, bool reconnecting); | |
+ | |
static void swd_finish_read(struct adiv5_dap *dap) | |
{ | |
const struct swd_driver *swd = adiv5_dap_swd_driver(dap); | |
@@ -69,6 +100,11 @@ static int swd_queue_dp_write(struct adiv5_dap *dap, unsigned reg, | |
static int swd_queue_dp_read(struct adiv5_dap *dap, unsigned reg, | |
uint32_t *data); | |
+static bool swd_dap_is_multidrop(struct adiv5_dap *dap) | |
+{ | |
+ return dap->tap->multidrop_targetsel != DP_TARGETSEL_INVALID; | |
+} | |
+ | |
static void swd_clear_sticky_errors(struct adiv5_dap *dap) | |
{ | |
const struct swd_driver *swd = adiv5_dap_swd_driver(dap); | |
@@ -185,6 +221,63 @@ static int swd_connect(struct adiv5_dap *dap) | |
return status; | |
} | |
+static uint32_t last_target_id; | |
+ | |
+static int swd_multidrop_connect(struct adiv5_dap *dap) | |
+{ | |
+ struct swd_multidrop_session_state *state = adiv5_dap_swd_multidrop_session_state(dap); | |
+ if (state->active_on_wire) { | |
+ /* No recursion for you */ | |
+ return ERROR_FAIL; | |
+ } | |
+ state->active_on_wire = true; | |
+ const struct swd_driver *swd = adiv5_dap_swd_driver(dap); | |
+ uint32_t dpidr = 0xdeadbeef, dlpidr = 0xdeadbeef; | |
+ int status; | |
+ | |
+ /* FIXME validate transport config ... is the | |
+ * configured DAP present (check IDCODE)? | |
+ * Is *only* one DAP configured? | |
+ * | |
+ * MUST READ DPIDR | |
+ */ | |
+ | |
+ /* Check if we should reset srst already when connecting, but not if reconnecting. */ | |
+ if (!dap->do_reconnect) { | |
+ enum reset_types jtag_reset_config = jtag_get_reset_config(); | |
+ | |
+ if (jtag_reset_config & RESET_CNCT_UNDER_SRST) { | |
+ if (jtag_reset_config & RESET_SRST_NO_GATING) | |
+ adapter_assert_reset(); | |
+ else | |
+ LOG_WARNING("\'srst_nogate\' reset_config option is required"); | |
+ } | |
+ } | |
+ | |
+ /* Note, debugport_init() does setup too */ | |
+ swd->switch_seq(DORMANT_TO_SWD); | |
+ | |
+ /* Clear link state, including the SELECT cache. */ | |
+ dap->do_reconnect = false; | |
+ dap_invalidate_cache(dap); | |
+ last_target_id = DP_TARGETSEL_INVALID; | |
+ | |
+ /* Pass true for reconnect, clearing sticky errors */ | |
+ status = swd_multidrop_select_target(dap, &dpidr, &dlpidr, true); | |
+ | |
+ if (status == ERROR_OK) { | |
+ LOG_INFO("SWD DPIDR %#8.8" PRIx32, dpidr); | |
+ LOG_INFO("SWD DLPIDR %#8.8" PRIx32, dlpidr); | |
+ dap->do_reconnect = false; | |
+ status = dap_dp_init(dap); | |
+ } else { | |
+ dap->do_reconnect = true; | |
+ } | |
+ | |
+ state->active_on_wire = false; | |
+ return status; | |
+} | |
+ | |
static int swd_send_sequence(struct adiv5_dap *dap, enum swd_special_seq seq) | |
{ | |
const struct swd_driver *swd = adiv5_dap_swd_driver(dap); | |
@@ -200,10 +293,15 @@ static inline int check_sync(struct adiv5_dap *dap) | |
static int swd_check_reconnect(struct adiv5_dap *dap) | |
{ | |
- if (dap->do_reconnect) | |
- return swd_connect(dap); | |
+ int status = ERROR_OK; | |
- return ERROR_OK; | |
+ /* check_reconnect is a noop for multidrop as everything is queued */ | |
+ if (!swd_dap_is_multidrop(dap)) { | |
+ if (dap->do_reconnect) | |
+ status = swd_connect(dap); | |
+ } | |
+ | |
+ return status; | |
} | |
static int swd_queue_ap_abort(struct adiv5_dap *dap, uint8_t *ack) | |
@@ -367,6 +465,7 @@ static void swd_quit(struct adiv5_dap *dap) | |
swd->switch_seq(SWD_TO_JTAG); | |
/* flush the queue before exit */ | |
swd->run(); | |
+ free(adiv5_dap_swd_transport_private(dap)); | |
} | |
const struct dap_ops swd_dap_ops = { | |
@@ -444,6 +543,283 @@ static int swd_init(struct command_context *ctx) | |
return ERROR_OK; | |
} | |
+static int swd_multidrop_queue_request(struct swd_multidrop_session_state *state, struct queued_request **request) | |
+{ | |
+ if (state->queued_request_count == state->request_queue_size) { | |
+ int new_size = state->request_queue_size * 2; | |
+ struct queued_request *new_queue = calloc(new_size, sizeof(struct queued_request)); | |
+ if (!new_queue) | |
+ return ERROR_FAIL; | |
+ memcpy(new_queue, state->request_queue, state->request_queue_size * sizeof(struct queued_request)); | |
+ free(state->request_queue); | |
+ state->request_queue = new_queue; | |
+ state->request_queue_size = new_size; | |
+ } | |
+ *request = &state->request_queue[state->queued_request_count++]; | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int swd_multidrop_queue_dp_read(struct adiv5_dap *dap, unsigned reg, | |
+ uint32_t *data) | |
+{ | |
+ struct swd_multidrop_session_state *state = adiv5_dap_swd_multidrop_session_state(dap); | |
+ int retval; | |
+ if (state->active_on_wire) { | |
+ retval = swd_queue_dp_read(dap, reg, data); | |
+ } else { | |
+ struct queued_request *r = NULL; | |
+ retval = swd_multidrop_queue_request(state, &r); | |
+ if (retval == ERROR_OK) { | |
+ r->type = dp_read; | |
+ r->reg = reg; | |
+ r->read = data; | |
+ } | |
+ } | |
+ return retval; | |
+} | |
+ | |
+static int swd_multidrop_queue_dp_write(struct adiv5_dap *dap, unsigned reg, | |
+ uint32_t data) | |
+{ | |
+ struct swd_multidrop_session_state *state = adiv5_dap_swd_multidrop_session_state(dap); | |
+ int retval; | |
+ if (state->active_on_wire) { | |
+ retval = swd_queue_dp_write(dap, reg, data); | |
+ } else { | |
+ struct queued_request *r = NULL; | |
+ retval = swd_multidrop_queue_request(state, &r); | |
+ if (retval == ERROR_OK) { | |
+ r->type = dp_write; | |
+ r->reg = reg; | |
+ r->write = data; | |
+ } | |
+ } | |
+ return retval; | |
+} | |
+ | |
+static int swd_multidrop_queue_ap_read(struct adiv5_ap *ap, unsigned reg, | |
+ uint32_t *data) | |
+{ | |
+ struct swd_multidrop_session_state *state = adiv5_dap_swd_multidrop_session_state(ap->dap); | |
+ int retval; | |
+ if (state->active_on_wire) { | |
+ retval = swd_queue_ap_read(ap, reg, data); | |
+ } else { | |
+ struct queued_request *r = NULL; | |
+ retval = swd_multidrop_queue_request(state, &r); | |
+ if (retval == ERROR_OK) { | |
+ r->type = ap_read; | |
+ r->ap = ap; | |
+ r->reg = reg; | |
+ r->read = data; | |
+ } | |
+ } | |
+ return retval; | |
+} | |
+ | |
+static int swd_multidrop_queue_ap_write(struct adiv5_ap *ap, unsigned reg, | |
+ uint32_t data) | |
+{ | |
+ struct swd_multidrop_session_state *state = adiv5_dap_swd_multidrop_session_state(ap->dap); | |
+ int retval; | |
+ | |
+ if (state->active_on_wire) { | |
+ retval = swd_queue_ap_write(ap, reg, data); | |
+ } else { | |
+ struct queued_request *r = NULL; | |
+ retval = swd_multidrop_queue_request(state, &r); | |
+ if (retval == ERROR_OK) { | |
+ r->type = ap_write; | |
+ r->ap = ap; | |
+ r->reg = reg; | |
+ r->write = data; | |
+ } | |
+ } | |
+ return retval; | |
+} | |
+ | |
+static int swd_multidrop_queue_ap_abort(struct adiv5_dap *dap, uint8_t *ack) | |
+{ | |
+ struct swd_multidrop_session_state *state = adiv5_dap_swd_multidrop_session_state(dap); | |
+ int retval; | |
+ if (state->active_on_wire) { | |
+ retval = swd_queue_ap_abort(dap, ack); | |
+ } else { | |
+ struct queued_request *r = NULL; | |
+ retval = swd_multidrop_queue_request(state, &r); | |
+ if (retval == ERROR_OK) { | |
+ r->type = ap_abort; | |
+ r->ack = ack; | |
+ } | |
+ } | |
+ return retval; | |
+} | |
+ | |
+/* todo: remove this (only used to reset link to dormant on last quit) or find it a better home */ | |
+static int32_t swd_multidrop_session_count; | |
+ | |
+static int swd_multidrop_select_target(struct adiv5_dap *dap, uint32_t *dpidr, uint32_t *dlpidr, bool reconnecting) | |
+{ | |
+ assert(adiv5_dap_swd_multidrop_session_state(dap)->active_on_wire); | |
+ assert(swd_dap_is_multidrop(dap)); | |
+ | |
+ int retval = ERROR_OK; | |
+ if (last_target_id != dap->tap->multidrop_targetsel || dap->do_reconnect) { | |
+ const struct swd_driver *driver = adiv5_dap_swd_driver(dap); | |
+ const int MAX_TRIES = 3; | |
+ for (int retry = MAX_TRIES; retry > 0; retry--) { | |
+ driver->switch_seq(LINE_RESET); | |
+ swd_queue_dp_write(dap, DP_TARGETSEL, dap->tap->multidrop_targetsel); | |
+ *dpidr = *dlpidr = 0; | |
+ swd_queue_dp_read(dap, DP_DPIDR, dpidr); | |
+ if (!reconnecting && retry == MAX_TRIES) { | |
+ /* Ideally just clear ORUN flag which is set by reset */ | |
+ swd_queue_dp_write(dap, DP_ABORT, ORUNERRCLR); | |
+ } else { | |
+ /* Clear all sticky errors during (including ORUN) */ | |
+ swd_clear_sticky_errors(dap); | |
+ } | |
+ swd_queue_dp_read(dap, DP_DLPIDR, dlpidr); | |
+ retval = swd_run(dap); | |
+ if (retval == ERROR_OK) { | |
+ if (1 != (*dlpidr & DP_TARGETSEL_DPID_MASK) || | |
+ (dap->tap->multidrop_targetsel & DP_TARGETSEL_INSTANCEID_MASK) | |
+ != (*dlpidr & DP_TARGETSEL_INSTANCEID_MASK)) { | |
+ LOG_INFO("Read incorrect DLIPDR %08x (possibly CTRL/STAT value) when selecting coreid %d", *dlpidr, | |
+ dap->tap->multidrop_targetsel >> DP_TARGETSEL_INSTANCEID_SHIFT); | |
+ retval = ERROR_FAIL; | |
+ } else { | |
+ LOG_DEBUG_IO("Selected core %d\n", dap->tap->multidrop_targetsel >> DP_TARGETSEL_INSTANCEID_SHIFT); | |
+ last_target_id = dap->tap->multidrop_targetsel; | |
+ } | |
+ break; | |
+ } else { | |
+ last_target_id = DP_TARGETSEL_INVALID; | |
+ LOG_DEBUG("Failed to select core %d%s", dap->tap->multidrop_targetsel >> DP_TARGETSEL_INSTANCEID_SHIFT, | |
+ retry > 1 ? ", retrying..." : ""); | |
+ } | |
+ } | |
+ } | |
+ return retval; | |
+} | |
+ | |
+static int swd_multidrop_deselect_target(struct adiv5_dap *dap) | |
+{ | |
+ assert(adiv5_dap_swd_multidrop_session_state(dap)->active_on_wire); | |
+ int retval = ERROR_OK; | |
+ static const bool unselect_target; /* = false */ | |
+ /* todo: decide if we really want to deselect the target */ | |
+ if (unselect_target) { | |
+ const struct swd_driver *driver = adiv5_dap_swd_driver(dap); | |
+ driver->switch_seq(LINE_RESET); | |
+ swd_queue_dp_write(dap, DP_TARGETSEL, DP_TARGETSEL_INVALID); | |
+ retval = swd_run(dap); | |
+ } | |
+ return retval; | |
+} | |
+ | |
+/** Executes all queued DAP operations. */ | |
+static int swd_multidrop_run(struct adiv5_dap *dap) | |
+{ | |
+ struct swd_multidrop_session_state *state = adiv5_dap_swd_multidrop_session_state(dap); | |
+ int retval; | |
+ if (state->active_on_wire) { | |
+ retval = swd_run(dap); | |
+ } else { | |
+ if (dap->do_reconnect) { | |
+ retval = swd_multidrop_connect(dap); | |
+ } else { | |
+ uint32_t dpidr, dlpidr; | |
+ state->active_on_wire = true; | |
+ retval = swd_multidrop_select_target(dap, &dpidr, &dlpidr, false); | |
+ state->active_on_wire = false; | |
+ } | |
+ | |
+ state->active_on_wire = true; | |
+ /* Send the queued command if all is well */ | |
+ for (int n = 0; n < state->queued_request_count && retval == ERROR_OK; n++) { | |
+ struct queued_request *request = state->request_queue + n; | |
+ switch (request->type) { | |
+ case dp_write: | |
+ retval = swd_queue_dp_write(dap, request->reg, request->write); | |
+ break; | |
+ case dp_read: | |
+ retval = swd_queue_dp_read(dap, request->reg, request->read); | |
+ break; | |
+ case ap_write: | |
+ retval = swd_queue_ap_write(request->ap, request->reg, request->write); | |
+ break; | |
+ case ap_read: | |
+ retval = swd_queue_ap_read(request->ap, request->reg, request->read); | |
+ break; | |
+ case ap_abort: | |
+ retval = swd_queue_ap_abort(dap, request->ack); | |
+ break; | |
+ default: | |
+ assert(false); | |
+ } | |
+ } | |
+ state->queued_request_count = 0; | |
+ | |
+ if (retval == ERROR_OK) | |
+ retval = swd_run(dap); | |
+ | |
+ if (retval == ERROR_OK) | |
+ swd_multidrop_deselect_target(dap); | |
+ | |
+ state->active_on_wire = false; | |
+ } | |
+ return retval; | |
+} | |
+ | |
+/** Put the SWJ-DP back to dormant mode */ | |
+static void swd_multidrop_quit(struct adiv5_dap *dap) | |
+{ | |
+ const struct swd_driver *swd = adiv5_dap_swd_driver(dap); | |
+ | |
+ if (0 == --swd_multidrop_session_count) | |
+ swd->switch_seq(SWD_TO_DORMANT); | |
+ | |
+ /* flush the queue before exit */ | |
+ swd_multidrop_run(dap); | |
+ | |
+ struct swd_multidrop_session_state *state = adiv5_dap_swd_multidrop_session_state(dap); | |
+ free(state->request_queue); | |
+ free(state); | |
+} | |
+ | |
+const struct dap_ops swd_multidrop_dap_ops = { | |
+ .connect = swd_multidrop_connect, | |
+ .queue_dp_read = swd_multidrop_queue_dp_read, | |
+ .queue_dp_write = swd_multidrop_queue_dp_write, | |
+ .queue_ap_read = swd_multidrop_queue_ap_read, | |
+ .queue_ap_write = swd_multidrop_queue_ap_write, | |
+ .queue_ap_abort = swd_multidrop_queue_ap_abort, | |
+ .run = swd_multidrop_run, | |
+ .quit = swd_multidrop_quit, | |
+}; | |
+ | |
+int swd_create_transport_session(struct adiv5_dap *dap, const struct dap_ops **dap_ops, void **transport_private) | |
+{ | |
+ if (swd_dap_is_multidrop(dap)) { | |
+ struct swd_multidrop_session_state *state = | |
+ (struct swd_multidrop_session_state *)calloc(1, sizeof(struct swd_multidrop_session_state)); | |
+ if (state) { | |
+ *dap_ops = &swd_multidrop_dap_ops; | |
+ state->request_queue_size = 8; | |
+ state->request_queue = calloc(state->request_queue_size, sizeof(struct queued_request)); | |
+ *transport_private = state; | |
+ swd_multidrop_session_count++; | |
+ return ERROR_OK; | |
+ } else { | |
+ return ERROR_FAIL; | |
+ } | |
+ } else { | |
+ *dap_ops = &swd_dap_ops; | |
+ return ERROR_OK; | |
+ } | |
+} | |
+ | |
static struct transport swd_transport = { | |
.name = "swd", | |
.select = swd_select, | |
@@ -456,8 +832,9 @@ static void swd_constructor(void) | |
transport_register(&swd_transport); | |
} | |
-/** Returns true if the current debug session | |
- * is using SWD as its transport. | |
+/** | |
+ * Returns true if the current debug session | |
+ * is using SWD (or SWD-multidrop) as its transport. | |
*/ | |
bool transport_is_swd(void) | |
{ | |
diff --git a/src/target/arm_adi_v5.h b/src/target/arm_adi_v5.h | |
index 8edfaa816..65f725b74 100644 | |
--- a/src/target/arm_adi_v5.h | |
+++ b/src/target/arm_adi_v5.h | |
@@ -155,6 +155,11 @@ | |
#define DP_SELECT_DPBANK 0x0000000F | |
#define DP_SELECT_INVALID 0x00FFFF00 /* Reserved bits one */ | |
+#define DP_TARGETSEL_INVALID 0xFFFFFFFF | |
+#define DP_TARGETSEL_DPID_MASK 0x0FFFFFFF | |
+#define DP_TARGETSEL_INSTANCEID_MASK 0xF0000000 | |
+#define DP_TARGETSEL_INSTANCEID_SHIFT 28 | |
+ | |
#define DP_APSEL_MAX (255) | |
#define DP_APSEL_INVALID (-1) | |
@@ -592,6 +597,7 @@ extern int dap_info_command(struct command_invocation *cmd, | |
extern int dap_register_commands(struct command_context *cmd_ctx); | |
extern const char *adiv5_dap_name(struct adiv5_dap *self); | |
extern const struct swd_driver *adiv5_dap_swd_driver(struct adiv5_dap *self); | |
+extern void *adiv5_dap_swd_transport_private(struct adiv5_dap *self); | |
extern int dap_cleanup_all(void); | |
struct adiv5_private_config { | |
diff --git a/src/target/arm_dap.c b/src/target/arm_dap.c | |
index 56442f183..9ebcf76bc 100644 | |
--- a/src/target/arm_dap.c | |
+++ b/src/target/arm_dap.c | |
@@ -29,10 +29,12 @@ | |
#include "helper/command.h" | |
#include "transport/transport.h" | |
#include "jtag/interface.h" | |
+#include "jtag/swd.h" | |
static LIST_HEAD(all_dap); | |
extern const struct dap_ops swd_dap_ops; | |
+extern const struct dap_ops swd_multidrop_dap_ops; | |
extern const struct dap_ops jtag_dp_ops; | |
extern struct adapter_driver *adapter_driver; | |
@@ -41,7 +43,8 @@ struct arm_dap_object { | |
struct list_head lh; | |
struct adiv5_dap dap; | |
char *name; | |
- const struct swd_driver *swd; | |
+ const struct swd_driver *driver; | |
+ void *transport_private; | |
}; | |
static void dap_instance_init(struct adiv5_dap *dap) | |
@@ -71,13 +74,20 @@ const char *adiv5_dap_name(struct adiv5_dap *self) | |
const struct swd_driver *adiv5_dap_swd_driver(struct adiv5_dap *self) | |
{ | |
struct arm_dap_object *obj = container_of(self, struct arm_dap_object, dap); | |
- return obj->swd; | |
+ return obj->driver; | |
+} | |
+ | |
+void *adiv5_dap_swd_transport_private(struct adiv5_dap *self) | |
+{ | |
+ struct arm_dap_object *obj = container_of(self, struct arm_dap_object, dap); | |
+ return obj->transport_private; | |
} | |
struct adiv5_dap *adiv5_get_dap(struct arm_dap_object *obj) | |
{ | |
return &obj->dap; | |
} | |
+ | |
struct adiv5_dap *dap_instance_by_jim_obj(Jim_Interp *interp, Jim_Obj *o) | |
{ | |
struct arm_dap_object *obj = NULL; | |
@@ -105,6 +115,8 @@ static int dap_init_all(void) | |
LOG_DEBUG("Initializing all DAPs ..."); | |
+ bool had_multidrop = false; | |
+ bool had_non_multidrop = false; | |
list_for_each_entry(obj, &all_dap, lh) { | |
struct adiv5_dap *dap = &obj->dap; | |
@@ -117,8 +129,19 @@ static int dap_init_all(void) | |
continue; | |
if (transport_is_swd()) { | |
- dap->ops = &swd_dap_ops; | |
- obj->swd = adapter_driver->swd_ops; | |
+ if (dap->tap->multidrop_targetsel == DP_TARGETSEL_INVALID) | |
+ had_non_multidrop = true; | |
+ else | |
+ had_multidrop = true; | |
+ | |
+ if (had_multidrop && had_non_multidrop) { | |
+ LOG_ERROR("Mixture of SWD multidrop DAPs and non multidrop DAPs is not currently supported"); | |
+ return ERROR_FAIL; | |
+ } | |
+ retval = swd_create_transport_session(dap, &dap->ops, &obj->transport_private); | |
+ if (retval != ERROR_OK) | |
+ return retval; | |
+ obj->driver = adapter_driver->swd_ops; | |
} else if (transport_is_dapdirect_swd()) { | |
dap->ops = adapter_driver->dap_swd_ops; | |
} else if (transport_is_dapdirect_jtag()) { | |
@@ -308,7 +331,12 @@ static int jim_dap_names(Jim_Interp *interp, int argc, Jim_Obj *const *argv) | |
COMMAND_HANDLER(handle_dap_init) | |
{ | |
- return dap_init_all(); | |
+ int retval; | |
+ retval = dap_init_all(); | |
+ if (retval != ERROR_OK) | |
+ LOG_INFO("DAP init failed"); | |
+ | |
+ return retval; | |
} | |
COMMAND_HANDLER(handle_dap_info_command) | |
diff --git a/src/target/cortex_m.c b/src/target/cortex_m.c | |
index ce2c426ce..a30dc6a81 100644 | |
--- a/src/target/cortex_m.c | |
+++ b/src/target/cortex_m.c | |
@@ -38,6 +38,7 @@ | |
#include "register.h" | |
#include "arm_opcodes.h" | |
#include "arm_semihosting.h" | |
+#include "smp.h" | |
#include <helper/time_support.h> | |
#include <rtt/rtt.h> | |
@@ -570,6 +571,78 @@ static int cortex_m_debug_entry(struct target *target) | |
return ERROR_OK; | |
} | |
+static struct target *get_cortex_m(struct target *target, int32_t coreid) | |
+{ | |
+ struct target_list *head; | |
+ struct target *curr; | |
+ | |
+ foreach_smp_target(head, target->head) { | |
+ curr = head->target; | |
+ if ((curr->coreid == coreid) && (curr->state == TARGET_HALTED)) | |
+ return curr; | |
+ } | |
+ return target; | |
+} | |
+ | |
+static int cortex_m_halt(struct target *target); | |
+static int cortex_m_poll(struct target *target); | |
+ | |
+static int cortex_m_halt_smp(struct target *target) | |
+{ | |
+ int retval = 0; | |
+ struct target_list *head; | |
+ struct target *curr; | |
+ foreach_smp_target(head, target->head) { | |
+ curr = head->target; | |
+ if ((curr != target) && (curr->state != TARGET_HALTED) | |
+ && target_was_examined(curr)) | |
+ retval += cortex_m_halt(curr); | |
+ } | |
+ return retval; | |
+} | |
+ | |
+static int update_halt_gdb(struct target *target) | |
+{ | |
+ struct target *gdb_target = NULL; | |
+ struct target_list *head; | |
+ struct target *curr; | |
+ int retval = 0; | |
+ | |
+ if (target->gdb_service && target->gdb_service->core[0] == -1) { | |
+ target->gdb_service->target = target; | |
+ target->gdb_service->core[0] = target->coreid; | |
+ retval += cortex_m_halt_smp(target); | |
+ } | |
+ | |
+ if (target->gdb_service) | |
+ gdb_target = target->gdb_service->target; | |
+ | |
+ foreach_smp_target(head, target->head) { | |
+ curr = head->target; | |
+ /* skip calling context */ | |
+ if (curr == target) | |
+ continue; | |
+ if (!target_was_examined(curr)) | |
+ continue; | |
+ /* skip targets that were already halted */ | |
+ if (curr->state == TARGET_HALTED) | |
+ continue; | |
+ /* Skip gdb_target; it alerts GDB so has to be polled as last one */ | |
+ if (curr == gdb_target) | |
+ continue; | |
+ | |
+ /* avoid recursion in cortex_m_poll() */ | |
+ curr->smp = 0; | |
+ cortex_m_poll(curr); | |
+ curr->smp = 1; | |
+ } | |
+ | |
+ /* after all targets were updated, poll the gdb serving target */ | |
+ if (gdb_target != NULL && gdb_target != target) | |
+ cortex_m_poll(gdb_target); | |
+ return retval; | |
+} | |
+ | |
static int cortex_m_poll(struct target *target) | |
{ | |
int detected_failure = ERROR_OK; | |
@@ -578,6 +651,18 @@ static int cortex_m_poll(struct target *target) | |
struct cortex_m_common *cortex_m = target_to_cm(target); | |
struct armv7m_common *armv7m = &cortex_m->armv7m; | |
+ /* toggle to another core is done by gdb as follow */ | |
+ /* maint packet J core_id */ | |
+ /* continue */ | |
+ /* the next polling trigger an halt event sent to gdb */ | |
+ if ((target->state == TARGET_HALTED) && (target->smp) && | |
+ (target->gdb_service) && | |
+ (target->gdb_service->target == NULL)) { | |
+ target->gdb_service->target = | |
+ get_cortex_m(target, target->gdb_service->core[1]); | |
+ target_call_event_callbacks(target, TARGET_EVENT_HALTED); | |
+ return retval; | |
+ } | |
/* Read from Debug Halting Control and Status Register */ | |
retval = mem_ap_read_atomic_u32(armv7m->debug_ap, DCB_DHCSR, &cortex_m->dcb_dhcsr); | |
if (retval != ERROR_OK) { | |
@@ -597,7 +682,7 @@ static int cortex_m_poll(struct target *target) | |
/* We have to execute the rest (the "finally" equivalent, but | |
* still throw this exception again). | |
*/ | |
- detected_failure = ERROR_FAIL; | |
+ detected_failure = ERROR_STICKY_CLEARED; | |
/* refresh status bits */ | |
retval = mem_ap_read_atomic_u32(armv7m->debug_ap, DCB_DHCSR, &cortex_m->dcb_dhcsr); | |
@@ -609,6 +694,7 @@ static int cortex_m_poll(struct target *target) | |
if (target->state != TARGET_RESET) { | |
target->state = TARGET_RESET; | |
LOG_INFO("%s: external reset detected", target_name(target)); | |
+ register_cache_invalidate(armv7m->arm.core_cache); | |
} | |
return ERROR_OK; | |
} | |
@@ -636,6 +722,12 @@ static int cortex_m_poll(struct target *target) | |
if (retval != ERROR_OK) | |
return retval; | |
+ if (target->smp) { | |
+ retval = update_halt_gdb(target); | |
+ if (retval != ERROR_OK) | |
+ return retval; | |
+ } | |
+ | |
if (arm_semihosting(target, &retval) != 0) | |
return retval; | |
@@ -647,6 +739,12 @@ static int cortex_m_poll(struct target *target) | |
if (retval != ERROR_OK) | |
return retval; | |
+ if (target->smp) { | |
+ retval = update_halt_gdb(target); | |
+ if (retval != ERROR_OK) | |
+ return retval; | |
+ } | |
+ | |
target_call_event_callbacks(target, TARGET_EVENT_DEBUG_HALTED); | |
} | |
} | |
@@ -789,8 +887,8 @@ void cortex_m_enable_breakpoints(struct target *target) | |
} | |
} | |
-static int cortex_m_resume(struct target *target, int current, | |
- target_addr_t address, int handle_breakpoints, int debug_execution) | |
+static int cortex_m_internal_restore(struct target *target, int current, | |
+ target_addr_t *address, int handle_breakpoints, int debug_execution) | |
{ | |
struct armv7m_common *armv7m = target_to_armv7m(target); | |
struct breakpoint *breakpoint = NULL; | |
@@ -840,7 +938,7 @@ static int cortex_m_resume(struct target *target, int current, | |
/* current = 1: continue on current pc, otherwise continue at <address> */ | |
r = armv7m->arm.pc; | |
if (!current) { | |
- buf_set_u32(r->value, 0, 32, address); | |
+ buf_set_u32(r->value, 0, 32, *address); | |
r->dirty = true; | |
r->valid = true; | |
} | |
@@ -854,6 +952,8 @@ static int cortex_m_resume(struct target *target, int current, | |
armv7m_maybe_skip_bkpt_inst(target, NULL); | |
resume_pc = buf_get_u32(r->value, 0, 32); | |
+ if (current) | |
+ *address = resume_pc; | |
armv7m_restore_context(target); | |
@@ -871,15 +971,67 @@ static int cortex_m_resume(struct target *target, int current, | |
} | |
} | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int cortex_m_internal_restart(struct target *target) | |
+{ | |
+ struct armv7m_common *armv7m = target_to_armv7m(target); | |
+ | |
/* Restart core */ | |
cortex_m_set_maskints_for_run(target); | |
cortex_m_write_debug_halt_mask(target, 0, C_HALT); | |
target->debug_reason = DBG_REASON_NOTHALTED; | |
+ target->state = TARGET_RUNNING; | |
/* registers are now invalid */ | |
register_cache_invalidate(armv7m->arm.core_cache); | |
+ return ERROR_OK; | |
+} | |
+ | |
+static int cortex_m_restore_smp(struct target *target, int handle_breakpoints) | |
+{ | |
+ int retval = 0; | |
+ struct target_list *head; | |
+ struct target *curr; | |
+ target_addr_t address; | |
+ foreach_smp_target(head, target->head) { | |
+ curr = head->target; | |
+ if ((curr != target) && (curr->state != TARGET_RUNNING) | |
+ && target_was_examined(curr)) { | |
+ /* resume current address , not in step mode */ | |
+ retval += cortex_m_internal_restore(curr, 1, &address, | |
+ handle_breakpoints, 0); | |
+ retval += cortex_m_internal_restart(curr); | |
+ } | |
+ } | |
+ return retval; | |
+} | |
+ | |
+static int cortex_m_resume(struct target *target, int current, | |
+ target_addr_t address, int handle_breakpoints, int debug_execution) | |
+{ | |
+ int retval = 0; | |
+ /* dummy resume for smp toggle in order to reduce gdb impact */ | |
+ if ((target->smp) && (target->gdb_service->core[1] != -1)) { | |
+ /* simulate a start and halt of target */ | |
+ target->gdb_service->target = NULL; | |
+ target->gdb_service->core[0] = target->gdb_service->core[1]; | |
+ /* fake resume at next poll we play the target core[1], see poll*/ | |
+ target_call_event_callbacks(target, TARGET_EVENT_RESUMED); | |
+ return 0; | |
+ } | |
+ cortex_m_internal_restore(target, current, &address, handle_breakpoints, debug_execution); | |
+ if (target->smp) { | |
+ target->gdb_service->core[0] = -1; | |
+ retval = cortex_m_restore_smp(target, handle_breakpoints); | |
+ if (retval != ERROR_OK) | |
+ return retval; | |
+ } | |
+ cortex_m_internal_restart(target); | |
+ uint32_t resume_pc = (uint32_t)address; | |
if (!debug_execution) { | |
target->state = TARGET_RUNNING; | |
target_call_event_callbacks(target, TARGET_EVENT_RESUMED); | |
@@ -1950,8 +2102,10 @@ int cortex_m_examine(struct target *target) | |
retval = cortex_m_find_mem_ap(swjdp, &armv7m->debug_ap); | |
if (retval != ERROR_OK) { | |
LOG_ERROR("Could not find MEM-AP to control the core"); | |
+ swjdp->do_reconnect = true; | |
return retval; | |
} | |
+ | |
} else { | |
armv7m->debug_ap = dap_ap(swjdp, cortex_m->apsel); | |
} | |
@@ -2409,6 +2563,27 @@ COMMAND_HANDLER(handle_cortex_m_mask_interrupts_command) | |
return ERROR_OK; | |
} | |
+COMMAND_HANDLER(cortex_m_handle_smp_gdb_command) | |
+{ | |
+ struct target *target = get_current_target(CMD_CTX); | |
+ int retval = ERROR_OK; | |
+ struct target_list *head; | |
+ head = target->head; | |
+ if (head != (struct target_list *)NULL) { | |
+ if (CMD_ARGC == 1) { | |
+ int coreid = 0; | |
+ COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], coreid); | |
+ if (ERROR_OK != retval) | |
+ return retval; | |
+ target->gdb_service->core[1] = coreid; | |
+ | |
+ } | |
+ command_print(CMD, "gdb coreid %" PRId32 " -> %" PRId32, target->gdb_service->core[0] | |
+ , target->gdb_service->core[1]); | |
+ } | |
+ return ERROR_OK; | |
+} | |
+ | |
COMMAND_HANDLER(handle_cortex_m_reset_config_command) | |
{ | |
struct target *target = get_current_target(CMD_CTX); | |
@@ -2455,6 +2630,13 @@ COMMAND_HANDLER(handle_cortex_m_reset_config_command) | |
} | |
static const struct command_registration cortex_m_exec_command_handlers[] = { | |
+ { | |
+ .name = "smp_gdb", | |
+ .handler = cortex_m_handle_smp_gdb_command, | |
+ .mode = COMMAND_EXEC, | |
+ .help = "display/fix current core played to gdb", | |
+ .usage = "", | |
+ }, | |
{ | |
.name = "maskisr", | |
.handler = handle_cortex_m_mask_interrupts_command, | |
@@ -2476,6 +2658,9 @@ static const struct command_registration cortex_m_exec_command_handlers[] = { | |
.help = "configure software reset handling", | |
.usage = "['sysresetreq'|'vectreset']", | |
}, | |
+ { | |
+ .chain = smp_command_handlers, | |
+ }, | |
COMMAND_REGISTRATION_DONE | |
}; | |
static const struct command_registration cortex_m_command_handlers[] = { | |
diff --git a/src/target/target.c b/src/target/target.c | |
index cab84b06b..a8f604462 100644 | |
--- a/src/target/target.c | |
+++ b/src/target/target.c | |
@@ -3010,6 +3010,10 @@ static int handle_target(void *priv) | |
if (!powerDropout && !srstAsserted) { | |
/* polling may fail silently until the target has been examined */ | |
retval = target_poll(target); | |
+ if (retval == ERROR_STICKY_CLEARED) { | |
+ /* retry if a sticky error was cleared */ | |
+ retval = target_poll(target); | |
+ } | |
if (retval != ERROR_OK) { | |
/* 100ms polling interval. Increase interval between polling up to 5000ms */ | |
if (target->backoff.times * polling_interval < 5000) { | |
@@ -3230,6 +3234,10 @@ int target_wait_state(struct target *target, enum target_state state, int ms) | |
for (;;) { | |
retval = target_poll(target); | |
+ if (retval == ERROR_STICKY_CLEARED) { | |
+ /* retry if a sticky error was cleared */ | |
+ retval = target_poll(target); | |
+ } | |
if (retval != ERROR_OK) | |
return retval; | |
if (target->state == state) | |
diff --git a/src/transport/transport.h b/src/transport/transport.h | |
index 6bf6aaced..98c288772 100644 | |
--- a/src/transport/transport.h | |
+++ b/src/transport/transport.h | |
@@ -93,6 +93,9 @@ COMMAND_HELPER(transport_list_parse, char ***vector); | |
int allow_transports(struct command_context *ctx, const char * const *vector); | |
bool transport_is_jtag(void); | |
+/** | |
+ * Note this returns true for swd multidrop too | |
+ */ | |
bool transport_is_swd(void); | |
bool transport_is_dapdirect_jtag(void); | |
bool transport_is_dapdirect_swd(void); | |
diff --git a/tcl/board/pico-debug.cfg b/tcl/board/pico-debug.cfg | |
new file mode 100644 | |
index 000000000..ba59f860a | |
--- /dev/null | |
+++ b/tcl/board/pico-debug.cfg | |
@@ -0,0 +1,10 @@ | |
+# SPDX-License-Identifier: GPL-2.0-or-later | |
+# pico-debug is a virtual CMSIS-DAP debug adapter | |
+# it runs on the very same RP2040 target being debugged without additional hardware | |
+# https://github.com/majbthrd/pico-debug | |
+ | |
+source [find interface/cmsis-dap.cfg] | |
+adapter speed 4000 | |
+ | |
+set CHIPNAME rp2040 | |
+source [find target/rp2040-core0.cfg] | |
diff --git a/tcl/interface/picoprobe.cfg b/tcl/interface/picoprobe.cfg | |
new file mode 100644 | |
index 000000000..7090050e7 | |
--- /dev/null | |
+++ b/tcl/interface/picoprobe.cfg | |
@@ -0,0 +1,3 @@ | |
+# Adapter section | |
+adapter driver picoprobe | |
+adapter speed 5000 | |
diff --git a/tcl/interface/raspberrypi-swd.cfg b/tcl/interface/raspberrypi-swd.cfg | |
new file mode 100644 | |
index 000000000..78f181676 | |
--- /dev/null | |
+++ b/tcl/interface/raspberrypi-swd.cfg | |
@@ -0,0 +1,12 @@ | |
+# Use RPI GPIO pins | |
+adapter driver bcm2835gpio | |
+ | |
+bcm2835gpio_speed_coeffs 146203 36 | |
+ | |
+# SWD swclk swdio | |
+# Header pin numbers | |
+bcm2835gpio_swd_nums 25 24 | |
+ | |
+transport select swd | |
+ | |
+adapter speed 1000 | |
diff --git a/tcl/target/rp2040-core0.cfg b/tcl/target/rp2040-core0.cfg | |
new file mode 100644 | |
index 000000000..79b80f9ea | |
--- /dev/null | |
+++ b/tcl/target/rp2040-core0.cfg | |
@@ -0,0 +1,39 @@ | |
+source [find target/swj-dp.tcl] | |
+source [find mem_helper.tcl] | |
+ | |
+set _CHIPNAME rp2040 | |
+set _CPUTAPID 0x01002927 | |
+set _ENDIAN little | |
+ | |
+swj_newdap $_CHIPNAME.core0 cpu -dp-id $_CPUTAPID -instance-id 0 | |
+ | |
+# NOTE target smp makes both targets act a single virtual target on one port for gdb | |
+# (without it you should be able to debug separately on two ports) | |
+# NOTE: "-rtos hwthread" creates a thread per core in smp mode (otherwise it is a single thread for the virtual target) | |
+ | |
+# Give OpenOCD SRAM1 (64k) to use for e.g. flash programming bounce buffers (should avoid algo stack etc) | |
+# Don't require save/restore, because this isn't something we'd do whilst a user app is running | |
+set _WORKSIZE 0x10000 | |
+set _WORKBASE 0x20010000 | |
+ | |
+#core 0 | |
+set _TARGETNAME_0 $_CHIPNAME.core0 | |
+dap create $_TARGETNAME_0.dap -chain-position $_CHIPNAME.core0.cpu | |
+target create $_TARGETNAME_0 cortex_m -endian $_ENDIAN -coreid 0 -dap $_TARGETNAME_0.dap -rtos hwthread | |
+$_TARGETNAME_0 configure -work-area-phys $_WORKBASE -work-area-size $_WORKSIZE -work-area-backup 0 | |
+cortex_m reset_config sysresetreq | |
+ | |
+target smp $_TARGETNAME_0 | |
+ | |
+set _FLASHNAME $_CHIPNAME.flash | |
+set _FLASHSIZE 0x200000 | |
+set _FLASHBASE 0x10000000 | |
+# name driver base, size in bytes, chip_width, bus_width, target used to access | |
+flash bank $_FLASHNAME rp2040_flash $_FLASHBASE $_FLASHSIZE 1 32 $_TARGETNAME_0 | |
+ | |
+# srst is not fitted so use SYSRESETREQ to perform a soft reset | |
+reset_config srst_nogate | |
+ | |
+gdb_flash_program enable | |
+gdb_memory_map enable | |
+ | |
diff --git a/tcl/target/rp2040-core1.cfg b/tcl/target/rp2040-core1.cfg | |
new file mode 100644 | |
index 000000000..cc4ff3888 | |
--- /dev/null | |
+++ b/tcl/target/rp2040-core1.cfg | |
@@ -0,0 +1,39 @@ | |
+source [find target/swj-dp.tcl] | |
+source [find mem_helper.tcl] | |
+ | |
+set _CHIPNAME rp2040 | |
+set _CPUTAPID 0x01002927 | |
+set _ENDIAN little | |
+ | |
+swj_newdap $_CHIPNAME.core1 cpu -dp-id $_CPUTAPID -instance-id 1 | |
+ | |
+# NOTE target smp makes both targets act a single virtual target on one port for gdb | |
+# (without it you should be able to debug separately on two ports) | |
+# NOTE: "-rtos hwthread" creates a thread per core in smp mode (otherwise it is a single thread for the virtual target) | |
+ | |
+# Give OpenOCD SRAM1 (64k) to use for e.g. flash programming bounce buffers (should avoid algo stack etc) | |
+# Don't require save/restore, because this isn't something we'd do whilst a user app is running | |
+set _WORKSIZE 0x10000 | |
+set _WORKBASE 0x20010000 | |
+ | |
+#core 1 | |
+set _TARGETNAME_1 $_CHIPNAME.core1 | |
+dap create $_TARGETNAME_1.dap -chain-position $_CHIPNAME.core1.cpu | |
+target create $_TARGETNAME_1 cortex_m -endian $_ENDIAN -coreid 1 -dap $_TARGETNAME_1.dap -rtos hwthread | |
+$_TARGETNAME_1 configure -work-area-phys $_WORKBASE -work-area-size $_WORKSIZE -work-area-backup 0 | |
+cortex_m reset_config sysresetreq | |
+ | |
+target smp $_TARGETNAME_1 | |
+ | |
+set _FLASHNAME $_CHIPNAME.flash | |
+set _FLASHSIZE 0x200000 | |
+set _FLASHBASE 0x10000000 | |
+# name driver base, size in bytes, chip_width, bus_width, target used to access | |
+flash bank $_FLASHNAME rp2040_flash $_FLASHBASE $_FLASHSIZE 1 32 $_TARGETNAME_1 | |
+ | |
+# srst is not fitted so use SYSRESETREQ to perform a soft reset | |
+reset_config srst_nogate | |
+ | |
+gdb_flash_program enable | |
+gdb_memory_map enable | |
+ | |
diff --git a/tcl/target/rp2040-rescue.cfg b/tcl/target/rp2040-rescue.cfg | |
new file mode 100644 | |
index 000000000..56298ba02 | |
--- /dev/null | |
+++ b/tcl/target/rp2040-rescue.cfg | |
@@ -0,0 +1,28 @@ | |
+source [find target/swj-dp.tcl] | |
+source [find mem_helper.tcl] | |
+ | |
+set _CHIPNAME rp2040 | |
+set _CPUTAPID 0x01002927 | |
+set _ENDIAN little | |
+ | |
+swj_newdap $_CHIPNAME.rescue_dp cpu -dp-id $_CPUTAPID -instance-id 0xf | |
+set _TARGETNAME_0 $_CHIPNAME.rescue | |
+dap create $_TARGETNAME_0.dap -chain-position $_CHIPNAME.rescue_dp.cpu -ignore-syspwrupack | |
+ | |
+# Have to init before we can do dpreg commands | |
+init | |
+ | |
+# The rescue debug port uses the AP CTRL/STAT bit DBGPWRUPREQ to reset the | |
+# PSM (power on state machine) of the RP2040 with a flag set in the | |
+# VREG_AND_POR_CHIP_RESET register. Once the reset is released | |
+# (by clearing the DBGPWRUPREQ flag), the bootrom will run, see this flag, | |
+# and halt. Allowing the user to load some fresh code, rather than loading | |
+# the potentially broken code stored in flash | |
+ | |
+# Clear DBGPWRUPREQ | |
+$_TARGETNAME_0.dap dpreg 0x4 0x00000000 | |
+ | |
+# Verifying CTRL/STAT is 0 | |
+$_TARGETNAME_0.dap dpreg 0x4 | |
+ | |
+echo "Now attach a debugger to your RP2040 and load some code" | |
diff --git a/tcl/target/rp2040.cfg b/tcl/target/rp2040.cfg | |
new file mode 100644 | |
index 000000000..b032f0567 | |
--- /dev/null | |
+++ b/tcl/target/rp2040.cfg | |
@@ -0,0 +1,53 @@ | |
+source [find target/swj-dp.tcl] | |
+source [find mem_helper.tcl] | |
+ | |
+set _CHIPNAME rp2040 | |
+set _CPUTAPID 0x01002927 | |
+set _ENDIAN little | |
+ | |
+swj_newdap $_CHIPNAME.core0 cpu -dp-id $_CPUTAPID -instance-id 0 | |
+swj_newdap $_CHIPNAME.core1 cpu -dp-id $_CPUTAPID -instance-id 1 | |
+ | |
+# NOTE target smp makes both targets act a single virtual target on one port for gdb | |
+# (without it you should be able to debug separately on two ports) | |
+# NOTE: "-rtos hwthread" creates a thread per core in smp mode (otherwise it is a single thread for the virtual target) | |
+ | |
+# Give OpenOCD SRAM1 (64k) to use for e.g. flash programming bounce buffers (should avoid algo stack etc) | |
+# Don't require save/restore, because this isn't something we'd do whilst a user app is running | |
+set _WORKSIZE 0x10000 | |
+set _WORKBASE 0x20010000 | |
+ | |
+#core 0 | |
+set _TARGETNAME_0 $_CHIPNAME.core0 | |
+dap create $_TARGETNAME_0.dap -chain-position $_CHIPNAME.core0.cpu | |
+target create $_TARGETNAME_0 cortex_m -endian $_ENDIAN -coreid 0 -dap $_TARGETNAME_0.dap -rtos hwthread | |
+$_TARGETNAME_0 configure -work-area-phys $_WORKBASE -work-area-size $_WORKSIZE -work-area-backup 0 | |
+cortex_m reset_config sysresetreq | |
+ | |
+#core 1 | |
+set _TARGETNAME_1 $_CHIPNAME.core1 | |
+dap create $_TARGETNAME_1.dap -chain-position $_CHIPNAME.core1.cpu | |
+target create $_TARGETNAME_1 cortex_m -endian $_ENDIAN -coreid 1 -dap $_TARGETNAME_1.dap -rtos hwthread | |
+$_TARGETNAME_1 configure -work-area-phys $_WORKBASE -work-area-size $_WORKSIZE -work-area-backup 0 | |
+cortex_m reset_config sysresetreq | |
+ | |
+target smp $_TARGETNAME_0 $_TARGETNAME_1 | |
+ | |
+set _FLASHNAME $_CHIPNAME.flash | |
+set _FLASHSIZE 0x200000 | |
+set _FLASHBASE 0x10000000 | |
+# name driver base, size in bytes, chip_width, bus_width, target used to access | |
+flash bank $_FLASHNAME rp2040_flash $_FLASHBASE $_FLASHSIZE 1 32 $_TARGETNAME_0 | |
+ | |
+# Openocd associates a flash bank with a target (in our case a core) and | |
+# running `openocd -c "program foo.elf"` just grabs the last selected core | |
+# from the TCL context. Select `core0` by default so that flash probe | |
+# succeeds. Note gdb understands there are 2 cores -- this is only for TCL. | |
+targets rp2040.core0 | |
+ | |
+# srst is not fitted so use SYSRESETREQ to perform a soft reset | |
+reset_config srst_nogate | |
+ | |
+gdb_flash_program enable | |
+gdb_memory_map enable | |
+ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment