| /* |
| * libiio - Library for interfacing industrial I/O (IIO) devices |
| * |
| * Copyright (C) 2015 Analog Devices, Inc. |
| * Author: Paul Cercueil <[email protected]> |
| * |
| * 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 1000 /* 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; |
| } |
| } |
| |
| #ifdef HAS_LIBUSB_GETVERSION |
| /* |
| * libusb_get_version was added 2012-04-17: v1.0.10, |
| * before LIBUSB_API_VERSION was added - Jan 8, 2014 |
| * so, you can't use that to determine if it is here |
| */ |
| { |
| struct libusb_version const *ver = libusb_get_version(); |
| iio_snprintf(buffer, sizeof(buffer), "%i.%i.%i.%i%s", |
| ver->major, ver->minor, ver->micro, |
| ver->nano, ver->rc); |
| ret = iio_context_add_attr(ctx, "usb,libusb", buffer); |
| if (ret < 0) |
| return ret; |
| } |
| #endif |
| 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:%u:%u: %i\n", |
| bus, address, 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; |
| } |