xdebug

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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:
Msrc/transport-dap.c | 25++++++++++++++-----------
Msrc/transport.h | 3+++
Msrc/usb.c | 242+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Msrc/usb.h | 2+-
Msrc/xdebug.c | 25+++++++++++++++++++++++++
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);