diff options
author | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2022-03-12 17:48:54 +0000 |
---|---|---|
committer | Arturs Artamonovs <arturs.artamonovs@protonmail.com> | 2022-03-12 17:48:54 +0000 |
commit | ec61b3e0e6084950eb4d5c95f83e442a01765a35 (patch) | |
tree | f9dee1721080ce33bf5452cc2f82deeafb047cc7 /hardware/src/libusb/os/linux_usbfs.c | |
parent | 4274248ca037308315f38ec77e66fddd8eae1395 (diff) | |
download | r820sdr-init-ec61b3e0e6084950eb4d5c95f83e442a01765a35.tar.gz r820sdr-init-ec61b3e0e6084950eb4d5c95f83e442a01765a35.zip |
Working project
Diffstat (limited to 'hardware/src/libusb/os/linux_usbfs.c')
-rw-r--r-- | hardware/src/libusb/os/linux_usbfs.c | 2807 |
1 files changed, 2807 insertions, 0 deletions
diff --git a/hardware/src/libusb/os/linux_usbfs.c b/hardware/src/libusb/os/linux_usbfs.c new file mode 100644 index 0000000..c300675 --- /dev/null +++ b/hardware/src/libusb/os/linux_usbfs.c @@ -0,0 +1,2807 @@ +/* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */ +/* + * Linux usbfs backend for libusb + * Copyright © 2007-2009 Daniel Drake <dsd@gentoo.org> + * Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com> + * Copyright © 2013 Nathan Hjelm <hjelmn@mac.com> + * Copyright © 2012-2013 Hans de Goede <hdegoede@redhat.com> + * Copyright © 2020 Chris Dickens <christopher.a.dickens@gmail.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. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libusbi.h" +#include "linux_usbfs.h" + +#include <alloca.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/utsname.h> +#include <sys/vfs.h> +#include <unistd.h> + +/* sysfs vs usbfs: + * opening a usbfs node causes the device to be resumed, so we attempt to + * avoid this during enumeration. + * + * sysfs allows us to read the kernel's in-memory copies of device descriptors + * and so forth, avoiding the need to open the device: + * - The binary "descriptors" file contains all config descriptors since + * 2.6.26, commit 217a9081d8e69026186067711131b77f0ce219ed + * - The binary "descriptors" file was added in 2.6.23, commit + * 69d42a78f935d19384d1f6e4f94b65bb162b36df, but it only contains the + * active config descriptors + * - The "busnum" file was added in 2.6.22, commit + * 83f7d958eab2fbc6b159ee92bf1493924e1d0f72 + * - The "devnum" file has been present since pre-2.6.18 + * - the "bConfigurationValue" file has been present since pre-2.6.18 + * + * If we have bConfigurationValue, busnum, and devnum, then we can determine + * the active configuration without having to open the usbfs node in RDWR mode. + * The busnum file is important as that is the only way we can relate sysfs + * devices to usbfs nodes. + * + * If we also have all descriptors, we can obtain the device descriptor and + * configuration without touching usbfs at all. + */ + +/* endianness for multi-byte fields: + * + * Descriptors exposed by usbfs have the multi-byte fields in the device + * descriptor as host endian. Multi-byte fields in the other descriptors are + * bus-endian. The kernel documentation says otherwise, but it is wrong. + * + * In sysfs all descriptors are bus-endian. + */ + +#define USBDEV_PATH "/dev" +#define USB_DEVTMPFS_PATH "/dev/bus/usb" + +/* use usbdev*.* device names in /dev instead of the usbfs bus directories */ +static int usbdev_names = 0; + +/* Linux has changed the maximum length of an individual isochronous packet + * over time. Initially this limit was 1,023 bytes, but Linux 2.6.18 + * (commit 3612242e527eb47ee4756b5350f8bdf791aa5ede) increased this value to + * 8,192 bytes to support higher bandwidth devices. Linux 3.10 + * (commit e2e2f0ea1c935edcf53feb4c4c8fdb4f86d57dd9) further increased this + * value to 49,152 bytes to support super speed devices. Linux 5.2 + * (commit 8a1dbc8d91d3d1602282c7e6b4222c7759c916fa) even further increased + * this value to 98,304 bytes to support super speed plus devices. + */ +static unsigned int max_iso_packet_len = 0; + +/* is sysfs available (mounted) ? */ +static int sysfs_available = -1; + +/* how many times have we initted (and not exited) ? */ +static int init_count = 0; + +/* have no authority to operate usb device directly */ +static int no_enumeration = 0; + +/* Serialize scan-devices, event-thread, and poll */ +usbi_mutex_static_t linux_hotplug_lock = USBI_MUTEX_INITIALIZER; + +static int linux_scan_devices(struct libusb_context *ctx); +static int detach_kernel_driver_and_claim(struct libusb_device_handle *, uint8_t); + +#if !defined(HAVE_LIBUDEV) +static int linux_default_scan_devices(struct libusb_context *ctx); +#endif + +struct kernel_version { + int major; + int minor; + int sublevel; +}; + +struct config_descriptor { + struct usbi_configuration_descriptor *desc; + size_t actual_len; +}; + +struct linux_device_priv { + char *sysfs_dir; + void *descriptors; + size_t descriptors_len; + struct config_descriptor *config_descriptors; + int active_config; /* cache val for !sysfs_available */ +}; + +struct linux_device_handle_priv { + int fd; + int fd_removed; + int fd_keep; + uint32_t caps; +}; + +enum reap_action { + NORMAL = 0, + /* submission failed after the first URB, so await cancellation/completion + * of all the others */ + SUBMIT_FAILED, + + /* cancelled by user or timeout */ + CANCELLED, + + /* completed multi-URB transfer in non-final URB */ + COMPLETED_EARLY, + + /* one or more urbs encountered a low-level error */ + ERROR, +}; + +struct linux_transfer_priv { + union { + struct usbfs_urb *urbs; + struct usbfs_urb **iso_urbs; + }; + + enum reap_action reap_action; + int num_urbs; + int num_retired; + enum libusb_transfer_status reap_status; + + /* next iso packet in user-supplied transfer to be populated */ + int iso_packet_offset; +}; + +static int dev_has_config0(struct libusb_device *dev) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + uint8_t idx; + + for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) { + config = &priv->config_descriptors[idx]; + if (config->desc->bConfigurationValue == 0) + return 1; + } + + return 0; +} + +static int get_usbfs_fd(struct libusb_device *dev, mode_t mode, int silent) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + char path[24]; + int fd; + + if (usbdev_names) + sprintf(path, USBDEV_PATH "/usbdev%u.%u", + dev->bus_number, dev->device_address); + else + sprintf(path, USB_DEVTMPFS_PATH "/%03u/%03u", + dev->bus_number, dev->device_address); + + fd = open(path, mode | O_CLOEXEC); + if (fd != -1) + return fd; /* Success */ + + if (errno == ENOENT) { + const long delay_ms = 10L; + const struct timespec delay_ts = { 0L, delay_ms * 1000L * 1000L }; + + if (!silent) + usbi_err(ctx, "File doesn't exist, wait %ld ms and try again", delay_ms); + + /* Wait 10ms for USB device path creation.*/ + nanosleep(&delay_ts, NULL); + + fd = open(path, mode | O_CLOEXEC); + if (fd != -1) + return fd; /* Success */ + } + + if (!silent) { + usbi_err(ctx, "libusb couldn't open USB device %s, errno=%d", path, errno); + if (errno == EACCES && mode == O_RDWR) + usbi_err(ctx, "libusb requires write access to USB device nodes"); + } + + if (errno == EACCES) + return LIBUSB_ERROR_ACCESS; + if (errno == ENOENT) + return LIBUSB_ERROR_NO_DEVICE; + return LIBUSB_ERROR_IO; +} + +/* check dirent for a /dev/usbdev%d.%d name + * optionally return bus/device on success */ +static int is_usbdev_entry(const char *name, uint8_t *bus_p, uint8_t *dev_p) +{ + int busnum, devnum; + + if (sscanf(name, "usbdev%d.%d", &busnum, &devnum) != 2) + return 0; + if (busnum < 0 || busnum > UINT8_MAX || devnum < 0 || devnum > UINT8_MAX) { + usbi_dbg(NULL, "invalid usbdev format '%s'", name); + return 0; + } + + usbi_dbg(NULL, "found: %s", name); + if (bus_p) + *bus_p = (uint8_t)busnum; + if (dev_p) + *dev_p = (uint8_t)devnum; + return 1; +} + +static const char *find_usbfs_path(void) +{ + const char *path; + DIR *dir; + struct dirent *entry; + + path = USB_DEVTMPFS_PATH; + dir = opendir(path); + if (dir) { + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + /* We assume if we find any files that it must be the right place */ + break; + } + + closedir(dir); + + if (entry) + return path; + } + + /* look for /dev/usbdev*.* if the normal place fails */ + path = USBDEV_PATH; + dir = opendir(path); + if (dir) { + while ((entry = readdir(dir))) { + if (entry->d_name[0] == '.') + continue; + + if (is_usbdev_entry(entry->d_name, NULL, NULL)) { + /* found one; that's enough */ + break; + } + } + + closedir(dir); + + if (entry) { + usbdev_names = 1; + return path; + } + } + +/* On udev based systems without any usb-devices /dev/bus/usb will not + * exist. So if we've not found anything and we're using udev for hotplug + * simply assume /dev/bus/usb rather then making libusb_init fail. + * Make the same assumption for Android where SELinux policies might block us + * from reading /dev on newer devices. */ +#if defined(HAVE_LIBUDEV) || defined(__ANDROID__) + return USB_DEVTMPFS_PATH; +#else + return NULL; +#endif +} + +static int get_kernel_version(struct libusb_context *ctx, + struct kernel_version *ver) +{ + struct utsname uts; + int atoms; + + if (uname(&uts) < 0) { + usbi_err(ctx, "uname failed, errno=%d", errno); + return -1; + } + + atoms = sscanf(uts.release, "%d.%d.%d", &ver->major, &ver->minor, &ver->sublevel); + if (atoms < 2) { + usbi_err(ctx, "failed to parse uname release '%s'", uts.release); + return -1; + } + + if (atoms < 3) + ver->sublevel = -1; + + usbi_dbg(ctx, "reported kernel version is %s", uts.release); + + return 0; +} + +static int kernel_version_ge(const struct kernel_version *ver, + int major, int minor, int sublevel) +{ + if (ver->major > major) + return 1; + else if (ver->major < major) + return 0; + + /* kmajor == major */ + if (ver->minor > minor) + return 1; + else if (ver->minor < minor) + return 0; + + /* kminor == minor */ + if (ver->sublevel == -1) + return sublevel == 0; + + return ver->sublevel >= sublevel; +} + +static int op_init(struct libusb_context *ctx) +{ + struct kernel_version kversion; + const char *usbfs_path; + int r; + + if (get_kernel_version(ctx, &kversion) < 0) + return LIBUSB_ERROR_OTHER; + + if (!kernel_version_ge(&kversion, 2, 6, 32)) { + usbi_err(ctx, "kernel version is too old (reported as %d.%d.%d)", + kversion.major, kversion.minor, + kversion.sublevel != -1 ? kversion.sublevel : 0); + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + usbfs_path = find_usbfs_path(); + if (!usbfs_path) { + usbi_err(ctx, "could not find usbfs"); + return LIBUSB_ERROR_OTHER; + } + + usbi_dbg(ctx, "found usbfs at %s", usbfs_path); + + if (!max_iso_packet_len) { + if (kernel_version_ge(&kversion, 5, 2, 0)) + max_iso_packet_len = 98304; + else if (kernel_version_ge(&kversion, 3, 10, 0)) + max_iso_packet_len = 49152; + else + max_iso_packet_len = 8192; + } + + usbi_dbg(ctx, "max iso packet length is (likely) %u bytes", max_iso_packet_len); + + if (sysfs_available == -1) { + struct statfs statfsbuf; + + r = statfs(SYSFS_MOUNT_PATH, &statfsbuf); + if (r == 0 && statfsbuf.f_type == SYSFS_MAGIC) { + usbi_dbg(ctx, "sysfs is available"); + sysfs_available = 1; + } else { + usbi_warn(ctx, "sysfs not mounted"); + sysfs_available = 0; + } + } + + if (no_enumeration) { + return LIBUSB_SUCCESS; + } + + r = LIBUSB_SUCCESS; + if (init_count == 0) { + /* start up hotplug event handler */ + r = linux_start_event_monitor(); + } + if (r == LIBUSB_SUCCESS) { + r = linux_scan_devices(ctx); + if (r == LIBUSB_SUCCESS) + init_count++; + else if (init_count == 0) + linux_stop_event_monitor(); + } else { + usbi_err(ctx, "error starting hotplug event monitor"); + } + + return r; +} + +static void op_exit(struct libusb_context *ctx) +{ + UNUSED(ctx); + + if (no_enumeration) { + return; + } + + assert(init_count != 0); + if (!--init_count) { + /* tear down event handler */ + linux_stop_event_monitor(); + } +} + +static int op_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap) +{ + UNUSED(ctx); + UNUSED(ap); + + if (option == LIBUSB_OPTION_NO_DEVICE_DISCOVERY) { + usbi_dbg(ctx, "no enumeration will be performed"); + no_enumeration = 1; + return LIBUSB_SUCCESS; + } + + return LIBUSB_ERROR_NOT_SUPPORTED; +} + +static int linux_scan_devices(struct libusb_context *ctx) +{ + int ret; + + usbi_mutex_static_lock(&linux_hotplug_lock); + +#if defined(HAVE_LIBUDEV) + ret = linux_udev_scan_devices(ctx); +#else + ret = linux_default_scan_devices(ctx); +#endif + + usbi_mutex_static_unlock(&linux_hotplug_lock); + + return ret; +} + +static void op_hotplug_poll(void) +{ + linux_hotplug_poll(); +} + +static int open_sysfs_attr(struct libusb_context *ctx, + const char *sysfs_dir, const char *attr) +{ + char filename[256]; + int fd; + + snprintf(filename, sizeof(filename), SYSFS_DEVICE_PATH "/%s/%s", sysfs_dir, attr); + fd = open(filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + if (errno == ENOENT) { + /* File doesn't exist. Assume the device has been + disconnected (see trac ticket #70). */ + return LIBUSB_ERROR_NO_DEVICE; + } + usbi_err(ctx, "open %s failed, errno=%d", filename, errno); + return LIBUSB_ERROR_IO; + } + + return fd; +} + +/* Note only suitable for attributes which always read >= 0, < 0 is error */ +static int read_sysfs_attr(struct libusb_context *ctx, + const char *sysfs_dir, const char *attr, int max_value, int *value_p) +{ + char buf[20], *endptr; + long value; + ssize_t r; + int fd; + + fd = open_sysfs_attr(ctx, sysfs_dir, attr); + if (fd < 0) + return fd; + + r = read(fd, buf, sizeof(buf) - 1); + if (r < 0) { + r = errno; + close(fd); + if (r == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + usbi_err(ctx, "attribute %s read failed, errno=%zd", attr, r); + return LIBUSB_ERROR_IO; + } + close(fd); + + if (r == 0) { + /* Certain attributes (e.g. bConfigurationValue) are not + * populated if the device is not configured. */ + *value_p = -1; + return 0; + } + + /* The kernel does *not* NUL-terminate the string, but every attribute + * should be terminated with a newline character. */ + if (!isdigit(buf[0])) { + usbi_err(ctx, "attribute %s doesn't have numeric value?", attr); + return LIBUSB_ERROR_IO; + } else if (buf[r - 1] != '\n') { + usbi_warn(ctx, "attribute %s doesn't end with newline?", attr); + } else { + /* Remove the terminating newline character */ + r--; + } + buf[r] = '\0'; + + errno = 0; + value = strtol(buf, &endptr, 10); + if (value < 0 || value > (long)max_value || errno) { + usbi_err(ctx, "attribute %s contains an invalid value: '%s'", attr, buf); + return LIBUSB_ERROR_INVALID_PARAM; + } else if (*endptr != '\0') { + /* Consider the value to be valid if the remainder is a '.' + * character followed by numbers. This occurs, for example, + * when reading the "speed" attribute for a low-speed device + * (e.g. "1.5") */ + if (*endptr == '.' && isdigit(*(endptr + 1))) { + endptr++; + while (isdigit(*endptr)) + endptr++; + } + if (*endptr != '\0') { + usbi_err(ctx, "attribute %s contains an invalid value: '%s'", attr, buf); + return LIBUSB_ERROR_INVALID_PARAM; + } + } + + *value_p = (int)value; + return 0; +} + +static int sysfs_scan_device(struct libusb_context *ctx, const char *devname) +{ + uint8_t busnum, devaddr; + int ret; + + ret = linux_get_device_address(ctx, 0, &busnum, &devaddr, NULL, devname, -1); + if (ret != LIBUSB_SUCCESS) + return ret; + + return linux_enumerate_device(ctx, busnum, devaddr, devname); +} + +/* read the bConfigurationValue for a device */ +static int sysfs_get_active_config(struct libusb_device *dev, int *config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + + return read_sysfs_attr(DEVICE_CTX(dev), priv->sysfs_dir, "bConfigurationValue", + UINT8_MAX, config); +} + +int linux_get_device_address(struct libusb_context *ctx, int detached, + uint8_t *busnum, uint8_t *devaddr, const char *dev_node, + const char *sys_name, int fd) +{ + int sysfs_val; + int r; + + usbi_dbg(ctx, "getting address for device: %s detached: %d", sys_name, detached); + /* can't use sysfs to read the bus and device number if the + * device has been detached */ + if (!sysfs_available || detached || !sys_name) { + if (!dev_node && fd >= 0) { + char *fd_path = alloca(PATH_MAX); + char proc_path[32]; + + /* try to retrieve the device node from fd */ + sprintf(proc_path, "/proc/self/fd/%d", fd); + r = readlink(proc_path, fd_path, PATH_MAX - 1); + if (r > 0) { + fd_path[r] = '\0'; + dev_node = fd_path; + } + } + + if (!dev_node) + return LIBUSB_ERROR_OTHER; + + /* will this work with all supported kernel versions? */ + if (!strncmp(dev_node, "/dev/bus/usb", 12)) + sscanf(dev_node, "/dev/bus/usb/%hhu/%hhu", busnum, devaddr); + else + return LIBUSB_ERROR_OTHER; + + return LIBUSB_SUCCESS; + } + + usbi_dbg(ctx, "scan %s", sys_name); + + r = read_sysfs_attr(ctx, sys_name, "busnum", UINT8_MAX, &sysfs_val); + if (r < 0) + return r; + *busnum = (uint8_t)sysfs_val; + + r = read_sysfs_attr(ctx, sys_name, "devnum", UINT8_MAX, &sysfs_val); + if (r < 0) + return r; + *devaddr = (uint8_t)sysfs_val; + + usbi_dbg(ctx, "bus=%u dev=%u", *busnum, *devaddr); + + return LIBUSB_SUCCESS; +} + +/* Return offset of the next config descriptor */ +static int seek_to_next_config(struct libusb_context *ctx, + uint8_t *buffer, size_t len) +{ + struct usbi_descriptor_header *header; + int offset; + + /* Start seeking past the config descriptor */ + offset = LIBUSB_DT_CONFIG_SIZE; + buffer += LIBUSB_DT_CONFIG_SIZE; + len -= LIBUSB_DT_CONFIG_SIZE; + + while (len > 0) { + if (len < 2) { + usbi_err(ctx, "short descriptor read %zu/2", len); + return LIBUSB_ERROR_IO; + } + + header = (struct usbi_descriptor_header *)buffer; + if (header->bDescriptorType == LIBUSB_DT_CONFIG) + return offset; + + if (len < header->bLength) { + usbi_err(ctx, "bLength overflow by %zu bytes", + (size_t)header->bLength - len); + return LIBUSB_ERROR_IO; + } + + offset += header->bLength; + buffer += header->bLength; + len -= header->bLength; + } + + usbi_err(ctx, "config descriptor not found"); + return LIBUSB_ERROR_IO; +} + +static int parse_config_descriptors(struct libusb_device *dev) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct usbi_device_descriptor *device_desc; + uint8_t idx, num_configs; + uint8_t *buffer; + size_t remaining; + + device_desc = priv->descriptors; + num_configs = device_desc->bNumConfigurations; + + if (num_configs == 0) + return 0; /* no configurations? */ + + priv->config_descriptors = malloc(num_configs * sizeof(priv->config_descriptors[0])); + if (!priv->config_descriptors) + return LIBUSB_ERROR_NO_MEM; + + buffer = (uint8_t *)priv->descriptors + LIBUSB_DT_DEVICE_SIZE; + remaining = priv->descriptors_len - LIBUSB_DT_DEVICE_SIZE; + + for (idx = 0; idx < num_configs; idx++) { + struct usbi_configuration_descriptor *config_desc; + uint16_t config_len; + + if (remaining < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "short descriptor read %zu/%d", + remaining, LIBUSB_DT_CONFIG_SIZE); + return LIBUSB_ERROR_IO; + } + + config_desc = (struct usbi_configuration_descriptor *)buffer; + if (config_desc->bDescriptorType != LIBUSB_DT_CONFIG) { + usbi_err(ctx, "descriptor is not a config desc (type 0x%02x)", + config_desc->bDescriptorType); + return LIBUSB_ERROR_IO; + } else if (config_desc->bLength < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "invalid descriptor bLength %u", + config_desc->bLength); + return LIBUSB_ERROR_IO; + } + + config_len = libusb_le16_to_cpu(config_desc->wTotalLength); + if (config_len < LIBUSB_DT_CONFIG_SIZE) { + usbi_err(ctx, "invalid wTotalLength %u", config_len); + return LIBUSB_ERROR_IO; + } + + if (priv->sysfs_dir) { + /* + * In sysfs wTotalLength is ignored, instead the kernel returns a + * config descriptor with verified bLength fields, with descriptors + * with an invalid bLength removed. + */ + uint16_t sysfs_config_len; + int offset; + + if (num_configs > 1 && idx < num_configs - 1) { + offset = seek_to_next_config(ctx, buffer, remaining); + if (offset < 0) + return offset; + sysfs_config_len = (uint16_t)offset; + } else { + sysfs_config_len = (uint16_t)remaining; + } + + if (config_len != sysfs_config_len) { + usbi_warn(ctx, "config length mismatch wTotalLength %u real %u", + config_len, sysfs_config_len); + config_len = sysfs_config_len; + } + } else { + /* + * In usbfs the config descriptors are wTotalLength bytes apart, + * with any short reads from the device appearing as holes in the file. + */ + if (config_len > remaining) { + usbi_warn(ctx, "short descriptor read %zu/%u", remaining, config_len); + config_len = (uint16_t)remaining; + } + } + + if (config_desc->bConfigurationValue == 0) + usbi_warn(ctx, "device has configuration 0"); + + priv->config_descriptors[idx].desc = config_desc; + priv->config_descriptors[idx].actual_len = config_len; + + buffer += config_len; + remaining -= config_len; + } + + return LIBUSB_SUCCESS; +} + +static int op_get_config_descriptor_by_value(struct libusb_device *dev, + uint8_t value, void **buffer) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + uint8_t idx; + + for (idx = 0; idx < dev->device_descriptor.bNumConfigurations; idx++) { + config = &priv->config_descriptors[idx]; + if (config->desc->bConfigurationValue == value) { + *buffer = config->desc; + return (int)config->actual_len; + } + } + + return LIBUSB_ERROR_NOT_FOUND; +} + +static int op_get_active_config_descriptor(struct libusb_device *dev, + void *buffer, size_t len) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + void *config_desc; + int active_config; + int r; + + if (priv->sysfs_dir) { + r = sysfs_get_active_config(dev, &active_config); + if (r < 0) + return r; + } else { + /* Use cached bConfigurationValue */ + active_config = priv->active_config; + } + + if (active_config == -1) { + usbi_err(DEVICE_CTX(dev), "device unconfigured"); + return LIBUSB_ERROR_NOT_FOUND; + } + + r = op_get_config_descriptor_by_value(dev, (uint8_t)active_config, &config_desc); + if (r < 0) + return r; + + len = MIN(len, (size_t)r); + memcpy(buffer, config_desc, len); + return len; +} + +static int op_get_config_descriptor(struct libusb_device *dev, + uint8_t config_index, void *buffer, size_t len) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct config_descriptor *config; + + if (config_index >= dev->device_descriptor.bNumConfigurations) + return LIBUSB_ERROR_NOT_FOUND; + + config = &priv->config_descriptors[config_index]; + len = MIN(len, config->actual_len); + memcpy(buffer, config->desc, len); + return len; +} + +/* send a control message to retrieve active configuration */ +static int usbfs_get_active_config(struct libusb_device *dev, int fd) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + uint8_t active_config = 0; + int r; + + struct usbfs_ctrltransfer ctrl = { + .bmRequestType = LIBUSB_ENDPOINT_IN, + .bRequest = LIBUSB_REQUEST_GET_CONFIGURATION, + .wValue = 0, + .wIndex = 0, + .wLength = 1, + .timeout = 1000, + .data = &active_config + }; + + r = ioctl(fd, IOCTL_USBFS_CONTROL, &ctrl); + if (r < 0) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + /* we hit this error path frequently with buggy devices :( */ + usbi_warn(DEVICE_CTX(dev), "get configuration failed, errno=%d", errno); + + /* assume the current configuration is the first one if we have + * the configuration descriptors, otherwise treat the device + * as unconfigured. */ + if (priv->config_descriptors) + priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue; + else + priv->active_config = -1; + } else if (active_config == 0) { + if (dev_has_config0(dev)) { + /* some buggy devices have a configuration 0, but we're + * reaching into the corner of a corner case here. */ + priv->active_config = 0; + } else { + priv->active_config = -1; + } + } else { + priv->active_config = (int)active_config; + } + + return LIBUSB_SUCCESS; +} + +static enum libusb_speed usbfs_get_speed(struct libusb_context *ctx, int fd) +{ + int r; + + r = ioctl(fd, IOCTL_USBFS_GET_SPEED, NULL); + switch (r) { + case USBFS_SPEED_UNKNOWN: return LIBUSB_SPEED_UNKNOWN; + case USBFS_SPEED_LOW: return LIBUSB_SPEED_LOW; + case USBFS_SPEED_FULL: return LIBUSB_SPEED_FULL; + case USBFS_SPEED_HIGH: return LIBUSB_SPEED_HIGH; + case USBFS_SPEED_WIRELESS: return LIBUSB_SPEED_HIGH; + case USBFS_SPEED_SUPER: return LIBUSB_SPEED_SUPER; + case USBFS_SPEED_SUPER_PLUS: return LIBUSB_SPEED_SUPER_PLUS; + default: + usbi_warn(ctx, "Error getting device speed: %d", r); + } + + return LIBUSB_SPEED_UNKNOWN; +} + +static int initialize_device(struct libusb_device *dev, uint8_t busnum, + uint8_t devaddr, const char *sysfs_dir, int wrapped_fd) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + struct libusb_context *ctx = DEVICE_CTX(dev); + size_t alloc_len; + int fd, speed, r; + ssize_t nb; + + dev->bus_number = busnum; + dev->device_address = devaddr; + + if (sysfs_dir) { + priv->sysfs_dir = strdup(sysfs_dir); + if (!priv->sysfs_dir) + return LIBUSB_ERROR_NO_MEM; + + /* Note speed can contain 1.5, in this case read_sysfs_attr() + will stop parsing at the '.' and return 1 */ + if (read_sysfs_attr(ctx, sysfs_dir, "speed", INT_MAX, &speed) == 0) { + switch (speed) { + case 1: dev->speed = LIBUSB_SPEED_LOW; break; + case 12: dev->speed = LIBUSB_SPEED_FULL; break; + case 480: dev->speed = LIBUSB_SPEED_HIGH; break; + case 5000: dev->speed = LIBUSB_SPEED_SUPER; break; + case 10000: dev->speed = LIBUSB_SPEED_SUPER_PLUS; break; + default: + usbi_warn(ctx, "unknown device speed: %d Mbps", speed); + } + } + } else if (wrapped_fd >= 0) { + dev->speed = usbfs_get_speed(ctx, wrapped_fd); + } + + /* cache descriptors in memory */ + if (sysfs_dir) { + fd = open_sysfs_attr(ctx, sysfs_dir, "descriptors"); + } else if (wrapped_fd < 0) { + fd = get_usbfs_fd(dev, O_RDONLY, 0); + } else { + fd = wrapped_fd; + r = lseek(fd, 0, SEEK_SET); + if (r < 0) { + usbi_err(ctx, "lseek failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + } + if (fd < 0) + return fd; + + alloc_len = 0; + do { + const size_t desc_read_length = 256; + uint8_t *read_ptr; + + alloc_len += desc_read_length; + priv->descriptors = usbi_reallocf(priv->descriptors, alloc_len); + if (!priv->descriptors) { + if (fd != wrapped_fd) + close(fd); + return LIBUSB_ERROR_NO_MEM; + } + read_ptr = (uint8_t *)priv->descriptors + priv->descriptors_len; + /* usbfs has holes in the file */ + if (!sysfs_dir) + memset(read_ptr, 0, desc_read_length); + nb = read(fd, read_ptr, desc_read_length); + if (nb < 0) { + usbi_err(ctx, "read descriptor failed, errno=%d", errno); + if (fd != wrapped_fd) + close(fd); + return LIBUSB_ERROR_IO; + } + priv->descriptors_len += (size_t)nb; + } while (priv->descriptors_len == alloc_len); + + if (fd != wrapped_fd) + close(fd); + + if (priv->descriptors_len < LIBUSB_DT_DEVICE_SIZE) { + usbi_err(ctx, "short descriptor read (%zu)", priv->descriptors_len); + return LIBUSB_ERROR_IO; + } + + r = parse_config_descriptors(dev); + if (r < 0) + return r; + + memcpy(&dev->device_descriptor, priv->descriptors, LIBUSB_DT_DEVICE_SIZE); + + if (sysfs_dir) { + /* sysfs descriptors are in bus-endian format */ + usbi_localize_device_descriptor(&dev->device_descriptor); + return LIBUSB_SUCCESS; + } + + /* cache active config */ + if (wrapped_fd < 0) + fd = get_usbfs_fd(dev, O_RDWR, 1); + else + fd = wrapped_fd; + if (fd < 0) { + /* cannot send a control message to determine the active + * config. just assume the first one is active. */ + usbi_warn(ctx, "Missing rw usbfs access; cannot determine " + "active configuration descriptor"); + if (priv->config_descriptors) + priv->active_config = (int)priv->config_descriptors[0].desc->bConfigurationValue; + else + priv->active_config = -1; /* No config dt */ + + return LIBUSB_SUCCESS; + } + + r = usbfs_get_active_config(dev, fd); + if (fd != wrapped_fd) + close(fd); + + return r; +} + +static int linux_get_parent_info(struct libusb_device *dev, const char *sysfs_dir) +{ + struct libusb_context *ctx = DEVICE_CTX(dev); + struct libusb_device *it; + char *parent_sysfs_dir, *tmp; + int ret, add_parent = 1; + + /* XXX -- can we figure out the topology when using usbfs? */ + if (!sysfs_dir || !strncmp(sysfs_dir, "usb", 3)) { + /* either using usbfs or finding the parent of a root hub */ + return LIBUSB_SUCCESS; + } + + parent_sysfs_dir = strdup(sysfs_dir); + if (!parent_sysfs_dir) + return LIBUSB_ERROR_NO_MEM; + + if ((tmp = strrchr(parent_sysfs_dir, '.')) || + (tmp = strrchr(parent_sysfs_dir, '-'))) { + dev->port_number = atoi(tmp + 1); + *tmp = '\0'; + } else { + usbi_warn(ctx, "Can not parse sysfs_dir: %s, no parent info", + parent_sysfs_dir); + free(parent_sysfs_dir); + return LIBUSB_SUCCESS; + } + + /* is the parent a root hub? */ + if (!strchr(parent_sysfs_dir, '-')) { + tmp = parent_sysfs_dir; + ret = asprintf(&parent_sysfs_dir, "usb%s", tmp); + free(tmp); + if (ret < 0) + return LIBUSB_ERROR_NO_MEM; + } + +retry: + /* find the parent in the context */ + usbi_mutex_lock(&ctx->usb_devs_lock); + for_each_device(ctx, it) { + struct linux_device_priv *priv = usbi_get_device_priv(it); + + if (priv->sysfs_dir) { + if (!strcmp(priv->sysfs_dir, parent_sysfs_dir)) { + dev->parent_dev = libusb_ref_device(it); + break; + } + } + } + usbi_mutex_unlock(&ctx->usb_devs_lock); + + if (!dev->parent_dev && add_parent) { + usbi_dbg(ctx, "parent_dev %s not enumerated yet, enumerating now", + parent_sysfs_dir); + sysfs_scan_device(ctx, parent_sysfs_dir); + add_parent = 0; + goto retry; + } + + usbi_dbg(ctx, "dev %p (%s) has parent %p (%s) port %u", dev, sysfs_dir, + dev->parent_dev, parent_sysfs_dir, dev->port_number); + + free(parent_sysfs_dir); + + return LIBUSB_SUCCESS; +} + +int linux_enumerate_device(struct libusb_context *ctx, + uint8_t busnum, uint8_t devaddr, const char *sysfs_dir) +{ + unsigned long session_id; + struct libusb_device *dev; + int r; + + /* FIXME: session ID is not guaranteed unique as addresses can wrap and + * will be reused. instead we should add a simple sysfs attribute with + * a session ID. */ + session_id = busnum << 8 | devaddr; + usbi_dbg(ctx, "busnum %u devaddr %u session_id %lu", busnum, devaddr, session_id); + + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev) { + /* device already exists in the context */ + usbi_dbg(ctx, "session_id %lu already exists", session_id); + libusb_unref_device(dev); + return LIBUSB_SUCCESS; + } + + usbi_dbg(ctx, "allocating new device for %u/%u (session %lu)", + busnum, devaddr, session_id); + dev = usbi_alloc_device(ctx, session_id); + if (!dev) + return LIBUSB_ERROR_NO_MEM; + + r = initialize_device(dev, busnum, devaddr, sysfs_dir, -1); + if (r < 0) + goto out; + r = usbi_sanitize_device(dev); + if (r < 0) + goto out; + + r = linux_get_parent_info(dev, sysfs_dir); + if (r < 0) + goto out; +out: + if (r < 0) + libusb_unref_device(dev); + else + usbi_connect_device(dev); + + return r; +} + +void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_name) +{ + struct libusb_context *ctx; + + usbi_mutex_static_lock(&active_contexts_lock); + for_each_context(ctx) { + linux_enumerate_device(ctx, busnum, devaddr, sys_name); + } + usbi_mutex_static_unlock(&active_contexts_lock); +} + +void linux_device_disconnected(uint8_t busnum, uint8_t devaddr) +{ + struct libusb_context *ctx; + struct libusb_device *dev; + unsigned long session_id = busnum << 8 | devaddr; + + usbi_mutex_static_lock(&active_contexts_lock); + for_each_context(ctx) { + dev = usbi_get_device_by_session_id(ctx, session_id); + if (dev) { + usbi_disconnect_device(dev); + libusb_unref_device(dev); + } else { + usbi_dbg(ctx, "device not found for session %lx", session_id); + } + } + usbi_mutex_static_unlock(&active_contexts_lock); +} + +#if !defined(HAVE_LIBUDEV) +static int parse_u8(const char *str, uint8_t *val_p) +{ + char *endptr; + long num; + + errno = 0; + num = strtol(str, &endptr, 10); + if (num < 0 || num > UINT8_MAX || errno) + return 0; + if (endptr == str || *endptr != '\0') + return 0; + + *val_p = (uint8_t)num; + return 1; +} + +/* open a bus directory and adds all discovered devices to the context */ +static int usbfs_scan_busdir(struct libusb_context *ctx, uint8_t busnum) +{ + DIR *dir; + char dirpath[20]; + struct dirent *entry; + int r = LIBUSB_ERROR_IO; + + sprintf(dirpath, USB_DEVTMPFS_PATH "/%03u", busnum); + usbi_dbg(ctx, "%s", dirpath); + dir = opendir(dirpath); + if (!dir) { + usbi_err(ctx, "opendir '%s' failed, errno=%d", dirpath, errno); + /* FIXME: should handle valid race conditions like hub unplugged + * during directory iteration - this is not an error */ + return r; + } + + while ((entry = readdir(dir))) { + uint8_t devaddr; + + if (entry->d_name[0] == '.') + continue; + + if (!parse_u8(entry->d_name, &devaddr)) { + usbi_dbg(ctx, "unknown dir entry %s", entry->d_name); + continue; + } + + if (linux_enumerate_device(ctx, busnum, devaddr, NULL)) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + + r = 0; + } + + closedir(dir); + return r; +} + +static int usbfs_get_device_list(struct libusb_context *ctx) +{ + struct dirent *entry; + DIR *buses; + uint8_t busnum, devaddr; + int r = 0; + + if (usbdev_names) + buses = opendir(USBDEV_PATH); + else + buses = opendir(USB_DEVTMPFS_PATH); + + if (!buses) { + usbi_err(ctx, "opendir buses failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + while ((entry = readdir(buses))) { + if (entry->d_name[0] == '.') + continue; + + if (usbdev_names) { + if (!is_usbdev_entry(entry->d_name, &busnum, &devaddr)) + continue; + + r = linux_enumerate_device(ctx, busnum, devaddr, NULL); + if (r < 0) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + } else { + if (!parse_u8(entry->d_name, &busnum)) { + usbi_dbg(ctx, "unknown dir entry %s", entry->d_name); + continue; + } + + r = usbfs_scan_busdir(ctx, busnum); + if (r < 0) + break; + } + } + + closedir(buses); + return r; + +} + +static int sysfs_get_device_list(struct libusb_context *ctx) +{ + DIR *devices = opendir(SYSFS_DEVICE_PATH); + struct dirent *entry; + int num_devices = 0; + int num_enumerated = 0; + + if (!devices) { + usbi_err(ctx, "opendir devices failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + while ((entry = readdir(devices))) { + if ((!isdigit(entry->d_name[0]) && strncmp(entry->d_name, "usb", 3)) + || strchr(entry->d_name, ':')) + continue; + + num_devices++; + + if (sysfs_scan_device(ctx, entry->d_name)) { + usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name); + continue; + } + + num_enumerated++; + } + + closedir(devices); + + /* successful if at least one device was enumerated or no devices were found */ + if (num_enumerated || !num_devices) + return LIBUSB_SUCCESS; + else + return LIBUSB_ERROR_IO; +} + +static int linux_default_scan_devices(struct libusb_context *ctx) +{ + /* we can retrieve device list and descriptors from sysfs or usbfs. + * sysfs is preferable, because if we use usbfs we end up resuming + * any autosuspended USB devices. however, sysfs is not available + * everywhere, so we need a usbfs fallback too. + */ + if (sysfs_available) + return sysfs_get_device_list(ctx); + else + return usbfs_get_device_list(ctx); +} +#endif + +static int initialize_handle(struct libusb_device_handle *handle, int fd) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r; + + hpriv->fd = fd; + + r = ioctl(fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps); + if (r < 0) { + if (errno == ENOTTY) + usbi_dbg(HANDLE_CTX(handle), "getcap not available"); + else + usbi_err(HANDLE_CTX(handle), "getcap failed, errno=%d", errno); + hpriv->caps = USBFS_CAP_BULK_CONTINUATION; + } + + return usbi_add_event_source(HANDLE_CTX(handle), hpriv->fd, POLLOUT); +} + +static int op_wrap_sys_device(struct libusb_context *ctx, + struct libusb_device_handle *handle, intptr_t sys_dev) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = (int)sys_dev; + uint8_t busnum, devaddr; + struct usbfs_connectinfo ci; + struct libusb_device *dev; + int r; + + r = linux_get_device_address(ctx, 1, &busnum, &devaddr, NULL, NULL, fd); + if (r < 0) { + r = ioctl(fd, IOCTL_USBFS_CONNECTINFO, &ci); + if (r < 0) { + usbi_err(ctx, "connectinfo failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + /* There is no ioctl to get the bus number. We choose 0 here + * as linux starts numbering buses from 1. */ + busnum = 0; + devaddr = ci.devnum; + } + + /* Session id is unused as we do not add the device to the list of + * connected devices. */ + usbi_dbg(ctx, "allocating new device for fd %d", fd); + dev = usbi_alloc_device(ctx, 0); + if (!dev) + return LIBUSB_ERROR_NO_MEM; + + r = initialize_device(dev, busnum, devaddr, NULL, fd); + if (r < 0) + goto out; + r = usbi_sanitize_device(dev); + if (r < 0) + goto out; + /* Consider the device as connected, but do not add it to the managed + * device list. */ + usbi_atomic_store(&dev->attached, 1); + handle->dev = dev; + + r = initialize_handle(handle, fd); + hpriv->fd_keep = 1; + +out: + if (r < 0) + libusb_unref_device(dev); + return r; +} + +static int op_open(struct libusb_device_handle *handle) +{ + int fd, r; + + fd = get_usbfs_fd(handle->dev, O_RDWR, 0); + if (fd < 0) { + if (fd == LIBUSB_ERROR_NO_DEVICE) { + /* device will still be marked as attached if hotplug monitor thread + * hasn't processed remove event yet */ + usbi_mutex_static_lock(&linux_hotplug_lock); + if (usbi_atomic_load(&handle->dev->attached)) { + usbi_dbg(HANDLE_CTX(handle), "open failed with no device, but device still attached"); + linux_device_disconnected(handle->dev->bus_number, + handle->dev->device_address); + } + usbi_mutex_static_unlock(&linux_hotplug_lock); + } + return fd; + } + + r = initialize_handle(handle, fd); + if (r < 0) + close(fd); + + return r; +} + +static void op_close(struct libusb_device_handle *dev_handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(dev_handle); + + /* fd may have already been removed by POLLERR condition in op_handle_events() */ + if (!hpriv->fd_removed) + usbi_remove_event_source(HANDLE_CTX(dev_handle), hpriv->fd); + if (!hpriv->fd_keep) + close(hpriv->fd); +} + +static int op_get_configuration(struct libusb_device_handle *handle, + uint8_t *config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(handle->dev); + int active_config; + int r; + + if (priv->sysfs_dir) { + r = sysfs_get_active_config(handle->dev, &active_config); + } else { + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + + r = usbfs_get_active_config(handle->dev, hpriv->fd); + if (r == LIBUSB_SUCCESS) + active_config = priv->active_config; + } + if (r < 0) + return r; + + if (active_config == -1) { + usbi_warn(HANDLE_CTX(handle), "device unconfigured"); + active_config = 0; + } + + *config = (uint8_t)active_config; + + return 0; +} + +static int op_set_configuration(struct libusb_device_handle *handle, int config) +{ + struct linux_device_priv *priv = usbi_get_device_priv(handle->dev); + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_SETCONFIGURATION, &config); + + if (r < 0) { + if (errno == EINVAL) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "set configuration failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + /* if necessary, update our cached active config descriptor */ + if (!priv->sysfs_dir) { + if (config == 0 && !dev_has_config0(handle->dev)) + config = -1; + + priv->active_config = config; + } + + return LIBUSB_SUCCESS; +} + +static int claim_interface(struct libusb_device_handle *handle, unsigned int iface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_CLAIMINTERFACE, &iface); + + if (r < 0) { + if (errno == ENOENT) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "claim interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return 0; +} + +static int release_interface(struct libusb_device_handle *handle, unsigned int iface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r = ioctl(fd, IOCTL_USBFS_RELEASEINTERFACE, &iface); + + if (r < 0) { + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "release interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return 0; +} + +static int op_set_interface(struct libusb_device_handle *handle, uint8_t interface, + uint8_t altsetting) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_setinterface setintf; + int r; + + setintf.interface = interface; + setintf.altsetting = altsetting; + r = ioctl(fd, IOCTL_USBFS_SETINTERFACE, &setintf); + if (r < 0) { + if (errno == EINVAL) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "set interface failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_clear_halt(struct libusb_device_handle *handle, + unsigned char endpoint) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + unsigned int _endpoint = endpoint; + int r = ioctl(fd, IOCTL_USBFS_CLEAR_HALT, &_endpoint); + + if (r < 0) { + if (errno == ENOENT) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "clear halt failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_reset_device(struct libusb_device_handle *handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + int r, ret = 0; + uint8_t i; + + /* Doing a device reset will cause the usbfs driver to get unbound + * from any interfaces it is bound to. By voluntarily unbinding + * the usbfs driver ourself, we stop the kernel from rebinding + * the interface after reset (which would end up with the interface + * getting bound to the in kernel driver if any). */ + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (handle->claimed_interfaces & (1UL << i)) + release_interface(handle, i); + } + + usbi_mutex_lock(&handle->lock); + r = ioctl(fd, IOCTL_USBFS_RESET, NULL); + if (r < 0) { + if (errno == ENODEV) { + ret = LIBUSB_ERROR_NOT_FOUND; + goto out; + } + + usbi_err(HANDLE_CTX(handle), "reset failed, errno=%d", errno); + ret = LIBUSB_ERROR_OTHER; + goto out; + } + + /* And re-claim any interfaces which were claimed before the reset */ + for (i = 0; i < USB_MAXINTERFACES; i++) { + if (!(handle->claimed_interfaces & (1UL << i))) + continue; + /* + * A driver may have completed modprobing during + * IOCTL_USBFS_RESET, and bound itself as soon as + * IOCTL_USBFS_RESET released the device lock + */ + r = detach_kernel_driver_and_claim(handle, i); + if (r) { + usbi_warn(HANDLE_CTX(handle), "failed to re-claim interface %u after reset: %s", + i, libusb_error_name(r)); + handle->claimed_interfaces &= ~(1UL << i); + ret = LIBUSB_ERROR_NOT_FOUND; + } + } +out: + usbi_mutex_unlock(&handle->lock); + return ret; +} + +static int do_streams_ioctl(struct libusb_device_handle *handle, long req, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r, fd = hpriv->fd; + struct usbfs_streams *streams; + + if (num_endpoints > 30) /* Max 15 in + 15 out eps */ + return LIBUSB_ERROR_INVALID_PARAM; + + streams = malloc(sizeof(*streams) + num_endpoints); + if (!streams) + return LIBUSB_ERROR_NO_MEM; + + streams->num_streams = num_streams; + streams->num_eps = num_endpoints; + memcpy(streams->eps, endpoints, num_endpoints); + + r = ioctl(fd, req, streams); + + free(streams); + + if (r < 0) { + if (errno == ENOTTY) + return LIBUSB_ERROR_NOT_SUPPORTED; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "streams-ioctl failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + return r; +} + +static int op_alloc_streams(struct libusb_device_handle *handle, + uint32_t num_streams, unsigned char *endpoints, int num_endpoints) +{ + return do_streams_ioctl(handle, IOCTL_USBFS_ALLOC_STREAMS, + num_streams, endpoints, num_endpoints); +} + +static int op_free_streams(struct libusb_device_handle *handle, + unsigned char *endpoints, int num_endpoints) +{ + return do_streams_ioctl(handle, IOCTL_USBFS_FREE_STREAMS, 0, + endpoints, num_endpoints); +} + +static void *op_dev_mem_alloc(struct libusb_device_handle *handle, size_t len) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + void *buffer; + + buffer = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, hpriv->fd, 0); + if (buffer == MAP_FAILED) { + usbi_err(HANDLE_CTX(handle), "alloc dev mem failed, errno=%d", errno); + return NULL; + } + return buffer; +} + +static int op_dev_mem_free(struct libusb_device_handle *handle, void *buffer, + size_t len) +{ + if (munmap(buffer, len) != 0) { + usbi_err(HANDLE_CTX(handle), "free dev mem failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } else { + return LIBUSB_SUCCESS; + } +} + +static int op_kernel_driver_active(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_getdriver getdrv; + int r; + + getdrv.interface = interface; + r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv); + if (r < 0) { + if (errno == ENODATA) + return 0; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "get driver failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return strcmp(getdrv.driver, "usbfs") != 0; +} + +static int op_detach_kernel_driver(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_ioctl command; + struct usbfs_getdriver getdrv; + int r; + + command.ifno = interface; + command.ioctl_code = IOCTL_USBFS_DISCONNECT; + command.data = NULL; + + getdrv.interface = interface; + r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv); + if (r == 0 && !strcmp(getdrv.driver, "usbfs")) + return LIBUSB_ERROR_NOT_FOUND; + + r = ioctl(fd, IOCTL_USBFS_IOCTL, &command); + if (r < 0) { + if (errno == ENODATA) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "detach failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + return 0; +} + +static int op_attach_kernel_driver(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int fd = hpriv->fd; + struct usbfs_ioctl command; + int r; + + command.ifno = interface; + command.ioctl_code = IOCTL_USBFS_CONNECT; + command.data = NULL; + + r = ioctl(fd, IOCTL_USBFS_IOCTL, &command); + if (r < 0) { + if (errno == ENODATA) + return LIBUSB_ERROR_NOT_FOUND; + else if (errno == EINVAL) + return LIBUSB_ERROR_INVALID_PARAM; + else if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + else if (errno == EBUSY) + return LIBUSB_ERROR_BUSY; + + usbi_err(HANDLE_CTX(handle), "attach failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } else if (r == 0) { + return LIBUSB_ERROR_NOT_FOUND; + } + + return 0; +} + +static int detach_kernel_driver_and_claim(struct libusb_device_handle *handle, + uint8_t interface) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + struct usbfs_disconnect_claim dc; + int r, fd = hpriv->fd; + + dc.interface = interface; + strcpy(dc.driver, "usbfs"); + dc.flags = USBFS_DISCONNECT_CLAIM_EXCEPT_DRIVER; + r = ioctl(fd, IOCTL_USBFS_DISCONNECT_CLAIM, &dc); + if (r == 0) + return 0; + switch (errno) { + case ENOTTY: + break; + case EBUSY: + return LIBUSB_ERROR_BUSY; + case EINVAL: + return LIBUSB_ERROR_INVALID_PARAM; + case ENODEV: + return LIBUSB_ERROR_NO_DEVICE; + default: + usbi_err(HANDLE_CTX(handle), "disconnect-and-claim failed, errno=%d", errno); + return LIBUSB_ERROR_OTHER; + } + + /* Fallback code for kernels which don't support the + disconnect-and-claim ioctl */ + r = op_detach_kernel_driver(handle, interface); + if (r != 0 && r != LIBUSB_ERROR_NOT_FOUND) + return r; + + return claim_interface(handle, interface); +} + +static int op_claim_interface(struct libusb_device_handle *handle, uint8_t interface) +{ + if (handle->auto_detach_kernel_driver) + return detach_kernel_driver_and_claim(handle, interface); + else + return claim_interface(handle, interface); +} + +static int op_release_interface(struct libusb_device_handle *handle, uint8_t interface) +{ + int r; + + r = release_interface(handle, interface); + if (r) + return r; + + if (handle->auto_detach_kernel_driver) + op_attach_kernel_driver(handle, interface); + + return 0; +} + +static void op_destroy_device(struct libusb_device *dev) +{ + struct linux_device_priv *priv = usbi_get_device_priv(dev); + + free(priv->config_descriptors); + free(priv->descriptors); + free(priv->sysfs_dir); +} + +/* URBs are discarded in reverse order of submission to avoid races. */ +static int discard_urbs(struct usbi_transfer *itransfer, int first, int last_plus_one) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + int i, ret = 0; + struct usbfs_urb *urb; + + for (i = last_plus_one - 1; i >= first; i--) { + if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) + urb = tpriv->iso_urbs[i]; + else + urb = &tpriv->urbs[i]; + + if (ioctl(hpriv->fd, IOCTL_USBFS_DISCARDURB, urb) == 0) + continue; + + if (errno == EINVAL) { + usbi_dbg(TRANSFER_CTX(transfer), "URB not found --> assuming ready to be reaped"); + if (i == (last_plus_one - 1)) + ret = LIBUSB_ERROR_NOT_FOUND; + } else if (errno == ENODEV) { + usbi_dbg(TRANSFER_CTX(transfer), "Device not found for URB --> assuming ready to be reaped"); + ret = LIBUSB_ERROR_NO_DEVICE; + } else { + usbi_warn(TRANSFER_CTX(transfer), "unrecognised discard errno %d", errno); + ret = LIBUSB_ERROR_OTHER; + } + } + return ret; +} + +static void free_iso_urbs(struct linux_transfer_priv *tpriv) +{ + int i; + + for (i = 0; i < tpriv->num_urbs; i++) { + struct usbfs_urb *urb = tpriv->iso_urbs[i]; + + if (!urb) + break; + free(urb); + } + + free(tpriv->iso_urbs); + tpriv->iso_urbs = NULL; +} + +static int submit_bulk_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb *urbs; + int is_out = IS_XFEROUT(transfer); + int bulk_buffer_len, use_bulk_continuation; + int num_urbs; + int last_urb_partial = 0; + int r; + int i; + + /* + * Older versions of usbfs place a 16kb limit on bulk URBs. We work + * around this by splitting large transfers into 16k blocks, and then + * submit all urbs at once. it would be simpler to submit one urb at + * a time, but there is a big performance gain doing it this way. + * + * Newer versions lift the 16k limit (USBFS_CAP_NO_PACKET_SIZE_LIM), + * using arbitrary large transfers can still be a bad idea though, as + * the kernel needs to allocate physical contiguous memory for this, + * which may fail for large buffers. + * + * The kernel solves this problem by splitting the transfer into + * blocks itself when the host-controller is scatter-gather capable + * (USBFS_CAP_BULK_SCATTER_GATHER), which most controllers are. + * + * Last, there is the issue of short-transfers when splitting, for + * short split-transfers to work reliable USBFS_CAP_BULK_CONTINUATION + * is needed, but this is not always available. + */ + if (hpriv->caps & USBFS_CAP_BULK_SCATTER_GATHER) { + /* Good! Just submit everything in one go */ + bulk_buffer_len = transfer->length ? transfer->length : 1; + use_bulk_continuation = 0; + } else if (hpriv->caps & USBFS_CAP_BULK_CONTINUATION) { + /* Split the transfers and use bulk-continuation to + avoid issues with short-transfers */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 1; + } else if (hpriv->caps & USBFS_CAP_NO_PACKET_SIZE_LIM) { + /* Don't split, assume the kernel can alloc the buffer + (otherwise the submit will fail with -ENOMEM) */ + bulk_buffer_len = transfer->length ? transfer->length : 1; + use_bulk_continuation = 0; + } else { + /* Bad, splitting without bulk-continuation, short transfers + which end before the last urb will not work reliable! */ + /* Note we don't warn here as this is "normal" on kernels < + 2.6.32 and not a problem for most applications */ + bulk_buffer_len = MAX_BULK_BUFFER_LENGTH; + use_bulk_continuation = 0; + } + + num_urbs = transfer->length / bulk_buffer_len; + + if (transfer->length == 0) { + num_urbs = 1; + } else if ((transfer->length % bulk_buffer_len) > 0) { + last_urb_partial = 1; + num_urbs++; + } + usbi_dbg(TRANSFER_CTX(transfer), "need %d urbs for new transfer with length %d", num_urbs, transfer->length); + urbs = calloc(num_urbs, sizeof(*urbs)); + if (!urbs) + return LIBUSB_ERROR_NO_MEM; + tpriv->urbs = urbs; + tpriv->num_urbs = num_urbs; + tpriv->num_retired = 0; + tpriv->reap_action = NORMAL; + tpriv->reap_status = LIBUSB_TRANSFER_COMPLETED; + + for (i = 0; i < num_urbs; i++) { + struct usbfs_urb *urb = &urbs[i]; + + urb->usercontext = itransfer; + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_BULK: + urb->type = USBFS_URB_TYPE_BULK; + urb->stream_id = 0; + break; + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + urb->type = USBFS_URB_TYPE_BULK; + urb->stream_id = itransfer->stream_id; + break; + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + urb->type = USBFS_URB_TYPE_INTERRUPT; + break; + } + urb->endpoint = transfer->endpoint; + urb->buffer = transfer->buffer + (i * bulk_buffer_len); + + /* don't set the short not ok flag for the last URB */ + if (use_bulk_continuation && !is_out && (i < num_urbs - 1)) + urb->flags = USBFS_URB_SHORT_NOT_OK; + + if (i == num_urbs - 1 && last_urb_partial) + urb->buffer_length = transfer->length % bulk_buffer_len; + else if (transfer->length == 0) + urb->buffer_length = 0; + else + urb->buffer_length = bulk_buffer_len; + + if (i > 0 && use_bulk_continuation) + urb->flags |= USBFS_URB_BULK_CONTINUATION; + + /* we have already checked that the flag is supported */ + if (is_out && i == num_urbs - 1 && + (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)) + urb->flags |= USBFS_URB_ZERO_PACKET; + + r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r == 0) + continue; + + if (errno == ENODEV) { + r = LIBUSB_ERROR_NO_DEVICE; + } else if (errno == ENOMEM) { + r = LIBUSB_ERROR_NO_MEM; + } else { + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + r = LIBUSB_ERROR_IO; + } + + /* if the first URB submission fails, we can simply free up and + * return failure immediately. */ + if (i == 0) { + usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy"); + free(urbs); + tpriv->urbs = NULL; + return r; + } + + /* if it's not the first URB that failed, the situation is a bit + * tricky. we may need to discard all previous URBs. there are + * complications: + * - discarding is asynchronous - discarded urbs will be reaped + * later. the user must not have freed the transfer when the + * discarded URBs are reaped, otherwise libusb will be using + * freed memory. + * - the earlier URBs may have completed successfully and we do + * not want to throw away any data. + * - this URB failing may be no error; EREMOTEIO means that + * this transfer simply didn't need all the URBs we submitted + * so, we report that the transfer was submitted successfully and + * in case of error we discard all previous URBs. later when + * the final reap completes we can report error to the user, + * or success if an earlier URB was completed successfully. + */ + tpriv->reap_action = errno == EREMOTEIO ? COMPLETED_EARLY : SUBMIT_FAILED; + + /* The URBs we haven't submitted yet we count as already + * retired. */ + tpriv->num_retired += num_urbs - i; + + /* If we completed short then don't try to discard. */ + if (tpriv->reap_action == COMPLETED_EARLY) + return 0; + + discard_urbs(itransfer, 0, i); + + usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d " + "discards before reporting error", i); + return 0; + } + + return 0; +} + +static int submit_iso_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb **urbs; + int num_packets = transfer->num_iso_packets; + int num_packets_remaining; + int i, j; + int num_urbs; + unsigned int packet_len; + unsigned int total_len = 0; + unsigned char *urb_buffer = transfer->buffer; + + if (num_packets < 1) + return LIBUSB_ERROR_INVALID_PARAM; + + /* usbfs places arbitrary limits on iso URBs. this limit has changed + * at least three times, but we attempt to detect this limit during + * init and check it here. if the kernel rejects the request due to + * its size, we return an error indicating such to the user. + */ + for (i = 0; i < num_packets; i++) { + packet_len = transfer->iso_packet_desc[i].length; + + if (packet_len > max_iso_packet_len) { + usbi_warn(TRANSFER_CTX(transfer), + "iso packet length of %u bytes exceeds maximum of %u bytes", + packet_len, max_iso_packet_len); + return LIBUSB_ERROR_INVALID_PARAM; + } + + total_len += packet_len; + } + + if (transfer->length < (int)total_len) + return LIBUSB_ERROR_INVALID_PARAM; + + /* usbfs limits the number of iso packets per URB */ + num_urbs = (num_packets + (MAX_ISO_PACKETS_PER_URB - 1)) / MAX_ISO_PACKETS_PER_URB; + + usbi_dbg(TRANSFER_CTX(transfer), "need %d urbs for new transfer with length %d", num_urbs, transfer->length); + + urbs = calloc(num_urbs, sizeof(*urbs)); + if (!urbs) + return LIBUSB_ERROR_NO_MEM; + + tpriv->iso_urbs = urbs; + tpriv->num_urbs = num_urbs; + tpriv->num_retired = 0; + tpriv->reap_action = NORMAL; + tpriv->iso_packet_offset = 0; + + /* allocate + initialize each URB with the correct number of packets */ + num_packets_remaining = num_packets; + for (i = 0, j = 0; i < num_urbs; i++) { + int num_packets_in_urb = MIN(num_packets_remaining, MAX_ISO_PACKETS_PER_URB); + struct usbfs_urb *urb; + size_t alloc_size; + int k; + + alloc_size = sizeof(*urb) + + (num_packets_in_urb * sizeof(struct usbfs_iso_packet_desc)); + urb = calloc(1, alloc_size); + if (!urb) { + free_iso_urbs(tpriv); + return LIBUSB_ERROR_NO_MEM; + } + urbs[i] = urb; + + /* populate packet lengths */ + for (k = 0; k < num_packets_in_urb; j++, k++) { + packet_len = transfer->iso_packet_desc[j].length; + urb->buffer_length += packet_len; + urb->iso_frame_desc[k].length = packet_len; + } + + urb->usercontext = itransfer; + urb->type = USBFS_URB_TYPE_ISO; + /* FIXME: interface for non-ASAP data? */ + urb->flags = USBFS_URB_ISO_ASAP; + urb->endpoint = transfer->endpoint; + urb->number_of_packets = num_packets_in_urb; + urb->buffer = urb_buffer; + + urb_buffer += urb->buffer_length; + num_packets_remaining -= num_packets_in_urb; + } + + /* submit URBs */ + for (i = 0; i < num_urbs; i++) { + int r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urbs[i]); + + if (r == 0) + continue; + + if (errno == ENODEV) { + r = LIBUSB_ERROR_NO_DEVICE; + } else if (errno == EINVAL) { + usbi_warn(TRANSFER_CTX(transfer), "submiturb failed, transfer too large"); + r = LIBUSB_ERROR_INVALID_PARAM; + } else if (errno == EMSGSIZE) { + usbi_warn(TRANSFER_CTX(transfer), "submiturb failed, iso packet length too large"); + r = LIBUSB_ERROR_INVALID_PARAM; + } else { + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + r = LIBUSB_ERROR_IO; + } + + /* if the first URB submission fails, we can simply free up and + * return failure immediately. */ + if (i == 0) { + usbi_dbg(TRANSFER_CTX(transfer), "first URB failed, easy peasy"); + free_iso_urbs(tpriv); + return r; + } + + /* if it's not the first URB that failed, the situation is a bit + * tricky. we must discard all previous URBs. there are + * complications: + * - discarding is asynchronous - discarded urbs will be reaped + * later. the user must not have freed the transfer when the + * discarded URBs are reaped, otherwise libusb will be using + * freed memory. + * - the earlier URBs may have completed successfully and we do + * not want to throw away any data. + * so, in this case we discard all the previous URBs BUT we report + * that the transfer was submitted successfully. then later when + * the final discard completes we can report error to the user. + */ + tpriv->reap_action = SUBMIT_FAILED; + + /* The URBs we haven't submitted yet we count as already + * retired. */ + tpriv->num_retired = num_urbs - i; + discard_urbs(itransfer, 0, i); + + usbi_dbg(TRANSFER_CTX(transfer), "reporting successful submission but waiting for %d " + "discards before reporting error", i); + return 0; + } + + return 0; +} + +static int submit_control_transfer(struct usbi_transfer *itransfer) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_device_handle_priv *hpriv = + usbi_get_device_handle_priv(transfer->dev_handle); + struct usbfs_urb *urb; + int r; + + if (transfer->length - LIBUSB_CONTROL_SETUP_SIZE > MAX_CTRL_BUFFER_LENGTH) + return LIBUSB_ERROR_INVALID_PARAM; + + urb = calloc(1, sizeof(*urb)); + if (!urb) + return LIBUSB_ERROR_NO_MEM; + tpriv->urbs = urb; + tpriv->num_urbs = 1; + tpriv->reap_action = NORMAL; + + urb->usercontext = itransfer; + urb->type = USBFS_URB_TYPE_CONTROL; + urb->endpoint = transfer->endpoint; + urb->buffer = transfer->buffer; + urb->buffer_length = transfer->length; + + r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urb); + if (r < 0) { + free(urb); + tpriv->urbs = NULL; + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + return 0; +} + +static int op_submit_transfer(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + return submit_control_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return submit_bulk_transfer(itransfer); + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return submit_iso_transfer(itransfer); + default: + usbi_err(TRANSFER_CTX(transfer), "unknown transfer type %u", transfer->type); + return LIBUSB_ERROR_INVALID_PARAM; + } +} + +static int op_cancel_transfer(struct usbi_transfer *itransfer) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int r; + + if (!tpriv->urbs) + return LIBUSB_ERROR_NOT_FOUND; + + r = discard_urbs(itransfer, 0, tpriv->num_urbs); + if (r != 0) + return r; + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + if (tpriv->reap_action == ERROR) + break; + /* else, fall through */ + default: + tpriv->reap_action = CANCELLED; + } + + return 0; +} + +static void op_clear_transfer_priv(struct usbi_transfer *itransfer) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_CONTROL: + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + if (tpriv->urbs) { + free(tpriv->urbs); + tpriv->urbs = NULL; + } + break; + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + if (tpriv->iso_urbs) { + free_iso_urbs(tpriv); + tpriv->iso_urbs = NULL; + } + break; + default: + usbi_err(TRANSFER_CTX(transfer), "unknown transfer type %u", transfer->type); + } +} + +static int handle_bulk_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + int urb_idx = urb - tpriv->urbs; + + usbi_mutex_lock(&itransfer->lock); + usbi_dbg(TRANSFER_CTX(transfer), "handling completion status %d of bulk urb %d/%d", urb->status, + urb_idx + 1, tpriv->num_urbs); + + tpriv->num_retired++; + + if (tpriv->reap_action != NORMAL) { + /* cancelled, submit_fail, or completed early */ + usbi_dbg(TRANSFER_CTX(transfer), "abnormal reap: urb status %d", urb->status); + + /* even though we're in the process of cancelling, it's possible that + * we may receive some data in these URBs that we don't want to lose. + * examples: + * 1. while the kernel is cancelling all the packets that make up an + * URB, a few of them might complete. so we get back a successful + * cancellation *and* some data. + * 2. we receive a short URB which marks the early completion condition, + * so we start cancelling the remaining URBs. however, we're too + * slow and another URB completes (or at least completes partially). + * (this can't happen since we always use BULK_CONTINUATION.) + * + * When this happens, our objectives are not to lose any "surplus" data, + * and also to stick it at the end of the previously-received data + * (closing any holes), so that libusb reports the total amount of + * transferred data and presents it in a contiguous chunk. + */ + if (urb->actual_length > 0) { + unsigned char *target = transfer->buffer + itransfer->transferred; + + usbi_dbg(TRANSFER_CTX(transfer), "received %d bytes of surplus data", urb->actual_length); + if (urb->buffer != target) { + usbi_dbg(TRANSFER_CTX(transfer), "moving surplus data from offset %zu to offset %zu", + (unsigned char *)urb->buffer - transfer->buffer, + target - transfer->buffer); + memmove(target, urb->buffer, urb->actual_length); + } + itransfer->transferred += urb->actual_length; + } + + if (tpriv->num_retired == tpriv->num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "abnormal reap: last URB handled, reporting"); + if (tpriv->reap_action != COMPLETED_EARLY && + tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_ERROR; + goto completed; + } + goto out_unlock; + } + + itransfer->transferred += urb->actual_length; + + /* Many of these errors can occur on *any* urb of a multi-urb + * transfer. When they do, we tear down the rest of the transfer. + */ + switch (urb->status) { + case 0: + break; + case -EREMOTEIO: /* short transfer */ + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "device removed"); + tpriv->reap_status = LIBUSB_TRANSFER_NO_DEVICE; + goto cancel_remaining; + case -EPIPE: + usbi_dbg(TRANSFER_CTX(transfer), "detected endpoint stall"); + if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_STALL; + goto cancel_remaining; + case -EOVERFLOW: + /* overflow can only ever occur in the last urb */ + usbi_dbg(TRANSFER_CTX(transfer), "overflow, actual_length=%d", urb->actual_length); + if (tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_OVERFLOW; + goto completed; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + usbi_dbg(TRANSFER_CTX(transfer), "low-level bus error %d", urb->status); + tpriv->reap_action = ERROR; + goto cancel_remaining; + default: + usbi_warn(ITRANSFER_CTX(itransfer), "unrecognised urb status %d", urb->status); + tpriv->reap_action = ERROR; + goto cancel_remaining; + } + + /* if we've reaped all urbs or we got less data than requested then we're + * done */ + if (tpriv->num_retired == tpriv->num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!"); + goto completed; + } else if (urb->actual_length < urb->buffer_length) { + usbi_dbg(TRANSFER_CTX(transfer), "short transfer %d/%d --> complete!", + urb->actual_length, urb->buffer_length); + if (tpriv->reap_action == NORMAL) + tpriv->reap_action = COMPLETED_EARLY; + } else { + goto out_unlock; + } + +cancel_remaining: + if (tpriv->reap_action == ERROR && tpriv->reap_status == LIBUSB_TRANSFER_COMPLETED) + tpriv->reap_status = LIBUSB_TRANSFER_ERROR; + + if (tpriv->num_retired == tpriv->num_urbs) /* nothing to cancel */ + goto completed; + + /* cancel remaining urbs and wait for their completion before + * reporting results */ + discard_urbs(itransfer, urb_idx + 1, tpriv->num_urbs); + +out_unlock: + usbi_mutex_unlock(&itransfer->lock); + return 0; + +completed: + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return tpriv->reap_action == CANCELLED ? + usbi_handle_transfer_cancellation(itransfer) : + usbi_handle_transfer_completion(itransfer, tpriv->reap_status); +} + +static int handle_iso_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct libusb_transfer *transfer = + USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + int num_urbs = tpriv->num_urbs; + int urb_idx = 0; + int i; + enum libusb_transfer_status status = LIBUSB_TRANSFER_COMPLETED; + + usbi_mutex_lock(&itransfer->lock); + for (i = 0; i < num_urbs; i++) { + if (urb == tpriv->iso_urbs[i]) { + urb_idx = i + 1; + break; + } + } + if (urb_idx == 0) { + usbi_err(TRANSFER_CTX(transfer), "could not locate urb!"); + usbi_mutex_unlock(&itransfer->lock); + return LIBUSB_ERROR_NOT_FOUND; + } + + usbi_dbg(TRANSFER_CTX(transfer), "handling completion status %d of iso urb %d/%d", urb->status, + urb_idx, num_urbs); + + /* copy isochronous results back in */ + + for (i = 0; i < urb->number_of_packets; i++) { + struct usbfs_iso_packet_desc *urb_desc = &urb->iso_frame_desc[i]; + struct libusb_iso_packet_descriptor *lib_desc = + &transfer->iso_packet_desc[tpriv->iso_packet_offset++]; + + lib_desc->status = LIBUSB_TRANSFER_COMPLETED; + switch (urb_desc->status) { + case 0: + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - device removed", i); + lib_desc->status = LIBUSB_TRANSFER_NO_DEVICE; + break; + case -EPIPE: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - detected endpoint stall", i); + lib_desc->status = LIBUSB_TRANSFER_STALL; + break; + case -EOVERFLOW: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - overflow error", i); + lib_desc->status = LIBUSB_TRANSFER_OVERFLOW; + break; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + case -EXDEV: + usbi_dbg(TRANSFER_CTX(transfer), "packet %d - low-level USB error %d", i, urb_desc->status); + lib_desc->status = LIBUSB_TRANSFER_ERROR; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), "packet %d - unrecognised urb status %d", + i, urb_desc->status); + lib_desc->status = LIBUSB_TRANSFER_ERROR; + break; + } + lib_desc->actual_length = urb_desc->actual_length; + } + + tpriv->num_retired++; + + if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */ + usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: urb status %d", urb->status); + + if (tpriv->num_retired == num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: last URB handled, reporting"); + free_iso_urbs(tpriv); + if (tpriv->reap_action == CANCELLED) { + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_cancellation(itransfer); + } else { + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_ERROR); + } + } + goto out; + } + + switch (urb->status) { + case 0: + break; + case -ENOENT: /* cancelled */ + case -ECONNRESET: + break; + case -ESHUTDOWN: + usbi_dbg(TRANSFER_CTX(transfer), "device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + default: + usbi_warn(TRANSFER_CTX(transfer), "unrecognised urb status %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + /* if we've reaped all urbs then we're done */ + if (tpriv->num_retired == num_urbs) { + usbi_dbg(TRANSFER_CTX(transfer), "all URBs in transfer reaped --> complete!"); + free_iso_urbs(tpriv); + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); + } + +out: + usbi_mutex_unlock(&itransfer->lock); + return 0; +} + +static int handle_control_completion(struct usbi_transfer *itransfer, + struct usbfs_urb *urb) +{ + struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); + int status; + + usbi_mutex_lock(&itransfer->lock); + usbi_dbg(ITRANSFER_CTX(itransfer), "handling completion status %d", urb->status); + + itransfer->transferred += urb->actual_length; + + if (tpriv->reap_action == CANCELLED) { + if (urb->status && urb->status != -ENOENT) + usbi_warn(ITRANSFER_CTX(itransfer), "cancel: unrecognised urb status %d", + urb->status); + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_cancellation(itransfer); + } + + switch (urb->status) { + case 0: + status = LIBUSB_TRANSFER_COMPLETED; + break; + case -ENOENT: /* cancelled */ + status = LIBUSB_TRANSFER_CANCELLED; + break; + case -ENODEV: + case -ESHUTDOWN: + usbi_dbg(ITRANSFER_CTX(itransfer), "device removed"); + status = LIBUSB_TRANSFER_NO_DEVICE; + break; + case -EPIPE: + usbi_dbg(ITRANSFER_CTX(itransfer), "unsupported control request"); + status = LIBUSB_TRANSFER_STALL; + break; + case -EOVERFLOW: + usbi_dbg(ITRANSFER_CTX(itransfer), "overflow, actual_length=%d", urb->actual_length); + status = LIBUSB_TRANSFER_OVERFLOW; + break; + case -ETIME: + case -EPROTO: + case -EILSEQ: + case -ECOMM: + case -ENOSR: + usbi_dbg(ITRANSFER_CTX(itransfer), "low-level bus error %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + default: + usbi_warn(ITRANSFER_CTX(itransfer), "unrecognised urb status %d", urb->status); + status = LIBUSB_TRANSFER_ERROR; + break; + } + + free(tpriv->urbs); + tpriv->urbs = NULL; + usbi_mutex_unlock(&itransfer->lock); + return usbi_handle_transfer_completion(itransfer, status); +} + +static int reap_for_handle(struct libusb_device_handle *handle) +{ + struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle); + int r; + struct usbfs_urb *urb = NULL; + struct usbi_transfer *itransfer; + struct libusb_transfer *transfer; + + r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb); + if (r < 0) { + if (errno == EAGAIN) + return 1; + if (errno == ENODEV) + return LIBUSB_ERROR_NO_DEVICE; + + usbi_err(HANDLE_CTX(handle), "reap failed, errno=%d", errno); + return LIBUSB_ERROR_IO; + } + + itransfer = urb->usercontext; + transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); + + usbi_dbg(HANDLE_CTX(handle), "urb type=%u status=%d transferred=%d", urb->type, urb->status, urb->actual_length); + + switch (transfer->type) { + case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: + return handle_iso_completion(itransfer, urb); + case LIBUSB_TRANSFER_TYPE_BULK: + case LIBUSB_TRANSFER_TYPE_BULK_STREAM: + case LIBUSB_TRANSFER_TYPE_INTERRUPT: + return handle_bulk_completion(itransfer, urb); + case LIBUSB_TRANSFER_TYPE_CONTROL: + return handle_control_completion(itransfer, urb); + default: + usbi_err(HANDLE_CTX(handle), "unrecognised transfer type %u", transfer->type); + return LIBUSB_ERROR_OTHER; + } +} + +static int op_handle_events(struct libusb_context *ctx, + void *event_data, unsigned int count, unsigned int num_ready) +{ + struct pollfd *fds = event_data; + unsigned int n; + int r; + + usbi_mutex_lock(&ctx->open_devs_lock); + for (n = 0; n < count && num_ready > 0; n++) { + struct pollfd *pollfd = &fds[n]; + struct libusb_device_handle *handle; + struct linux_device_handle_priv *hpriv = NULL; + int reap_count; + + if (!pollfd->revents) + continue; + + num_ready--; + for_each_open_device(ctx, handle) { + hpriv = usbi_get_device_handle_priv(handle); + if (hpriv->fd == pollfd->fd) + break; + } + + if (!hpriv || hpriv->fd != pollfd->fd) { + usbi_err(ctx, "cannot find handle for fd %d", + pollfd->fd); + continue; + } + + if (pollfd->revents & POLLERR) { + /* remove the fd from the pollfd set so that it doesn't continuously + * trigger an event, and flag that it has been removed so op_close() + * doesn't try to remove it a second time */ + usbi_remove_event_source(HANDLE_CTX(handle), hpriv->fd); + hpriv->fd_removed = 1; + + /* device will still be marked as attached if hotplug monitor thread + * hasn't processed remove event yet */ + usbi_mutex_static_lock(&linux_hotplug_lock); + if (usbi_atomic_load(&handle->dev->attached)) + linux_device_disconnected(handle->dev->bus_number, + handle->dev->device_address); + usbi_mutex_static_unlock(&linux_hotplug_lock); + + if (hpriv->caps & USBFS_CAP_REAP_AFTER_DISCONNECT) { + do { + r = reap_for_handle(handle); + } while (r == 0); + } + + usbi_handle_disconnect(handle); + continue; + } + + reap_count = 0; + do { + r = reap_for_handle(handle); + } while (r == 0 && ++reap_count <= 25); + + if (r == 1 || r == LIBUSB_ERROR_NO_DEVICE) + continue; + else if (r < 0) + goto out; + } + + r = 0; +out: + usbi_mutex_unlock(&ctx->open_devs_lock); + return r; +} + +const struct usbi_os_backend usbi_backend = { + .name = "Linux usbfs", + .caps = USBI_CAP_HAS_HID_ACCESS|USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER, + .init = op_init, + .exit = op_exit, + .set_option = op_set_option, + .hotplug_poll = op_hotplug_poll, + .get_active_config_descriptor = op_get_active_config_descriptor, + .get_config_descriptor = op_get_config_descriptor, + .get_config_descriptor_by_value = op_get_config_descriptor_by_value, + + .wrap_sys_device = op_wrap_sys_device, + .open = op_open, + .close = op_close, + .get_configuration = op_get_configuration, + .set_configuration = op_set_configuration, + .claim_interface = op_claim_interface, + .release_interface = op_release_interface, + + .set_interface_altsetting = op_set_interface, + .clear_halt = op_clear_halt, + .reset_device = op_reset_device, + + .alloc_streams = op_alloc_streams, + .free_streams = op_free_streams, + + .dev_mem_alloc = op_dev_mem_alloc, + .dev_mem_free = op_dev_mem_free, + + .kernel_driver_active = op_kernel_driver_active, + .detach_kernel_driver = op_detach_kernel_driver, + .attach_kernel_driver = op_attach_kernel_driver, + + .destroy_device = op_destroy_device, + + .submit_transfer = op_submit_transfer, + .cancel_transfer = op_cancel_transfer, + .clear_transfer_priv = op_clear_transfer_priv, + + .handle_events = op_handle_events, + + .device_priv_size = sizeof(struct linux_device_priv), + .device_handle_priv_size = sizeof(struct linux_device_handle_priv), + .transfer_priv_size = sizeof(struct linux_transfer_priv), +}; |