cmsisdap: add support for USB Bulk backend

This commit is contained in:
aisuneko icecat
2026-07-02 08:43:35 +02:00
committed by Gwenhael Goavec-Merou
parent b8036312cd
commit 53180a83f2
4 changed files with 527 additions and 76 deletions
+451 -59
View File
@@ -3,8 +3,14 @@
* Copyright (c) 2021 Gwenhael Goavec-Merou <[email protected]>
*/
#include <cstdint>
#include <cstring>
#ifdef ENABLE_CMSISDAP_V1
#include <hidapi.h>
#endif
#ifdef ENABLE_CMSISDAP_V2
#include <libusb.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
@@ -87,15 +93,87 @@ enum cmsisdap_status {
DAP_ERROR = 0xff
};
CmsisDAP::CmsisDAP(const cable_t &cable, int index, int8_t verbose):_verbose(verbose>0),
_device_idx(0), _vid(cable.vid), _pid(cable.pid),
_serial_number(L""), _dev(NULL), _num_tms(0), _is_connect(false)
enum cmsisdap_backend_type {
BACKEND_NONE = -1,
BACKEND_HID = 0,
BACKEND_USBBULK = 1,
};
CmsisDAP::CmsisDAP(const cable_t &cable, int index, uint32_t clkHZ, int8_t verbose):_verbose(verbose>0),
_device_idx(0), _vid(cable.vid), _pid(cable.pid), _serial_number(L""),
#ifdef ENABLE_CMSISDAP_V1
_hid_dev(NULL),
#endif
#ifdef ENABLE_CMSISDAP_V2
_usb_dev(NULL), _ctx(NULL),
#endif
_packet_size(0),
_ep_in(0), _ep_out(0), _num_tms(0), _is_connect(false), _backend(BACKEND_NONE)
{
std::vector<struct hid_device_info *> dev_found;
_ll_buffer = (unsigned char *)malloc(sizeof(unsigned char) * 65);
_ll_buffer = (unsigned char *)malloc(sizeof(unsigned char) * 1024);
if (!_ll_buffer)
throw std::runtime_error("internal buffer allocation failed");
_buffer = _ll_buffer+2;
char t[256];
#ifdef ENABLE_CMSISDAP_V2
try {
_backend = BACKEND_USBBULK;
initWithBulk(cable, verbose);
} catch (std::runtime_error const& e) {
snprintf(t, sizeof(t), "try USB bulk init but failed with: %s", e.what());
printInfo(t);
_backend = BACKEND_NONE;
}
#endif
#ifdef ENABLE_CMSISDAP_V1
if (_backend == BACKEND_NONE) {
try {
_backend = BACKEND_HID;
initWithHID(cable, index, verbose);
} catch (std::runtime_error const& e) {
snprintf(t, sizeof(t), "try HID init but failed with: %s", e.what());
printInfo(t);
_backend = BACKEND_NONE;
}
}
#endif
if (_backend == BACKEND_NONE)
throw std::runtime_error("Error: no USB backend available");
if (clkHZ > 0)
setClkFreq(clkHZ);
}
CmsisDAP::~CmsisDAP()
{
/* disconnect and close device
* and free context
*/
switch(_backend){
#ifdef ENABLE_CMSISDAP_V1
case BACKEND_HID:
if (_is_connect)
dapDisconnect();
if (_hid_dev)
hid_close(_hid_dev);
hid_exit();
break;
#endif
#ifdef ENABLE_CMSISDAP_V2
case BACKEND_USBBULK:
libusb_close(_usb_dev);
libusb_exit(_ctx);
break;
#endif
default:
break;
}
if (_ll_buffer)
free(_ll_buffer);
}
#ifdef ENABLE_CMSISDAP_V1
void CmsisDAP::initWithHID(const cable_t &cable, int index, int8_t verbose){
std::vector<struct hid_device_info *> dev_found;
/* only hid support */
struct hid_device_info *devs, *cur_dev;
@@ -157,11 +235,13 @@ CmsisDAP::CmsisDAP(const cable_t &cable, int index, int8_t verbose):_verbose(ver
/* store params about device to use */
_vid = dev_found[_device_idx]->vendor_id;
_pid = dev_found[_device_idx]->product_id;
_vendor = dev_found[_device_idx]->manufacturer_string;
_product_name = dev_found[_device_idx]->product_string;
if (dev_found[_device_idx]->serial_number != NULL)
_serial_number = std::wstring(dev_found[_device_idx]->serial_number);
/* open the device */
_dev = hid_open_path(dev_found[_device_idx]->path);
if (!_dev) {
_hid_dev = hid_open_path(dev_found[_device_idx]->path);
if (!_hid_dev) {
throw std::runtime_error(
std::string("Couldn't open device. Check permissions for ") + dev_found[_device_idx]->path);
}
@@ -206,7 +286,7 @@ CmsisDAP::CmsisDAP(const cable_t &cable, int index, int8_t verbose):_verbose(ver
memset(_buffer, 0, 63);
int res = read_info(INFO_ID_HWCAP, _buffer, 63);
if (res < 0) {
hid_close(_dev);
hid_close(_hid_dev);
hid_exit();
char t[256];
snprintf(t, sizeof(t), "Error %d for command %d\n", res, INFO_ID_HWCAP);
@@ -216,34 +296,265 @@ CmsisDAP::CmsisDAP(const cable_t &cable, int index, int8_t verbose):_verbose(ver
if (verbose)
printf("Hardware cap %02x %02x %02x\n", _buffer[0], _buffer[1], _buffer[2]);
if (!(_buffer[2] & (1 << 1))) {
hid_close(_dev);
hid_close(_hid_dev);
hid_exit();
throw std::runtime_error("JTAG is not supported by the probe");
}
/* send connect */
if (dapConnect() != 1) {
hid_close(_dev);
hid_close(_hid_dev);
hid_exit();
throw std::runtime_error("DAP connection in JTAG mode failed");
}
}
CmsisDAP::~CmsisDAP()
printInfo("HID init successful");
}
#endif
#ifdef ENABLE_CMSISDAP_V2
void CmsisDAP::initWithBulk(const cable_t &cable, int8_t verbose)
{
/* disconnect and close device
* and free context
*/
if (_is_connect)
dapDisconnect();
if (_dev)
hid_close(_dev);
hid_exit();
if (libusb_init(&_ctx) != 0)
throw std::runtime_error("libusb init failed");
if (_ll_buffer)
free(_ll_buffer);
std::vector<cmsis_dap_v2_dev_t> devices = findCmsisDapDevices(cable.vid, cable.pid);
/* Check if one and only device is found
* otherwise fails when no devices found or more than one present
*/
if (devices.empty()) {
libusb_exit(_ctx);
throw std::runtime_error("Error: failed to find compatible CMSIS-DAP device");
}
if (devices.size() > 1) {
for (auto &d : devices)
libusb_close(d.handle);
libusb_exit(_ctx);
throw std::runtime_error(
"Error: more than one device. Please provide VID/PID");
}
cmsis_dap_v2_dev_t &dev = devices[0];
_usb_dev = dev.handle;
_vid = dev.vid;
_pid = dev.pid;
_serial_number = dev.serial;
_vendor = dev.vendor;
_product_name = dev.product;
printInfo("Found 1 compatible device:");
char val[256];
snprintf(val, sizeof(val), "\t0x%04x 0x%04x", _vid, _pid);
printInfo(val);
int current_config;
if (libusb_get_configuration(_usb_dev, &current_config) != 0) {
libusb_close(_usb_dev);
libusb_exit(_ctx);
throw std::runtime_error("could not find current configuration");
}
if (dev.config_num != current_config) {
int ret = libusb_set_configuration(_usb_dev, dev.config_num);
if (ret != 0 && ret != LIBUSB_ERROR_NOT_SUPPORTED) {
libusb_close(_usb_dev);
libusb_exit(_ctx);
throw std::runtime_error("could not set current configuration");
}
}
if (libusb_claim_interface(_usb_dev, dev.interface_num) != 0) {
libusb_close(_usb_dev);
libusb_exit(_ctx);
throw std::runtime_error("could not claim interface");
}
_packet_size = dev.packet_size;
_ep_out = dev.ep_out;
_ep_in = dev.ep_in;
if (!libusb_dev_mem_alloc(_usb_dev, _packet_size)) {
libusb_close(_usb_dev);
libusb_exit(_ctx);
throw std::runtime_error("failed to alloc DMA memory for device");
}
if (verbose) {
display_info(INFO_ID_VID , DAPLINK_INFO_STRING);
display_info(INFO_ID_PID , DAPLINK_INFO_STRING);
display_info(INFO_ID_SERNUM , DAPLINK_INFO_STRING);
display_info(INFO_ID_FWVERS , DAPLINK_INFO_STRING);
display_info(INFO_ID_TARGET_DEV_VENDOR , DAPLINK_INFO_STRING);
display_info(INFO_ID_TARGET_DEV_NAME , DAPLINK_INFO_STRING);
display_info(INFO_ID_HWCAP , DAPLINK_INFO_BYTE);
display_info(INFO_ID_SWO_TRACE_BUF_SIZE, DAPLINK_INFO_WORD);
display_info(INFO_ID_MAX_PKT_CNT , DAPLINK_INFO_BYTE);
display_info(INFO_ID_MAX_PKT_SZ , DAPLINK_INFO_SHORT);
}
memset(_buffer, 0, 63);
int res = read_info(INFO_ID_HWCAP, _buffer, 63);
if (res < 0) {
libusb_close(_usb_dev);
libusb_exit(_ctx);
char t[256];
snprintf(t, sizeof(t), "Error %d for command %d\n", res, INFO_ID_HWCAP);
throw std::runtime_error(t);
}
if (verbose)
printf("Hardware cap %02x %02x %02x\n", _buffer[0], _buffer[1], _buffer[2]);
if (!(_buffer[2] & (1 << 1))) {
libusb_close(_usb_dev);
libusb_exit(_ctx);
throw std::runtime_error("JTAG is not supported by the probe");
}
if (dapConnect() != 1) {
libusb_close(_usb_dev);
libusb_exit(_ctx);
throw std::runtime_error("DAP connection in JTAG mode failed");
}
printInfo("USB bulk init successful");
}
/* Enumerate all USB devices and return those that expose a CMSIS-DAP v2
* bulk interface.
* Enumerate may be filtered when vid and pid are non-zero.
* Each returned entry carries an already-open libusb handle; the caller
* is responsible for closing handles it does not use.
*/
std::vector<CmsisDAP::cmsis_dap_v2_dev_t>
CmsisDAP::findCmsisDapDevices(uint16_t vid, uint16_t pid)
{
std::vector<cmsis_dap_v2_dev_t> result;
struct libusb_device **devs;
int devs_len = libusb_get_device_list(_ctx, &devs);
if (devs_len <= 0)
return result;
for (int i = 0; i < devs_len; i++) {
struct libusb_device *dev = devs[i];
struct libusb_device_descriptor dev_desc;
if (libusb_get_device_descriptor(dev, &dev_desc) != 0) {
printWarn("could not get device descriptor for device " + std::to_string(i));
continue;
}
/* optional VID/PID filter: if either is non-zero, both must match */
if ((vid != 0 || pid != 0) &&
(dev_desc.idVendor != vid || dev_desc.idProduct != pid))
continue;
libusb_device_handle *handle = NULL;
if (libusb_open(dev, &handle) != 0 || !handle)
continue;
cmsis_dap_v2_dev_t candidate = {};
candidate.handle = handle;
candidate.vid = dev_desc.idVendor;
candidate.pid = dev_desc.idProduct;
char str[256];
memset(str, 0, sizeof(str));
if (libusb_get_string_descriptor_ascii(handle, dev_desc.iSerialNumber,
(uint8_t *)str, sizeof(str)) >= 0)
candidate.serial = std::wstring(str, str + std::strlen(str));
memset(str, 0, sizeof(str));
if (libusb_get_string_descriptor_ascii(handle, dev_desc.iManufacturer,
(uint8_t *)str, sizeof(str)) >= 0)
candidate.vendor = std::wstring(str, str + std::strlen(str));
memset(str, 0, sizeof(str));
if (libusb_get_string_descriptor_ascii(handle, dev_desc.iProduct,
(uint8_t *)str, sizeof(str)) >= 0)
candidate.product = std::wstring(str, str + std::strlen(str));
bool intf_found = false;
for (int cfg = 0; cfg < dev_desc.bNumConfigurations && !intf_found; cfg++) {
struct libusb_config_descriptor *cfg_desc;
if (libusb_get_config_descriptor(dev, cfg, &cfg_desc) != 0) {
char t[256];
snprintf(t, sizeof(t), "could not get configuration descriptor %d "
"for device 0x%04x:0x%04x", cfg, candidate.vid, candidate.pid);
printError(t);
continue;
}
for (int intf = 0; intf < cfg_desc->bNumInterfaces && !intf_found; intf++) {
const struct libusb_interface_descriptor *intf_desc =
&cfg_desc->interface[intf].altsetting[0];
/* require two bulk endpoints: [0] OUT, [1] IN */
if (intf_desc->bNumEndpoints < 2 ||
(intf_desc->endpoint[0].bmAttributes & 3) != LIBUSB_TRANSFER_TYPE_BULK ||
(intf_desc->endpoint[0].bEndpointAddress & 0x80) != LIBUSB_ENDPOINT_OUT ||
(intf_desc->endpoint[1].bmAttributes & 3) != LIBUSB_TRANSFER_TYPE_BULK ||
(intf_desc->endpoint[1].bEndpointAddress & 0x80) != LIBUSB_ENDPOINT_IN)
continue;
/* check whether the interface string contains "CMSIS-DAP" */
bool intf_str_valid = false;
if (intf_desc->iInterface != 0) {
char intf_str[256] = {0};
if (libusb_get_string_descriptor_ascii(handle, intf_desc->iInterface,
(uint8_t *)intf_str, sizeof(intf_str)) >= 0 &&
std::strstr(intf_str, "CMSIS-DAP"))
intf_str_valid = true;
}
/* CMSIS-DAP v2 spec requires vendor-specific class/subclass/protocol.
* Accept deviations (e.g. KitProg3 uses class 0) only when the
* interface string reliably identifies the interface as CMSIS-DAP
* and the class is not a well-known one (CDC, MSC …). */
if (intf_desc->bInterfaceClass != LIBUSB_CLASS_VENDOR_SPEC ||
intf_desc->bInterfaceSubClass != 0 ||
intf_desc->bInterfaceProtocol != 0) {
if (intf_str_valid &&
(intf_desc->bInterfaceClass == 0 ||
intf_desc->bInterfaceClass > 0x12)) {
char t[256];
snprintf(t, sizeof(t), "Using interface %d with wrong class %d, "
"subclass %d or protocol %d",
intf_desc->bInterfaceNumber,
intf_desc->bInterfaceClass,
intf_desc->bInterfaceSubClass,
intf_desc->bInterfaceProtocol);
printWarn(t);
} else {
continue;
}
}
candidate.interface_num = intf_desc->bInterfaceNumber;
candidate.config_num = cfg_desc->bConfigurationValue;
candidate.ep_out = intf_desc->endpoint[0].bEndpointAddress;
candidate.ep_in = intf_desc->endpoint[1].bEndpointAddress;
candidate.packet_size = intf_desc->endpoint[0].wMaxPacketSize;
intf_found = true;
}
libusb_free_config_descriptor(cfg_desc);
}
if (intf_found)
result.push_back(candidate);
else
libusb_close(handle);
}
libusb_free_device_list(devs, true);
return result;
}
#endif
/* send connect instruction (0x02) to switch
* in JTAG mode (0x02)
*/
@@ -504,34 +815,70 @@ int CmsisDAP::flush()
int CmsisDAP::xfer(uint8_t instruction, int tx_len,
uint8_t *rx_buff, int rx_len)
{
(void)tx_len;
int ret = -1, bulk_len = 0;
_ll_buffer[0] = 0;
_ll_buffer[1] = instruction;
int ret = hid_write(_dev, _ll_buffer, 65);
if (ret == -1) {
printf("Error\n");
return ret;
switch(_backend){
#ifdef ENABLE_CMSISDAP_V1
case BACKEND_HID:
ret = hid_write(_hid_dev, _ll_buffer, 65);
if (ret == -1) {
printError("Error: HID write failed\n");
return ret;
}
ret = hid_read_timeout(_hid_dev, _ll_buffer, 65, 1000);
if (ret <= 0) {
if (ret == 0)
printError("Error: HID read timeout\n");
else if (ret == -1)
printError("Error: HID comm failed\n");
return ret;
}
break;
#endif
#ifdef ENABLE_CMSISDAP_V2
case BACKEND_USBBULK:
memset(&_ll_buffer[1 + tx_len + 1], 0, 64 - (tx_len + 1));
ret = libusb_bulk_transfer(_usb_dev, _ep_out, &_ll_buffer[1], 64, &bulk_len, 1000);
if (ret != 0) {
printError("Error: Bulk write failed\n");
return ret;
}
memset(&_ll_buffer[0], 0, 1024);
ret = libusb_bulk_transfer(_usb_dev, _ep_in, &_ll_buffer[0], _packet_size, &bulk_len, 1000);
// sleep for 1ms to ensure that polling behavior aligns with HID backend
usleep(1000);
if (ret != 0 && ret != LIBUSB_ERROR_TIMEOUT) {
printError("Error: Bulk read failed\n");
return ret;
}
if(bulk_len == 0){
printError("Error: Bulk timeout\n");
return -1;
}
// if (rx_buff && bulk_len < rx_len + 2) {
// printf("bulk short read: expected %d, got %d\n", rx_len + 2, bulk_len);
// }
ret = bulk_len;
break;
#endif
default:
printError("Error: unknown USB backend\n");
break;
}
ret = hid_read_timeout(_dev, _ll_buffer, 65, 1000);
if (ret <= 0) {
if (ret == 0)
printError("Error timeout\n");
else if (ret == -1)
printError("Error comm\n");
return ret;
}
if (_ll_buffer[0] != instruction && _ll_buffer[1] != DAP_OK) {
printf("Error: command error");
if (_ll_buffer[0] != instruction) {
printError("Error: command error\n");
return -1;
}
if (_ll_buffer[1] != DAP_OK) {
printError("Error: DAP status error\n");
return -1;
}
if (rx_buff) {
memcpy(rx_buff, _buffer, rx_len);
}
return ret;
}
@@ -541,28 +888,61 @@ int CmsisDAP::xfer(uint8_t instruction, int tx_len,
*/
int CmsisDAP::xfer(int tx_len, uint8_t *rx_buff, int rx_len)
{
(void)tx_len;
int ret = -1, bulk_len = 0;
_ll_buffer[0] = 0;
int ret = hid_write(_dev, _ll_buffer, 65);
if (ret == -1) {
printf("Error\n");
return ret;
switch(_backend){
#ifdef ENABLE_CMSISDAP_V1
case BACKEND_HID:
ret = hid_write(_hid_dev, _ll_buffer, 65);
if (ret == -1) {
printError("Error: HID write failed\n");
return ret;
}
ret = hid_read_timeout(_hid_dev, _ll_buffer, 65, 1000);
if (ret <= 0) {
if (ret == 0)
printError("Error: HID read timeout\n");
else if (ret == -1)
printError("Error: HID comm failed\n");
return ret;
}
break;
#endif
#ifdef ENABLE_CMSISDAP_V2
case BACKEND_USBBULK:
memset(&_ll_buffer[1 + tx_len + 1], 0, 64 - (tx_len + 1));
ret = libusb_bulk_transfer(_usb_dev, _ep_out, &_ll_buffer[1], 64, &bulk_len, 1000);
if (ret != 0) {
printError("Error: Bulk write failed\n");
return ret;
}
memset(&_ll_buffer[0], 0, 1024);
ret = libusb_bulk_transfer(_usb_dev, _ep_in, &_ll_buffer[0], _packet_size, &bulk_len, 1000);
// sleep for 1ms to ensure that polling behavior aligns with HID backend
usleep(1000);
if (ret != 0 && ret != LIBUSB_ERROR_TIMEOUT) {
printError("Error: Bulk read failed\n");
return ret;
}
if(bulk_len == 0){
printError("Error: Bulk timeout\n");
return -1;
}
// if (rx_buff && bulk_len < rx_len + 2) {
// printf("bulk short read: expected %d, got %d\n", rx_len + 2, bulk_len);
// }
ret = bulk_len;
break;
#endif
default:
printError("Error: unknown USB backend\n");
break;
}
ret = hid_read_timeout(_dev, _ll_buffer, 65, 1000);
if (ret <= 0) {
if (ret == 0)
printf("Error timeout\n");
else if (ret == -1)
printf("Error comm\n");
return ret;
}
if (rx_len)
memmove(rx_buff, _ll_buffer, rx_len);
return ret;
}
@@ -604,10 +984,22 @@ void CmsisDAP::display_info(uint8_t info, uint8_t type)
printError("\t" + cmsisdap_info_id_str[info] + " : NA");
return;
}
} else if (info == INFO_ID_TARGET_DEV_NAME || \
info == INFO_ID_TARGET_DEV_VENDOR) {
/* nothing */
return;
} else if (info == INFO_ID_TARGET_DEV_NAME) {
if (!_product_name.empty()){
snprintf(val, sizeof(val), "\t%s: %ls",
cmsisdap_info_id_str[info].c_str(), _product_name.c_str());
} else {
printError("\t" + cmsisdap_info_id_str[info] + " : NA");
return;
}
} else if (info == INFO_ID_TARGET_DEV_VENDOR) {
if (!_vendor.empty()){
snprintf(val, sizeof(val), "\t%s: %ls",
cmsisdap_info_id_str[info].c_str(), _vendor.c_str());
} else {
printError("\t" + cmsisdap_info_id_str[info] + " : NA");
return;
}
} else {
printError("\t" + cmsisdap_info_id_str[info] + " : NA");
return;