From d8c87b536af0adb853b277b5c05db66613df5838 Mon Sep 17 00:00:00 2001 From: Robin Getz Date: Mon, 15 Nov 2021 10:02:20 -0500 Subject: [PATCH] usb: when scanning provide option to qualify USB vendor ID or product ID When we do a USB scan, we open each device looking for a string "IIO", and if it is not, then we close the device. The archirecture of libusb is that when we are doing this, other applications are locked out from using that device. This is known to cause some user problems on other devices (not IIO) - most notibly HackRF and USRP. This change adds the ability to restricting scans to certain vendors, or specific product IDs - which is what most purpose build software is looking for. (GNU Radio doesn't want to open every device, only the ones it knows about). This should allow us to play nicer with others. Examples: usb,local local,usb=0456:b673,usb=0456:b672 usb=0456:* VENDOR ID and PRODUCT ID are hexadecimal numbers (no prefix needed), "*" (match any). By default (no vid:pid provided) all devices are opened and checked (which is the previous behavour). Signed-off-by: Robin Getz Signed-off-by: Paul Cercueil --- iio-private.h | 2 +- iio.h | 10 ++++++--- scan.c | 19 ++++++++++++++-- usb.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 85 insertions(+), 7 deletions(-) diff --git a/iio-private.h b/iio-private.h index 71a46afef..9cc50a5c5 100644 --- a/iio-private.h +++ b/iio-private.h @@ -249,7 +249,7 @@ struct iio_context * serial_create_context_from_uri(const char *uri); int local_context_scan(struct iio_scan_result *scan_result); -int usb_context_scan(struct iio_scan_result *scan_result); +int usb_context_scan(struct iio_scan_result *scan_result, const char *args); int dnssd_context_scan(struct iio_scan_result *scan_result); diff --git a/iio.h b/iio.h index 07a1a8004..7442a9f3b 100644 --- a/iio.h +++ b/iio.h @@ -244,10 +244,14 @@ enum iio_event_direction { * * NOTE: Libiio version 0.20 and above can handle multiple * strings, for instance "local:usb:", "ip:usb:", "local:usb:ip:", and - * require a colon as the delimiter. If NULL, the local, USB and IP - * backends will be scanned. + * require a colon as the delimiter. * Libiio version 0.24 and above prefer a comma instead of colon as the - * delimiter. */ + * delimiter, and handle specifying backend-specific information. For + * instance, "local,usb=0456:*" will scan the local backend and limit + * scans on USB to vendor ID 0x0456, and accept all product IDs. The + * "usb=0456:b673" string would limit the scan to the device with this + * particular VID/PID. Both IDs are expected in hexadecimal, no 0x + * prefix needed. */ __api __check_ret struct iio_scan_context * iio_create_scan_context( const char *backend, unsigned int flags); diff --git a/scan.c b/scan.c index 4e9e72ebc..33f2ce2cf 100644 --- a/scan.c +++ b/scan.c @@ -42,8 +42,10 @@ ssize_t iio_scan_context_get_info_list(struct iio_scan_context *ctx, /* Since tokens are all null terminated, it's safe to use strcmp on them */ if (WITH_LOCAL_BACKEND && !strcmp(token, "local")) { ret = local_context_scan(&scan_result); - } else if (WITH_USB_BACKEND && !strcmp(token, "usb")) { - ret = usb_context_scan(&scan_result); + } else if (WITH_USB_BACKEND && (!strcmp(token, "usb") || + !strncmp(token, "usb=", sizeof("usb=") - 1))) { + token = token[3] == '=' ? token + 4 : NULL; + ret = usb_context_scan(&scan_result, token); } else if (HAVE_DNS_SD && !strcmp(token, "ip")) { ret = dnssd_context_scan(&scan_result); } else { @@ -108,6 +110,7 @@ struct iio_scan_context * iio_create_scan_context( const char *backend, unsigned int flags) { struct iio_scan_context *ctx; + char *ptr, *ptr2; unsigned int i, len; /* "flags" must be zero for now */ @@ -135,6 +138,18 @@ struct iio_scan_context * iio_create_scan_context( for (i = 0; i < len; i++) if (ctx->backendopts[i] == ':') ctx->backendopts[i] = ','; + + /* The only place where a colon is accepted is in the usb arguments: + * usb=vid:pid */ + for (ptr = strstr(ctx->backendopts, "usb="); ptr; + ptr = strstr(ptr, "usb=")) { + ptr += sizeof("usb="); + strtoul(ptr, &ptr2, 16); + + /* The USB backend will take care of errors */ + if (ptr2 != ptr && *ptr2 == ',') + *ptr2 = ':'; + } } return ctx; diff --git a/usb.c b/usb.c index 97a4496b9..e12fb332f 100644 --- a/usb.c +++ b/usb.c @@ -1200,14 +1200,58 @@ static int usb_fill_context_info(struct iio_context_info *info, return 0; } -int usb_context_scan(struct iio_scan_result *scan_result) +static int parse_vid_pid(const char *vid_pid, uint16_t *vid, uint16_t *pid) +{ + unsigned long val; + char *ptr; + + /* + * vid_pid string must be either: + * - NULL: scan everything, + * - "vid:*": scan all devices with the given VID, + * - "vid:pid": scan the device with the given VID/PID. + * IDs are given in hexadecimal, and the 0x prefix is not required. + */ + + *vid = 0; + *pid = 0; + + if (!vid_pid) + return 0; + + val = strtoul(vid_pid, &ptr, 16); + if (ptr == vid_pid || val > 0xFFFF || *ptr != ':') + return -EINVAL; + + *vid = (uint16_t) val; + + vid_pid = ptr + 1; + + if (*vid_pid == '*') + return vid_pid[1] == '\0' ? 0 : -EINVAL; + + val = strtoul(vid_pid, &ptr, 16); + if (ptr == vid_pid || val > 0xFFFF || *ptr != '\0') + return -EINVAL; + + *pid = (uint16_t) val; + + return 0; +} + +int usb_context_scan(struct iio_scan_result *scan_result, const char *args) { struct iio_context_info *info; libusb_device **device_list; libusb_context *ctx; + uint16_t vid, pid; unsigned int i; int ret; + ret = parse_vid_pid(args, &vid, &pid); + if (ret) + return ret; + ret = libusb_init(&ctx); if (ret < 0) return -(int) libusb_to_errno(ret); @@ -1222,6 +1266,21 @@ int usb_context_scan(struct iio_scan_result *scan_result) struct libusb_device_handle *hdl; struct libusb_device *dev = device_list[i]; unsigned int intrfc = 0; + struct libusb_device_descriptor device_descriptor; + + /* If we are given a pid or vid, use that to qualify for things, + * this avoids open/closing random devices & potentially locking + * (blocking them) from other applications + */ + if(vid || pid) { + ret = libusb_get_device_descriptor(dev, &device_descriptor); + if (ret) + continue; + if (vid && vid != device_descriptor.idVendor) + continue; + if (pid && pid != device_descriptor.idProduct) + continue; + } ret = libusb_open(dev, &hdl); if (ret)