diff options
-rw-r--r-- | include/osmocom/usb/libusb.h | 75 | ||||
-rw-r--r-- | src/usb/osmo_libusb.c | 378 |
2 files changed, 452 insertions, 1 deletions
diff --git a/include/osmocom/usb/libusb.h b/include/osmocom/usb/libusb.h index 7f10f74c..382c86e1 100644 --- a/include/osmocom/usb/libusb.h +++ b/include/osmocom/usb/libusb.h @@ -1,6 +1,81 @@ #pragma once +/* libusb utilities + * + * (C) 2010-2019 by Harald Welte <hwelte@hmw-consulting.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ #include <libusb.h> +#define USB_MAX_PATH_LEN 20 + +struct dev_id { + uint16_t vendor_id; + uint16_t product_id; +}; + +/* structure describing a single matching interface found */ +struct usb_interface_match { + /* libusb device E*/ + libusb_device *usb_dev; + /* Vendor ID of the device running matching interface */ + uint16_t vendor; + /* Product ID of the device running matching interface */ + uint16_t product; + /* USB Bus Address */ + uint8_t addr; + /* physical path */ + char path[USB_MAX_PATH_LEN]; + /* configuration of matching interface */ + uint8_t configuration; + /* interface number of matching interface */ + uint8_t interface; + /* altsetting of matching interface */ + uint8_t altsetting; + /* bInterfaceClass of matching interface */ + uint8_t class; + /* bInterfaceSubClass of matching interface */ + uint8_t sub_class; + /* bInterfaceProtocol of matching interface */ + uint8_t protocol; + /* index of string descriptor of matching interface */ + uint8_t string_idx; +}; + + +char *osmo_libusb_dev_get_path_buf(char *buf, size_t bufsize, libusb_device *dev); +char *osmo_libusb_dev_get_path_c(void *ctx, libusb_device *dev); + +libusb_device **osmo_libusb_find_matching_usb_devs(void *ctx, struct libusb_context *luctx, + const struct dev_id *dev_ids); + +int osmo_libusb_dev_find_matching_interfaces(libusb_device *dev, int class, int sub_class, + int protocol, struct usb_interface_match *out, + unsigned int out_len); + +int osmo_libusb_find_matching_interfaces(libusb_context *luctx, const struct dev_id *dev_ids, + int class, int sub_class, int protocol, + struct usb_interface_match *out, unsigned int out_len); + +libusb_device_handle *osmo_libusb_open_claim_interface(void *ctx, libusb_context *luctx, + const struct usb_interface_match *ifm); + +int osmo_libusb_get_ep_addrs(libusb_device_handle *devh, unsigned int if_num, + uint8_t *out, uint8_t *in, uint8_t *irq); + + int osmo_libusb_init(libusb_context **luctx); void osmo_libusb_exit(libusb_context *luctx); diff --git a/src/usb/osmo_libusb.c b/src/usb/osmo_libusb.c index fda95739..5b012b86 100644 --- a/src/usb/osmo_libusb.c +++ b/src/usb/osmo_libusb.c @@ -1,6 +1,6 @@ /* libosmocore integration with libusb-1.0 * - * (C) 2019 by Harald Welte <laforge@gnumonks.org> + * (C) 2019-2019 by Harald Welte <laforge@gnumonks.org> * All Rights Reserved. * * SPDX-License-Identifier: GPL-2.0+ @@ -33,6 +33,8 @@ #include <libusb.h> +#include <osmocom/usb/libusb.h> + /*********************************************************************** * logging integration ***********************************************************************/ @@ -104,7 +106,381 @@ static void osmo_usb_removed_cb(int fd, void *user_data) talloc_free(ofd); } +/*********************************************************************** + * utility functions + ***********************************************************************/ + +/*! obtain the string representation of the USB device path of given device. + * \param[out] buf Output string buffer + * \param[in] bufsize Size of output string buffer in bytes + * \param[in] dev USB device whose bus path we want to obtain + * \returns pointer to 'buf' in case of success; NULL in case of error */ +char *osmo_libusb_dev_get_path_buf(char *buf, size_t bufsize, libusb_device *dev) +{ +#if (defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000102) || \ + (defined(LIBUSBX_API_VERSION) && LIBUSBX_API_VERSION >= 0x01000102) + struct osmo_strbuf sb = { .buf = buf, .len = bufsize }; + uint8_t path[8]; + int r,j; + r = libusb_get_port_numbers(dev, path, sizeof(path)); + if (r > 0) { + OSMO_STRBUF_PRINTF(sb, "%d-%d", libusb_get_bus_number(dev), path[0]); + for (j = 1; j < r; j++){ + OSMO_STRBUF_PRINTF(sb, ".%d", path[j]); + } + } + return buf; +#else +# warning "libusb too old - building without USB path support!" + return NULL; +#endif +} + +/*! obtain the string representation of the USB device path of given device. + * \param[in] talloc context from which to dynamically allocate output string buffer + * \param[in] dev USB device whose bus path we want to obtain + * \returns pointer to 'buf' in case of success; NULL in case of error */ +char *osmo_libusb_dev_get_path_c(void *ctx, libusb_device *dev) +{ + char *buf = talloc_zero_size(ctx, USB_MAX_PATH_LEN); + if (!buf) + return NULL; + return osmo_libusb_dev_get_path_buf(buf, USB_MAX_PATH_LEN, dev); +} + +static int match_dev_id(const struct libusb_device_descriptor *desc, const struct dev_id *id) +{ + if ((desc->idVendor == id->vendor_id) && (desc->idProduct == id->product_id)) + return 1; + return 0; +} + +static int match_dev_ids(const struct libusb_device_descriptor *desc, const struct dev_id *ids) +{ + const struct dev_id *id; + + for (id = ids; id->vendor_id || id->product_id; id++) { + if (match_dev_id(desc, id)) + return 1; + } + return 0; +} + +/*! Find USB devices matching the specified list of USB VendorID/ProductIDs + * \param[in] ctx talloc context from which to allocate output data + * \param[in] luctx libusb context on which to operate + * \param[in] dev_ids zero-terminated array of VendorId/ProductId tuples + * \returns array of up to 256 libusb_device pointers; NULL in case of error */ +libusb_device **osmo_libusb_find_matching_usb_devs(void *ctx, struct libusb_context *luctx, + const struct dev_id *dev_ids) +{ + libusb_device **list; + libusb_device **out = talloc_zero_array(ctx, libusb_device *, 256); + libusb_device **cur = out; + unsigned int i; + int rc; + + if (!out) + return NULL; + + rc = libusb_get_device_list(luctx, &list); + if (rc <= 0) { + perror("No USB devices found"); + talloc_free(out); + return NULL; + } + + for (i = 0; list[i] != NULL; i++) { + struct libusb_device_descriptor dev_desc; + libusb_device *dev = list[i]; + + rc = libusb_get_device_descriptor(dev, &dev_desc); + if (rc < 0) { + perror("Couldn't get device descriptor\n"); + libusb_unref_device(dev); + continue; + } + + if (match_dev_ids(&dev_desc, dev_ids)) { + *cur = dev; + cur++; + /* overflow check */ + if (cur >= out + 256) + break; + } else + libusb_unref_device(dev); + } + if (cur == out) { + libusb_free_device_list(list, 1); + talloc_free(out); + return NULL; + } + + libusb_free_device_list(list, 0); + return out; +} + +/*! find a matching interface among all interfaces of the given USB device. + * \param[in] dev USB device in which we shall search + * \param[in] class USB Interface Class to look for + * \param[in] sub_class USB Interface Subclass to look for + * \param[in] protocol USB Interface Protocol to look for + * \param[out] out User-allocated array for storing matches + * \param[in] out_len Length of out array + * \returns number of matching interfaces; negative in case of error */ +int osmo_libusb_dev_find_matching_interfaces(libusb_device *dev, int class, int sub_class, + int protocol, struct usb_interface_match *out, + unsigned int out_len) +{ + struct libusb_device_descriptor dev_desc; + int rc, i, out_idx = 0; + uint8_t addr; + char pathbuf[USB_MAX_PATH_LEN]; + char *path; + + rc = libusb_get_device_descriptor(dev, &dev_desc); + if (rc < 0) { + perror("Couldn't get device descriptor\n"); + return -EIO; + } + + addr = libusb_get_device_address(dev); + path = osmo_libusb_dev_get_path_buf(pathbuf, sizeof(pathbuf), dev); + /* iterate over all configurations */ + for (i = 0; i < dev_desc.bNumConfigurations; i++) { + struct libusb_config_descriptor *conf_desc; + int j; + + rc = libusb_get_config_descriptor(dev, i, &conf_desc); + if (rc < 0) { + fprintf(stderr, "Couldn't get config descriptor %u\n", i); + continue; + } + /* iterate over all interfaces */ + for (j = 0; j < conf_desc->bNumInterfaces; j++) { + const struct libusb_interface *intf = &conf_desc->interface[j]; + int k; + /* iterate over all alternate settings */ + for (k = 0; k < intf->num_altsetting; k++) { + const struct libusb_interface_descriptor *if_desc; + if_desc = &intf->altsetting[k]; + if (class >= 0 && if_desc->bInterfaceClass != class) + continue; + if (sub_class >= 0 && if_desc->bInterfaceSubClass != sub_class) + continue; + if (protocol >= 0 && if_desc->bInterfaceProtocol != protocol) + continue; + /* MATCH! */ + out[out_idx].usb_dev = dev; + out[out_idx].vendor = dev_desc.idVendor; + out[out_idx].product = dev_desc.idProduct; + out[out_idx].addr = addr; + strncpy(out[out_idx].path, path, sizeof(out[out_idx].path)-1); + out[out_idx].path[sizeof(out[out_idx].path)-1] = '\0'; + out[out_idx].configuration = conf_desc->bConfigurationValue; + out[out_idx].interface = if_desc->bInterfaceNumber; + out[out_idx].altsetting = if_desc->bAlternateSetting; + out[out_idx].class = if_desc->bInterfaceClass; + out[out_idx].sub_class = if_desc->bInterfaceSubClass; + out[out_idx].protocol = if_desc->bInterfaceProtocol; + out[out_idx].string_idx = if_desc->iInterface; + out_idx++; + if (out_idx >= out_len) + return out_idx; + } + } + } + return out_idx; +} + +/*! find matching interfaces among a list devices of specified VendorId/ProductID tuples. + * \param[in] luctx libusb context on which to operate + * \param[in] dev_ids zero-terminated array of VendorId/ProductId tuples + * \param[in] class USB Interface Class to look for + * \param[in] sub_class USB Interface Subclass to look for + * \param[in] protocol USB Interface Protocol to look for + * \param[out] out User-allocated array for storing matches + * \param[in] out_len Length of out array + * \returns number of matching interfaces; negative in case of error */ +int osmo_libusb_find_matching_interfaces(libusb_context *luctx, const struct dev_id *dev_ids, + int class, int sub_class, int protocol, + struct usb_interface_match *out, unsigned int out_len) +{ + struct usb_interface_match *out_cur = out; + unsigned int out_len_remain = out_len; + libusb_device **list; + libusb_device **dev; + + list = osmo_libusb_find_matching_usb_devs(NULL, luctx, dev_ids); + if (!list) + return 0; + + for (dev = list; *dev; dev++) { + int rc; + +#if 0 + struct libusb_device_descriptor dev_desc; + uint8_t ports[8]; + uint8_t addr; + rc = libusb_get_device_descriptor(*dev, &dev_desc); + if (rc < 0) { + perror("Cannot get device descriptor"); + continue; + } + + addr = libusb_get_device_address(*dev); + + rc = libusb_get_port_numbers(*dev, ports, sizeof(ports)); + if (rc < 0) { + perror("Cannot get device path"); + continue; + } + + printf("Found USB Device %04x:%04x at address %d\n", + dev_desc.idVendor, dev_desc.idProduct, addr); +#endif + + rc = osmo_libusb_dev_find_matching_interfaces(*dev, class, sub_class, + protocol, out_cur, out_len_remain); + if (rc < 0) + continue; + out_cur += rc; + out_len_remain -= rc; + + } + + /* unref / free list */ + for (dev = list; *dev; dev++) + libusb_unref_device(*dev); + talloc_free(list); + + return out_len - out_len_remain; +} + +/*! open matching USB device and claim interface + * \param[in] ctx talloc context to use for related allocations + * \param[in] luctx libusb context on which to operate + * \param[in] ifm interface match describing interface to claim + * \returns libusb device chandle on success; NULL on error */ +libusb_device_handle *osmo_libusb_open_claim_interface(void *ctx, libusb_context *luctx, + const struct usb_interface_match *ifm) +{ + int rc, config; + struct dev_id dev_ids[] = { { ifm->vendor, ifm->product }, { 0, 0 } }; + libusb_device **list; + libusb_device **dev; + libusb_device_handle *usb_devh = NULL; + + list = osmo_libusb_find_matching_usb_devs(ctx, luctx, dev_ids); + if (!list) { + perror("No USB device with matching VID/PID"); + return NULL; + } + + for (dev = list; *dev; dev++) { + int addr; + char pathbuf[USB_MAX_PATH_LEN]; + char *path; + + addr = libusb_get_device_address(*dev); + path = osmo_libusb_dev_get_path_buf(pathbuf, sizeof(pathbuf), *dev); + if ((ifm->addr && addr == ifm->addr) || + (strlen(ifm->path) && !strcmp(path, ifm->path))) { + rc = libusb_open(*dev, &usb_devh); + if (rc < 0) { + fprintf(stderr, "Cannot open device: %s\n", libusb_error_name(rc)); + usb_devh = NULL; + break; + } + rc = libusb_get_configuration(usb_devh, &config); + if (rc < 0) { + fprintf(stderr, "Cannot get current configuration: %s\n", libusb_error_name(rc)); + libusb_close(usb_devh); + usb_devh = NULL; + break; + } + if (config != ifm->configuration) { + rc = libusb_set_configuration(usb_devh, ifm->configuration); + if (rc < 0) { + fprintf(stderr, "Cannot set configuration: %s\n", libusb_error_name(rc)); + libusb_close(usb_devh); + usb_devh = NULL; + break; + } + } + rc = libusb_claim_interface(usb_devh, ifm->interface); + if (rc < 0) { + fprintf(stderr, "Cannot claim interface: %s\n", libusb_error_name(rc)); + libusb_close(usb_devh); + usb_devh = NULL; + break; + } + rc = libusb_set_interface_alt_setting(usb_devh, ifm->interface, ifm->altsetting); + if (rc < 0) { + fprintf(stderr, "Cannot set interface altsetting: %s\n", libusb_error_name(rc)); + libusb_release_interface(usb_devh, ifm->interface); + libusb_close(usb_devh); + usb_devh = NULL; + break; + } + } + } + + /* unref / free list */ + for (dev = list; *dev; dev++) + libusb_unref_device(*dev); + talloc_free(list); + + return usb_devh; +} + +/*! obtain the endpoint addresses for a given USB interface. + * \param[in] devh USB device handle on which to operate + * \param[in] if_num USB Interface number on which to operate + * \param[out] out user-provided storage for OUT endpoint number + * \param[out] in user-provided storage for IN endpoint number + * \param[out] irq user-provided storage for IRQ endpoint number + * \returns 0 in case of success; negative in case of error */ +int osmo_libusb_get_ep_addrs(libusb_device_handle *devh, unsigned int if_num, + uint8_t *out, uint8_t *in, uint8_t *irq) +{ + libusb_device *dev = libusb_get_device(devh); + struct libusb_config_descriptor *cdesc; + const struct libusb_interface_descriptor *idesc; + const struct libusb_interface *iface; + int rc, l; + + rc = libusb_get_active_config_descriptor(dev, &cdesc); + if (rc < 0) + return rc; + + iface = &cdesc->interface[if_num]; + /* FIXME: we assume there's no altsetting */ + idesc = &iface->altsetting[0]; + + for (l = 0; l < idesc->bNumEndpoints; l++) { + const struct libusb_endpoint_descriptor *edesc = &idesc->endpoint[l]; + switch (edesc->bmAttributes & 3) { + case LIBUSB_TRANSFER_TYPE_BULK: + if (edesc->bEndpointAddress & 0x80) { + if (in) + *in = edesc->bEndpointAddress; + } else { + if (out) + *out = edesc->bEndpointAddress; + } + break; + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (irq) + *irq = edesc->bEndpointAddress; + break; + default: + break; + } + } + return 0; +} /*********************************************************************** * initialization ***********************************************************************/ |