commit 2bc1b60f6f1793a27e90ebe1c754f492573d925c
parent 2537c9af05232aca8fa5646319b9640ff1d7c7bf
Author: Brian Swetland <swetland@frotz.net>
Date: Mon, 6 Mar 2023 15:53:32 -0800
xdebug/transport: improve usb device selection
- commandline option to specify usb VID:PID: -usb
- commandline option to require a serial number: -sn
- require an interface of class 0xFF (vendor) with two bulk
endpoints, on in, one out
- if a vid:pid is not specified, require that the device
class/subclass/protocol is 0:*:*, FF:*:*, or EF:02:01
- if a vid:pid is not specified, require that the interface
string descriptor contains "CMSIS-DAP"
- select endpoint and interface numbers from descriptors
- use sysfs for string descriptor lookups, if possible,
to avoid having to open the device just to lookup strings
Diffstat:
5 files changed, 255 insertions(+), 42 deletions(-)
diff --git a/src/transport-dap.c b/src/transport-dap.c
@@ -460,18 +460,21 @@ int dc_attach(DC* dc, unsigned flags, unsigned tgt, uint32_t* idcode) {
}
+static unsigned dc_vid = 0;
+static unsigned dc_pid = 0;
+static const char* dc_serialno = NULL;
+
+void dc_require_vid_pid(unsigned vid, unsigned pid) {
+ dc_vid = vid;
+ dc_pid = pid;
+}
+
+void dc_require_serialno(const char* sn) {
+ dc_serialno = sn;
+}
+
static usb_handle* usb_connect(void) {
- usb_handle *usb;
-
- usb = usb_open(0x1fc9, 0x0143, 0);
- if (usb == 0) {
- usb = usb_open(0x2e8a, 0x000c, 42);
- if (usb == 0) {
- ERROR("cannot find device\n");
- return NULL;
- }
- }
- return usb;
+ return usb_open(dc_vid, dc_pid, dc_serialno);
}
// setup a newly connected DAP device
diff --git a/src/transport.h b/src/transport.h
@@ -5,6 +5,9 @@
#include <stdint.h>
+void dc_require_vid_pid(unsigned vid, unsigned pid);
+void dc_require_serialno(const char* sn);
+
#define DC_OK 0
#define DC_ERR_FAILED -1 // generic internal failure
#define DC_ERR_BAD_PARAMS -2 // Invalid parameters
diff --git a/src/usb.c b/src/usb.c
@@ -3,6 +3,9 @@
#include <stdlib.h>
#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
#include <libusb-1.0/libusb.h>
@@ -14,53 +17,118 @@ struct usb_handle {
unsigned eo;
};
-static libusb_context *usb_ctx = NULL;
-
-usb_handle *usb_open(unsigned vid, unsigned pid, unsigned ifc) {
- usb_handle *usb;
- int r;
+int get_sysfs_path(libusb_device* dev, char* path, int max) {
+ if (max < 0) return -1;
+ uint8_t num[8];
+ uint8_t bus = libusb_get_bus_number(dev);
+ int count = libusb_get_port_numbers(dev, num, 8);
+ if (count < 1) return -1;
+ int r = snprintf(path, max, "/sys/bus/usb/devices/%u-%u", bus, num[0]);
+ if ((r >= max) || (r < 0)) return -1;
+ int len = r;
+ for (unsigned n = 1; n < count; n++) {
+ r = snprintf(path + len, max - len, ".%u", num[n]);
+ if ((r >= (max - len)) || (r < 0)) return -1;
+ len += r;
+ }
+ return len;
+}
- if (usb_ctx == NULL) {
- if (libusb_init(&usb_ctx) < 0) {
- usb_ctx = NULL;
- return NULL;
+int get_vendor_bulk_ifc(struct libusb_config_descriptor *cd, uint8_t* iifc,
+ uint8_t *ino, uint8_t *eptin, uint8_t *eptout) {
+ const struct libusb_interface_descriptor *id;
+ for (unsigned i = 0; i < cd->bNumInterfaces; i++) {
+ if (cd->interface[i].num_altsetting != 1) {
+ // keep it simple: skip interfaces with alt settings
+ continue;
+ }
+ id = &cd->interface[i].altsetting[0];
+ if (id->bInterfaceClass != 0xFF) {
+ // require vendor ifc class
+ continue;
+ }
+ if (id->bNumEndpoints != 2) {
+ // must have two endpoints
+ continue;
+ }
+ // look for one bulk in, one bulk out
+ const struct libusb_endpoint_descriptor *e0 = id->endpoint + 0;
+ const struct libusb_endpoint_descriptor *e1 = id->endpoint + 1;
+ if ((e0->bmAttributes & 3) != LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK) {
+ continue;
+ }
+ if ((e1->bmAttributes & 3) != LIBUSB_ENDPOINT_TRANSFER_TYPE_BULK) {
+ continue;
+ }
+ if ((e0->bEndpointAddress & 0x80) &&
+ (!(e1->bEndpointAddress & 0x80))) {
+ *eptin = e0->bEndpointAddress;
+ *eptout = e1->bEndpointAddress;
+ goto match;
+ } else if ((e1->bEndpointAddress & 0x80) &&
+ (!(e0->bEndpointAddress & 0x80))) {
+ *eptin = e1->bEndpointAddress;
+ *eptout = e0->bEndpointAddress;
+ goto match;
+ } else {
+ continue;
}
}
+ return -1;
+match:
+ *ino = id->bInterfaceNumber;
+ *iifc = id->iInterface;
+ return 0;
+}
+
+usb_handle *usb_try_open(libusb_device* dev, const char* sn,
+ unsigned isn, unsigned iifc,
+ unsigned ino, unsigned ei, unsigned eo) {
+ unsigned char text[256];
+ usb_handle *usb;
+ int r;
usb = malloc(sizeof(usb_handle));
if (usb == 0) {
return NULL;
}
- /* TODO: extract from descriptors */
- switch (ifc) {
- case 0:
- usb->ei = 0x81;
- usb->eo = 0x01;
- break;
- case 1:
- usb->ei = 0x82;
- usb->eo = 0x02;
- break;
- case 42:
- usb->ei = 0x85;
- usb->eo = 0x04;
- ifc = 0;
- break;
- default:
+ if (libusb_open(dev, &usb->dev) < 0) {
goto fail;
}
- usb->dev = libusb_open_device_with_vid_pid(usb_ctx, vid, pid);
- if (usb->dev == NULL) {
- goto fail;
+ if (sn && (isn != 0)) {
+ r = libusb_get_string_descriptor_ascii(usb->dev, isn, text, 256);
+ if (r < 0) {
+ goto fail;
+ }
+ if (strlen(sn) != r) {
+ goto fail;
+ }
+ if (memcmp(text, sn, r)) {
+ goto fail;
+ }
}
+ if (iifc != 0) {
+ r = libusb_get_string_descriptor_ascii(usb->dev, iifc, text, 255);
+ if (r < 0) {
+ goto fail;
+ }
+ text[r] = 0;
+ if (!strstr((void*)text, "CMSIS-DAP")) {
+ goto fail;
+ }
+ }
+
+ usb->ei = ei;
+ usb->eo = eo;
+
// This causes problems on re-attach. Maybe need for OSX?
// On Linux it's completely happy without us explicitly setting a configuration.
//r = libusb_set_configuration(usb->dev, 1);
- r = libusb_claim_interface(usb->dev, ifc);
+ r = libusb_claim_interface(usb->dev, ino);
if (r < 0) {
- fprintf(stderr, "failed to claim interface #%d\n", ifc);
+ fprintf(stderr, "failed to claim interface #%d\n", ino);
goto close_fail;
}
@@ -80,6 +148,120 @@ fail:
return NULL;
}
+static int read_sysfs(const char* path, char* text, int len) {
+ int fd;
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ return -1;
+ }
+ int r = read(fd, text, len);
+ if ((r < 1) || (text[r-1] != '\n')) {
+ return -1;
+ }
+ text[r-1] = 0;
+ return 0;
+}
+
+static libusb_context *usb_ctx = NULL;
+
+usb_handle *usb_open(unsigned vid, unsigned pid, const char* sn) {
+ usb_handle *usb = NULL;
+
+ if (usb_ctx == NULL) {
+ if (libusb_init(&usb_ctx) < 0) {
+ usb_ctx = NULL;
+ return NULL;
+ }
+ }
+
+ uint8_t ino, eo, ei, iifc;
+ libusb_device** list;
+ int count = libusb_get_device_list(usb_ctx, &list);
+ for (int n = 0; n < count; n++) {
+ struct libusb_device_descriptor dd;
+ //const struct libusb_interface_descriptor *id;
+ if (libusb_get_device_descriptor(list[n], &dd) != 0) {
+ continue;
+ }
+ int isn = dd.iSerialNumber;
+ if (vid) {
+ // exact match requested
+ if ((vid != dd.idVendor) || (pid != dd.idProduct)) {
+ continue;
+ }
+ } else if (dd.bDeviceClass == 0x00) {
+ // use interface class: okay
+ } else if (dd.bDeviceClass == 0xFF) {
+ // vendor class: okay
+ } else if ((dd.bDeviceClass == 0xEF) &&
+ (dd.bDeviceSubClass == 0x02) &&
+ (dd.bDeviceProtocol == 0x01)) {
+ // interface association descriptor: okay
+ } else {
+ continue;
+ }
+ struct libusb_config_descriptor *cd;
+ if (libusb_get_active_config_descriptor(list[n], &cd) != 0) {
+ continue;
+ }
+ int r = get_vendor_bulk_ifc(cd, &iifc, &ino, &ei, &eo);
+ libusb_free_config_descriptor(cd);
+ if (r != 0) {
+ continue;
+ }
+#if 0
+ printf("%02x %02x %02x %04x %04x %02x %02x %02x\n",
+ dd.bDeviceClass,
+ dd.bDeviceSubClass,
+ dd.bDeviceProtocol,
+ dd.idVendor,
+ dd.idProduct,
+ ino, ei, eo);
+#endif
+ // try to validate serialno and interface description
+ // using sysfs so we don't need to open the device
+ // to rule it out
+ char path[512];
+ char text[256];
+ int len = get_sysfs_path(list[n], path, 512 - 64);
+ if (len < 0) {
+ // should never happen, but just in case
+ continue;
+ }
+ if (sn) {
+ sprintf(path + len, "/serial");
+ if (read_sysfs(path, text, sizeof(text)) == 0) {
+ if (strcmp(sn, text)) {
+ continue;
+ }
+ // matched here, so don't check after libusb_open()
+ isn = 0;
+ }
+ }
+ if (vid == 0) {
+ // if we're wildcarding it, check interface
+ sprintf(path + len, ":%u.%u/interface", 1, 0);
+ if (read_sysfs(path, text, sizeof(text)) == 0) {
+ if (strstr(text, "CMSIS-DAP") == 0) {
+ continue;
+ }
+ // matched here, so don't check after libusb_open()
+ iifc = 0;
+ }
+ } else {
+ // if not wildcarding, don't enforce this check in usb_try_open()
+ iifc = 0;
+ }
+
+ if ((usb = usb_try_open(list[n], sn, isn, iifc, ino, ei, eo)) != NULL) {
+ break;
+ }
+ }
+ if (count >= 0) {
+ libusb_free_device_list(list, 1);
+ }
+ return usb;
+}
+
void usb_close(usb_handle *usb) {
libusb_close(usb->dev);
free(usb);
diff --git a/src/usb.h b/src/usb.h
@@ -10,7 +10,7 @@ typedef struct usb_handle usb_handle;
/* simple usb api for devices with bulk in+out interfaces */
-usb_handle *usb_open(unsigned vid, unsigned pid, unsigned ifc);
+usb_handle *usb_open(unsigned vid, unsigned pid, const char* sn);
void usb_close(usb_handle *usb);
int usb_read(usb_handle *usb, void *data, int len);
int usb_read_forever(usb_handle *usb, void *data, int len);
diff --git a/src/xdebug.c b/src/xdebug.c
@@ -168,6 +168,31 @@ void handle_line(char *line, unsigned len) {
static tui_ch_t* ch;
int main(int argc, char** argv) {
+ for (int n = 1; n < argc; n++) {
+ if (!strcmp(argv[n], "-usb")) {
+ n++;
+ if (n == argc) {
+ fprintf(stderr, "option -usb requires vid:pid\n");
+ return -1;
+ }
+ char *x = strchr(argv[n], ':');
+ if (x == NULL) {
+ fprintf(stderr, "option -usb requires vid:pid\n");
+ return -1;
+ }
+ dc_require_vid_pid(strtoul(argv[n], 0, 16), strtoul(x+1, 0, 16));
+ } else if (!strcmp(argv[n], "-sn")) {
+ n++;
+ if (n == argc) {
+ fprintf(stderr, "option -sn requires serialno\n");
+ return -1;
+ }
+ dc_require_serialno(argv[n]);
+ } else {
+ fprintf(stderr, "unknown option '%s'\n", argv[n]);
+ return -1;
+ }
+ }
tui_init();
tui_ch_create(&ch, 0);
dc_create(&dc);