dap: add CMSIS-DAP compatible controller
Add CMSIS-DAP compatible controller which is a handler between the host interface and SWD driver. The controller follows CMSIS-DAP reference implementation. It expects a request buffer from the host interface, splits it to simple transfers and forwards to the DP driver, and finally returns a response buffer to the host. Interface to the host can be implemented with USB HID device support. Controller implements only SW-DP support and is tested with pyOCD and ADIv5.x. Signed-off-by: Johann Fischer <j.fischer@phytec.de>
This commit is contained in:
parent
3cf630fb0b
commit
7c9259abbc
7 changed files with 1107 additions and 30 deletions
|
@ -2,45 +2,45 @@
|
|||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: >
|
||||
This is a representation of the Serial Wire Debug Port interface
|
||||
implementation by GPIO bit-banging.
|
||||
This is a representation of the Serial Wire Debug Port interface
|
||||
implementation by GPIO bit-banging.
|
||||
|
||||
compatible: "zephyr,swdp-gpio"
|
||||
|
||||
include: base.yaml
|
||||
|
||||
properties:
|
||||
clk-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWCLK output
|
||||
clk-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWCLK output
|
||||
|
||||
dout-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWDIO output
|
||||
dout-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWDIO output
|
||||
|
||||
din-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWDIO input
|
||||
din-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWDIO input
|
||||
|
||||
dnoe-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWDIO NOE output
|
||||
dnoe-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWDIO NOE output
|
||||
|
||||
noe-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWD NOE output
|
||||
noe-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for SWD NOE output
|
||||
|
||||
reset-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for RESET output
|
||||
reset-gpios:
|
||||
type: phandle-array
|
||||
required: true
|
||||
description: GPIO pin used for RESET output
|
||||
|
||||
port-write-cycles:
|
||||
type: int
|
||||
required: true
|
||||
description: Number of processor cycles for I/O Port write operations
|
||||
port-write-cycles:
|
||||
type: int
|
||||
required: true
|
||||
description: Number of processor cycles for I/O Port write operations
|
||||
|
|
|
@ -37,6 +37,7 @@ add_subdirectory_ifdef(CONFIG_ARM_SIP_SVC_SUBSYS sip_svc)
|
|||
add_subdirectory_ifdef(CONFIG_BINDESC bindesc)
|
||||
add_subdirectory_ifdef(CONFIG_BT bluetooth)
|
||||
add_subdirectory_ifdef(CONFIG_CONSOLE_SUBSYS console)
|
||||
add_subdirectory_ifdef(CONFIG_DAP dap)
|
||||
add_subdirectory_ifdef(CONFIG_DEMAND_PAGING demand_paging)
|
||||
add_subdirectory_ifdef(CONFIG_DISK_ACCESS disk)
|
||||
add_subdirectory_ifdef(CONFIG_DSP dsp)
|
||||
|
|
|
@ -11,6 +11,7 @@ source "subsys/bindesc/Kconfig"
|
|||
source "subsys/bluetooth/Kconfig"
|
||||
source "subsys/canbus/Kconfig"
|
||||
source "subsys/console/Kconfig"
|
||||
source "subsys/dap/Kconfig"
|
||||
source "subsys/debug/Kconfig"
|
||||
source "subsys/demand_paging/Kconfig"
|
||||
source "subsys/dfu/Kconfig"
|
||||
|
|
7
subsys/dap/CMakeLists.txt
Normal file
7
subsys/dap/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Copyright (c) 2019, PHYTEC Messtechnik GmbH
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
zephyr_include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
zephyr_library()
|
||||
zephyr_library_sources(cmsis_dap.c)
|
24
subsys/dap/Kconfig
Normal file
24
subsys/dap/Kconfig
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Copyright (c) 2018-2019 PHYTEC Messtechnik GmbH
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
menuconfig DAP
|
||||
bool "Debug Access Port support [EXPERIMENTAL]"
|
||||
select EXPERIMENTAL
|
||||
select DP_DRIVER
|
||||
help
|
||||
Debug Access Port support (currently CMSIS DAP only)
|
||||
|
||||
if DAP
|
||||
|
||||
config CMSIS_DAP_PACKET_COUNT
|
||||
int "Maximum packet buffers for request and response data."
|
||||
default 4
|
||||
range 1 255
|
||||
help
|
||||
Maximum packet buffers for request and response data.
|
||||
|
||||
module = DAP
|
||||
module-str = dap
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
|
||||
endif # DAP
|
925
subsys/dap/cmsis_dap.c
Normal file
925
subsys/dap/cmsis_dap.c
Normal file
|
@ -0,0 +1,925 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 PHYTEC Messtechnik GmbH
|
||||
* Copyright (c) 2023 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is based on DAP.c from CMSIS-DAP Source (Revision: V2.0.0)
|
||||
* https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/DAP/Firmware
|
||||
* Copyright (c) 2013-2017 ARM Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <zephyr/drivers/swdp.h>
|
||||
|
||||
#include <cmsis_dap.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(dap, CONFIG_DAP_LOG_LEVEL);
|
||||
|
||||
const char dap_fw_ver[] = DAP_FW_VER;
|
||||
|
||||
#define DAP_STATE_CONNECTED 0
|
||||
|
||||
struct dap_context {
|
||||
struct device *swdp_dev;
|
||||
atomic_t state;
|
||||
uint8_t debug_port;
|
||||
uint8_t capabilities;
|
||||
uint8_t pkt_size;
|
||||
struct {
|
||||
/* Idle cycles after transfer */
|
||||
uint8_t idle_cycles;
|
||||
/* Number of retries after WAIT response */
|
||||
uint16_t retry_count;
|
||||
/* Number of retries if read value does not match */
|
||||
uint16_t match_retry;
|
||||
/* Match Mask */
|
||||
uint32_t match_mask;
|
||||
} transfer;
|
||||
};
|
||||
|
||||
static struct dap_context dap_ctx[1];
|
||||
|
||||
/* Get DAP Information */
|
||||
static uint16_t dap_info(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
uint8_t *info = response + 1;
|
||||
uint8_t id = request[0];
|
||||
uint8_t length = 0U;
|
||||
|
||||
switch (id) {
|
||||
case DAP_ID_VENDOR:
|
||||
LOG_DBG("ID_VENDOR unsupported");
|
||||
break;
|
||||
case DAP_ID_PRODUCT:
|
||||
LOG_DBG("ID_PRODUCT unsupported");
|
||||
break;
|
||||
case DAP_ID_SER_NUM:
|
||||
LOG_DBG("ID_SER_NUM unsupported");
|
||||
break;
|
||||
case DAP_ID_FW_VER:
|
||||
LOG_DBG("ID_FW_VER");
|
||||
memcpy(info, dap_fw_ver, sizeof(dap_fw_ver));
|
||||
length = (uint8_t)sizeof(dap_fw_ver);
|
||||
break;
|
||||
case DAP_ID_DEVICE_VENDOR:
|
||||
LOG_DBG("ID_DEVICE_VENDOR unsupported");
|
||||
break;
|
||||
case DAP_ID_DEVICE_NAME:
|
||||
LOG_DBG("ID_DEVICE_NAME unsupported");
|
||||
break;
|
||||
case DAP_ID_CAPABILITIES:
|
||||
info[0] = ctx->capabilities;
|
||||
LOG_DBG("ID_CAPABILITIES 0x%0x", info[0]);
|
||||
length = 1U;
|
||||
break;
|
||||
case DAP_ID_SWO_BUFFER_SIZE:
|
||||
LOG_DBG("ID_SWO_BUFFER_SIZE unsupported");
|
||||
break;
|
||||
case DAP_ID_PACKET_SIZE:
|
||||
LOG_DBG("ID_PACKET_SIZE");
|
||||
sys_put_le16(ctx->capabilities, &info[0]);
|
||||
length = 2U;
|
||||
break;
|
||||
case DAP_ID_PACKET_COUNT:
|
||||
LOG_DBG("ID_PACKET_COUNT");
|
||||
info[0] = CONFIG_CMSIS_DAP_PACKET_COUNT;
|
||||
length = 1U;
|
||||
break;
|
||||
default:
|
||||
LOG_DBG("unsupported ID");
|
||||
break;
|
||||
}
|
||||
|
||||
response[0] = (uint8_t)length;
|
||||
|
||||
return length + 1U;
|
||||
}
|
||||
|
||||
/* Process Host Status command and prepare response */
|
||||
static uint16_t dap_host_status(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
switch (request[0]) {
|
||||
case DAP_DEBUGGER_CONNECTED:
|
||||
if (request[1]) {
|
||||
LOG_INF("Debugger connected");
|
||||
} else {
|
||||
LOG_INF("Debugger disconnected");
|
||||
}
|
||||
break;
|
||||
case DAP_TARGET_RUNNING:
|
||||
LOG_DBG("unsupported");
|
||||
break;
|
||||
default:
|
||||
*response = DAP_ERROR;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
response[0] = DAP_OK;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/* Process Connect command and prepare response */
|
||||
static uint16_t dap_connect(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
const struct swdp_api *api = ctx->swdp_dev->api;
|
||||
uint8_t port;
|
||||
|
||||
if (request[0] == DAP_PORT_AUTODETECT) {
|
||||
port = DAP_PORT_SWD;
|
||||
} else {
|
||||
port = request[0];
|
||||
}
|
||||
|
||||
switch (port) {
|
||||
case DAP_PORT_SWD:
|
||||
LOG_INF("port swd");
|
||||
ctx->debug_port = DAP_PORT_SWD;
|
||||
|
||||
if (atomic_test_and_set_bit(&ctx->state,
|
||||
DAP_STATE_CONNECTED)) {
|
||||
LOG_ERR("DAP device is already connected");
|
||||
port = DAP_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
api->swdp_port_on(ctx->swdp_dev);
|
||||
break;
|
||||
case DAP_PORT_JTAG:
|
||||
LOG_ERR("port unsupported");
|
||||
port = DAP_ERROR;
|
||||
break;
|
||||
default:
|
||||
LOG_DBG("port disabled");
|
||||
port = DAP_PORT_DISABLED;
|
||||
break;
|
||||
}
|
||||
|
||||
response[0] = port;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/* Process Disconnect command and prepare response */
|
||||
static uint16_t dap_disconnect(struct dap_context *const ctx,
|
||||
uint8_t *const response)
|
||||
{
|
||||
const struct swdp_api *api = ctx->swdp_dev->api;
|
||||
|
||||
LOG_DBG("");
|
||||
|
||||
ctx->debug_port = DAP_PORT_DISABLED;
|
||||
|
||||
if (atomic_test_bit(&ctx->state, DAP_STATE_CONNECTED)) {
|
||||
api->swdp_port_off(ctx->swdp_dev);
|
||||
} else {
|
||||
LOG_WRN("DAP device is not connected");
|
||||
}
|
||||
|
||||
response[0] = DAP_OK;
|
||||
atomic_clear_bit(&ctx->state, DAP_STATE_CONNECTED);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Process Delay command and prepare response */
|
||||
static uint16_t dap_delay(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
uint16_t delay = sys_get_le16(&request[0]);
|
||||
|
||||
LOG_DBG("dap delay %u ms", delay);
|
||||
|
||||
k_busy_wait(delay * USEC_PER_MSEC);
|
||||
response[0] = DAP_OK;
|
||||
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/* Process Reset Target command and prepare response */
|
||||
static uint16_t dap_reset_target(struct dap_context *const ctx,
|
||||
uint8_t *const response)
|
||||
{
|
||||
response[0] = DAP_OK;
|
||||
response[1] = 0U;
|
||||
LOG_WRN("unsupported");
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
/* Process SWJ Pins command and prepare response */
|
||||
static uint16_t dap_swj_pins(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
const struct swdp_api *api = ctx->swdp_dev->api;
|
||||
uint8_t value = request[0];
|
||||
uint8_t select = request[1];
|
||||
uint32_t wait = sys_get_le32(&request[2]);
|
||||
uint8_t state;
|
||||
|
||||
if (!atomic_test_bit(&ctx->state, DAP_STATE_CONNECTED)) {
|
||||
LOG_ERR("DAP device is not connected");
|
||||
response[0] = DAP_ERROR;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/* Skip if nothing selected. */
|
||||
if (select) {
|
||||
api->swdp_set_pins(ctx->swdp_dev, select, value);
|
||||
}
|
||||
|
||||
/* TODO: implement wait */
|
||||
api->swdp_get_pins(ctx->swdp_dev, &state);
|
||||
LOG_ERR("select 0x%02x, value 0x%02x, wait %u, state 0x%02x",
|
||||
select, value, wait, state);
|
||||
|
||||
response[0] = state;
|
||||
|
||||
return sizeof(state);
|
||||
}
|
||||
|
||||
/* Process SWJ Clock command and prepare response */
|
||||
static uint16_t dap_swj_clock(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
const struct swdp_api *api = ctx->swdp_dev->api;
|
||||
uint32_t clk = sys_get_le32(&request[0]);
|
||||
|
||||
LOG_DBG("clock %d", clk);
|
||||
|
||||
if (atomic_test_bit(&ctx->state, DAP_STATE_CONNECTED)) {
|
||||
if (clk) {
|
||||
api->swdp_set_clock(ctx->swdp_dev, clk);
|
||||
response[0] = DAP_OK;
|
||||
} else {
|
||||
response[0] = DAP_ERROR;
|
||||
}
|
||||
} else {
|
||||
LOG_WRN("DAP device is not connected");
|
||||
response[0] = DAP_OK;
|
||||
}
|
||||
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/* Process SWJ Sequence command and prepare response */
|
||||
static uint16_t dap_swj_sequence(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
const struct swdp_api *api = ctx->swdp_dev->api;
|
||||
uint16_t count = request[0];
|
||||
|
||||
LOG_DBG("count %u", count);
|
||||
|
||||
if (count == 0U) {
|
||||
count = 256U;
|
||||
}
|
||||
|
||||
if (!atomic_test_bit(&ctx->state, DAP_STATE_CONNECTED)) {
|
||||
LOG_ERR("DAP device is not connected");
|
||||
response[0] = DAP_ERROR;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
api->swdp_sequence(ctx->swdp_dev, count, &request[1]);
|
||||
response[0] = DAP_OK;
|
||||
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/* Process SWD Configure command and prepare response */
|
||||
static uint16_t dap_swdp_configure(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
const struct swdp_api *api = ctx->swdp_dev->api;
|
||||
uint8_t turnaround = (request[0] & 0x03U) + 1U;
|
||||
bool data_phase = (request[0] & 0x04U) ? true : false;
|
||||
|
||||
if (!atomic_test_bit(&ctx->state, DAP_STATE_CONNECTED)) {
|
||||
LOG_ERR("DAP device is not connected");
|
||||
response[0] = DAP_ERROR;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
api->swdp_configure(ctx->swdp_dev, turnaround, data_phase);
|
||||
response[0] = DAP_OK;
|
||||
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/* Process Transfer Configure command and prepare response */
|
||||
static uint16_t dap_transfer_cfg(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
|
||||
ctx->transfer.idle_cycles = request[0];
|
||||
ctx->transfer.retry_count = sys_get_le16(&request[1]);
|
||||
ctx->transfer.match_retry = sys_get_le16(&request[3]);
|
||||
LOG_DBG("idle_cycles %d, retry_count %d, match_retry %d",
|
||||
ctx->transfer.idle_cycles,
|
||||
ctx->transfer.retry_count,
|
||||
ctx->transfer.match_retry);
|
||||
|
||||
response[0] = DAP_OK;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
static inline uint8_t do_swdp_transfer(struct dap_context *const ctx,
|
||||
const uint8_t req_val,
|
||||
uint32_t *data)
|
||||
{
|
||||
const struct swdp_api *api = ctx->swdp_dev->api;
|
||||
uint32_t retry = ctx->transfer.retry_count;
|
||||
uint8_t rspns_val;
|
||||
|
||||
do {
|
||||
api->swdp_transfer(ctx->swdp_dev,
|
||||
req_val,
|
||||
data,
|
||||
ctx->transfer.idle_cycles,
|
||||
&rspns_val);
|
||||
} while ((rspns_val == SWDP_ACK_WAIT) && retry--);
|
||||
|
||||
return rspns_val;
|
||||
}
|
||||
|
||||
static uint8_t swdp_transfer_match(struct dap_context *const ctx,
|
||||
const uint8_t req_val,
|
||||
const uint32_t match_val)
|
||||
{
|
||||
uint32_t match_retry = ctx->transfer.match_retry;
|
||||
uint32_t data;
|
||||
uint8_t rspns_val;
|
||||
|
||||
if (req_val & SWDP_REQUEST_APnDP) {
|
||||
/* Post AP read, result will be returned on the next transfer */
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, NULL);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
return rspns_val;
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
/*
|
||||
* Read register until its value matches
|
||||
* or retry counter expires
|
||||
*/
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, &data);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
return rspns_val;
|
||||
}
|
||||
|
||||
} while (((data & ctx->transfer.match_mask) != match_val) &&
|
||||
match_retry--);
|
||||
|
||||
if ((data & ctx->transfer.match_mask) != match_val) {
|
||||
rspns_val |= DAP_TRANSFER_MISMATCH;
|
||||
}
|
||||
|
||||
return rspns_val;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process SWD Transfer command and prepare response
|
||||
* pyOCD counterpart is _encode_transfer_data.
|
||||
* Packet format: one byte DAP_index (ignored)
|
||||
* one bytes transfer_count
|
||||
* following by number transfer_count pairs of
|
||||
* one byte request (register)
|
||||
* four byte data (for write request only)
|
||||
*/
|
||||
static uint16_t dap_swdp_transfer(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
uint8_t *rspns_buf;
|
||||
const uint8_t *req_buf;
|
||||
uint8_t rspns_cnt = 0;
|
||||
uint8_t rspns_val = 0;
|
||||
bool post_read = false;
|
||||
uint32_t check_write = 0;
|
||||
uint8_t req_cnt;
|
||||
uint8_t req_val;
|
||||
uint32_t match_val;
|
||||
uint32_t data;
|
||||
|
||||
/* Ignore DAP index request[0] */
|
||||
req_cnt = request[1];
|
||||
req_buf = request + sizeof(req_cnt) + 1;
|
||||
rspns_buf = response + (sizeof(rspns_cnt) + sizeof(rspns_val));
|
||||
|
||||
for (; req_cnt; req_cnt--) {
|
||||
req_val = *req_buf++;
|
||||
if (req_val & SWDP_REQUEST_RnW) {
|
||||
/* Read register */
|
||||
if (post_read) {
|
||||
/*
|
||||
* Read was posted before, read previous AP
|
||||
* data or post next AP read.
|
||||
*/
|
||||
if ((req_val & (SWDP_REQUEST_APnDP |
|
||||
DAP_TRANSFER_MATCH_VALUE)) !=
|
||||
SWDP_REQUEST_APnDP) {
|
||||
req_val = DP_RDBUFF | SWDP_REQUEST_RnW;
|
||||
post_read = false;
|
||||
}
|
||||
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, &data);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Store previous AP data */
|
||||
sys_put_le32(data, rspns_buf);
|
||||
rspns_buf += sizeof(data);
|
||||
}
|
||||
if (req_val & DAP_TRANSFER_MATCH_VALUE) {
|
||||
LOG_INF("match value read");
|
||||
/* Read with value match */
|
||||
match_val = sys_get_le32(req_buf);
|
||||
req_buf += sizeof(match_val);
|
||||
|
||||
rspns_val = swdp_transfer_match(ctx, req_val, match_val);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (req_val & SWDP_REQUEST_APnDP) {
|
||||
/* Normal read */
|
||||
if (!post_read) {
|
||||
/* Post AP read */
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, NULL);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
break;
|
||||
}
|
||||
post_read = true;
|
||||
}
|
||||
} else {
|
||||
/* Read DP register */
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, &data);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
break;
|
||||
}
|
||||
/* Store data */
|
||||
sys_put_le32(data, rspns_buf);
|
||||
rspns_buf += sizeof(data);
|
||||
}
|
||||
check_write = 0U;
|
||||
} else {
|
||||
/* Write register */
|
||||
if (post_read) {
|
||||
/* Read previous data */
|
||||
rspns_val = do_swdp_transfer(ctx,
|
||||
DP_RDBUFF | SWDP_REQUEST_RnW,
|
||||
&data);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Store previous data */
|
||||
sys_put_le32(data, rspns_buf);
|
||||
rspns_buf += sizeof(data);
|
||||
post_read = false;
|
||||
}
|
||||
/* Load data */
|
||||
data = sys_get_le32(req_buf);
|
||||
req_buf += sizeof(data);
|
||||
if (req_val & DAP_TRANSFER_MATCH_MASK) {
|
||||
/* Write match mask */
|
||||
ctx->transfer.match_mask = data;
|
||||
rspns_val = SWDP_ACK_OK;
|
||||
} else {
|
||||
/* Write DP/AP register */
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, &data);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
break;
|
||||
}
|
||||
|
||||
check_write = 1U;
|
||||
}
|
||||
}
|
||||
rspns_cnt++;
|
||||
}
|
||||
|
||||
if (rspns_val == SWDP_ACK_OK) {
|
||||
if (post_read) {
|
||||
/* Read previous data */
|
||||
rspns_val = do_swdp_transfer(ctx,
|
||||
DP_RDBUFF | SWDP_REQUEST_RnW,
|
||||
&data);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Store previous data */
|
||||
sys_put_le32(data, rspns_buf);
|
||||
rspns_buf += sizeof(data);
|
||||
} else if (check_write) {
|
||||
/* Check last write */
|
||||
rspns_val = do_swdp_transfer(ctx,
|
||||
DP_RDBUFF | SWDP_REQUEST_RnW,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
response[0] = rspns_cnt;
|
||||
response[1] = rspns_val;
|
||||
|
||||
return (rspns_buf - response);
|
||||
}
|
||||
|
||||
/* Delegate DAP Transfer command */
|
||||
static uint16_t dap_transfer(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
uint16_t retval;
|
||||
|
||||
if (!atomic_test_bit(&ctx->state, DAP_STATE_CONNECTED)) {
|
||||
LOG_ERR("DAP device is not connected");
|
||||
response[0] = DAP_ERROR;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
switch (ctx->debug_port) {
|
||||
case DAP_PORT_SWD:
|
||||
retval = dap_swdp_transfer(ctx, request, response);
|
||||
break;
|
||||
case DAP_PORT_JTAG:
|
||||
default:
|
||||
LOG_ERR("port unsupported");
|
||||
response[0] = DAP_ERROR;
|
||||
retval = 1U;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process SWD DAP_TransferBlock command and prepare response.
|
||||
* pyOCD counterpart is _encode_transfer_block_data.
|
||||
* Packet format: one byte DAP_index (ignored)
|
||||
* two bytes transfer_count
|
||||
* one byte block_request (register)
|
||||
* data[transfer_count * sizeof(uint32_t)]
|
||||
*/
|
||||
static uint16_t dap_swdp_transferblock(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
uint32_t data;
|
||||
uint8_t *rspns_buf;
|
||||
const uint8_t *req_buf;
|
||||
uint16_t rspns_cnt = 0;
|
||||
uint16_t req_cnt;
|
||||
uint8_t rspns_val = 0;
|
||||
uint8_t req_val;
|
||||
|
||||
req_cnt = sys_get_le16(&request[1]);
|
||||
req_val = request[3];
|
||||
req_buf = request + (sizeof(req_cnt) + sizeof(req_val) + 1);
|
||||
rspns_buf = response + (sizeof(rspns_cnt) + sizeof(rspns_val));
|
||||
|
||||
if (req_cnt == 0U) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (req_val & SWDP_REQUEST_RnW) {
|
||||
/* Read register block */
|
||||
if (req_val & SWDP_REQUEST_APnDP) {
|
||||
/* Post AP read */
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, NULL);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
while (req_cnt--) {
|
||||
/* Read DP/AP register */
|
||||
if ((req_cnt == 0U) &&
|
||||
(req_val & SWDP_REQUEST_APnDP)) {
|
||||
/* Last AP read */
|
||||
req_val = DP_RDBUFF | SWDP_REQUEST_RnW;
|
||||
}
|
||||
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, &data);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
/* Store data */
|
||||
sys_put_le32(data, rspns_buf);
|
||||
rspns_buf += sizeof(data);
|
||||
rspns_cnt++;
|
||||
}
|
||||
} else {
|
||||
/* Write register block */
|
||||
while (req_cnt--) {
|
||||
/* Load data */
|
||||
data = sys_get_le32(req_buf);
|
||||
req_buf += sizeof(data);
|
||||
/* Write DP/AP register */
|
||||
rspns_val = do_swdp_transfer(ctx, req_val, &data);
|
||||
if (rspns_val != SWDP_ACK_OK) {
|
||||
goto end;
|
||||
}
|
||||
|
||||
rspns_cnt++;
|
||||
}
|
||||
/* Check last write */
|
||||
rspns_val = do_swdp_transfer(ctx, DP_RDBUFF | SWDP_REQUEST_RnW, NULL);
|
||||
}
|
||||
|
||||
end:
|
||||
sys_put_le16(rspns_cnt, &response[0]);
|
||||
response[2] = rspns_val;
|
||||
|
||||
LOG_DBG("Received %u, to transmit %u, response count %u",
|
||||
req_buf - request,
|
||||
rspns_buf - response,
|
||||
rspns_cnt * 4);
|
||||
|
||||
return (rspns_buf - response);
|
||||
}
|
||||
|
||||
/* Delegate Transfer Block command */
|
||||
static uint16_t dap_transferblock(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
uint16_t retval;
|
||||
|
||||
if (!atomic_test_bit(&ctx->state, DAP_STATE_CONNECTED)) {
|
||||
LOG_ERR("DAP device is not connected");
|
||||
/* Clear response count */
|
||||
sys_put_le16(0U, &response[0]);
|
||||
/* Clear DAP response (ACK) value */
|
||||
response[2] = 0U;
|
||||
return 3U;
|
||||
}
|
||||
|
||||
switch (ctx->debug_port) {
|
||||
case DAP_PORT_SWD:
|
||||
retval = dap_swdp_transferblock(ctx, request, response);
|
||||
break;
|
||||
case DAP_PORT_JTAG:
|
||||
default:
|
||||
LOG_ERR("port unsupported");
|
||||
/* Clear response count */
|
||||
sys_put_le16(0U, &response[0]);
|
||||
/* Clear DAP response (ACK) value */
|
||||
response[2] = 0U;
|
||||
retval = 3U;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Process SWD Write ABORT command and prepare response */
|
||||
static uint16_t dap_swdp_writeabort(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
const struct swdp_api *api = ctx->swdp_dev->api;
|
||||
/* Load data (Ignore DAP index in request[0]) */
|
||||
uint32_t data = sys_get_le32(&request[1]);
|
||||
|
||||
/* Write Abort register */
|
||||
api->swdp_transfer(ctx->swdp_dev, DP_ABORT, &data,
|
||||
ctx->transfer.idle_cycles, NULL);
|
||||
|
||||
response[0] = DAP_OK;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/* Delegate DAP Write ABORT command */
|
||||
static uint16_t dap_writeabort(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
uint16_t retval;
|
||||
|
||||
if (!atomic_test_bit(&ctx->state, DAP_STATE_CONNECTED)) {
|
||||
LOG_ERR("DAP device is not connected");
|
||||
response[0] = DAP_ERROR;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
switch (ctx->debug_port) {
|
||||
case DAP_PORT_SWD:
|
||||
retval = dap_swdp_writeabort(ctx, request, response);
|
||||
break;
|
||||
case DAP_PORT_JTAG:
|
||||
default:
|
||||
LOG_ERR("port unsupported");
|
||||
response[0] = DAP_ERROR;
|
||||
retval = 1U;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/* Process DAP Vendor command request */
|
||||
static uint16_t dap_process_vendor_cmd(struct dap_context *const ctx,
|
||||
const uint8_t *const request,
|
||||
uint8_t *const response)
|
||||
{
|
||||
response[0] = ID_DAP_INVALID;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process DAP command request and prepare response
|
||||
* request: pointer to request data
|
||||
* response: pointer to response data
|
||||
* return: number of bytes in response
|
||||
*
|
||||
* All the subsequent command functions have the same parameter
|
||||
* and return value structure.
|
||||
*/
|
||||
static uint16_t dap_process_cmd(struct dap_context *const ctx,
|
||||
const uint8_t *request,
|
||||
uint8_t *response)
|
||||
{
|
||||
uint16_t retval;
|
||||
|
||||
LOG_HEXDUMP_DBG(request, 8, "req");
|
||||
|
||||
if ((*request >= ID_DAP_VENDOR0) && (*request <= ID_DAP_VENDOR31)) {
|
||||
return dap_process_vendor_cmd(ctx, request, response);
|
||||
}
|
||||
|
||||
*response++ = *request;
|
||||
LOG_DBG("request 0x%02x", *request);
|
||||
|
||||
switch (*request++) {
|
||||
case ID_DAP_INFO:
|
||||
retval = dap_info(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_HOST_STATUS:
|
||||
retval = dap_host_status(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_CONNECT:
|
||||
retval = dap_connect(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_DISCONNECT:
|
||||
retval = dap_disconnect(ctx, response);
|
||||
break;
|
||||
case ID_DAP_DELAY:
|
||||
retval = dap_delay(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_RESET_TARGET:
|
||||
retval = dap_reset_target(ctx, response);
|
||||
break;
|
||||
case ID_DAP_SWJ_PINS:
|
||||
retval = dap_swj_pins(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_SWJ_CLOCK:
|
||||
retval = dap_swj_clock(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_SWJ_SEQUENCE:
|
||||
retval = dap_swj_sequence(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_SWDP_CONFIGURE:
|
||||
retval = dap_swdp_configure(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_JTAG_SEQUENCE:
|
||||
LOG_ERR("JTAG sequence unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
case ID_DAP_JTAG_CONFIGURE:
|
||||
LOG_ERR("JTAG configure unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
case ID_DAP_JTAG_IDCODE:
|
||||
LOG_ERR("JTAG IDCODE unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
case ID_DAP_TRANSFER_CONFIGURE:
|
||||
retval = dap_transfer_cfg(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_TRANSFER:
|
||||
retval = dap_transfer(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_TRANSFER_BLOCK:
|
||||
retval = dap_transferblock(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_WRITE_ABORT:
|
||||
retval = dap_writeabort(ctx, request, response);
|
||||
break;
|
||||
case ID_DAP_SWO_TRANSPORT:
|
||||
LOG_ERR("SWO Transport unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
case ID_DAP_SWO_MODE:
|
||||
LOG_ERR("SWO Mode unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
case ID_DAP_SWO_BAUDRATE:
|
||||
LOG_ERR("SWO Baudrate unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
case ID_DAP_SWO_CONTROL:
|
||||
LOG_ERR("SWO Control unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
case ID_DAP_SWO_STATUS:
|
||||
LOG_ERR("SWO Status unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
case ID_DAP_SWO_DATA:
|
||||
LOG_ERR("SWO Data unsupported");
|
||||
retval = 1;
|
||||
*response = DAP_ERROR;
|
||||
break;
|
||||
|
||||
default:
|
||||
*(response - 1) = ID_DAP_INVALID;
|
||||
return 1U;
|
||||
}
|
||||
|
||||
return (1U + retval);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute DAP command (process request and prepare response)
|
||||
* request: pointer to request data
|
||||
* response: pointer to response data
|
||||
* return: number of bytes in response
|
||||
*/
|
||||
uint32_t dap_execute_cmd(const uint8_t *request,
|
||||
uint8_t *response)
|
||||
{
|
||||
uint32_t retval;
|
||||
uint16_t n;
|
||||
uint8_t count;
|
||||
|
||||
if (request[0] == ID_DAP_EXECUTE_COMMANDS) {
|
||||
/* copy command and increment */
|
||||
*response++ = *request++;
|
||||
count = request[0];
|
||||
request += sizeof(count);
|
||||
response[0] = count;
|
||||
response += sizeof(count);
|
||||
retval = sizeof(count) + 1U;
|
||||
LOG_WRN("(untested) ID DAP EXECUTE_COMMANDS count %u", count);
|
||||
while (count--) {
|
||||
n = dap_process_cmd(&dap_ctx[0], request, response);
|
||||
retval += n;
|
||||
request += n;
|
||||
response += n;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
return dap_process_cmd(&dap_ctx[0], request, response);
|
||||
}
|
||||
|
||||
int dap_setup(const struct device *const dev)
|
||||
{
|
||||
dap_ctx[0].swdp_dev = (void *)dev;
|
||||
|
||||
if (!device_is_ready(dap_ctx[0].swdp_dev)) {
|
||||
LOG_ERR("SWD driver not ready");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Default settings */
|
||||
dap_ctx[0].pkt_size = 64U;
|
||||
dap_ctx[0].debug_port = 0U;
|
||||
dap_ctx[0].transfer.idle_cycles = 0U;
|
||||
dap_ctx[0].transfer.retry_count = 100U;
|
||||
dap_ctx[0].transfer.match_retry = 0U;
|
||||
dap_ctx[0].transfer.match_mask = 0U;
|
||||
dap_ctx[0].capabilities = DAP_SUPPORTS_ATOMIC_COMMANDS |
|
||||
DAP_DP_SUPPORTS_SWD;
|
||||
|
||||
return 0;
|
||||
}
|
119
subsys/dap/cmsis_dap.h
Normal file
119
subsys/dap/cmsis_dap.h
Normal file
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2019 PHYTEC Messtechnik GmbH
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is based on DAP.c from CMSIS-DAP Source (Revision: V2.0.0)
|
||||
* https://github.com/ARM-software/CMSIS_5/tree/develop/CMSIS/DAP/Firmware
|
||||
* Copyright (c) 2013-2017 ARM Limited. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief DAP controller private header
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_INCLUDE_CMSIS_DAP_H_
|
||||
#define ZEPHYR_INCLUDE_CMSIS_DAP_H_
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
/* Firmware Version */
|
||||
#define DAP_FW_VER "1.10"
|
||||
|
||||
/* DAP Command IDs */
|
||||
#define ID_DAP_INFO 0x00U
|
||||
#define ID_DAP_HOST_STATUS 0x01U
|
||||
#define ID_DAP_CONNECT 0x02U
|
||||
#define ID_DAP_DISCONNECT 0x03U
|
||||
#define ID_DAP_TRANSFER_CONFIGURE 0x04U
|
||||
#define ID_DAP_TRANSFER 0x05U
|
||||
#define ID_DAP_TRANSFER_BLOCK 0x06U
|
||||
#define ID_DAP_TRANSFER_ABORT 0x07U
|
||||
#define ID_DAP_WRITE_ABORT 0x08U
|
||||
#define ID_DAP_DELAY 0x09U
|
||||
#define ID_DAP_RESET_TARGET 0x0AU
|
||||
#define ID_DAP_SWJ_PINS 0x10U
|
||||
#define ID_DAP_SWJ_CLOCK 0x11U
|
||||
#define ID_DAP_SWJ_SEQUENCE 0x12U
|
||||
#define ID_DAP_SWDP_CONFIGURE 0x13U
|
||||
#define ID_DAP_JTAG_SEQUENCE 0x14U
|
||||
#define ID_DAP_JTAG_CONFIGURE 0x15U
|
||||
#define ID_DAP_JTAG_IDCODE 0x16U
|
||||
#define ID_DAP_SWO_TRANSPORT 0x17U
|
||||
#define ID_DAP_SWO_MODE 0x18U
|
||||
#define ID_DAP_SWO_BAUDRATE 0x19U
|
||||
#define ID_DAP_SWO_CONTROL 0x1AU
|
||||
#define ID_DAP_SWO_STATUS 0x1BU
|
||||
#define ID_DAP_SWO_DATA 0x1CU
|
||||
|
||||
#define ID_DAP_QUEUE_COMMANDS 0x7EU
|
||||
#define ID_DAP_EXECUTE_COMMANDS 0x7FU
|
||||
|
||||
/* DAP Vendor Command IDs */
|
||||
#define ID_DAP_VENDOR0 0x80U
|
||||
#define ID_DAP_VENDOR31 0x9FU
|
||||
#define ID_DAP_INVALID 0xFFU
|
||||
|
||||
/* DAP Status Code */
|
||||
#define DAP_OK 0U
|
||||
#define DAP_ERROR 0xFFU
|
||||
|
||||
/* DAP ID */
|
||||
#define DAP_ID_VENDOR 0x01U
|
||||
#define DAP_ID_PRODUCT 0x02U
|
||||
#define DAP_ID_SER_NUM 0x03U
|
||||
#define DAP_ID_FW_VER 0x04U
|
||||
#define DAP_ID_DEVICE_VENDOR 0x05U
|
||||
#define DAP_ID_DEVICE_NAME 0x06U
|
||||
#define DAP_ID_CAPABILITIES 0xF0U
|
||||
#define DAP_ID_SWO_BUFFER_SIZE 0xFDU
|
||||
#define DAP_ID_PACKET_COUNT 0xFEU
|
||||
#define DAP_ID_PACKET_SIZE 0xFFU
|
||||
|
||||
/* DAP Host Status */
|
||||
#define DAP_DEBUGGER_CONNECTED 0U
|
||||
#define DAP_TARGET_RUNNING 1U
|
||||
|
||||
/* DAP Port */
|
||||
#define DAP_PORT_AUTODETECT 0U
|
||||
#define DAP_PORT_DISABLED 0U
|
||||
#define DAP_PORT_SWD 1U
|
||||
#define DAP_PORT_JTAG 2U
|
||||
|
||||
/* DAP transfer request bits */
|
||||
#define DAP_TRANSFER_MATCH_VALUE BIT(4)
|
||||
#define DAP_TRANSFER_MATCH_MASK BIT(5)
|
||||
|
||||
/* DAP transfer response bits */
|
||||
#define DAP_TRANSFER_MISMATCH BIT(4)
|
||||
|
||||
/* DAP controller capabilities */
|
||||
#define DAP_DP_SUPPORTS_SWD BIT(0)
|
||||
#define DAP_DP_SUPPORTS_JTAG BIT(1)
|
||||
#define DAP_SWO_SUPPORTS_UART BIT(2)
|
||||
#define DAP_SWO_SUPPORTS_MANCHESTER BIT(3)
|
||||
#define DAP_SUPPORTS_ATOMIC_COMMANDS BIT(4)
|
||||
#define DAP_SUPPORTS_TIMESTAMP_CLOCK BIT(5)
|
||||
#define DAP_SWO_SUPPORTS_STREAM BIT(6)
|
||||
|
||||
/* DP Register (DPv1) */
|
||||
#define DP_IDCODE 0x00U
|
||||
#define DP_ABORT 0x00U
|
||||
#define DP_CTRL_STAT 0x04U
|
||||
#define DP_SELECT 0x08U
|
||||
#define DP_RESEND 0x08U
|
||||
#define DP_RDBUFF 0x0CU
|
||||
|
||||
#define DAP_MBMSG_REGISTER_IFACE 0x0U
|
||||
#define DAP_MBMSG_FROM_IFACE 0x1U
|
||||
#define DAP_MBMSG_FROM_CONTROLLER 0x2U
|
||||
|
||||
/* Keep it internal until an other interface has been implemented. */
|
||||
int dap_setup(const struct device *const dev);
|
||||
uint32_t dap_execute_cmd(const uint8_t *request, uint8_t *response);
|
||||
|
||||
#endif /* ZEPHYR_INCLUDE_CMSIS_DAP_H_ */
|
Loading…
Add table
Add a link
Reference in a new issue