From 03c73b395f2328c15ad8f432b2f88a0251228cd1 Mon Sep 17 00:00:00 2001 From: Alexey Gerenkov Date: Mon, 22 Jul 2024 20:41:10 +0300 Subject: [PATCH] tools: Adds script to detect Espressif devkits connected to the host --- CMakeLists.txt | 1 + Makefile.am | 4 + src/jtag/adapter.c | 12 ++ src/jtag/drivers/esp_usb_jtag.c | 47 +++- src/jtag/drivers/ftdi.c | 49 +++++ src/jtag/drivers/libusb_helper.c | 98 +++++++++ src/jtag/drivers/libusb_helper.h | 5 + src/jtag/drivers/mpsse.c | 5 + src/jtag/drivers/mpsse.h | 1 + tcl/esp-config-schema.json | 42 ++++ tcl/esp-config.json | 55 ++++- tcl/interface/ftdi/esp32_devkitj_v1.cfg | 6 + tools/esp_detect_config.py | 273 ++++++++++++++++++++++++ 13 files changed, 582 insertions(+), 16 deletions(-) create mode 100755 tools/esp_detect_config.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 3eda2dcca2..6b9c48f45a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ install(TARGETS openocd DESTINATION ${bindir}) install(DIRECTORY tcl/ DESTINATION ${pkgdatadir}/scripts) install(FILES contrib/60-openocd.rules DESTINATION ${pkgdatadir}/contrib) install(DIRECTORY contrib/libdcc DESTINATION ${pkgdatadir}/contrib) +install(FILES tools/esp_detect_config.py DESTINATION ${pkgdatadir}/tools) get_property(ulink_firmware TARGET ocdjtagdrivers PROPERTY ULINK_FIRMWARE) if(BUILD_JLINK) install(FILES ${ulink_firmware} DESTINATION ${pkgdatadir}/OpenULINK) diff --git a/Makefile.am b/Makefile.am index 16a21d8e35..fa9729021c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -19,6 +19,10 @@ nobase_dist_pkgdata_DATA = \ contrib/libdcc/README \ contrib/60-openocd.rules +esptoolsdir = $(pkgdatadir)/espressif/tools +dist_esptools_DATA = \ + tools/esp_detect_config.py + SUBDIRS = DIST_SUBDIRS = bin_PROGRAMS = diff --git a/src/jtag/adapter.c b/src/jtag/adapter.c index bbf1cb3d2e..d8119c44a9 100644 --- a/src/jtag/adapter.c +++ b/src/jtag/adapter.c @@ -66,6 +66,10 @@ static const struct gpio_map { [ADAPTER_GPIO_IDX_LED] = { "led", ADAPTER_GPIO_DIRECTION_OUTPUT, true, true, }, }; +#ifdef HAVE_LIBUSB_GET_PORT_NUMBERS +static void adapter_usb_set_location(const char *location); +#endif + bool is_adapter_initialized(void) { return adapter_config.adapter_initialized; @@ -146,6 +150,14 @@ int adapter_init(struct command_context *cmd_ctx) return ERROR_JTAG_INIT_FAILED; } +#ifdef HAVE_LIBUSB_GET_PORT_NUMBERS + char *loc = getenv("OPENOCD_USB_ADAPTER_LOCATION"); + if (loc) { + LOG_INFO("use USB location specified via env var '%s'", loc); + adapter_usb_set_location(loc); + } +#endif + retval = adapter_driver->init(); if (retval != ERROR_OK) return retval; diff --git a/src/jtag/drivers/esp_usb_jtag.c b/src/jtag/drivers/esp_usb_jtag.c index 84336e8677..a3728b2841 100644 --- a/src/jtag/drivers/esp_usb_jtag.c +++ b/src/jtag/drivers/esp_usb_jtag.c @@ -915,11 +915,37 @@ COMMAND_HANDLER(esp_usb_jtag_chip_id) return ERROR_OK; } -extern void libusb_list_devices(void); +COMMAND_HANDLER(esp_usb_jtag_get_location) +{ + char dev_loc[128]; + + if (!priv->usb_device) { + command_print(CMD, "Can not get device location! No open device."); + return ERROR_FAIL; + } + + if (jtag_libusb_get_dev_location_by_handle(priv->usb_device, dev_loc, sizeof(dev_loc)) != ERROR_OK) { + command_print(CMD, "Cannot get location for open usb device!"); + return ERROR_FAIL; + } + + command_print(CMD, "%s", dev_loc); + return ERROR_OK; +} -COMMAND_HANDLER(esp_usb_jtag_list) +COMMAND_HANDLER(esp_usb_jtag_dev_list) { - libusb_list_devices(); + const uint16_t vids[] = { esp_usb_vid, 0 }; /* must be null terminated */ + const uint16_t pids[] = { esp_usb_pid, 0 }; /* must be null terminated */ + int cnt, i; + char **locations; + + cnt = jtag_libusb_get_devs_locations(vids, pids, &locations); + for (i = 0; i < cnt; i++) + command_print(CMD, "%s", locations[i]); + + jtag_libusb_free_devs_locations(locations, cnt); + return ERROR_OK; } @@ -967,11 +993,18 @@ static const struct command_registration esp_usb_jtag_subcommands[] = { .usage = "chip_id", }, { - .name = "list", - .handler = &esp_usb_jtag_list, + .name = "list_devs", + .handler = &esp_usb_jtag_dev_list, + .mode = COMMAND_ANY, + .help = "list devices", + .usage = "list_devs", + }, + { + .name = "get_location", + .handler = &esp_usb_jtag_get_location, .mode = COMMAND_ANY, - .help = "list", - .usage = "list", + .help = "get device location", + .usage = "get_location", }, COMMAND_REGISTRATION_DONE }; diff --git a/src/jtag/drivers/ftdi.c b/src/jtag/drivers/ftdi.c index db302aafcc..2ac0b2f7ab 100644 --- a/src/jtag/drivers/ftdi.c +++ b/src/jtag/drivers/ftdi.c @@ -75,6 +75,8 @@ /* FTDI access library includes */ #include "mpsse.h" +#include "libusb_helper.h" + #define JTAG_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT) #define JTAG_MODE_ALT (LSB_FIRST | NEG_EDGE_IN | NEG_EDGE_OUT) #define SWD_MODE (LSB_FIRST | POS_EDGE_IN | NEG_EDGE_OUT) @@ -918,6 +920,39 @@ COMMAND_HANDLER(ftdi_handle_tdo_sample_edge_command) return ERROR_OK; } +COMMAND_HANDLER(ftdi_handle_get_location) +{ + char dev_loc[128]; + + struct libusb_device_handle *usb_dev = mpsse_get_usb_device(mpsse_ctx); + if (!usb_dev) { + command_print(CMD, "Can not get device location! No open device."); + return ERROR_FAIL; + } + + if (jtag_libusb_get_dev_location_by_handle(usb_dev, dev_loc, sizeof(dev_loc)) != ERROR_OK) { + command_print(CMD, "Cannot get location for open usb device!"); + return ERROR_FAIL; + } + + command_print(CMD, "%s", dev_loc); + return ERROR_OK; +} + +COMMAND_HANDLER(ftdi_handle_dev_list) +{ + int cnt, i; + char **locations; + + cnt = jtag_libusb_get_devs_locations(ftdi_vid, ftdi_pid, &locations); + for (i = 0; i < cnt; i++) + command_print(CMD, "%s", locations[i]); + + jtag_libusb_free_devs_locations(locations, cnt); + + return ERROR_OK; +} + static const struct command_registration ftdi_subcommand_handlers[] = { { .name = "device_desc", @@ -979,6 +1014,20 @@ static const struct command_registration ftdi_subcommand_handlers[] = { "allow signalling speed increase)", .usage = "(rising|falling)", }, + { + .name = "list_devs", + .handler = &ftdi_handle_dev_list, + .mode = COMMAND_ANY, + .help = "list devices", + .usage = "list_devs", + }, + { + .name = "get_location", + .handler = &ftdi_handle_get_location, + .mode = COMMAND_ANY, + .help = "get device location", + .usage = "get_location", + }, COMMAND_REGISTRATION_DONE }; diff --git a/src/jtag/drivers/libusb_helper.c b/src/jtag/drivers/libusb_helper.c index 480091dfa9..510d750062 100644 --- a/src/jtag/drivers/libusb_helper.c +++ b/src/jtag/drivers/libusb_helper.c @@ -145,6 +145,104 @@ static bool jtag_libusb_match_serial(struct libusb_device_handle *device, return match; } +static int jtag_libusb_get_dev_location(struct libusb_device *dev, char *loc, int loc_len) +{ + int k, len, wr; + uint8_t dev_bus = 0; + uint8_t port_path[MAX_USB_PORTS]; + int path_len = 0; + +#ifdef HAVE_LIBUSB_GET_PORT_NUMBERS + path_len = libusb_get_port_numbers(dev, port_path, MAX_USB_PORTS); + if (path_len == LIBUSB_ERROR_OVERFLOW) { + LOG_WARNING("cannot determine path to usb device! (more than %i ports in path)", MAX_USB_PORTS); + return ERROR_FAIL; + } + dev_bus = libusb_get_bus_number(dev); +#endif /* HAVE_LIBUSB_GET_PORT_NUMBERS */ + + len = snprintf(loc, loc_len, "%d", dev_bus); + if (len < 0 || len >= (loc_len - len)) { + *loc = 0; + return ERROR_FAIL; + } + + for (k = 0; k < path_len; k++) { + wr = snprintf(&loc[len], loc_len - len, k == 0 ? "-%d" : ".%d", port_path[k]); + if (wr < 0 || wr >= (loc_len - len)) { + *loc = 0; + return ERROR_FAIL; + } + len += wr; + } + return ERROR_OK; +} + +int jtag_libusb_get_dev_location_by_handle(struct libusb_device_handle *dev, char *loc, int loc_len) +{ + return jtag_libusb_get_dev_location(libusb_get_device(dev), loc, loc_len); +} + +int jtag_libusb_get_devs_locations(const uint16_t vids[], const uint16_t pids[], char ***locations) +{ + int cnt, idx, devs_cnt = 0; + struct libusb_device **list; + struct libusb_device_descriptor desc; + char **locs; + /* -[.]... */ + #define MAX_DEV_LOCATION_LEN 128 + + cnt = libusb_get_device_list(jtag_libusb_context, &list); + if (cnt <= 0) { + LOG_WARNING("Cannot get devices list (%d)!", cnt); + return 0; + } + + locs = calloc(cnt, sizeof(char *)); + if (!locs) { + LOG_ERROR("Unable to allocate space USB devices list!"); + libusb_free_device_list(list, 1); + return 0; + } + for (idx = 0; idx < cnt; idx++) { + if (libusb_get_device_descriptor(list[idx], &desc) != 0) + continue; + if (!jtag_libusb_match_ids(&desc, vids, pids)) + continue; + + locs[devs_cnt] = calloc(1, MAX_DEV_LOCATION_LEN); + if (!locs[devs_cnt]) { + LOG_ERROR("Unable to allocate space USB device location!"); + jtag_libusb_free_devs_locations(locs, devs_cnt); + libusb_free_device_list(list, true); + return 0; + } + if (jtag_libusb_get_dev_location(list[idx], locs[devs_cnt], MAX_DEV_LOCATION_LEN) != ERROR_OK) + LOG_WARNING("Cannot get location for usb device!"); + + devs_cnt++; + } + *locations = locs; + + libusb_free_device_list(list, true); + + return devs_cnt; +} + +void jtag_libusb_free_devs_locations(char *locations[], int cnt) +{ + int i; + + if (!locations || cnt == 0) + return; + + for (i = 0; i < cnt; i++) { + if (locations[i]) + free(locations[i]); + } + free(locations); +} + int jtag_libusb_open(const uint16_t vids[], const uint16_t pids[], const char *product, struct libusb_device_handle **out, adapter_get_alternate_serial_fn adapter_get_alternate_serial) diff --git a/src/jtag/drivers/libusb_helper.h b/src/jtag/drivers/libusb_helper.h index 6c36dfab03..c766efcae6 100644 --- a/src/jtag/drivers/libusb_helper.h +++ b/src/jtag/drivers/libusb_helper.h @@ -93,4 +93,9 @@ uint8_t *oocd_libusb_dev_mem_alloc(libusb_device_handle *devh, int oocd_libusb_dev_mem_free(libusb_device_handle *devh, uint8_t *buffer, size_t length); +int jtag_libusb_get_dev_location_by_handle(struct libusb_device_handle *dev, char *loc, int loc_len); +int jtag_libusb_get_devs_locations(const uint16_t vids[], const uint16_t pids[], char ***locations); +void jtag_libusb_free_devs_locations(char *locations[], int cnt); + + #endif /* OPENOCD_JTAG_DRIVERS_LIBUSB_HELPER_H */ diff --git a/src/jtag/drivers/mpsse.c b/src/jtag/drivers/mpsse.c index fceb2a1681..8e9aebb19b 100644 --- a/src/jtag/drivers/mpsse.c +++ b/src/jtag/drivers/mpsse.c @@ -421,6 +421,11 @@ bool mpsse_is_high_speed(struct mpsse_ctx *ctx) return ctx->type != TYPE_FT2232C; } +struct libusb_device_handle *mpsse_get_usb_device(struct mpsse_ctx *ctx) +{ + return ctx->usb_dev; +} + void mpsse_purge(struct mpsse_ctx *ctx) { int err; diff --git a/src/jtag/drivers/mpsse.h b/src/jtag/drivers/mpsse.h index 737560d956..622b5c34f1 100644 --- a/src/jtag/drivers/mpsse.h +++ b/src/jtag/drivers/mpsse.h @@ -40,6 +40,7 @@ struct mpsse_ctx *mpsse_open(const uint16_t *vid, const uint16_t *pid, const cha const char *serial, const char *location, int channel); void mpsse_close(struct mpsse_ctx *ctx); bool mpsse_is_high_speed(struct mpsse_ctx *ctx); +struct libusb_device_handle *mpsse_get_usb_device(struct mpsse_ctx *ctx); /* Command queuing. These correspond to the MPSSE commands with the same names, but no need to care * about bit/byte transfer or data length limitation. Read data is guaranteed to be available only diff --git a/tcl/esp-config-schema.json b/tcl/esp-config-schema.json index 83298fe9a0..948b14dddb 100644 --- a/tcl/esp-config-schema.json +++ b/tcl/esp-config-schema.json @@ -16,6 +16,13 @@ "$ref": "#/definitions/targetsInfo" } }, + "interfaces": { + "description": "List of debug interfaces", + "type": "array", + "items": { + "$ref": "#/definitions/interfacesInfo" + } + }, "boards": { "description": "List of boards", "type": "array", @@ -49,6 +56,14 @@ "name": { "description": "Name of the target e.g. 'ESP32'", "type": "string" + }, + "idcode": { + "description": "JTAG IDCODE of the chip", + "type": "string" + }, + "ocdid": { + "description": "OCDID of Xtensa chip", + "type": "string" } }, "required": [ @@ -56,6 +71,24 @@ "name" ] }, + "interfacesInfo": { + "type": "object", + "description": "Information about debug interface", + "properties": { + "id": { + "description": "Interface ID. Actually this is OpenOCD debug adapter driver name.", + "type": "string" + }, + "config_file": { + "description": "Config file of debug interface e.g. 'ftdi/esp32_devkitj_v1.cfg'. Path relative to 'interface' sub-dir.", + "type": "string" + }, + "command": { + "description": "Command for the debug interface driver, e.g. 'ftdi'", + "type": "string" + } + } + }, "boardsInfo": { "type": "object", "description": "Information about a board", @@ -78,6 +111,15 @@ "items": { "type": "string" } + }, + "interface": { + "description": "Interface ID. Actually this is OpenOCD debug adapter driver name.", + "type": "string", + "$ref": "#/definitions/interfacesInfo/properties/id" + }, + "location": { + "description": "Location of the board, e.g. for USB it will look like 'usb://-[.]...'.", + "type": "string" } }, "required": [ diff --git a/tcl/esp-config.json b/tcl/esp-config.json index 77c682647d..7178da4363 100644 --- a/tcl/esp-config.json +++ b/tcl/esp-config.json @@ -1,39 +1,48 @@ { "version" : "1.0", "targets" : [ - { "id": "esp32", "name": "ESP32" }, - { "id": "esp32s2", "name": "ESP32-S2" }, - { "id": "esp32s3", "name": "ESP32-S3" }, - { "id": "esp32c2", "name": "ESP32-C2" }, - { "id": "esp32c3", "name": "ESP32-C3" }, - { "id": "esp32c5", "name": "ESP32-C5" }, - { "id": "esp32c6", "name": "ESP32-C6" }, - { "id": "esp32h2", "name": "ESP32-H2" }, - { "id": "esp32p4", "name": "ESP32-P4" } + { "id": "esp32", "name": "ESP32", "idcode": "0x120034e5", "ocdid": "0x0733bff2"}, + { "id": "esp32s2", "name": "ESP32-S2", "idcode": "0x120034e5", "ocdid": "0x03339fd2" }, + { "id": "esp32s3", "name": "ESP32-S3", "idcode": "0x120034e5", "ocdid": "0x0b339fd2" }, + { "id": "esp32c2", "name": "ESP32-C2", "idcode": "0x0000cc25" }, + { "id": "esp32c3", "name": "ESP32-C3", "idcode": "0x00005c25" }, + { "id": "esp32c5", "name": "ESP32-C5", "idcode": "0x00017c25" }, + { "id": "esp32c6", "name": "ESP32-C6", "idcode": "0x0000dc25" }, + { "id": "esp32h2", "name": "ESP32-H2", "idcode": "0x00010c25" }, + { "id": "esp32p4", "name": "ESP32-P4", "idcode": "0x00012c25" } + ], + "interfaces" : [ + { "id": "ftdi", "config_file": "ftdi/esp32_devkitj_v1.cfg", "command": "ftdi" }, + { "id": "esp_usb_jtag", "config_file": "esp_usb_jtag.cfg", "command": "espusbjtag" }, + { "id": "esp_usb_bridge", "config_file": "esp_usb_bridge.cfg", "command": "espusbjtag" } ], "boards" : [ { "name": "ESP-WROVER-KIT 3.3V", "description": "ESP-WROVER-KIT with 3.3V ESP32-WROVER-B module", "target": "esp32", + "interface": "ftdi", "config_files": ["board/esp32-wrover-kit-3.3v.cfg"] }, { "name": "ESP-WROVER-KIT 1.8V", "description": "ESP-WROVER-KIT with 1.8V ESP32-WROVER-B module", "target": "esp32", + "interface": "ftdi", "config_files": ["board/esp32-wrover-kit-1.8v.cfg"] }, { "name": "ESP32-ETHERNET-KIT", "description": "ESP32-ETHERNET-KIT with ESP32-WROVER-E module", "target": "esp32", + "interface": "ftdi", "config_files": ["board/esp32-ethernet-kit-3.3v.cfg"] }, { "name": "ESP32 chip (via ESP-PROG)", "description": "ESP32 debugging via ESP-PROG board", "target": "esp32", + "interface": "ftdi", "config_files": [ "interface/ftdi/esp32_devkitj_v1.cfg", "target/esp32.cfg" @@ -43,6 +52,7 @@ "name": "ESP32 chip (via ESP-PROG-2)", "description": "ESP32 debugging via ESP-PROG-2 board", "target": "esp32", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32-bridge.cfg" ] @@ -51,6 +61,7 @@ "name": "ESP32-SOLO-1 module (via ESP-PROG)", "description": "ESP32-SOLO-1 debugging via ESP-PROG board", "target": "esp32", + "interface": "ftdi", "config_files": [ "interface/ftdi/esp32_devkitj_v1.cfg", "target/esp32-solo-1.cfg" @@ -60,12 +71,14 @@ "name": "ESP32-S2-KALUGA-1", "description": "ESP32-S2-KALUGA-1 kit", "target": "esp32s2", + "interface": "ftdi", "config_files": ["board/esp32s2-kaluga-1.cfg"] }, { "name": "ESP32-S2 chip (via ESP-PROG)", "description": "ESP32-S2 debugging via ESP-PROG board", "target": "esp32s2", + "interface": "ftdi", "config_files": [ "interface/ftdi/esp32_devkitj_v1.cfg", "target/esp32s2.cfg" @@ -75,6 +88,7 @@ "name": "ESP32-S2 chip (via ESP-PROG-2)", "description": "ESP32-S2 debugging via ESP-PROG-2 board", "target": "esp32s2", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32s2-bridge.cfg" ] @@ -83,6 +97,7 @@ "name": "ESP32-S3 chip (via builtin USB-JTAG)", "description": "ESP32-S3 debugging via builtin USB-JTAG", "target": "esp32s3", + "interface": "esp_usb_jtag", "config_files": [ "board/esp32s3-builtin.cfg" ] @@ -91,6 +106,7 @@ "name": "ESP32-S3 chip (via ESP-PROG)", "description": "ESP32-S3 debugging via ESP-PROG board", "target": "esp32s3", + "interface": "ftdi", "config_files": [ "interface/ftdi/esp32_devkitj_v1.cfg", "target/esp32s3.cfg" @@ -100,6 +116,7 @@ "name": "ESP32-S3 chip (via ESP-PROG-2)", "description": "ESP32-S3 debugging via ESP-PROG-2 board", "target": "esp32s3", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32s3-bridge.cfg" ] @@ -108,6 +125,7 @@ "name": "ESP32-C2 chip (via ESP-PROG)", "description": "ESP32-C2 debugging via ESP-PROG board", "target": "esp32c2", + "interface": "ftdi", "config_files": [ "board/esp32c2-ftdi.cfg" ] @@ -116,6 +134,7 @@ "name": "ESP32-C2 chip (via ESP-PROG-2)", "description": "ESP32-C2 debugging via ESP-PROG-2 board", "target": "esp32c2", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32c2-bridge.cfg" ] @@ -124,6 +143,7 @@ "name": "ESP32-C3 chip (via builtin USB-JTAG)", "description": "ESP32-C3 debugging via builtin USB-JTAG", "target": "esp32c3", + "interface": "esp_usb_jtag", "config_files": [ "board/esp32c3-builtin.cfg" ] @@ -132,6 +152,7 @@ "name": "ESP32-C3 chip (via ESP-PROG)", "description": "ESP32-C3 debugging via ESP-PROG board", "target": "esp32c3", + "interface": "ftdi", "config_files": [ "board/esp32c3-ftdi.cfg" ] @@ -140,6 +161,7 @@ "name": "ESP32-C3 chip (via ESP-PROG-2)", "description": "ESP32-C3 debugging via ESP-PROG-2 board", "target": "esp32c3", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32c3-bridge.cfg" ] @@ -148,6 +170,7 @@ "name": "ESP32-C5 chip (via builtin USB-JTAG)", "description": "ESP32-C5 debugging via builtin USB-JTAG", "target": "esp32c5", + "interface": "esp_usb_jtag", "config_files": [ "board/esp32c5-builtin.cfg" ] @@ -156,6 +179,7 @@ "name": "ESP32-C5 chip (via ESP-PROG)", "description": "ESP32-C5 debugging via ESP-PROG board", "target": "esp32c5", + "interface": "ftdi", "config_files": [ "board/esp32c5-ftdi.cfg" ] @@ -164,6 +188,7 @@ "name": "ESP32-C5 chip (via ESP-PROG-2)", "description": "ESP32-C5 debugging via ESP-PROG-2 board", "target": "esp32c5", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32c5-bridge.cfg" ] @@ -172,6 +197,7 @@ "name": "ESP32-C6 chip (via builtin USB-JTAG)", "description": "ESP32-C6 debugging via builtin USB-JTAG", "target": "esp32c6", + "interface": "esp_usb_jtag", "config_files": [ "board/esp32c6-builtin.cfg" ] @@ -180,6 +206,7 @@ "name": "ESP32-C6 chip (via ESP-PROG)", "description": "ESP32-C6 debugging via ESP-PROG board", "target": "esp32c6", + "interface": "ftdi", "config_files": [ "board/esp32c6-ftdi.cfg" ] @@ -188,6 +215,7 @@ "name": "ESP32-C6 chip (via ESP-PROG-2)", "description": "ESP32-C6 debugging via ESP-PROG-2 board", "target": "esp32c6", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32c6-bridge.cfg" ] @@ -196,6 +224,7 @@ "name": "ESP32-C6 chip with LP core (via builtin USB-JTAG)", "description": "ESP32-C6 with LP core debugging via builtin USB-JTAG", "target": "esp32c6", + "interface": "esp_usb_jtag", "config_files": [ "board/esp32c6-lpcore-builtin.cfg" ] @@ -204,6 +233,7 @@ "name": "ESP32-C6 chip with LP core (via ESP-PROG)", "description": "ESP32-C6 with LP core debugging via ESP-PROG board", "target": "esp32c6", + "interface": "ftdi", "config_files": [ "board/esp32c6-lpcore-ftdi.cfg" ] @@ -212,6 +242,7 @@ "name": "ESP32-C6 chip with LP core (via ESP-PROG-2)", "description": "ESP32-C6 with LP core debugging via ESP-PROG-2 board", "target": "esp32c6", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32c6-lpcore-bridge.cfg" ] @@ -244,6 +275,7 @@ "name": "ESP32-H2 chip (via builtin USB-JTAG)", "description": "ESP32-H2 debugging via builtin USB-JTAG", "target": "esp32h2", + "interface": "esp_usb_jtag", "config_files": [ "board/esp32h2-builtin.cfg" ] @@ -252,6 +284,7 @@ "name": "ESP32-H2 chip (via ESP-PROG)", "description": "ESP32-H2 debugging via ESP-PROG board", "target": "esp32h2", + "interface": "ftdi", "config_files": [ "board/esp32h2-ftdi.cfg" ] @@ -260,6 +293,7 @@ "name": "ESP32-H2 chip (via ESP-PROG-2)", "description": "ESP32-H2 debugging via ESP-PROG-2 board", "target": "esp32h2", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32h2-bridge.cfg" ] @@ -268,6 +302,7 @@ "name": "ESP32-P4 chip (via builtin USB-JTAG)", "description": "ESP32-P4 debugging via builtin USB-JTAG", "target": "esp32p4", + "interface": "esp_usb_jtag", "config_files": [ "board/esp32p4-builtin.cfg" ] @@ -276,6 +311,7 @@ "name": "ESP32-P4 chip (via ESP-PROG)", "description": "ESP32-P4 debugging via ESP-PROG board", "target": "esp32p4", + "interface": "ftdi", "config_files": [ "board/esp32p4-ftdi.cfg" ] @@ -284,6 +320,7 @@ "name": "ESP32-P4 chip (via ESP-PROG-2)", "description": "ESP32-P4 debugging via ESP-PROG-2 board", "target": "esp32p4", + "interface": "esp_usb_bridge", "config_files": [ "board/esp32p4-bridge.cfg" ] diff --git a/tcl/interface/ftdi/esp32_devkitj_v1.cfg b/tcl/interface/ftdi/esp32_devkitj_v1.cfg index 6b8b5f0e16..ddd78c1cd7 100644 --- a/tcl/interface/ftdi/esp32_devkitj_v1.cfg +++ b/tcl/interface/ftdi/esp32_devkitj_v1.cfg @@ -5,6 +5,7 @@ # adapter driver ftdi + ftdi vid_pid 0x0403 0x6010 0x0403 0x6014 # interface 1 is the uart @@ -19,6 +20,11 @@ ftdi layout_signal LED2 -data 0x2000 ftdi layout_signal LED3 -data 0x4000 ftdi layout_signal LED4 -data 0x8000 +# Actually this should be in board config file, but we need this for board config detecting script to work. +# That script runs OpenOCD with interface config file only, in that case FTDI +# driver complains that transport is not selected +transport select jtag + # ESP32 series chips do not have a TRST input, and the SRST line is connected to the EN pin. # The target code doesn't handle SRST reset properly yet, so this is commented out: # ftdi layout_signal nSRST -oe 0x0020 diff --git a/tools/esp_detect_config.py b/tools/esp_detect_config.py new file mode 100755 index 0000000000..e7dd05928e --- /dev/null +++ b/tools/esp_detect_config.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-3.0-or-later + +import subprocess +import json +import argparse +import logging +import os.path +import socket +import time +import copy +import tempfile + + +class OpenOcd: + COMMAND_TOKEN = '\x1a' + TAPINS_NARSEL = 0x1C + TAPINS_NARSEL_ADRLEN = 8 + TAPINS_NARSEL_DATALEN = 32 + XDMREG_OCDID_NAR = 0x40 + + def __init__(self, oocd_path, scripts_dir, iface_config, host="127.0.0.1", port=6666, start_tmo=1, iface_cmd="", env=None): + self.host = host + self.port = port + self.buf_size = 4096 + self.sock = None + self.iface_cmd = iface_cmd + + run_args = [oocd_path] + if len(scripts_dir): + run_args += ["-s", scripts_dir] + run_args += ["-f", iface_config] + logging.debug("Run OpenOCD with args: %s", run_args) + self.proc = subprocess.Popen(args=run_args, bufsize=0, stdout=subprocess.PIPE, stdin=None, + stderr=subprocess.STDOUT, universal_newlines=True, env=env) + if self.proc.poll(): + logging.error("Failed to start OpenOCD %d!", self.proc.returncode) + self.readout_all_output() + raise RuntimeError + time.sleep(start_tmo) + # logging.error(self.proc.stdout.read(20)) + + def readout_all_output(self): + try: + outs,_ = self.proc.communicate(timeout=5) + except subprocess.TimeoutExpired: + logging.error("Failed to wait for OpenOCD output! Kill it.") + self.proc.kill() + outs,_ = self.proc.communicate() + logging.debug(outs) + + def connect(self): + try: + self._connect() + except ConnectionRefusedError: + logging.warning("Failed to connect to OpenOCD!") + self.proc.kill() + self.readout_all_output() + raise ConnectionRefusedError + return self + + def disconnect(self): + self._disconnect() + self.proc.kill() + self.readout_all_output() + + def __enter__(self): + self.connect() + + def __exit__(self, type, value, traceback): + self.disconnect() + + def _connect(self): + logging.debug("Try to connect to %s:%d", self.host, self.port) + self.sock = socket.create_connection((self.host, self.port), timeout=5) + logging.debug("Connected to %s:%d", self.host, self.port) + + def _disconnect(self): + if not self.sock: + return + try: + self.send("exit") + finally: + if self.sock: + self.sock.close() + + def send(self, cmd): + """Send a command string to TCL RPC. Return the result that was read.""" + data = (cmd + OpenOcd.COMMAND_TOKEN).encode("utf-8") + logging.debug("-> {%s}", data) + self.sock.send(data) + return self._recv() + + def _recv(self): + """Read from the stream until the token (\x1a) was received.""" + data = bytes() + while True: + chunk = self.sock.recv(self.buf_size) + data += chunk + if bytes(OpenOcd.COMMAND_TOKEN, encoding="utf-8") in chunk: + break + + logging.debug("<- {%s}", data) + + data = data.decode("utf-8").strip() + data = data[:-1] # strip trailing \x1a + + return data + + def is_xtensa(self, idcode): + return idcode == "0x120034e5" + + def xtensa_dm_read_reg(self, tap_id, reg_addr): + # scan NARSEL instruction to IR + self.send("irscan {} 0x{:x}".format(tap_id, OpenOcd.TAPINS_NARSEL)) + # scan reg addr to DR + self.send("drscan {} 0x{:x} 0x{:x}".format(tap_id, OpenOcd.TAPINS_NARSEL_ADRLEN, reg_addr << 1)) + # scan reg value to DR + return "0x" + self.send("drscan {} 0x{:x} 0x0".format(tap_id, OpenOcd.TAPINS_NARSEL_DATALEN)) + + def taps(self): + logging.debug("TAPs") + taps_str = self.send("scan_chain") + logging.debug(taps_str) + lines = taps_str.splitlines() + # Skip 2 heading lines + # TapName Enabled IdCode Expected IrLen IrCap IrMask + # -- ------------------- -------- ---------- ---------- ----- ----- ------ + if len(lines) < 3: + return [] + taps = [] + for line in lines[2:]: + comps = line.strip().split() + tap = {"name": comps[1], "idcode": comps[3], "ocdid": None} + if self.is_xtensa(tap["idcode"]): + tap["ocdid"] = self.xtensa_dm_read_reg(tap["name"], OpenOcd.XDMREG_OCDID_NAR) + taps.append(tap) + return taps + + def usb_devices(self): + devs_paths = self.send(self.iface_cmd + " list_devs") + devs_paths = devs_paths.splitlines() + logging.debug(devs_paths) + return devs_paths + + def default_usb_device(self): + dev_path = self.send(self.iface_cmd + " get_location") + logging.debug(dev_path) + return dev_path + +class OpenOcdRunError(RuntimeError): + pass + +def detect_and_populate_config(oocd, scripts, config_file, host, port, start_delay, + iface_id, iface_cmd, usb_location, esp_cfg): + try: + my_env = os.environ.copy() + if usb_location: + my_env["OPENOCD_USB_ADAPTER_LOCATION"] = usb_location + ocd = OpenOcd(oocd, scripts, config_file, host, port, start_delay, iface_cmd, env=my_env) + ocd.connect() + except: + raise OpenOcdRunError() + + try: + devices = ocd.usb_devices() + curr_dev = ocd.default_usb_device() + logging.info("Default device %s", curr_dev) + devices.remove(curr_dev) + logging.info("Found other devices %s", devices) + + iface_boards = [] + for tap in ocd.taps(): + logging.info("Found TAP %s, idcode %s, ocdid %s", tap["name"], tap["idcode"], tap["ocdid"]) + for tgt in esp_cfg["targets"]: + if tgt["idcode"] != tap["idcode"]: + continue + if tgt.get("ocdid") != tap["ocdid"]: + continue + logging.info("Matched idcode %s, ocdid %s for target %s", tap["idcode"], tap["ocdid"], tgt["name"]) + + found = False + for cfg_board in esp_cfg["boards"]: + if cfg_board["target"] == tgt["id"] and cfg_board["interface"] == iface_id: + # Check if we already have similar board entry. + # Similar board entries for the same target and USB + # location mean multiple cores (one per TAP) + duplicate = False + for b in iface_boards: + if b["name"] == cfg_board["name"]: + duplicate = True + break + if not duplicate: + found = True + logging.info("Found board %s", cfg_board["name"]) + iface_boards.append(cfg_board) + iface_boards[-1]["location"] = "usb://" + (usb_location if usb_location else curr_dev) + if not found: + logging.debug("No board entry found for idcode %s target %s @ iface %s!", + tgt["idcode"], tgt["name"], iface_id) + finally: + ocd.disconnect() + return iface_boards,devices + + +def main(): # type: () -> None + + parser = argparse.ArgumentParser('Espressif OpenOCD Config Detecting Tool') + + parser.add_argument('--esp-config', '-c', help='Path to Espressif OpenOCD config json file', type=str) + parser.add_argument('--oocd', '-b', help='Path to OpenOCD binary', type=str, default="openocd") + parser.add_argument('--scripts', '-s', help='Path to OpenOCD scripts', type=str, default="") + parser.add_argument('--debug', '-d', help='Debug level: 0-4', type=int, default=1) + parser.add_argument('--start-delay', '-r', help='OpenOCD startup time', type=int, default=1) + parser.add_argument('--host', '-a', help='OpenOCD host IP addr', type=str, default="127.0.0.1") + parser.add_argument('--port', '-p', help='OpenOCD TCL port number', type=int, default=6666) + parser.add_argument('--output', '-o', help='Output filename', type=str, default="stdout") + + args = parser.parse_args() + + if args.debug == 0: + log_level = logging.CRITICAL + elif args.debug == 1: + log_level = logging.ERROR + elif args.debug == 2: + log_level = logging.WARNING + elif args.debug == 3: + log_level = logging.INFO + else: + log_level = logging.DEBUG + logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level) + + with open(args.esp_config, "r") as f: + esp_cfg = json.load(f) + + cfg_out = copy.deepcopy(esp_cfg) + cfg_out["boards"] = [] + cfg_out.pop("interfaces", None) + + for iface in esp_cfg["interfaces"]: + logging.info("Check interface %s", iface) + extra_devs = [] + try: + iface_boards,extra_devs = detect_and_populate_config(args.oocd, args.scripts, + os.path.join("interface", iface["config_file"]), + args.host, args.port, args.start_delay, + iface["id"], iface["command"], None, esp_cfg) + except OpenOcdRunError: + logging.warning("Skip interface %s", iface) + continue + cfg_out["boards"].extend(iface_boards) + + for dev in extra_devs: + logging.info("Check interface %s @ %s", iface, dev) + try: + iface_boards,_ = detect_and_populate_config(args.oocd, args.scripts, + os.path.join("interface", iface["config_file"]), + args.host, args.port, args.start_delay, + iface["id"], iface["command"], dev, esp_cfg) + except OpenOcdRunError: + logging.warning("Skip interface %s @ %s", iface, dev) + continue + cfg_out["boards"].extend(iface_boards) + + if args.output == "stdout": + print(json.dumps(cfg_out)) + else: + with open(args.output, "w") as f: + json.dump(cfg_out, f, indent=2) + + +if __name__ == '__main__': + main()