diff --git a/configure.ac b/configure.ac index b508f2ade..01077847b 100644 --- a/configure.ac +++ b/configure.ac @@ -204,6 +204,9 @@ m4_define([RSHIM_ADAPTER], m4_define([AMTJTAGACCEL_ADAPTER], [[[amtjtagaccel], [Amontec JTAG-Accelerator driver], [AMTJTAGACCEL]]]) +m4_define([CMSIS_DAP_TCP_ADAPTER], + [[[cmsis_dap_tcp], [CMSIS-DAP v2 compliant dongle (TCP)], [CMSIS_DAP_TCP]]]) + m4_define([HOST_ARM_BITBANG_ADAPTERS], [[[ep93xx], [Bitbanging on EP93xx-based SBCs], [EP93XX]], [[at91rm9200], [Bitbanging on AT91RM9200-based SBCs], [AT91RM9200]]]) @@ -327,7 +330,8 @@ AC_ARG_ADAPTERS([ JTAG_VPI_ADAPTER, RSHIM_ADAPTER, XVC_ADAPTERS, - LIBJAYLINK_ADAPTERS + LIBJAYLINK_ADAPTERS, + CMSIS_DAP_TCP_ADAPTER ],[auto]) AC_ARG_ADAPTERS([ @@ -623,6 +627,7 @@ PROCESS_ADAPTERS([LIBGPIOD_ADAPTERS], ["x$use_libgpiod" = "xyes"], [Linux libgpi PROCESS_ADAPTERS([DMEM_ADAPTER], ["x$is_linux" = "xyes"], [Linux /dev/mem]) 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([XVC_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"], @@ -682,6 +687,8 @@ AS_IF([test "x$enable_stlink" != "xno" -o "x$enable_ti_icdi" != "xno" -o "x$enab AM_CONDITIONAL([HLADAPTER_STLINK], [test "x$enable_stlink" != "xno"]) AM_CONDITIONAL([HLADAPTER_ICDI], [test "x$enable_ti_icdi" != "xno"]) AM_CONDITIONAL([HLADAPTER_NULINK], [test "x$enable_nulink" != "xno"]) +AM_CONDITIONAL([CMSIS_DAP_CORE], + [test "x$enable_cmsis_dap" != "xno" -o "x$enable_cmsis_dap_v2" != "xno" -o "x$enable_cmsis_dap_tcp" != "xno"]) AS_IF([test "x$enable_jlink" != "xno"], [ AS_IF([test "x$use_internal_libjaylink" = "xyes"], [ @@ -832,6 +839,7 @@ m4_foreach([adapterTuple], [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/doc/openocd.texi b/doc/openocd.texi index 4d8f79558..6c7aa86ec 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -2621,7 +2621,7 @@ ch347 activity_led n4 @deffn {Interface Driver} {cmsis-dap} ARM CMSIS-DAP compliant based adapter v1 (USB HID based) -or v2 (USB bulk). +or v2 (USB bulk or TCP/IP). @deffn {Config Command} {cmsis-dap vid_pid} [vid pid]+ The vendor ID and product ID of the CMSIS-DAP device. If not specified @@ -2632,14 +2632,17 @@ cmsis-dap vid_pid 0xc251 0xf001 0x0d28 0x0204 @end example @end deffn -@deffn {Config Command} {cmsis-dap backend} [@option{auto}|@option{usb_bulk}|@option{hid}] +@deffn {Config Command} {cmsis-dap backend} [@option{auto}|@option{usb_bulk}|@option{hid}|@option{tcp}] Specifies how to communicate with the adapter: @itemize @minus -@item @option{hid} Use HID generic reports - CMSIS-DAP v1 +@item @option{hid} Use USB HID generic reports - CMSIS-DAP v1 @item @option{usb_bulk} Use USB bulk - CMSIS-DAP v2 -@item @option{auto} First try USB bulk CMSIS-DAP v2, if not found try HID CMSIS-DAP v1. -This is the default if @command{cmsis-dap backend} is not specified. +@item @option{tcp} Use TCP/IP instead of USB +@item @option{auto} First try USB bulk CMSIS-DAP v2, if not found try USB HID +CMSIS-DAP v1, if not found try TCP (if @command{cmsis-dap tcp host} is +configured). This is the default if @command{cmsis-dap backend} is not +specified. @end itemize @end deffn @@ -2649,6 +2652,36 @@ In most cases need not to be specified and interfaces are searched by interface string or for user class interface. @end deffn +@deffn {Config Command} {cmsis-dap tcp host} hostname +Specifies the @var{hostname} or IP address of the remote programmer. For use +with 'tcp' backend only. +@example +cmsis-dap backend tcp +cmsis-dap tcp host 192.168.1.4 +@end example +@end deffn + +@deffn {Config Command} {cmsis-dap tcp port} port +Specifies the TCP @var{port} number used by the remote programmer for DAP +commands. The default port is 4441. For use with 'tcp' backend only. +@example +cmsis-dap backend tcp +cmsis-dap tcp host 192.168.1.4 +cmsis-dap tcp port 4441 +@end example +@end deffn + +@deffn {Config Command} {cmsis-dap tcp min_timeout} milliseconds +Sets a lower bound on the requested timeout in @var{milliseconds} to wait for +response packets from the remote programmer when using the 'tcp' backend. The +user may want to use a larger value on slower networks, to avoid getting +command mismatch errors. Default value is 150 milliseconds. For use with 'tcp' +backend only. +@example +cmsis-dap tcp min_timeout 200 +@end example +@end deffn + @deffn {Command} {cmsis-dap quirk} [@option{enable}|@option{disable}] Enables or disables the following workarounds of known CMSIS-DAP adapter quirks: diff --git a/src/helper/replacements.h b/src/helper/replacements.h index ecc0e5e95..747c2ae09 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_recv_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 3a3fe0772..5216e5d77 100644 --- a/src/jtag/drivers/Makefile.am +++ b/src/jtag/drivers/Makefile.am @@ -186,15 +186,17 @@ endif if OPENJTAG DRIVERFILES += %D%/openjtag.c endif +if CMSIS_DAP_CORE +DRIVERFILES += %D%/cmsis_dap.c +endif if CMSIS_DAP_HID DRIVERFILES += %D%/cmsis_dap_usb_hid.c -DRIVERFILES += %D%/cmsis_dap.c endif if CMSIS_DAP_USB DRIVERFILES += %D%/cmsis_dap_usb_bulk.c -if !CMSIS_DAP_HID -DRIVERFILES += %D%/cmsis_dap.c endif +if CMSIS_DAP_TCP +DRIVERFILES += %D%/cmsis_dap_tcp.c endif if IMX_GPIO DRIVERFILES += %D%/imx_gpio.c diff --git a/src/jtag/drivers/cmsis_dap.c b/src/jtag/drivers/cmsis_dap.c index b82647da7..3e1a3c5f0 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 */ @@ -2275,8 +2282,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", @@ -2293,6 +2300,15 @@ static const struct command_registration cmsis_dap_subcommand_handlers[] = { .help = "USB bulk backend-specific commands", .usage = "", }, +#endif +#if BUILD_CMSIS_DAP_TCP + { + .name = "tcp", + .chain = cmsis_dap_tcp_subcommand_handlers, + .mode = COMMAND_ANY, + .help = "TCP backend-specific commands", + .usage = "", + }, #endif COMMAND_REGISTRATION_DONE }; diff --git a/src/jtag/drivers/cmsis_dap.h b/src/jtag/drivers/cmsis_dap.h index 26dc6f0cf..880fc3350 100644 --- a/src/jtag/drivers/cmsis_dap.h +++ b/src/jtag/drivers/cmsis_dap.h @@ -77,7 +77,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 000000000..a654c6bff --- /dev/null +++ b/src/jtag/drivers/cmsis_dap_tcp.c @@ -0,0 +1,439 @@ +// 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 * + * * + * Adapted from cmsis_dap_usb_hid.c. Copyright (C) 2013-2018 by: * + * Mickaƫl Thomas * + * Maksym Hilliaka * + * Phillip Pearson * + * Paul Fertser * + * mike brown * + * Spencer Oliver * + * * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#ifdef HAVE_NETDB_H +#include +#endif +#ifdef HAVE_NETINET_TCP_H +#include +#endif +#include +#include +#ifdef HAVE_SYS_SOCKET_H +#include +#endif +#include + +#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. + +/* When flushing after an error, the CMSIS-DAP driver assumes the pipeline is + * empty if it doesn't get a response after a short 10 msec timeout. While this + * works for USB, it may not work for TCP/IP due to higher network latency. TCP + * response packets may take longer to arrive. We set a lower bound on timeout + * for blocking reads, to give enough time for packets to arrive. + * + * The user may override this default value by setting the parameter + * 'cmsis-dap tcp min_timeout' + */ +#define DEFAULT_MIN_TIMEOUT_MS 150 + +/* 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 __attribute__((packed)) 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. +}; + +/* Defines for struct cmsis_dap_tcp_packet_hdr requested by reviewer. */ +#define HEADER_SIGNATURE_OFFSET 0 +#define HEADER_LENGTH_OFFSET sizeof(uint32_t) +#define HEADER_PACKET_TYPE_OFFSET (sizeof(uint32_t) + sizeof(uint16_t)) +#define HEADER_RESERVED_OFFSET (sizeof(uint32_t) + sizeof(uint16_t) + \ + sizeof(uint8_t)) +#define HEADER_SIZE (sizeof(uint32_t) + sizeof(uint16_t) + \ + 2 * sizeof(uint8_t)) + +struct cmsis_dap_backend_data { + int sockfd; +}; + +static char *cmsis_dap_tcp_host; +static char *const cmsis_dap_tcp_port_default = STRINGIFY(CMSIS_DAP_TCP_PORT); +static char *cmsis_dap_tcp_port = cmsis_dap_tcp_port_default; +static int cmsis_dap_tcp_min_timeout_ms = DEFAULT_MIN_TIMEOUT_MS; + +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[] __attribute__((unused)), + uint16_t pids[] __attribute__((unused)), + const char *serial __attribute__((unused))) +{ + // Skip the open if the user has not provided a hostname. + if (!cmsis_dap_tcp_host) { + LOG_DEBUG("No TCP hostname, skipping open."); + return ERROR_FAIL; + } + + // Ignore vids, pids, serial. We use host and port subcommands instead. + + 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_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_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_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 retval; + } + 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 inline int readall_socket(int handle, void *buffer, unsigned int count) +{ + // Return after all count bytes available, or timeout, or error. + return recv(handle, buffer, count, MSG_WAITALL); +} + +static inline int peekall_socket(int handle, void *buffer, unsigned int count) +{ + /* Data remains unread on the socket until recv() is called later without + * the MSG_PEEK flag. Return after all count bytes available, or timeout, + * or error. + */ + return recv(handle, buffer, count, MSG_PEEK | MSG_WAITALL); +} + +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; + if (wait_ms) { + LOG_DEBUG_IO("CMSIS-DAP: using tcp timeout %d msec", wait_ms); + + // Don't use very short timeouts with TCP/IP as it may not be as fast + // to respond as USB. User configurable minimum value. + if (wait_ms < cmsis_dap_tcp_min_timeout_ms) { + wait_ms = cmsis_dap_tcp_min_timeout_ms; + LOG_DEBUG_IO("CMSIS-DAP: extending timeout to %d msec", wait_ms); + } + } + socket_recv_timeout(dap->bdata->sockfd, wait_ms); + + if (blocking == CMSIS_DAP_NON_BLOCKING) + socket_nonblock(dap->bdata->sockfd); + else + socket_block(dap->bdata->sockfd); + + // Peek at the header first to find the length. + int retval = peekall_socket(dap->bdata->sockfd, dap->packet_buffer, + HEADER_SIZE); + LOG_DEBUG_IO("Reading header returned %d", retval); + if (retval == 0) { + LOG_DEBUG_IO("CMSIS-DAP: tcp timeout reached 1"); + return ERROR_TIMEOUT_REACHED; + } else if (retval == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (blocking == CMSIS_DAP_NON_BLOCKING) + return ERROR_TIMEOUT_REACHED; + + LOG_DEBUG_IO("CMSIS-DAP: tcp timeout reached 2. timeout = %d msec", + wait_ms); + return ERROR_TIMEOUT_REACHED; + } + + LOG_ERROR("CMSIS-DAP: error reading header"); + log_socket_error("peek_socket"); + return ERROR_FAIL; + } else if (retval != HEADER_SIZE) { + LOG_ERROR("CMSIS-DAP: short header read"); + log_socket_error("peek_socket header short read"); + return ERROR_FAIL; + } + + struct cmsis_dap_tcp_packet_hdr header; + header.signature = le_to_h_u32(dap->packet_buffer + + HEADER_SIGNATURE_OFFSET); + header.length = le_to_h_u16(dap->packet_buffer + HEADER_LENGTH_OFFSET); + header.packet_type = dap->packet_buffer[HEADER_PACKET_TYPE_OFFSET]; + header.reserved = dap->packet_buffer[HEADER_RESERVED_OFFSET]; + + 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 + HEADER_SIZE > dap->packet_buffer_size) { + LOG_ERROR("CMSIS-DAP: Packet length %d too large to fit.", + header.length); + return ERROR_FAIL; + } + + // Read the complete packet. + int read_len = HEADER_SIZE + header.length; + LOG_DEBUG_IO("Reading %d bytes (%d payload)...", read_len, header.length); + retval = readall_socket(dap->bdata->sockfd, dap->packet_buffer, read_len); + + if (retval == 0) { + LOG_DEBUG_IO("CMSIS-DAP: tcp timeout reached 3"); + 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 != read_len) { + LOG_ERROR("CMSIS-DAP: short read. retval = %d. read_len = %d. " + "blocking = %s. wait_ms = %d", retval, read_len, + (blocking == CMSIS_DAP_NON_BLOCKING) ? "yes" : "no", wait_ms); + 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 __attribute__((unused))) +{ + const unsigned int len = txlen + HEADER_SIZE; + 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(dap->packet_buffer + HEADER_SIGNATURE_OFFSET, + DAP_PKT_HDR_SIGNATURE); + h_u16_to_le(dap->packet_buffer + HEADER_LENGTH_OFFSET, txlen); + dap->packet_buffer[HEADER_PACKET_TYPE_OFFSET] = DAP_PKT_TYPE_REQUEST; + dap->packet_buffer[HEADER_RESERVED_OFFSET] = 0; + + /* write data to device */ + LOG_DEBUG_IO("Writing %d bytes (%d payload)", len, txlen); + 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. + unsigned int packet_buffer_size = pkt_sz + HEADER_SIZE; + 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 + HEADER_SIZE; + dap->response = dap->packet_buffer + HEADER_SIZE; + 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_port) +{ + if (CMD_ARGC != 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + if (cmsis_dap_tcp_port != cmsis_dap_tcp_port_default) + free(cmsis_dap_tcp_port); + + cmsis_dap_tcp_port = strdup(CMD_ARGV[0]); + if (!cmsis_dap_tcp_port) { + LOG_ERROR("CMSIS-DAP: out of memory"); + return ERROR_FAIL; + } + return ERROR_OK; +} + +COMMAND_HANDLER(cmsis_dap_handle_tcp_host) +{ + if (CMD_ARGC != 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + free(cmsis_dap_tcp_host); + cmsis_dap_tcp_host = strdup(CMD_ARGV[0]); + if (!cmsis_dap_tcp_host) { + LOG_ERROR("CMSIS-DAP: out of memory"); + return ERROR_FAIL; + } + return ERROR_OK; +} + +COMMAND_HANDLER(cmsis_dap_handle_tcp_min_timeout) +{ + if (CMD_ARGC != 1) + return ERROR_COMMAND_SYNTAX_ERROR; + + COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], cmsis_dap_tcp_min_timeout_ms); + LOG_INFO("CMSIS-DAP: using minimum timeout of %d ms for TCP packets.", + cmsis_dap_tcp_min_timeout_ms); + 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 = "", + }, + { + .name = "port", + .handler = &cmsis_dap_handle_tcp_port, + .mode = COMMAND_CONFIG, + .help = "set the port number to use for DAP (for TCP backend only)", + .usage = "", + }, + { + .name = "min_timeout", + .handler = &cmsis_dap_handle_tcp_min_timeout, + .mode = COMMAND_CONFIG, + .help = "set the minimum timeout in milliseconds to wait for response " + "packets (for TCP backend only)", + .usage = "", + }, + 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 000000000..7889fb944 --- /dev/null +++ b/tcl/interface/cmsis-dap-tcp.cfg @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +# +# cmsis-dap-tcp - CMSIS-DAP protocol over TCP/IP. +# + +adapter driver cmsis-dap +cmsis-dap backend tcp + +# Specify the hostname or IP address of your programmer. +# cmsis-dap tcp host 192.168.1.4 + +# Optionally specify a port number. Default port is 4441. +# cmsis-dap tcp port 4441 + +# Optionally set a lower bound on packet timeouts (milliseconds), if using a +# slow network. +# cmsis-dap tcp min_timeout 300