forked from auracaster/openocd
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 example 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 on an ESP32-C6 programmer and STM32F401RE target shows the following performance: - loading 96KB image to RAM: 80 KB/sec - dumping 96KB image from RAM: 72 KB/sec - flashing 512KB image completes in about 13.5 seconds (including erase, program, and verify). Change-Id: I6e3e45016bd16ef2259561b1046788f5536b0687 Signed-off-by: Brian Kuschak <bkuschak@gmail.com> Reviewed-on: https://review.openocd.org/c/openocd/+/8973 Reviewed-by: Tomas Vanek <vanekt@fbl.cz> Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com> Tested-by: jenkins
This commit is contained in:
committed by
Tomas Vanek
parent
64ed1c74d5
commit
fcff4b712c
+9
-1
@@ -204,6 +204,9 @@ m4_define([RSHIM_ADAPTER],
|
|||||||
m4_define([AMTJTAGACCEL_ADAPTER],
|
m4_define([AMTJTAGACCEL_ADAPTER],
|
||||||
[[[amtjtagaccel], [Amontec JTAG-Accelerator driver], [AMTJTAGACCEL]]])
|
[[[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],
|
m4_define([HOST_ARM_BITBANG_ADAPTERS],
|
||||||
[[[ep93xx], [Bitbanging on EP93xx-based SBCs], [EP93XX]],
|
[[[ep93xx], [Bitbanging on EP93xx-based SBCs], [EP93XX]],
|
||||||
[[at91rm9200], [Bitbanging on AT91RM9200-based SBCs], [AT91RM9200]]])
|
[[at91rm9200], [Bitbanging on AT91RM9200-based SBCs], [AT91RM9200]]])
|
||||||
@@ -327,7 +330,8 @@ AC_ARG_ADAPTERS([
|
|||||||
JTAG_VPI_ADAPTER,
|
JTAG_VPI_ADAPTER,
|
||||||
RSHIM_ADAPTER,
|
RSHIM_ADAPTER,
|
||||||
XVC_ADAPTERS,
|
XVC_ADAPTERS,
|
||||||
LIBJAYLINK_ADAPTERS
|
LIBJAYLINK_ADAPTERS,
|
||||||
|
CMSIS_DAP_TCP_ADAPTER
|
||||||
],[auto])
|
],[auto])
|
||||||
|
|
||||||
AC_ARG_ADAPTERS([
|
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([DMEM_ADAPTER], ["x$is_linux" = "xyes"], [Linux /dev/mem])
|
||||||
PROCESS_ADAPTERS([SYSFSGPIO_ADAPTER], ["x$is_linux" = "xyes"], [Linux sysfs])
|
PROCESS_ADAPTERS([SYSFSGPIO_ADAPTER], ["x$is_linux" = "xyes"], [Linux sysfs])
|
||||||
PROCESS_ADAPTERS([REMOTE_BITBANG_ADAPTER], [true], [unused])
|
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([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([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"],
|
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_STLINK], [test "x$enable_stlink" != "xno"])
|
||||||
AM_CONDITIONAL([HLADAPTER_ICDI], [test "x$enable_ti_icdi" != "xno"])
|
AM_CONDITIONAL([HLADAPTER_ICDI], [test "x$enable_ti_icdi" != "xno"])
|
||||||
AM_CONDITIONAL([HLADAPTER_NULINK], [test "x$enable_nulink" != "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$enable_jlink" != "xno"], [
|
||||||
AS_IF([test "x$use_internal_libjaylink" = "xyes"], [
|
AS_IF([test "x$use_internal_libjaylink" = "xyes"], [
|
||||||
@@ -832,6 +839,7 @@ m4_foreach([adapterTuple], [USB1_ADAPTERS,
|
|||||||
AMTJTAGACCEL_ADAPTER,
|
AMTJTAGACCEL_ADAPTER,
|
||||||
HOST_ARM_BITBANG_ADAPTERS,
|
HOST_ARM_BITBANG_ADAPTERS,
|
||||||
HOST_ARM_OR_AARCH64_BITBANG_ADAPTERS,
|
HOST_ARM_OR_AARCH64_BITBANG_ADAPTERS,
|
||||||
|
CMSIS_DAP_TCP_ADAPTER,
|
||||||
DUMMY_ADAPTER,
|
DUMMY_ADAPTER,
|
||||||
OPTIONAL_LIBRARIES,
|
OPTIONAL_LIBRARIES,
|
||||||
COVERAGE],
|
COVERAGE],
|
||||||
|
|||||||
+38
-5
@@ -2621,7 +2621,7 @@ ch347 activity_led n4
|
|||||||
|
|
||||||
@deffn {Interface Driver} {cmsis-dap}
|
@deffn {Interface Driver} {cmsis-dap}
|
||||||
ARM CMSIS-DAP compliant based adapter v1 (USB HID based)
|
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]+
|
@deffn {Config Command} {cmsis-dap vid_pid} [vid pid]+
|
||||||
The vendor ID and product ID of the CMSIS-DAP device. If not specified
|
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 example
|
||||||
@end deffn
|
@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:
|
Specifies how to communicate with the adapter:
|
||||||
|
|
||||||
@itemize @minus
|
@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{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.
|
@item @option{tcp} Use TCP/IP instead of USB
|
||||||
This is the default if @command{cmsis-dap backend} is not specified.
|
@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 itemize
|
||||||
@end deffn
|
@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.
|
interface string or for user class interface.
|
||||||
@end deffn
|
@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}]
|
@deffn {Command} {cmsis-dap quirk} [@option{enable}|@option{disable}]
|
||||||
Enables or disables the following workarounds of known CMSIS-DAP adapter
|
Enables or disables the following workarounds of known CMSIS-DAP adapter
|
||||||
quirks:
|
quirks:
|
||||||
|
|||||||
@@ -225,6 +225,20 @@ static inline int socket_select(int max_fd,
|
|||||||
#endif
|
#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
|
#ifndef HAVE_ELF_H
|
||||||
|
|
||||||
typedef uint32_t Elf32_Addr;
|
typedef uint32_t Elf32_Addr;
|
||||||
|
|||||||
@@ -186,15 +186,17 @@ endif
|
|||||||
if OPENJTAG
|
if OPENJTAG
|
||||||
DRIVERFILES += %D%/openjtag.c
|
DRIVERFILES += %D%/openjtag.c
|
||||||
endif
|
endif
|
||||||
|
if CMSIS_DAP_CORE
|
||||||
|
DRIVERFILES += %D%/cmsis_dap.c
|
||||||
|
endif
|
||||||
if CMSIS_DAP_HID
|
if CMSIS_DAP_HID
|
||||||
DRIVERFILES += %D%/cmsis_dap_usb_hid.c
|
DRIVERFILES += %D%/cmsis_dap_usb_hid.c
|
||||||
DRIVERFILES += %D%/cmsis_dap.c
|
|
||||||
endif
|
endif
|
||||||
if CMSIS_DAP_USB
|
if CMSIS_DAP_USB
|
||||||
DRIVERFILES += %D%/cmsis_dap_usb_bulk.c
|
DRIVERFILES += %D%/cmsis_dap_usb_bulk.c
|
||||||
if !CMSIS_DAP_HID
|
|
||||||
DRIVERFILES += %D%/cmsis_dap.c
|
|
||||||
endif
|
endif
|
||||||
|
if CMSIS_DAP_TCP
|
||||||
|
DRIVERFILES += %D%/cmsis_dap_tcp.c
|
||||||
endif
|
endif
|
||||||
if IMX_GPIO
|
if IMX_GPIO
|
||||||
DRIVERFILES += %D%/imx_gpio.c
|
DRIVERFILES += %D%/imx_gpio.c
|
||||||
|
|||||||
@@ -52,9 +52,16 @@ const struct cmsis_dap_backend cmsis_dap_hid_backend = {
|
|||||||
};
|
};
|
||||||
#endif
|
#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[] = {
|
static const struct cmsis_dap_backend *const cmsis_dap_backends[] = {
|
||||||
&cmsis_dap_usb_backend,
|
&cmsis_dap_usb_backend,
|
||||||
&cmsis_dap_hid_backend,
|
&cmsis_dap_hid_backend,
|
||||||
|
&cmsis_dap_tcp_backend,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* USB Config */
|
/* USB Config */
|
||||||
@@ -2275,8 +2282,8 @@ static const struct command_registration cmsis_dap_subcommand_handlers[] = {
|
|||||||
.name = "backend",
|
.name = "backend",
|
||||||
.handler = &cmsis_dap_handle_backend_command,
|
.handler = &cmsis_dap_handle_backend_command,
|
||||||
.mode = COMMAND_CONFIG,
|
.mode = COMMAND_CONFIG,
|
||||||
.help = "set the communication backend to use (USB bulk or HID).",
|
.help = "set the communication backend to use (USB bulk or HID, or TCP).",
|
||||||
.usage = "(auto | usb_bulk | hid)",
|
.usage = "(auto | usb_bulk | hid | tcp)",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
.name = "quirk",
|
.name = "quirk",
|
||||||
@@ -2293,6 +2300,15 @@ static const struct command_registration cmsis_dap_subcommand_handlers[] = {
|
|||||||
.help = "USB bulk backend-specific commands",
|
.help = "USB bulk backend-specific commands",
|
||||||
.usage = "<cmd>",
|
.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
|
#endif
|
||||||
COMMAND_REGISTRATION_DONE
|
COMMAND_REGISTRATION_DONE
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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_hid_backend;
|
||||||
extern const struct cmsis_dap_backend cmsis_dap_usb_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_usb_subcommand_handlers[];
|
||||||
|
extern const struct command_registration cmsis_dap_tcp_subcommand_handlers[];
|
||||||
|
|
||||||
#define REPORT_ID_SIZE 1
|
#define REPORT_ID_SIZE 1
|
||||||
|
|
||||||
|
|||||||
@@ -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 <bkuschak@gmail.com> *
|
||||||
|
* *
|
||||||
|
* Adapted from cmsis_dap_usb_hid.c. Copyright (C) 2013-2018 by: *
|
||||||
|
* Mickaël Thomas <mickael9@gmail.com> *
|
||||||
|
* Maksym Hilliaka <oter@frozen-team.com> *
|
||||||
|
* Phillip Pearson <pp@myelin.co.nz> *
|
||||||
|
* Paul Fertser <fercerpav@gmail.com> *
|
||||||
|
* mike brown <mike@theshedworks.org.uk> *
|
||||||
|
* Spencer Oliver <spen@spen-soft.co.uk> *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <hidapi.h>
|
||||||
|
#ifdef HAVE_NETDB_H
|
||||||
|
#include <netdb.h>
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_NETINET_TCP_H
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#endif
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#ifdef HAVE_SYS_SOCKET_H
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#endif
|
||||||
|
#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.
|
||||||
|
|
||||||
|
/* 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 = "<host_name>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.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 = "<port_number>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.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 = "<milliseconds>",
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
};
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user