This is an automated email from Gerrit. "Brian Kuschak <bkusc...@gmail.com>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/8973
-- gerrit commit 76734b1388535aeff1a87715880b0c6e3945970c Author: Brian Kuschak <bkusc...@gmail.com> Date: Wed Jun 11 12:41:04 2025 +0800 jtag/drivers/cmsis_dap: add new backend cmsis_dap_tcp Create a new backend for cmsis_dap driver that allows CMSIS-DAP protocol to run over TCP/IP instead of USB. An implementation of the firmware for an SWD programmer that uses this cmsis_dap_tcp protocol can be found at the link below. https://github.com/bkuschak/cmsis_dap_tcp_esp32 Using this cmsis_dap_tcp backend with the firmware above, flashing a 64KB image to an STM32 completes in about 8 seconds. Change-Id: I6e3e45016bd16ef2259561b1046788f5536b0687 Signed-off-by: Brian Kuschak <bkusc...@gmail.com> diff --git a/configure.ac b/configure.ac index 4b94716299..22e5e7bd05 100644 --- a/configure.ac +++ b/configure.ac @@ -206,6 +206,9 @@ m4_define([HOST_ARM_OR_AARCH64_BITBANG_ADAPTERS], [[imx_gpio], [Bitbanging on NXP IMX processors], [IMX_GPIO]], [[am335xgpio], [Bitbanging on AM335x (as found in Beaglebones)], [AM335XGPIO]]]) +m4_define([CMSIS_DAP_TCP_ADAPTER], + [[[cmsis_dap_tcp], [CMSIS-DAP v2 compliant dongle (TCP)], [CMSIS_DAP_TCP]]]) + # The word 'Adapter' in "Dummy Adapter" below must begin with a capital letter # because there is an M4 macro called 'adapter'. m4_define([DUMMY_ADAPTER], @@ -338,7 +341,8 @@ AC_ARG_ADAPTERS([ JTAG_VPI_ADAPTER, RSHIM_ADAPTER, PCIE_ADAPTERS, - LIBJAYLINK_ADAPTERS + LIBJAYLINK_ADAPTERS, + CMSIS_DAP_TCP_ADAPTER ],[auto]) AC_ARG_ADAPTERS([ @@ -648,6 +652,7 @@ PROCESS_ADAPTERS([LIBFTDI_USB1_ADAPTERS], ["x$use_libftdi" = "xyes" -a "x$use_li PROCESS_ADAPTERS([LIBGPIOD_ADAPTERS], ["x$use_libgpiod" = "xyes"], [Linux libgpiod]) PROCESS_ADAPTERS([SYSFSGPIO_ADAPTER], ["x$is_linux" = "xyes"], [Linux sysfs]) PROCESS_ADAPTERS([REMOTE_BITBANG_ADAPTER], [true], [unused]) +PROCESS_ADAPTERS([CMSIS_DAP_TCP_ADAPTER], [true], [unused]) PROCESS_ADAPTERS([LIBJAYLINK_ADAPTERS], ["x$use_internal_libjaylink" = "xyes" -o "x$use_libjaylink" = "xyes"], [libjaylink-0.2]) PROCESS_ADAPTERS([PCIE_ADAPTERS], ["x$is_linux" = "xyes" -a "x$ac_cv_header_linux_pci_h" = "xyes"], [Linux build]) PROCESS_ADAPTERS([SERIAL_PORT_ADAPTERS], ["x$can_build_buspirate" = "xyes"], @@ -854,6 +859,7 @@ m4_foreach([adapter], [USB1_ADAPTERS, AMTJTAGACCEL_ADAPTER, HOST_ARM_BITBANG_ADAPTERS, HOST_ARM_OR_AARCH64_BITBANG_ADAPTERS, + CMSIS_DAP_TCP_ADAPTER, DUMMY_ADAPTER, OPTIONAL_LIBRARIES, COVERAGE], diff --git a/src/helper/replacements.h b/src/helper/replacements.h index ecc0e5e955..95322e96ac 100644 --- a/src/helper/replacements.h +++ b/src/helper/replacements.h @@ -225,6 +225,20 @@ static inline int socket_select(int max_fd, #endif } +static inline int socket_timeout(int fd, unsigned long timeout_msec) +{ +#ifdef _WIN32 + DWORD timeout = timeout_msec; + return setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, + sizeof(timeout)); +#else + struct timeval tv; + tv.tv_sec = timeout_msec / 1000; + tv.tv_usec = (timeout_msec % 1000) * 1000; + return setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); +#endif +} + #ifndef HAVE_ELF_H typedef uint32_t Elf32_Addr; diff --git a/src/jtag/drivers/Makefile.am b/src/jtag/drivers/Makefile.am index b0dd8e3ad1..0942777145 100644 --- a/src/jtag/drivers/Makefile.am +++ b/src/jtag/drivers/Makefile.am @@ -198,6 +198,10 @@ if !CMSIS_DAP_HID DRIVERFILES += %D%/cmsis_dap.c endif endif +if CMSIS_DAP_TCP +DRIVERFILES += %D%/cmsis_dap_tcp.c +DRIVERFILES += %D%/cmsis_dap.c +endif if IMX_GPIO DRIVERFILES += %D%/imx_gpio.c endif diff --git a/src/jtag/drivers/cmsis_dap.c b/src/jtag/drivers/cmsis_dap.c index 2bfcfcc2b0..7f8eabeb43 100644 --- a/src/jtag/drivers/cmsis_dap.c +++ b/src/jtag/drivers/cmsis_dap.c @@ -52,9 +52,16 @@ const struct cmsis_dap_backend cmsis_dap_hid_backend = { }; #endif +#if BUILD_CMSIS_DAP_TCP == 0 +const struct cmsis_dap_backend cmsis_dap_tcp_backend = { + .name = "tcp" +}; +#endif + static const struct cmsis_dap_backend *const cmsis_dap_backends[] = { &cmsis_dap_usb_backend, &cmsis_dap_hid_backend, + &cmsis_dap_tcp_backend, }; /* USB Config */ @@ -2272,8 +2279,8 @@ static const struct command_registration cmsis_dap_subcommand_handlers[] = { .name = "backend", .handler = &cmsis_dap_handle_backend_command, .mode = COMMAND_CONFIG, - .help = "set the communication backend to use (USB bulk or HID).", - .usage = "(auto | usb_bulk | hid)", + .help = "set the communication backend to use (USB bulk or HID, or TCP).", + .usage = "(auto | usb_bulk | hid | tcp)", }, { .name = "quirk", @@ -2290,6 +2297,15 @@ static const struct command_registration cmsis_dap_subcommand_handlers[] = { .help = "USB bulk backend-specific commands", .usage = "<cmd>", }, +#endif +#if BUILD_CMSIS_DAP_TCP + { + .name = "tcp", + .chain = cmsis_dap_tcp_subcommand_handlers, + .mode = COMMAND_ANY, + .help = "TCP backend-specific commands", + .usage = "<cmd>", + }, #endif COMMAND_REGISTRATION_DONE }; diff --git a/src/jtag/drivers/cmsis_dap.h b/src/jtag/drivers/cmsis_dap.h index aded0e54a3..1052bd3589 100644 --- a/src/jtag/drivers/cmsis_dap.h +++ b/src/jtag/drivers/cmsis_dap.h @@ -78,7 +78,9 @@ struct cmsis_dap_backend { extern const struct cmsis_dap_backend cmsis_dap_hid_backend; extern const struct cmsis_dap_backend cmsis_dap_usb_backend; +extern const struct cmsis_dap_backend cmsis_dap_tcp_backend; extern const struct command_registration cmsis_dap_usb_subcommand_handlers[]; +extern const struct command_registration cmsis_dap_tcp_subcommand_handlers[]; #define REPORT_ID_SIZE 1 diff --git a/src/jtag/drivers/cmsis_dap_tcp.c b/src/jtag/drivers/cmsis_dap_tcp.c new file mode 100644 index 0000000000..b44df9a10e --- /dev/null +++ b/src/jtag/drivers/cmsis_dap_tcp.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/*************************************************************************** + * Provides CMSIS-DAP protocol over a TCP/IP socket. * + * UART and SWO are currently unsupported. * + * * + * Copyright (C) 2025 by Brian Kuschak <bkusc...@gmail.com> * + * * + * Adapted from cmsis_dap_usb_hid.c. Copyright (C) 2013-2018 by: * + * Mickaƫl Thomas <micka...@gmail.com> * + * Maksym Hilliaka <o...@frozen-team.com> * + * Phillip Pearson <p...@myelin.co.nz> * + * Paul Fertser <fercer...@gmail.com> * + * mike brown <m...@theshedworks.org.uk> * + * Spencer Oliver <s...@spen-soft.co.uk> * + * * + * Example usage: * + * adapter driver cmsis-dap * + * cmsis-dap backend tcp * + * cmsis-dap tcp host 192.168.1.4 * + * cmsis-dap tcp dap_port 4441 * + * transport select swd * + * adapter speed 2000 * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <hidapi.h> +#include <netdb.h> +#include <netinet/tcp.h> +#include <stdbool.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include "helper/command.h" +#include "helper/log.h" +#include "helper/replacements.h" +#include "helper/system.h" +#include "cmsis_dap.h" + +#define STRINGIFY(x) #x + +// If the protocol changes in the future, the SIGNATURE should also be changed. +#define DAP_PKT_HDR_SIGNATURE 0x00504144 // "DAP" +#define DAP_PKT_TYPE_REQUEST 0x01 +#define DAP_PKT_TYPE_RESPONSE 0x02 + +#define CMSIS_DAP_TCP_PORT 4441 // Default. Can be overridden. +#define CMSIS_DAP_PACKET_SIZE 1024 // Max payload size not including + // header. + +// CMSIS-DAP requests are variable length. With CMSIS-DAP over USB, the +// transfer sizes are preserved by the USB stack. However, TCP/IP is stream +// oriented so we perform our own packetization to preserve the boundaries +// between each request. This short header is prepended to each CMSIS-DAP +// request and response before being sent over the socket. Little endian format +// is used for multibyte values. +struct cmsis_dap_tcp_packet_hdr { + uint32_t signature; // "DAP" + uint16_t length; // Not including header length. + uint8_t packet_type; + uint8_t reserved; // Reserved for future use. +}; + +struct cmsis_dap_backend_data { + int sockfd; +}; + +static char *cmsis_dap_tcp_host; +static char *cmsis_dap_tcp_dap_port = STRINGIFY(CMSIS_DAP_TCP_PORT); + +static void cmsis_dap_tcp_close(struct cmsis_dap *dap); +static int cmsis_dap_tcp_alloc(struct cmsis_dap *dap, unsigned int pkt_sz); +static void cmsis_dap_tcp_free(struct cmsis_dap *dap); + +static int cmsis_dap_tcp_open(struct cmsis_dap *dap, uint16_t vids[], uint16_t + pids[], const char *serial) +{ + // Ignore vids, pids, serial. We use host and port subcommands instead. + (void)vids; + (void)pids; + (void)serial; + + dap->bdata = malloc(sizeof(struct cmsis_dap_backend_data)); + if (!dap->bdata) { + LOG_ERROR("CMSIS-DAP: unable to allocate memory"); + return ERROR_FAIL; + } + + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM + }; + struct addrinfo *result, *rp; + int fd = 0; + + LOG_INFO("CMSIS-DAP: Connecting to %s:%s using TCP backend", + cmsis_dap_tcp_host ? cmsis_dap_tcp_host : "localhost", + cmsis_dap_tcp_dap_port); + + /* Some of the following code was taken from remote_bitbang.c */ + /* Obtain address(es) matching host/port */ + int s = getaddrinfo(cmsis_dap_tcp_host, cmsis_dap_tcp_dap_port, &hints, + &result); + if (s != 0) { + LOG_ERROR("CMSIS-DAP: getaddrinfo: %s\n", gai_strerror(s)); + free(dap->bdata); + return ERROR_FAIL; + } + + /* getaddrinfo() returns a list of address structures. + Try each address until we successfully connect(2). + If socket(2) (or connect(2)) fails, we (close the socket + and) try the next address. */ + + for (rp = result; rp ; rp = rp->ai_next) { + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) + continue; + + if (connect(fd, rp->ai_addr, rp->ai_addrlen) != -1) { + LOG_DEBUG("Connected."); + break; /* Success */ + } + + close(fd); + } + + freeaddrinfo(result); + + if (!rp) { /* No address succeeded */ + LOG_ERROR("CMSIS-DAP: unable to connect to device %s:%s", + cmsis_dap_tcp_host ? cmsis_dap_tcp_host : "localhost", + cmsis_dap_tcp_dap_port); + log_socket_error("Failed to connect"); + free(dap->bdata); + dap->bdata = NULL; + return ERROR_FAIL; + } + + /* Set NODELAY to minimize latency. */ + int one = 1; + /* On Windows optval has to be a const char *. */ + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char *)&one, sizeof(one)); + + dap->bdata->sockfd = fd; + + int retval = cmsis_dap_tcp_alloc(dap, CMSIS_DAP_PACKET_SIZE); + if (retval != ERROR_OK) { + cmsis_dap_tcp_close(dap); + return ERROR_FAIL; + } + return ERROR_OK; +} + +static void cmsis_dap_tcp_close(struct cmsis_dap *dap) +{ + if (close_socket(dap->bdata->sockfd) != 0) { + log_socket_error("close_socket"); + } + + if(dap->bdata) + free(dap->bdata); + dap->bdata = NULL; + cmsis_dap_tcp_free(dap); +} + +static int cmsis_dap_tcp_read(struct cmsis_dap *dap, int transfer_timeout_ms, + enum cmsis_dap_blocking blocking) +{ + int wait_ms = (blocking == CMSIS_DAP_NON_BLOCKING) ? 0 : + transfer_timeout_ms; + socket_timeout(dap->bdata->sockfd, wait_ms); + + if(blocking == CMSIS_DAP_NON_BLOCKING) + socket_nonblock(dap->bdata->sockfd); + else + socket_block(dap->bdata->sockfd); + + // Read the header first to find the length, then read the rest. + struct cmsis_dap_tcp_packet_hdr *header = (void*)dap->packet_buffer; + int retval = read_socket(dap->bdata->sockfd, dap->packet_buffer, + sizeof(*header)); + LOG_DEBUG_IO("Reading header returned %d", retval); + + if (retval == 0) { + return ERROR_TIMEOUT_REACHED; + } else if (retval == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return ERROR_TIMEOUT_REACHED; + } else { + LOG_ERROR("CMSIS-DAP: error reading header"); + log_socket_error("read_socket"); + return ERROR_FAIL; + } + } else if (retval != sizeof(*header)) { + log_socket_error("read_socket header short read"); + return ERROR_FAIL; + } + + header->signature = le_to_h_u32((void*)&header->signature); + header->length = le_to_h_u16((void*)&header->length); + + if(header->signature != DAP_PKT_HDR_SIGNATURE) { + LOG_ERROR("CMSIS-DAP: Unrecognized packet signature 0x%08x", + header->signature); + return ERROR_FAIL; + } else if(header->packet_type != DAP_PKT_TYPE_RESPONSE) { + LOG_ERROR("CMSIS-DAP: Unrecognized packet type 0x%02x", + header->packet_type); + return ERROR_FAIL; + } else if(header->length + sizeof(*header) > dap->packet_buffer_size) { + LOG_ERROR("CMSIS-DAP: Packet length %d too large to fit.", + header->length); + return ERROR_FAIL; + } + + LOG_DEBUG_IO("Reading %d bytes...", header->length); + retval = read_socket(dap->bdata->sockfd, dap->packet_buffer + + sizeof(*header), header->length); + + if (retval == 0) { + return ERROR_TIMEOUT_REACHED; + } else if (retval == -1) { + LOG_ERROR("CMSIS-DAP: error reading data"); + log_socket_error("read_socket"); + return ERROR_FAIL; + } else if (retval != header->length) { + log_socket_error("read_socket short read"); + return ERROR_FAIL; + } + return retval; +} + +static int cmsis_dap_tcp_write(struct cmsis_dap *dap, int txlen, int + timeout_ms) +{ + (void) timeout_ms; + + struct cmsis_dap_tcp_packet_hdr *header = (void*)dap->packet_buffer; + const unsigned int len = txlen + sizeof(*header); + if(len > dap->packet_buffer_size) { + LOG_ERROR("CMSIS-DAP: Packet length %d exceeds TCP buffer size!", len); + return ERROR_FAIL; + } + + /* Set the header values. */ + h_u32_to_le((void*)&header->signature, DAP_PKT_HDR_SIGNATURE); + h_u16_to_le((void*)&header->length, txlen); + header->packet_type = DAP_PKT_TYPE_REQUEST; + header->reserved = 0; + + /* write data to device */ + LOG_DEBUG_IO("Writing %d bytes", len); + int retval = write_socket(dap->bdata->sockfd, dap->packet_buffer, len); + if (retval < 0) { + log_socket_error("write_socket"); + return ERROR_FAIL; + } else if (retval != (int)len) { + LOG_ERROR("CMSIS-DAP: error writing data"); + log_socket_error("write_socket short write"); + return ERROR_FAIL; + } + return retval; +} + +static int cmsis_dap_tcp_alloc(struct cmsis_dap *dap, unsigned int pkt_sz) +{ + // Reserve space for the packet header. + struct cmsis_dap_tcp_packet_hdr header; + unsigned int packet_buffer_size = pkt_sz + sizeof(header); + uint8_t *buf = malloc(packet_buffer_size); + if (!buf) { + LOG_ERROR("CMSIS-DAP: unable to allocate CMSIS-DAP packet buffer"); + return ERROR_FAIL; + } + + dap->packet_buffer = buf; + dap->packet_size = pkt_sz; + dap->packet_usable_size = pkt_sz; + dap->packet_buffer_size = packet_buffer_size; + + dap->command = dap->packet_buffer + sizeof(header); + dap->response = dap->packet_buffer + sizeof(header); + return ERROR_OK; +} + +static void cmsis_dap_tcp_free(struct cmsis_dap *dap) +{ + free(dap->packet_buffer); + dap->packet_buffer = NULL; +} + +static void cmsis_dap_tcp_cancel_all(struct cmsis_dap *dap) +{ +} + +COMMAND_HANDLER(cmsis_dap_handle_tcp_dap_port) +{ + if (CMD_ARGC == 1) + cmsis_dap_tcp_dap_port = strdup(CMD_ARGV[0]); + else + LOG_ERROR("CMSIS-DAP: expected exactly one argument to " + "cmsis-dap tcp dap_port <port_number>"); + + return ERROR_OK; +} + +COMMAND_HANDLER(cmsis_dap_handle_tcp_host) +{ + if (CMD_ARGC == 1) + cmsis_dap_tcp_host = strdup(CMD_ARGV[0]); + else + LOG_ERROR("CMSIS-DAP: expected exactly one argument to " + "cmsis-dap tcp host <host_name>"); + + return ERROR_OK; +} + +const struct command_registration cmsis_dap_tcp_subcommand_handlers[] = { + { + .name = "host", + .handler = &cmsis_dap_handle_tcp_host, + .mode = COMMAND_CONFIG, + .help = "set the host name to use (for TCP backend only)", + .usage = "<host_name>", + }, + { + .name = "dap_port", + .handler = &cmsis_dap_handle_tcp_dap_port, + .mode = COMMAND_CONFIG, + .help = "set the port number to use for DAP (for TCP backend only)", + .usage = "<port_number>", + }, + COMMAND_REGISTRATION_DONE +}; + +const struct cmsis_dap_backend cmsis_dap_tcp_backend = { + .name = "tcp", + .open = cmsis_dap_tcp_open, + .close = cmsis_dap_tcp_close, + .read = cmsis_dap_tcp_read, + .write = cmsis_dap_tcp_write, + .packet_buffer_alloc = cmsis_dap_tcp_alloc, + .packet_buffer_free = cmsis_dap_tcp_free, + .cancel_all = cmsis_dap_tcp_cancel_all, +}; diff --git a/tcl/interface/cmsis-dap-tcp.cfg b/tcl/interface/cmsis-dap-tcp.cfg new file mode 100644 index 0000000000..ddd9971c19 --- /dev/null +++ b/tcl/interface/cmsis-dap-tcp.cfg @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# +# cmsis-dap-tcp - CMSIS-DAP protocol over TCP/IP. +# +# Change the host and dap_port parameters to match your programmer. +# + +adapter driver cmsis-dap +cmsis-dap backend tcp +cmsis-dap tcp host 192.168.1.4 +cmsis-dap tcp dap_port 4441 --