diff options
Diffstat (limited to 'src/usb.c')
-rw-r--r-- | src/usb.c | 1175 |
1 files changed, 1175 insertions, 0 deletions
diff --git a/src/usb.c b/src/usb.c new file mode 100644 index 0000000..2a48744 --- /dev/null +++ b/src/usb.c @@ -0,0 +1,1175 @@ +/* + * libiio - Library for interfacing industrial I/O (IIO) devices + * + * Copyright (C) 2015 Analog Devices, Inc. + * Author: Paul Cercueil <paul.cercueil@analog.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * */ + +#include "iio-lock.h" +#include "iio-private.h" +#include "iiod-client.h" + +#include <ctype.h> +#include <errno.h> +#include <libusb-1.0/libusb.h> +#include <stdbool.h> +#include <string.h> + +#ifdef ERROR +#undef ERROR +#endif + +#include "debug.h" + +#define DEFAULT_TIMEOUT_MS 5000 + +/* Endpoint for non-streaming operations */ +#define EP_OPS 1 + +#define IIO_INTERFACE_NAME "IIO" + +struct iio_usb_ep_couple { + unsigned char addr_in, addr_out; + unsigned int pipe_id; + bool in_use; + + struct iio_mutex *lock; +}; + +struct iio_usb_io_context { + struct iio_usb_ep_couple *ep; + + struct iio_mutex *lock; + bool cancelled; + struct libusb_transfer *transfer; +}; + +struct iio_context_pdata { + libusb_context *ctx; + libusb_device_handle *hdl; + unsigned int interface; + + struct iiod_client *iiod_client; + + /* Lock for non-streaming operations */ + struct iio_mutex *lock; + + /* Lock for endpoint reservation */ + struct iio_mutex *ep_lock; + + struct iio_usb_ep_couple *io_endpoints; + unsigned int nb_ep_couples; + + unsigned int timeout_ms; + + struct iio_usb_io_context io_ctx; +}; + +struct iio_device_pdata { + struct iio_mutex *lock; + + bool opened; + struct iio_usb_io_context io_ctx; +}; + +static const unsigned int libusb_to_errno_codes[] = { + [- LIBUSB_ERROR_INVALID_PARAM] = EINVAL, + [- LIBUSB_ERROR_ACCESS] = EACCES, + [- LIBUSB_ERROR_NO_DEVICE] = ENODEV, + [- LIBUSB_ERROR_NOT_FOUND] = ENXIO, + [- LIBUSB_ERROR_BUSY] = EBUSY, + [- LIBUSB_ERROR_TIMEOUT] = ETIMEDOUT, + [- LIBUSB_ERROR_OVERFLOW] = EIO, + [- LIBUSB_ERROR_PIPE] = EPIPE, + [- LIBUSB_ERROR_INTERRUPTED] = EINTR, + [- LIBUSB_ERROR_NO_MEM] = ENOMEM, + [- LIBUSB_ERROR_NOT_SUPPORTED] = ENOSYS, +}; + +static unsigned int libusb_to_errno(int error) +{ + switch ((enum libusb_error) error) { + case LIBUSB_ERROR_INVALID_PARAM: + case LIBUSB_ERROR_ACCESS: + case LIBUSB_ERROR_NO_DEVICE: + case LIBUSB_ERROR_NOT_FOUND: + case LIBUSB_ERROR_BUSY: + case LIBUSB_ERROR_TIMEOUT: + case LIBUSB_ERROR_PIPE: + case LIBUSB_ERROR_INTERRUPTED: + case LIBUSB_ERROR_NO_MEM: + case LIBUSB_ERROR_NOT_SUPPORTED: + return libusb_to_errno_codes[- (int) error]; + case LIBUSB_ERROR_IO: + case LIBUSB_ERROR_OTHER: + case LIBUSB_ERROR_OVERFLOW: + default: + return EIO; + } +} + +static int usb_io_context_init(struct iio_usb_io_context *io_ctx) +{ + io_ctx->lock = iio_mutex_create(); + if (!io_ctx->lock) + return -ENOMEM; + + return 0; +} + +static void usb_io_context_exit(struct iio_usb_io_context *io_ctx) +{ + if (io_ctx->lock) { + iio_mutex_destroy(io_ctx->lock); + io_ctx->lock = NULL; + } +} + +static int usb_get_version(const struct iio_context *ctx, + unsigned int *major, unsigned int *minor, char git_tag[8]) +{ + return iiod_client_get_version(ctx->pdata->iiod_client, + &ctx->pdata->io_ctx, major, minor, git_tag); +} + +static unsigned int usb_calculate_remote_timeout(unsigned int timeout) +{ + /* XXX(pcercuei): We currently hardcode timeout / 2 for the backend used + * by the remote. Is there something better to do here? */ + return timeout / 2; +} + +#define USB_PIPE_CTRL_TIMEOUT 200 /* These should not take long */ + +#define IIO_USD_CMD_RESET_PIPES 0 +#define IIO_USD_CMD_OPEN_PIPE 1 +#define IIO_USD_CMD_CLOSE_PIPE 2 + +static int usb_reset_pipes(struct iio_context_pdata *pdata) +{ + int ret; + + ret = libusb_control_transfer(pdata->hdl, LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_INTERFACE, IIO_USD_CMD_RESET_PIPES, + 0, pdata->interface, NULL, 0, USB_PIPE_CTRL_TIMEOUT); + if (ret < 0) + return -(int) libusb_to_errno(ret); + return 0; +} + +static int usb_open_pipe(struct iio_context_pdata *pdata, unsigned int pipe_id) +{ + int ret; + + ret = libusb_control_transfer(pdata->hdl, LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_INTERFACE, IIO_USD_CMD_OPEN_PIPE, + pipe_id, pdata->interface, NULL, 0, USB_PIPE_CTRL_TIMEOUT); + if (ret < 0) + return -(int) libusb_to_errno(ret); + return 0; +} + +static int usb_close_pipe(struct iio_context_pdata *pdata, unsigned int pipe_id) +{ + int ret; + + ret = libusb_control_transfer(pdata->hdl, LIBUSB_REQUEST_TYPE_VENDOR | + LIBUSB_RECIPIENT_INTERFACE, IIO_USD_CMD_CLOSE_PIPE, + pipe_id, pdata->interface, NULL, 0, USB_PIPE_CTRL_TIMEOUT); + if (ret < 0) + return -(int) libusb_to_errno(ret); + return 0; +} + +static int usb_reserve_ep_unlocked(const struct iio_device *dev) +{ + struct iio_context_pdata *pdata = dev->ctx->pdata; + unsigned int i; + + for (i = 0; i < pdata->nb_ep_couples; i++) { + struct iio_usb_ep_couple *ep = &pdata->io_endpoints[i]; + + if (!ep->in_use) { + ep->in_use = true; + + dev->pdata->io_ctx.ep = ep; + dev->pdata->lock = ep->lock; + return 0; + } + } + + return -EBUSY; +} + +static void usb_free_ep_unlocked(const struct iio_device *dev) +{ + struct iio_context_pdata *pdata = dev->ctx->pdata; + unsigned int i; + + for (i = 0; i < pdata->nb_ep_couples; i++) { + struct iio_usb_ep_couple *ep = &pdata->io_endpoints[i]; + + if (ep->lock == dev->pdata->lock) { + ep->in_use = false; + return; + } + } +} + +static int usb_open(const struct iio_device *dev, + size_t samples_count, bool cyclic) +{ + struct iio_context_pdata *ctx_pdata = dev->ctx->pdata; + struct iio_device_pdata *pdata = dev->pdata; + int ret = -EBUSY; + + iio_mutex_lock(ctx_pdata->ep_lock); + + pdata->io_ctx.cancelled = false; + + if (pdata->opened) + goto out_unlock; + + ret = usb_reserve_ep_unlocked(dev); + if (ret) + goto out_unlock; + + ret = usb_open_pipe(ctx_pdata, pdata->io_ctx.ep->pipe_id); + if (ret) { + char err_str[1024]; + + iio_strerror(-ret, err_str, sizeof(err_str)); + ERROR("Failed to open pipe: %s\n", err_str); + usb_free_ep_unlocked(dev); + goto out_unlock; + } + + iio_mutex_lock(pdata->lock); + + ret = iiod_client_open_unlocked(ctx_pdata->iiod_client, &pdata->io_ctx, + dev, samples_count, cyclic); + + if (!ret) { + unsigned int remote_timeout = + usb_calculate_remote_timeout(ctx_pdata->timeout_ms); + + ret = iiod_client_set_timeout(ctx_pdata->iiod_client, + &pdata->io_ctx, remote_timeout); + } + + pdata->opened = !ret; + + iio_mutex_unlock(pdata->lock); + + if (ret) { + usb_close_pipe(ctx_pdata, pdata->io_ctx.ep->pipe_id); + usb_free_ep_unlocked(dev); + } + +out_unlock: + iio_mutex_unlock(ctx_pdata->ep_lock); + return ret; +} + +static int usb_close(const struct iio_device *dev) +{ + struct iio_context_pdata *ctx_pdata = dev->ctx->pdata; + struct iio_device_pdata *pdata = dev->pdata; + int ret = -EBADF; + + iio_mutex_lock(ctx_pdata->ep_lock); + if (!pdata->opened) + goto out_unlock; + + iio_mutex_lock(pdata->lock); + ret = iiod_client_close_unlocked(ctx_pdata->iiod_client, &pdata->io_ctx, + dev); + pdata->opened = false; + + iio_mutex_unlock(pdata->lock); + + usb_close_pipe(ctx_pdata, pdata->io_ctx.ep->pipe_id); + + usb_free_ep_unlocked(dev); + +out_unlock: + iio_mutex_unlock(ctx_pdata->ep_lock); + return ret; +} + +static ssize_t usb_read(const struct iio_device *dev, void *dst, size_t len, + uint32_t *mask, size_t words) +{ + struct iio_device_pdata *pdata = dev->pdata; + ssize_t ret; + + iio_mutex_lock(pdata->lock); + ret = iiod_client_read_unlocked(dev->ctx->pdata->iiod_client, + &pdata->io_ctx, dev, dst, len, mask, words); + iio_mutex_unlock(pdata->lock); + + return ret; +} + +static ssize_t usb_write(const struct iio_device *dev, + const void *src, size_t len) +{ + struct iio_device_pdata *pdata = dev->pdata; + ssize_t ret; + + iio_mutex_lock(pdata->lock); + ret = iiod_client_write_unlocked(dev->ctx->pdata->iiod_client, + &pdata->io_ctx, dev, src, len); + iio_mutex_unlock(pdata->lock); + + return ret; +} + +static ssize_t usb_read_dev_attr(const struct iio_device *dev, + const char *attr, char *dst, size_t len, enum iio_attr_type type) +{ + struct iio_context_pdata *pdata = dev->ctx->pdata; + + return iiod_client_read_attr(pdata->iiod_client, + &pdata->io_ctx, dev, NULL, attr, + dst, len, type); +} + +static ssize_t usb_write_dev_attr(const struct iio_device *dev, + const char *attr, const char *src, size_t len, enum iio_attr_type type) +{ + struct iio_context_pdata *pdata = dev->ctx->pdata; + + return iiod_client_write_attr(pdata->iiod_client, + &pdata->io_ctx, dev, NULL, attr, + src, len, type); +} + +static ssize_t usb_read_chn_attr(const struct iio_channel *chn, + const char *attr, char *dst, size_t len) +{ + struct iio_context_pdata *pdata = chn->dev->ctx->pdata; + + return iiod_client_read_attr(pdata->iiod_client, + &pdata->io_ctx, chn->dev, chn, attr, + dst, len, false); +} + +static ssize_t usb_write_chn_attr(const struct iio_channel *chn, + const char *attr, const char *src, size_t len) +{ + struct iio_context_pdata *pdata = chn->dev->ctx->pdata; + + return iiod_client_write_attr(pdata->iiod_client, + &pdata->io_ctx, chn->dev, chn, attr, + src, len, false); +} + +static int usb_set_kernel_buffers_count(const struct iio_device *dev, + unsigned int nb_blocks) +{ + struct iio_context_pdata *pdata = dev->ctx->pdata; + + return iiod_client_set_kernel_buffers_count(pdata->iiod_client, + &pdata->io_ctx, dev, nb_blocks); +} + +static int usb_set_timeout(struct iio_context *ctx, unsigned int timeout) +{ + struct iio_context_pdata *pdata = ctx->pdata; + unsigned int remote_timeout = usb_calculate_remote_timeout(timeout); + int ret; + + ret = iiod_client_set_timeout(pdata->iiod_client, + &pdata->io_ctx, remote_timeout); + if (!ret) + pdata->timeout_ms = timeout; + + return ret; +} + +static void usb_shutdown(struct iio_context *ctx) +{ + unsigned int i; + + usb_io_context_exit(&ctx->pdata->io_ctx); + + for (i = 0; i < ctx->nb_devices; i++) + usb_close(ctx->devices[i]); + + iio_mutex_destroy(ctx->pdata->lock); + iio_mutex_destroy(ctx->pdata->ep_lock); + + for (i = 0; i < ctx->pdata->nb_ep_couples; i++) + if (ctx->pdata->io_endpoints[i].lock) + iio_mutex_destroy(ctx->pdata->io_endpoints[i].lock); + if (ctx->pdata->io_endpoints) + free(ctx->pdata->io_endpoints); + + for (i = 0; i < ctx->nb_devices; i++) { + struct iio_device *dev = ctx->devices[i]; + + usb_io_context_exit(&dev->pdata->io_ctx); + free(dev->pdata); + } + + iiod_client_destroy(ctx->pdata->iiod_client); + + usb_reset_pipes(ctx->pdata); /* Close everything */ + + libusb_close(ctx->pdata->hdl); + libusb_exit(ctx->pdata->ctx); + free(ctx->pdata); +} + +static int iio_usb_match_interface(const struct libusb_config_descriptor *desc, + struct libusb_device_handle *hdl, unsigned int interface) +{ + const struct libusb_interface *iface; + unsigned int i; + + if (interface >= desc->bNumInterfaces) + return -EINVAL; + + iface = &desc->interface[interface]; + + for (i = 0; i < (unsigned int) iface->num_altsetting; i++) { + const struct libusb_interface_descriptor *idesc = + &iface->altsetting[i]; + char name[64]; + int ret; + + if (idesc->iInterface == 0) + continue; + + ret = libusb_get_string_descriptor_ascii(hdl, idesc->iInterface, + (unsigned char *) name, sizeof(name)); + if (ret < 0) + return -(int) libusb_to_errno(ret); + + if (!strcmp(name, IIO_INTERFACE_NAME)) + return (int) i; + } + + return -EPERM; +} + +static int iio_usb_match_device(struct libusb_device *dev, + struct libusb_device_handle *hdl, + unsigned int *interface) +{ + struct libusb_config_descriptor *desc; + unsigned int i; + int ret; + + ret = libusb_get_active_config_descriptor(dev, &desc); + if (ret) + return -(int) libusb_to_errno(ret); + + for (i = 0, ret = -EPERM; ret == -EPERM && + i < desc->bNumInterfaces; i++) + ret = iio_usb_match_interface(desc, hdl, i); + + libusb_free_config_descriptor(desc); + if (ret < 0) + return ret; + + DEBUG("Found IIO interface on device %u:%u using interface %u\n", + libusb_get_bus_number(dev), + libusb_get_device_address(dev), i - 1); + + *interface = i - 1; + return ret; +} + +static void usb_cancel(const struct iio_device *dev) +{ + struct iio_device_pdata *ppdata = dev->pdata; + + iio_mutex_lock(ppdata->io_ctx.lock); + if (ppdata->io_ctx.transfer && !ppdata->io_ctx.cancelled) + libusb_cancel_transfer(ppdata->io_ctx.transfer); + ppdata->io_ctx.cancelled = true; + iio_mutex_unlock(ppdata->io_ctx.lock); +} + +static const struct iio_backend_ops usb_ops = { + .get_version = usb_get_version, + .open = usb_open, + .close = usb_close, + .read = usb_read, + .write = usb_write, + .read_device_attr = usb_read_dev_attr, + .read_channel_attr = usb_read_chn_attr, + .write_device_attr = usb_write_dev_attr, + .write_channel_attr = usb_write_chn_attr, + .set_kernel_buffers_count = usb_set_kernel_buffers_count, + .set_timeout = usb_set_timeout, + .shutdown = usb_shutdown, + + .cancel = usb_cancel, +}; + +static void LIBUSB_CALL sync_transfer_cb(struct libusb_transfer *transfer) +{ + int *completed = transfer->user_data; + *completed = 1; +} + +static int usb_sync_transfer(struct iio_context_pdata *pdata, + struct iio_usb_io_context *io_ctx, unsigned int ep_type, + char *data, size_t len, int *transferred) +{ + unsigned int ep; + struct libusb_transfer *transfer; + int completed = 0; + int ret; + + /* + * If the size of the data to transfer is too big, the + * IOCTL_USBFS_SUBMITURB ioctl (called by libusb) might fail with + * errno set to ENOMEM, as the kernel might use contiguous allocation + * for the URB if the driver doesn't support scatter-gather. + * To prevent that, we support URBs of 1 MiB maximum. The iiod-client + * code will handle this properly and ask for a new transfer. + */ + if (len > 1 * 1024 * 1024) + len = 1 * 1024 * 1024; + + if (ep_type == LIBUSB_ENDPOINT_IN) + ep = io_ctx->ep->addr_in; + else + ep = io_ctx->ep->addr_out; + + /* + * For cancellation support the check whether the buffer has already been + * cancelled and the allocation as well as the assignment of the new + * transfer needs to happen in one atomic step. Otherwise it is possible + * that the cancellation is missed and transfer is not aborted. + */ + iio_mutex_lock(io_ctx->lock); + if (io_ctx->cancelled) { + ret = -EBADF; + goto unlock; + } + + transfer = libusb_alloc_transfer(0); + if (!transfer) { + ret = -ENOMEM; + goto unlock; + } + + transfer->user_data = &completed; + + libusb_fill_bulk_transfer(transfer, pdata->hdl, ep, + (unsigned char *) data, (int) len, sync_transfer_cb, + &completed, pdata->timeout_ms); + transfer->type = LIBUSB_TRANSFER_TYPE_BULK; + + ret = libusb_submit_transfer(transfer); + if (ret) { + ret = -(int) libusb_to_errno(ret); + libusb_free_transfer(transfer); + goto unlock; + } + + io_ctx->transfer = transfer; +unlock: + iio_mutex_unlock(io_ctx->lock); + if (ret) + return ret; + + while (!completed) { + ret = libusb_handle_events_completed(pdata->ctx, &completed); + if (ret < 0) { + if (ret == LIBUSB_ERROR_INTERRUPTED) + continue; + libusb_cancel_transfer(transfer); + continue; + } + } + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + *transferred = transfer->actual_length; + ret = 0; + break; + case LIBUSB_TRANSFER_TIMED_OUT: + ret = -ETIMEDOUT; + break; + case LIBUSB_TRANSFER_STALL: + ret = -EPIPE; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + ret = -ENODEV; + break; + case LIBUSB_TRANSFER_CANCELLED: + ret = -EBADF; + break; + default: + ret = -EIO; + break; + } + + /* Same as above. This needs to be atomic in regards to usb_cancel(). */ + iio_mutex_lock(io_ctx->lock); + io_ctx->transfer = NULL; + iio_mutex_unlock(io_ctx->lock); + + libusb_free_transfer(transfer); + + return ret; +} + +static ssize_t write_data_sync(struct iio_context_pdata *pdata, + void *ep, const char *data, size_t len) +{ + int transferred, ret; + + ret = usb_sync_transfer(pdata, ep, LIBUSB_ENDPOINT_OUT, (char *) data, + len, &transferred); + if (ret) + return ret; + else + return (ssize_t) transferred; +} + +static ssize_t read_data_sync(struct iio_context_pdata *pdata, + void *ep, char *buf, size_t len) +{ + int transferred, ret; + + ret = usb_sync_transfer(pdata, ep, LIBUSB_ENDPOINT_IN, buf, len, + &transferred); + if (ret) + return ret; + else + return transferred; +} + +static const struct iiod_client_ops usb_iiod_client_ops = { + .write = write_data_sync, + .read = read_data_sync, + .read_line = read_data_sync, +}; + +static int usb_verify_eps(const struct libusb_interface_descriptor *iface) +{ + unsigned int i, eps = iface->bNumEndpoints; + + /* Check that we have an even number of endpoints, and that input/output + * endpoints are interleaved */ + + if (eps < 2 || eps % 2) + return -EINVAL; + + for (i = 0; i < eps; i += 2) { + if (!(iface->endpoint[i + 0].bEndpointAddress + & LIBUSB_ENDPOINT_IN)) + return -EINVAL; + + if (iface->endpoint[i + 1].bEndpointAddress + & LIBUSB_ENDPOINT_IN) + return -EINVAL; + } + + return 0; +} + +static int usb_populate_context_attrs(struct iio_context *ctx, + libusb_device *dev, libusb_device_handle *hdl) +{ + struct libusb_device_descriptor dev_desc; + char buffer[64]; + unsigned int i; + int ret; + + struct { + const char *attr; + uint8_t idx; + } attrs[3]; + + libusb_get_device_descriptor(dev, &dev_desc); + + attrs[0].attr = "usb,vendor"; + attrs[0].idx = dev_desc.iManufacturer; + attrs[1].attr = "usb,product"; + attrs[1].idx = dev_desc.iProduct; + attrs[2].attr = "usb,serial"; + attrs[2].idx = dev_desc.iSerialNumber; + + iio_snprintf(buffer, sizeof(buffer), "%04hx", dev_desc.idVendor); + ret = iio_context_add_attr(ctx, "usb,idVendor", buffer); + if (ret < 0) + return ret; + + iio_snprintf(buffer, sizeof(buffer), "%04hx", dev_desc.idProduct); + ret = iio_context_add_attr(ctx, "usb,idProduct", buffer); + if (ret < 0) + return ret; + + iio_snprintf(buffer, sizeof(buffer), "%1hhx.%1hhx", + (unsigned char)((dev_desc.bcdUSB >> 8) & 0xf), + (unsigned char)((dev_desc.bcdUSB >> 4) & 0xf)); + ret = iio_context_add_attr(ctx, "usb,release", buffer); + if (ret < 0) + return ret; + + for (i = 0; i < ARRAY_SIZE(attrs); i++) { + if (attrs[i].idx) { + ret = libusb_get_string_descriptor_ascii(hdl, + attrs[i].idx, (unsigned char *) buffer, + sizeof(buffer)); + if (ret < 0) + return -(int) libusb_to_errno(ret); + + ret = iio_context_add_attr(ctx, attrs[i].attr, buffer); + if (ret < 0) + return ret; + } + } + + return 0; +} + +struct iio_context * usb_create_context(unsigned int bus, + unsigned int address, unsigned int interface) +{ + libusb_context *usb_ctx; + libusb_device_handle *hdl; + const struct libusb_interface_descriptor *iface; + libusb_device *usb_dev; + struct libusb_config_descriptor *conf_desc; + libusb_device **device_list; + struct iio_context *ctx; + struct iio_context_pdata *pdata; + char err_str[1024]; + unsigned int i; + int ret; + + pdata = zalloc(sizeof(*pdata)); + if (!pdata) { + ERROR("Unable to allocate pdata\n"); + ret = -ENOMEM; + goto err_set_errno; + } + + pdata->lock = iio_mutex_create(); + if (!pdata->lock) { + ERROR("Unable to create mutex\n"); + ret = -ENOMEM; + goto err_free_pdata; + } + + pdata->ep_lock = iio_mutex_create(); + if (!pdata->ep_lock) { + ERROR("Unable to create mutex\n"); + ret = -ENOMEM; + goto err_destroy_mutex; + } + + pdata->iiod_client = iiod_client_new(pdata, pdata->lock, + &usb_iiod_client_ops); + if (!pdata->iiod_client) { + ERROR("Unable to create IIOD client\n"); + ret = -errno; + goto err_destroy_ep_mutex; + } + + ret = libusb_init(&usb_ctx); + if (ret) { + ret = -(int) libusb_to_errno(ret); + ERROR("Unable to init libusb: %i\n", ret); + goto err_destroy_iiod_client; + } + + libusb_get_device_list(usb_ctx, &device_list); + + usb_dev = NULL; + + for (i = 0; device_list[i]; i++) { + libusb_device *dev = device_list[i]; + + if (bus == libusb_get_bus_number(dev) && + address == libusb_get_device_address(dev)) { + usb_dev = dev; + + ret = libusb_open(usb_dev, &hdl); + /* + * Workaround for libusb on Windows >= 8.1. A device + * might appear twice in the list with one device being + * bogus and only partially initialized. libusb_open() + * returns LIBUSB_ERROR_NOT_SUPPORTED for such devices, + * which should never happen for normal devices. So if + * we find such a device skip it and keep looking. + */ + if (ret == LIBUSB_ERROR_NOT_SUPPORTED) { + WARNING("Skipping broken USB device. Please upgrade libusb.\n"); + usb_dev = NULL; + continue; + } + + break; + } + } + + libusb_free_device_list(device_list, true); + + if (!usb_dev) { + ret = -ENODEV; + goto err_libusb_exit; + } + + if (ret) { + ret = -(int) libusb_to_errno(ret); + ERROR("Unable to open device\n"); + goto err_libusb_exit; + } + +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000016) + libusb_set_auto_detach_kernel_driver(hdl, true); +#endif + + ret = libusb_claim_interface(hdl, interface); + if (ret) { + ret = -(int) libusb_to_errno(ret); + ERROR("Unable to claim interface %u: %i\n", interface, ret); + goto err_libusb_close; + } + + ret = libusb_get_active_config_descriptor(usb_dev, &conf_desc); + if (ret) { + ret = -(int) libusb_to_errno(ret); + ERROR("Unable to get config descriptor: %i\n", ret); + goto err_libusb_close; + } + + iface = &conf_desc->interface[interface].altsetting[0]; + + ret = usb_verify_eps(iface); + if (ret) { + ERROR("Invalid configuration of endpoints\n"); + goto err_free_config_descriptor; + } + + pdata->nb_ep_couples = iface->bNumEndpoints / 2; + + DEBUG("Found %hhu usable i/o endpoint couples\n", pdata->nb_ep_couples); + + pdata->io_endpoints = calloc(pdata->nb_ep_couples, + sizeof(*pdata->io_endpoints)); + if (!pdata->io_endpoints) { + ERROR("Unable to allocate endpoints\n"); + ret = -ENOMEM; + goto err_free_config_descriptor; + } + + for (i = 0; i < pdata->nb_ep_couples; i++) { + struct iio_usb_ep_couple *ep = &pdata->io_endpoints[i]; + + ep->addr_in = iface->endpoint[i * 2 + 0].bEndpointAddress; + ep->addr_out = iface->endpoint[i * 2 + 1].bEndpointAddress; + ep->pipe_id = i; + + DEBUG("Couple %i with endpoints 0x%x / 0x%x\n", i, + ep->addr_in, ep->addr_out); + + ep->lock = iio_mutex_create(); + if (!ep->lock) { + ERROR("Unable to create mutex\n"); + ret = -ENOMEM; + goto err_free_endpoints; + } + } + + pdata->ctx = usb_ctx; + pdata->hdl = hdl; + pdata->timeout_ms = DEFAULT_TIMEOUT_MS; + pdata->interface = interface; + + ret = usb_io_context_init(&pdata->io_ctx); + if (ret) + goto err_free_endpoints; + + /* We reserve the first I/O endpoint couple for global operations */ + pdata->io_ctx.ep = &pdata->io_endpoints[0]; + pdata->io_ctx.ep->in_use = true; + + ret = usb_reset_pipes(pdata); + if (ret) { + iio_strerror(-ret, err_str, sizeof(err_str)); + ERROR("Failed to reset pipes: %s\n", err_str); + goto err_io_context_exit; + } + + ret = usb_open_pipe(pdata, 0); + if (ret) { + iio_strerror(-ret, err_str, sizeof(err_str)); + ERROR("Failed to open control pipe: %s\n", err_str); + goto err_io_context_exit; + } + + ctx = iiod_client_create_context(pdata->iiod_client, &pdata->io_ctx); + if (!ctx) { + ret = -errno; + goto err_reset_pipes; + } + + libusb_free_config_descriptor(conf_desc); + + ctx->name = "usb"; + ctx->ops = &usb_ops; + ctx->pdata = pdata; + + for (i = 0; i < ctx->nb_devices; i++) { + struct iio_device *dev = ctx->devices[i]; + + dev->pdata = zalloc(sizeof(*dev->pdata)); + if (!dev->pdata) { + ERROR("Unable to allocate memory\n"); + ret = -ENOMEM; + goto err_context_destroy; + } + + ret = usb_io_context_init(&dev->pdata->io_ctx); + if (ret) + goto err_context_destroy; + } + + ret = usb_populate_context_attrs(ctx, usb_dev, hdl); + if (ret < 0) + goto err_context_destroy; + + return ctx; + +err_context_destroy: + iio_context_destroy(ctx); + errno = -ret; + return NULL; + +err_reset_pipes: + usb_reset_pipes(pdata); /* Close everything */ +err_io_context_exit: + usb_io_context_exit(&pdata->io_ctx); +err_free_endpoints: + for (i = 0; i < pdata->nb_ep_couples; i++) + if (pdata->io_endpoints[i].lock) + iio_mutex_destroy(pdata->io_endpoints[i].lock); + if (pdata->io_endpoints) + free(pdata->io_endpoints); +err_free_config_descriptor: + libusb_free_config_descriptor(conf_desc); +err_libusb_close: + libusb_close(hdl); +err_libusb_exit: + libusb_exit(usb_ctx); +err_destroy_iiod_client: + iiod_client_destroy(pdata->iiod_client); +err_destroy_ep_mutex: + iio_mutex_destroy(pdata->ep_lock); +err_destroy_mutex: + iio_mutex_destroy(pdata->lock); +err_free_pdata: + free(pdata); +err_set_errno: + errno = -ret; + return NULL; +} + +struct iio_context * usb_create_context_from_uri(const char *uri) +{ + long bus, address, interface; + char *end; + const char *ptr; + + if (strncmp(uri, "usb:", sizeof("usb:") - 1) != 0) + goto err_bad_uri; + + ptr = (const char *) ((uintptr_t) uri + sizeof("usb:") - 1); + if (!isdigit(*ptr)) + goto err_bad_uri; + + bus = strtol(ptr, &end, 10); + if (ptr == end || *end != '.') + goto err_bad_uri; + + ptr = (const char *) ((uintptr_t) end + 1); + if (!isdigit(*ptr)) + goto err_bad_uri; + + address = strtol(ptr, &end, 10); + if (ptr == end) + goto err_bad_uri; + + if (*end == '\0') { + interface = 0; + } else if (*end == '.') { + ptr = (const char *) ((uintptr_t) end + 1); + if (!isdigit(*ptr)) + goto err_bad_uri; + + interface = strtol(ptr, &end, 10); + if (ptr == end || *end != '\0') + goto err_bad_uri; + } else { + goto err_bad_uri; + } + + if (bus < 0 || address < 0 || interface < 0) + goto err_bad_uri; + + return usb_create_context((unsigned int) bus, + (unsigned int) address, (unsigned int) interface); + +err_bad_uri: + ERROR("Bad URI: \'%s\'\n", uri); + errno = EINVAL; + return NULL; +} + +static int usb_fill_context_info(struct iio_context_info *info, + struct libusb_device *dev, struct libusb_device_handle *hdl, + unsigned int interface) +{ + struct libusb_device_descriptor desc; + char manufacturer[64], product[64], serial[64]; + char uri[sizeof("usb:127.255.255")]; + char description[sizeof(manufacturer) + sizeof(product) + + sizeof(serial) + sizeof("0000:0000 ( ), serial=")]; + int ret; + + libusb_get_device_descriptor(dev, &desc); + + iio_snprintf(uri, sizeof(uri), "usb:%d.%d.%u", + libusb_get_bus_number(dev), libusb_get_device_address(dev), + interface); + + if (desc.iManufacturer == 0) { + manufacturer[0] = '\0'; + } else { + ret = libusb_get_string_descriptor_ascii(hdl, + desc.iManufacturer, + (unsigned char *) manufacturer, + sizeof(manufacturer)); + if (ret < 0) + manufacturer[0] = '\0'; + } + + if (desc.iProduct == 0) { + product[0] = '\0'; + } else { + ret = libusb_get_string_descriptor_ascii(hdl, + desc.iProduct, (unsigned char *) product, + sizeof(product)); + if (ret < 0) + product[0] = '\0'; + } + + if (desc.iSerialNumber == 0) { + serial[0] = '\0'; + } else { + ret = libusb_get_string_descriptor_ascii(hdl, + desc.iSerialNumber, (unsigned char *) serial, + sizeof(serial)); + if (ret < 0) + serial[0] = '\0'; + } + + iio_snprintf(description, sizeof(description), + "%04x:%04x (%s %s), serial=%s", desc.idVendor, + desc.idProduct, manufacturer, product, serial); + + info->uri = iio_strdup(uri); + if (!info->uri) + return -ENOMEM; + + info->description = iio_strdup(description); + if (!info->description) + return -ENOMEM; + + return 0; +} + +struct iio_scan_backend_context { + libusb_context *ctx; +}; + +struct iio_scan_backend_context * usb_context_scan_init(void) +{ + struct iio_scan_backend_context *ctx; + int ret; + + ctx = malloc(sizeof(*ctx)); + if (!ctx) { + errno = ENOMEM; + return NULL; + } + + ret = libusb_init(&ctx->ctx); + if (ret) { + free(ctx); + errno = (int) libusb_to_errno(ret); + return NULL; + } + + return ctx; +} + +void usb_context_scan_free(struct iio_scan_backend_context *ctx) +{ + libusb_exit(ctx->ctx); + free(ctx); +} + +int usb_context_scan(struct iio_scan_backend_context *ctx, + struct iio_scan_result *scan_result) +{ + struct iio_context_info **info; + libusb_device **device_list; + unsigned int i; + int ret; + + ret = libusb_get_device_list(ctx->ctx, &device_list); + if (ret < 0) + return -(int) libusb_to_errno(ret); + + for (i = 0; device_list[i]; i++) { + struct libusb_device_handle *hdl; + struct libusb_device *dev = device_list[i]; + unsigned int interface = 0; + + ret = libusb_open(dev, &hdl); + if (ret) + continue; + + if (!iio_usb_match_device(dev, hdl, &interface)) { + info = iio_scan_result_add(scan_result, 1); + if (!info) + ret = -ENOMEM; + else + ret = usb_fill_context_info(*info, dev, hdl, + interface); + } + + libusb_close(hdl); + if (ret < 0) + goto cleanup_free_device_list; + } + + ret = 0; + +cleanup_free_device_list: + libusb_free_device_list(device_list, true); + return ret; +} |