Keep LIBUSB_OPTION_WEAK_AUTHORITY as a macro with same value
[platform/upstream/libusb.git] / libusb / os / linux_usbfs.c
index 9912d65..c300675 100644 (file)
@@ -1,10 +1,11 @@
 /* -*- Mode: C; c-basic-offset:8 ; indent-tabs-mode:t -*- */
 /*
- * Linux usbfs backend for libusbx
+ * 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
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
-#include "config.h"
+#include "libusbi.h"
+#include "linux_usbfs.h"
 
-#include <assert.h>
+#include <alloca.h>
 #include <ctype.h>
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <poll.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include <sys/mman.h>
 #include <sys/utsname.h>
+#include <sys/vfs.h>
 #include <unistd.h>
 
-#include "libusb.h"
-#include "libusbi.h"
-#include "linux_usbfs.h"
-
 /* sysfs vs usbfs:
  * opening a usbfs node causes the device to be resumed, so we attempt to
  * avoid this during enumeration.
@@ -49,7 +45,7 @@
  * 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 
+ *    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
  * In sysfs all descriptors are bus-endian.
  */
 
-static const char *usbfs_path = NULL;
+#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 2.6.32 adds support for a bulk continuation URB flag. this basically
- * allows us to mark URBs as being part of a specific logical transfer when
- * we submit them to the kernel. then, on any error except a cancellation, all
- * URBs within that transfer will be cancelled and no more URBs will be
- * accepted for the transfer, meaning that no more data can creep in.
- *
- * The BULK_CONTINUATION flag must be set on all URBs within a bulk transfer
- * (in either direction) except the first.
- * For IN transfers, we must also set SHORT_NOT_OK on all URBs except the
- * last; it means that the kernel should treat a short reply as an error.
- * For OUT transfers, SHORT_NOT_OK must not be set. it isn't needed (OUT
- * transfers can't be short unless there's already some sort of error), and
- * setting this flag is disallowed (a kernel with USB debugging enabled will
- * reject such URBs).
+/* 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 int supports_flag_bulk_continuation = -1;
+static unsigned int max_iso_packet_len = 0;
 
-/* Linux 2.6.31 fixes support for the zero length packet URB flag. This
- * allows us to mark URBs that should be followed by a zero length data
- * packet, which can be required by device- or class-specific protocols.
- */
-static int supports_flag_zero_packet = -1;
-
-/* clock ID for monotonic clock, as not all clock sources are available on all
- * systems. appropriate choice made at initialization time. */
-static clockid_t monotonic_clkid = -1;
-
-/* Linux 2.6.22 (commit 83f7d958eab2fbc6b159ee92bf1493924e1d0f72) adds a busnum
- * to sysfs, so we can relate devices. This also implies that we can read
- * the active configuration through bConfigurationValue */
-static int sysfs_can_relate_devices = -1;
-
-/* Linux 2.6.26 (commit 217a9081d8e69026186067711131b77f0ce219ed) adds all
- * config descriptors (rather then just the active config) to the sysfs
- * descriptors file, so from then on we can use them. */
-static int sysfs_has_descriptors = -1;
+/* is sysfs available (mounted) ? */
+static int sysfs_available = -1;
 
 /* how many times have we initted (and not exited) ? */
-static volatile int init_count = 0;
+static int init_count = 0;
+
+/* have no authority to operate usb device directly */
+static int no_enumeration = 0;
 
-/* Serialize hotplug start/stop, scan-devices, event-thread, and poll */
+/* Serialize scan-devices, event-thread, and poll */
 usbi_mutex_static_t linux_hotplug_lock = USBI_MUTEX_INITIALIZER;
 
-static int linux_start_event_monitor(void);
-static int linux_stop_event_monitor(void);
 static int linux_scan_devices(struct libusb_context *ctx);
-static int sysfs_scan_device(struct libusb_context *ctx, const char *devname);
-static int detach_kernel_driver_and_claim(struct libusb_device_handle *, int);
+static int detach_kernel_driver_and_claim(struct libusb_device_handle *, uint8_t);
 
-#if !defined(USE_UDEV)
-static int linux_default_scan_devices (struct libusb_context *ctx);
+#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;
-       unsigned char *descriptors;
-       int descriptors_len;
-       int active_config; /* cache val for !sysfs_can_relate_devices  */
+       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;
 };
 
@@ -177,29 +165,57 @@ struct linux_transfer_priv {
        int iso_packet_offset;
 };
 
-static int _get_usbfs_fd(struct libusb_device *dev, mode_t mode, int silent)
+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[PATH_MAX];
+       char path[24];
        int fd;
 
        if (usbdev_names)
-               snprintf(path, PATH_MAX, "%s/usbdev%d.%d",
-                       usbfs_path, dev->bus_number, dev->device_address);
+               sprintf(path, USBDEV_PATH "/usbdev%u.%u",
+                       dev->bus_number, dev->device_address);
        else
-               snprintf(path, PATH_MAX, "%s/%03d/%03d",
-                       usbfs_path, dev->bus_number, dev->device_address);
+               sprintf(path, USB_DEVTMPFS_PATH "/%03u/%03u",
+                       dev->bus_number, dev->device_address);
 
-       fd = open(path, mode);
+       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, "libusbx couldn't open USB device %s: %s",
-                        path, strerror(errno));
+               usbi_err(ctx, "libusb couldn't open USB device %s, errno=%d", path, errno);
                if (errno == EACCES && mode == O_RDWR)
-                       usbi_err(ctx, "libusbx requires write access to USB "
-                                     "device nodes.");
+                       usbi_err(ctx, "libusb requires write access to USB device nodes");
        }
 
        if (errno == EACCES)
@@ -209,218 +225,182 @@ static int _get_usbfs_fd(struct libusb_device *dev, mode_t mode, int silent)
        return LIBUSB_ERROR_IO;
 }
 
-static struct linux_device_priv *_device_priv(struct libusb_device *dev)
-{
-       return (struct linux_device_priv *) dev->os_priv;
-}
-
-static struct linux_device_handle_priv *_device_handle_priv(
-       struct libusb_device_handle *handle)
-{
-       return (struct linux_device_handle_priv *) handle->os_priv;
-}
-
 /* check dirent for a /dev/usbdev%d.%d name
  * optionally return bus/device on success */
-static int _is_usbdev_entry(struct dirent *entry, int *bus_p, int *dev_p)
+static int is_usbdev_entry(const char *name, uint8_t *bus_p, uint8_t *dev_p)
 {
        int busnum, devnum;
 
-       if (sscanf(entry->d_name, "usbdev%d.%d", &busnum, &devnum) != 2)
+       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("found: %s", entry->d_name);
-       if (bus_p != NULL)
-               *bus_p = busnum;
-       if (dev_p != NULL)
-               *dev_p = devnum;
+       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 int check_usb_vfs(const char *dirname)
+static const char *find_usbfs_path(void)
 {
+       const char *path;
        DIR *dir;
        struct dirent *entry;
-       int found = 0;
 
-       dir = opendir(dirname);
-       if (!dir)
-               return 0;
+       path = USB_DEVTMPFS_PATH;
+       dir = opendir(path);
+       if (dir) {
+               while ((entry = readdir(dir))) {
+                       if (entry->d_name[0] == '.')
+                               continue;
 
-       while ((entry = readdir(dir)) != NULL) {
-               if (entry->d_name[0] == '.')
-                       continue;
+                       /* We assume if we find any files that it must be the right place */
+                       break;
+               }
 
-               /* We assume if we find any files that it must be the right place */
-               found = 1;
-               break;
-       }
+               closedir(dir);
 
-       closedir(dir);
-       return found;
-}
+               if (entry)
+                       return path;
+       }
 
-static const char *find_usbfs_path(void)
-{
-       const char *path = "/dev/bus/usb";
-       const char *ret = NULL;
+       /* 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 (check_usb_vfs(path)) {
-               ret = path;
-       } else {
-               path = "/proc/bus/usb";
-               if (check_usb_vfs(path))
-                       ret = path;
-       }
-
-       /* look for /dev/usbdev*.* if the normal places fail */
-       if (ret == NULL) {
-               struct dirent *entry;
-               DIR *dir;
-
-               path = "/dev";
-               dir = opendir(path);
-               if (dir != NULL) {
-                       while ((entry = readdir(dir)) != NULL) {
-                               if (_is_usbdev_entry(entry, NULL, NULL)) {
-                                       /* found one; that's enough */
-                                       ret = path;
-                                       usbdev_names = 1;
-                                       break;
-                               }
+                       if (is_usbdev_entry(entry->d_name, NULL, NULL)) {
+                               /* found one; that's enough */
+                               break;
                        }
-                       closedir(dir);
                }
-       }
 
-       if (ret != NULL)
-               usbi_dbg("found usbfs at %s", ret);
+               closedir(dir);
 
-       return ret;
-}
-
-/* the monotonic clock is not usable on all systems (e.g. embedded ones often
- * seem to lack it). fall back to REALTIME if we have to. */
-static clockid_t find_monotonic_clock(void)
-{
-#ifdef CLOCK_MONOTONIC
-       struct timespec ts;
-       int r;
+               if (entry) {
+                       usbdev_names = 1;
+                       return path;
+               }
+       }
 
-       /* Linux 2.6.28 adds CLOCK_MONOTONIC_RAW but we don't use it
-        * because it's not available through timerfd */
-       r = clock_gettime(CLOCK_MONOTONIC, &ts);
-       if (r == 0)
-               return CLOCK_MONOTONIC;
-       usbi_dbg("monotonic clock doesn't work, errno %d", errno);
+/* 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
-
-       return CLOCK_REALTIME;
 }
 
-static int kernel_version_ge(int major, int minor, int sublevel)
+static int get_kernel_version(struct libusb_context *ctx,
+       struct kernel_version *ver)
 {
        struct utsname uts;
-       int atoms, kmajor, kminor, ksublevel;
+       int atoms;
 
-       if (uname(&uts) < 0)
+       if (uname(&uts) < 0) {
+               usbi_err(ctx, "uname failed, errno=%d", errno);
                return -1;
-       atoms = sscanf(uts.release, "%d.%d.%d", &kmajor, &kminor, &ksublevel);
-       if (atoms < 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);
 
-       if (kmajor > major)
+       return 0;
+}
+
+static int kernel_version_ge(const struct kernel_version *ver,
+       int major, int minor, int sublevel)
+{
+       if (ver->major > major)
                return 1;
-       if (kmajor < major)
+       else if (ver->major < major)
                return 0;
 
        /* kmajor == major */
-       if (atoms < 2)
-               return 0 == minor && 0 == sublevel;
-       if (kminor > minor)
+       if (ver->minor > minor)
                return 1;
-       if (kminor < minor)
+       else if (ver->minor < minor)
                return 0;
 
        /* kminor == minor */
-       if (atoms < 3)
-               return 0 == sublevel;
+       if (ver->sublevel == -1)
+               return sublevel == 0;
 
-       return ksublevel >= sublevel;
+       return ver->sublevel >= sublevel;
 }
 
 static int op_init(struct libusb_context *ctx)
 {
-       struct stat statbuf;
+       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;
        }
 
-       if (monotonic_clkid == -1)
-               monotonic_clkid = find_monotonic_clock();
-
-       if (supports_flag_bulk_continuation == -1) {
-               /* bulk continuation URB flag available from Linux 2.6.32 */
-               supports_flag_bulk_continuation = kernel_version_ge(2,6,32);
-               if (supports_flag_bulk_continuation == -1) {
-                       usbi_err(ctx, "error checking for bulk continuation support");
-                       return LIBUSB_ERROR_OTHER;
-               }
-       }
-
-       if (supports_flag_bulk_continuation)
-               usbi_dbg("bulk continuation flag supported");
+       usbi_dbg(ctx, "found usbfs at %s", usbfs_path);
 
-       if (-1 == supports_flag_zero_packet) {
-               /* zero length packet URB flag fixed since Linux 2.6.31 */
-               supports_flag_zero_packet = kernel_version_ge(2,6,31);
-               if (-1 == supports_flag_zero_packet) {
-                       usbi_err(ctx, "error checking for zero length packet support");
-                       return LIBUSB_ERROR_OTHER;
-               }
+       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;
        }
 
-       if (supports_flag_zero_packet)
-               usbi_dbg("zero length packet flag supported");
+       usbi_dbg(ctx, "max iso packet length is (likely) %u bytes", max_iso_packet_len);
 
-       if (-1 == sysfs_has_descriptors) {
-               /* sysfs descriptors has all descriptors since Linux 2.6.26 */
-               sysfs_has_descriptors = kernel_version_ge(2,6,26);
-               if (-1 == sysfs_has_descriptors) {
-                       usbi_err(ctx, "error checking for sysfs descriptors");
-                       return LIBUSB_ERROR_OTHER;
-               }
-       }
+       if (sysfs_available == -1) {
+               struct statfs statfsbuf;
 
-       if (-1 == sysfs_can_relate_devices) {
-               /* sysfs has busnum since Linux 2.6.22 */
-               sysfs_can_relate_devices = kernel_version_ge(2,6,22);
-               if (-1 == sysfs_can_relate_devices) {
-                       usbi_err(ctx, "error checking for sysfs busnum");
-                       return LIBUSB_ERROR_OTHER;
-               }
-       }
-
-       if (sysfs_can_relate_devices || sysfs_has_descriptors) {
-               r = stat(SYSFS_DEVICE_PATH, &statbuf);
-               if (r != 0 || !S_ISDIR(statbuf.st_mode)) {
+               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_can_relate_devices = 0;
-                       sysfs_has_descriptors = 0;
+                       sysfs_available = 0;
                }
        }
 
-       if (sysfs_can_relate_devices)
-               usbi_dbg("sysfs can relate devices");
-
-       if (sysfs_has_descriptors)
-               usbi_dbg("sysfs has complete descriptors");
+       if (no_enumeration) {
+               return LIBUSB_SUCCESS;
+       }
 
-       usbi_mutex_static_lock(&linux_hotplug_lock);
        r = LIBUSB_SUCCESS;
        if (init_count == 0) {
                /* start up hotplug event handler */
@@ -432,72 +412,79 @@ static int op_init(struct libusb_context *ctx)
                        init_count++;
                else if (init_count == 0)
                        linux_stop_event_monitor();
-       } else
+       } else {
                usbi_err(ctx, "error starting hotplug event monitor");
-       usbi_mutex_static_unlock(&linux_hotplug_lock);
+       }
 
        return r;
 }
 
-static void op_exit(void)
+static void op_exit(struct libusb_context *ctx)
 {
-       usbi_mutex_static_lock(&linux_hotplug_lock);
+       UNUSED(ctx);
+
+       if (no_enumeration) {
+               return;
+       }
+
        assert(init_count != 0);
        if (!--init_count) {
                /* tear down event handler */
-               (void)linux_stop_event_monitor();
+               linux_stop_event_monitor();
        }
-       usbi_mutex_static_unlock(&linux_hotplug_lock);
 }
 
-static int linux_start_event_monitor(void)
+static int op_set_option(struct libusb_context *ctx, enum libusb_option option, va_list ap)
 {
-#if defined(USE_UDEV)
-       return linux_udev_start_event_monitor();
-#else
-       return linux_netlink_start_event_monitor();
-#endif
-}
+       UNUSED(ctx);
+       UNUSED(ap);
 
-static int linux_stop_event_monitor(void)
-{
-#if defined(USE_UDEV)
-       return linux_udev_stop_event_monitor();
-#else
-       return linux_netlink_stop_event_monitor();
-#endif
+       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)
 {
-#if defined(USE_UDEV)
-       return linux_udev_scan_devices(ctx);
+       int ret;
+
+       usbi_mutex_static_lock(&linux_hotplug_lock);
+
+#if defined(HAVE_LIBUDEV)
+       ret = linux_udev_scan_devices(ctx);
 #else
-       return linux_default_scan_devices(ctx);
+       ret = linux_default_scan_devices(ctx);
 #endif
+
+       usbi_mutex_static_unlock(&linux_hotplug_lock);
+
+       return ret;
 }
 
 static void op_hotplug_poll(void)
 {
-#if defined(USE_UDEV)
-       linux_udev_hotplug_poll();
-#else
-       linux_netlink_hotplug_poll();
-#endif
+       linux_hotplug_poll();
 }
 
-static int _open_sysfs_attr(struct libusb_device *dev, const char *attr)
+static int open_sysfs_attr(struct libusb_context *ctx,
+       const char *sysfs_dir, const char *attr)
 {
-       struct linux_device_priv *priv = _device_priv(dev);
-       char filename[PATH_MAX];
+       char filename[256];
        int fd;
 
-       snprintf(filename, PATH_MAX, "%s/%s/%s",
-               SYSFS_DEVICE_PATH, priv->sysfs_dir, attr);
-       fd = open(filename, O_RDONLY);
+       snprintf(filename, sizeof(filename), SYSFS_DEVICE_PATH "/%s/%s", sysfs_dir, attr);
+       fd = open(filename, O_RDONLY | O_CLOEXEC);
        if (fd < 0) {
-               usbi_err(DEVICE_CTX(dev),
-                       "open %s failed ret=%d errno=%d", filename, fd, errno);
+               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;
        }
 
@@ -505,314 +492,350 @@ static int _open_sysfs_attr(struct libusb_device *dev, const char *attr)
 }
 
 /* Note only suitable for attributes which always read >= 0, < 0 is error */
-static int __read_sysfs_attr(struct libusb_context *ctx,
-       const char *devname, const char *attr)
+static int read_sysfs_attr(struct libusb_context *ctx,
+       const char *sysfs_dir, const char *attr, int max_value, int *value_p)
 {
-       char filename[PATH_MAX];
-       FILE *f;
-       int r, value;
+       char buf[20], *endptr;
+       long value;
+       ssize_t r;
+       int fd;
 
-       snprintf(filename, PATH_MAX, "%s/%s/%s", SYSFS_DEVICE_PATH,
-                devname, attr);
-       f = fopen(filename, "r");
-       if (f == NULL) {
-               if (errno == ENOENT) {
-                       /* File doesn't exist. Assume the device has been
-                          disconnected (see trac ticket #70). */
+       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, "open %s failed errno=%d", filename, errno);
+               usbi_err(ctx, "attribute %s read failed, errno=%zd", attr, r);
                return LIBUSB_ERROR_IO;
        }
+       close(fd);
 
-       r = fscanf(f, "%d", &value);
-       fclose(f);
-       if (r != 1) {
-               usbi_err(ctx, "fscanf %s returned %d, errno=%d", attr, r, errno);
-               return LIBUSB_ERROR_NO_DEVICE; /* For unplug race (trac #70) */
+       if (r == 0) {
+               /* Certain attributes (e.g. bConfigurationValue) are not
+                * populated if the device is not configured. */
+               *value_p = -1;
+               return 0;
        }
-       if (value < 0) {
-               usbi_err(ctx, "%s contains a negative value", filename);
+
+       /* 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';
 
-       return value;
+       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 op_get_device_descriptor(struct libusb_device *dev,
-       unsigned char *buffer, int *host_endian)
+static int sysfs_scan_device(struct libusb_context *ctx, const char *devname)
 {
-       struct linux_device_priv *priv = _device_priv(dev);
+       uint8_t busnum, devaddr;
+       int ret;
 
-       *host_endian = sysfs_has_descriptors ? 0 : 1;
-       memcpy(buffer, priv->descriptors, DEVICE_DESC_LENGTH);
+       ret = linux_get_device_address(ctx, 0, &busnum, &devaddr, NULL, devname, -1);
+       if (ret != LIBUSB_SUCCESS)
+               return ret;
 
-       return 0;
+       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)
 {
-       char *endptr;
-       char tmp[4] = {0, 0, 0, 0};
-       long num;
-       int fd;
-       ssize_t r;
-
-       fd = _open_sysfs_attr(dev, "bConfigurationValue");
-       if (fd < 0)
-               return fd;
-
-       r = read(fd, tmp, sizeof(tmp));
-       close(fd);
-       if (r < 0) {
-               usbi_err(DEVICE_CTX(dev), 
-                       "read bConfigurationValue failed ret=%d errno=%d", r, errno);
-               return LIBUSB_ERROR_IO;
-       } else if (r == 0) {
-               usbi_dbg("device unconfigured");
-               *config = -1;
-               return 0;
-       }
-
-       if (tmp[sizeof(tmp) - 1] != 0) {
-               usbi_err(DEVICE_CTX(dev), "not null-terminated?");
-               return LIBUSB_ERROR_IO;
-       } else if (tmp[0] == 0) {
-               usbi_err(DEVICE_CTX(dev), "no configuration value?");
-               return LIBUSB_ERROR_IO;
-       }
-
-       num = strtol(tmp, &endptr, 10);
-       if (endptr == tmp) {
-               usbi_err(DEVICE_CTX(dev), "error converting '%s' to integer", tmp);
-               return LIBUSB_ERROR_IO;
-       }
+       struct linux_device_priv *priv = usbi_get_device_priv(dev);
 
-       *config = (int) num;
-       return 0;
+       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 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_attr;
+       int sysfs_val;
+       int r;
 
-       usbi_dbg("getting address for device: %s detached: %d", sys_name, detached);
+       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_can_relate_devices || detached || NULL == sys_name) {
-               if (NULL == dev_node) {
-                       return LIBUSB_ERROR_OTHER;
+       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/%hhd/%hhd", busnum, devaddr);
-               } else if (!strncmp(dev_node, "/proc/bus/usb", 13)) {
-                       sscanf (dev_node, "/proc/bus/usb/%hhd/%hhd", busnum, devaddr);
-               }
+               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("scan %s", sys_name);
-
-       sysfs_attr = __read_sysfs_attr(ctx, sys_name, "busnum");
-       if (0 > sysfs_attr)
-               return sysfs_attr;
-       if (sysfs_attr > 255)
-               return LIBUSB_ERROR_INVALID_PARAM;
-       *busnum = (uint8_t) sysfs_attr;
+       usbi_dbg(ctx, "scan %s", sys_name);
 
-       sysfs_attr = __read_sysfs_attr(ctx, sys_name, "devnum");
-       if (0 > sysfs_attr)
-               return sysfs_attr;
-       if (sysfs_attr > 255)
-               return LIBUSB_ERROR_INVALID_PARAM;
+       r = read_sysfs_attr(ctx, sys_name, "busnum", UINT8_MAX, &sysfs_val);
+       if (r < 0)
+               return r;
+       *busnum = (uint8_t)sysfs_val;
 
-       *devaddr = (uint8_t) sysfs_attr;
+       r = read_sysfs_attr(ctx, sys_name, "devnum", UINT8_MAX, &sysfs_val);
+       if (r < 0)
+               return r;
+       *devaddr = (uint8_t)sysfs_val;
 
-       usbi_dbg("bus=%d dev=%d", *busnum, *devaddr);
+       usbi_dbg(ctx, "bus=%u dev=%u", *busnum, *devaddr);
 
        return LIBUSB_SUCCESS;
 }
 
-/* Return offset of the next descriptor with the given type */
-static int seek_to_next_descriptor(struct libusb_context *ctx,
-       uint8_t descriptor_type, unsigned char *buffer, int size)
+/* Return offset of the next config descriptor */
+static int seek_to_next_config(struct libusb_context *ctx,
+       uint8_t *buffer, size_t len)
 {
-       struct usb_descriptor_header header;
-       int i;
+       struct usbi_descriptor_header *header;
+       int offset;
 
-       for (i = 0; size >= 0; i += header.bLength, size -= header.bLength) {
-               if (size == 0)
-                       return LIBUSB_ERROR_NOT_FOUND;
+       /* Start seeking past the config descriptor */
+       offset = LIBUSB_DT_CONFIG_SIZE;
+       buffer += LIBUSB_DT_CONFIG_SIZE;
+       len -= LIBUSB_DT_CONFIG_SIZE;
 
-               if (size < 2) {
-                       usbi_err(ctx, "short descriptor read %d/2", size);
+       while (len > 0) {
+               if (len < 2) {
+                       usbi_err(ctx, "short descriptor read %zu/2", len);
                        return LIBUSB_ERROR_IO;
                }
-               usbi_parse_descriptor(buffer + i, "bb", &header, 0);
 
-               if (i && header.bDescriptorType == descriptor_type)
-                       return i;
+               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, "bLength overflow by %d bytes", -size);
+
+       usbi_err(ctx, "config descriptor not found");
        return LIBUSB_ERROR_IO;
 }
 
-/* Return offset to next config */
-static int seek_to_next_config(struct libusb_context *ctx,
-       unsigned char *buffer, int size)
+static int parse_config_descriptors(struct libusb_device *dev)
 {
-       struct libusb_config_descriptor config;
+       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;
 
-       if (size == 0)
-               return LIBUSB_ERROR_NOT_FOUND;
+       device_desc = priv->descriptors;
+       num_configs = device_desc->bNumConfigurations;
 
-       if (size < LIBUSB_DT_CONFIG_SIZE) {
-               usbi_err(ctx, "short descriptor read %d/%d",
-                        size, LIBUSB_DT_CONFIG_SIZE);
-               return LIBUSB_ERROR_IO;
-       }
+       if (num_configs == 0)
+               return 0;       /* no configurations? */
 
-       usbi_parse_descriptor(buffer, "bbwbbbbb", &config, 0);
-       if (config.bDescriptorType != LIBUSB_DT_CONFIG) {
-               usbi_err(ctx, "descriptor is not a config desc (type 0x%02x)",
-                        config.bDescriptorType);
-               return LIBUSB_ERROR_IO;
-       }
+       priv->config_descriptors = malloc(num_configs * sizeof(priv->config_descriptors[0]));
+       if (!priv->config_descriptors)
+               return LIBUSB_ERROR_NO_MEM;
 
-       /*
-        * In usbfs the config descriptors are config.wTotalLength bytes apart,
-        * with any short reads from the device appearing as holes in the file.
-        *
-        * In sysfs wTotalLength is ignored, instead the kernel returns a
-        * config descriptor with verified bLength fields, with descriptors
-        * with an invalid bLength removed.
-        */
-       if (sysfs_has_descriptors) {
-               int next = seek_to_next_descriptor(ctx, LIBUSB_DT_CONFIG,
-                                                  buffer, size);
-               if (next == LIBUSB_ERROR_NOT_FOUND)
-                       next = size;
-               if (next < 0)
-                       return next;
-
-               if (next != config.wTotalLength)
-                       usbi_warn(ctx, "config length mismatch wTotalLength "
-                                 "%d real %d", config.wTotalLength, next);
-               return next;
-       } else {
-               if (config.wTotalLength < LIBUSB_DT_CONFIG_SIZE) {
-                       usbi_err(ctx, "invalid wTotalLength %d",
-                                config.wTotalLength);
+       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;
-               } else if (config.wTotalLength > size) {
-                       usbi_warn(ctx, "short descriptor read %d/%d",
-                                 size, config.wTotalLength);
-                       return size;
-               } else
-                       return config.wTotalLength;
+               }
+
+               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, unsigned char **buffer, int *host_endian)
+       uint8_t value, void **buffer)
 {
-       struct libusb_context *ctx = DEVICE_CTX(dev);
-       struct linux_device_priv *priv = _device_priv(dev);
-       unsigned char *descriptors = priv->descriptors;
-       int size = priv->descriptors_len;
-       struct libusb_config_descriptor *config;
-
-       *buffer = NULL;
-       /* Unlike the device desc. config descs. are always in raw format */
-       *host_endian = 0;
-
-       /* Skip device header */
-       descriptors += DEVICE_DESC_LENGTH;
-       size -= DEVICE_DESC_LENGTH;
-
-       /* Seek till the config is found, or till "EOF" */
-       while (1) {
-               int next = seek_to_next_config(ctx, descriptors, size);
-               if (next < 0)
-                       return next;
-               config = (struct libusb_config_descriptor *)descriptors;
-               if (config->bConfigurationValue == value) {
-                       *buffer = descriptors;
-                       return next;
+       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;
                }
-               size -= next;
-               descriptors += next;
        }
+
+       return LIBUSB_ERROR_NOT_FOUND;
 }
 
 static int op_get_active_config_descriptor(struct libusb_device *dev,
-       unsigned char *buffer, size_t len, int *host_endian)
+       void *buffer, size_t len)
 {
-       int r, config;
-       unsigned char *config_desc;
+       struct linux_device_priv *priv = usbi_get_device_priv(dev);
+       void *config_desc;
+       int active_config;
+       int r;
 
-       if (sysfs_can_relate_devices) {
-               r = sysfs_get_active_config(dev, &config);
+       if (priv->sysfs_dir) {
+               r = sysfs_get_active_config(dev, &active_config);
                if (r < 0)
                        return r;
        } else {
                /* Use cached bConfigurationValue */
-               struct linux_device_priv *priv = _device_priv(dev);
-               config = priv->active_config;
+               active_config = priv->active_config;
        }
-       if (config == -1)
+
+       if (active_config == -1) {
+               usbi_err(DEVICE_CTX(dev), "device unconfigured");
                return LIBUSB_ERROR_NOT_FOUND;
+       }
 
-       r = op_get_config_descriptor_by_value(dev, config, &config_desc,
-                                             host_endian);
+       r = op_get_config_descriptor_by_value(dev, (uint8_t)active_config, &config_desc);
        if (r < 0)
                return r;
 
-       len = MIN(len, 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, unsigned char *buffer, size_t len, int *host_endian)
+       uint8_t config_index, void *buffer, size_t len)
 {
-       struct linux_device_priv *priv = _device_priv(dev);
-       unsigned char *descriptors = priv->descriptors;
-       int i, r, size = priv->descriptors_len;
-
-       /* Unlike the device desc. config descs. are always in raw format */
-       *host_endian = 0;
-
-       /* Skip device header */
-       descriptors += DEVICE_DESC_LENGTH;
-       size -= DEVICE_DESC_LENGTH;
+       struct linux_device_priv *priv = usbi_get_device_priv(dev);
+       struct config_descriptor *config;
 
-       /* Seek till the config is found, or till "EOF" */
-       for (i = 0; ; i++) {
-               r = seek_to_next_config(DEVICE_CTX(dev), descriptors, size);
-               if (r < 0)
-                       return r;
-               if (i == config_index)
-                       break;
-               size -= r;
-               descriptors += r;
-       }
+       if (config_index >= dev->device_descriptor.bNumConfigurations)
+               return LIBUSB_ERROR_NOT_FOUND;
 
-       len = MIN(len, r);
-       memcpy(buffer, descriptors, len);
+       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)
 {
-       unsigned char active_config = 0;
+       struct linux_device_priv *priv = usbi_get_device_priv(dev);
+       uint8_t active_config = 0;
        int r;
 
        struct usbfs_ctrltransfer ctrl = {
@@ -831,133 +854,168 @@ static int usbfs_get_active_config(struct libusb_device *dev, int fd)
                        return LIBUSB_ERROR_NO_DEVICE;
 
                /* we hit this error path frequently with buggy devices :( */
-               usbi_warn(DEVICE_CTX(dev),
-                       "get_configuration failed ret=%d errno=%d", r, errno);
-               return LIBUSB_ERROR_IO;
+               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 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)
+       uint8_t devaddr, const char *sysfs_dir, int wrapped_fd)
 {
-       struct linux_device_priv *priv = _device_priv(dev);
+       struct linux_device_priv *priv = usbi_get_device_priv(dev);
        struct libusb_context *ctx = DEVICE_CTX(dev);
-       int descriptors_size = 512; /* Begin with a 1024 byte alloc */
-       int fd, speed;
-       ssize_t r;
+       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 = malloc(strlen(sysfs_dir) + 1);
+               priv->sysfs_dir = strdup(sysfs_dir);
                if (!priv->sysfs_dir)
                        return LIBUSB_ERROR_NO_MEM;
-               strcpy(priv->sysfs_dir, sysfs_dir);
 
-               /* Note speed can contain 1.5, in this case __read_sysfs_attr
+               /* Note speed can contain 1.5, in this case read_sysfs_attr()
                   will stop parsing at the '.' and return 1 */
-               speed = __read_sysfs_attr(DEVICE_CTX(dev), sysfs_dir, "speed");
-               if (speed >= 0) {
+               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(DEVICE_CTX(dev), "Unknown device speed: %d Mbps", speed);
+                               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_has_descriptors)
-               fd = _open_sysfs_attr(dev, "descriptors");
-       else
-               fd = _get_usbfs_fd(dev, O_RDONLY, 0);
+       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 {
-               descriptors_size *= 2;
-               priv->descriptors = usbi_reallocf(priv->descriptors,
-                                                 descriptors_size);
+               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) {
-                       close(fd);
+                       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_has_descriptors) {
-                       memset(priv->descriptors + priv->descriptors_len,
-                              0, descriptors_size - priv->descriptors_len);
-               }
-               r = read(fd, priv->descriptors + priv->descriptors_len,
-                        descriptors_size - priv->descriptors_len);
-               if (r < 0) {
-                       usbi_err(ctx, "read descriptor failed ret=%d errno=%d",
-                                fd, errno);
-                       close(fd);
+               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 += r;
-       } while (priv->descriptors_len == descriptors_size);
-       
-       close(fd);
+               priv->descriptors_len += (size_t)nb;
+       } while (priv->descriptors_len == alloc_len);
 
-       if (priv->descriptors_len < DEVICE_DESC_LENGTH) {
-               usbi_err(ctx, "short descriptor read (%d)",
-                        priv->descriptors_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;
        }
 
-       if (sysfs_can_relate_devices)
+       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 */
-       fd = _get_usbfs_fd(dev, O_RDWR, 1);
+       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->descriptors_len >=
-                               (DEVICE_DESC_LENGTH + LIBUSB_DT_CONFIG_SIZE)) {
-                       struct libusb_config_descriptor config;
-                       usbi_parse_descriptor(
-                               priv->descriptors + DEVICE_DESC_LENGTH,
-                               "bbwbbbbb", &config, 0);
-                       priv->active_config = config.bConfigurationValue;
-               } else
+               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 (r > 0) {
-               priv->active_config = r;
-               r = LIBUSB_SUCCESS;
-       } else if (r == 0) {
-               /* some buggy devices have a configuration 0, but we're
-                * reaching into the corner of a corner case here, so let's
-                * not support buggy devices in these circumstances.
-                * stick to the specs: a configuration value of 0 means
-                * unconfigured. */
-               usbi_dbg("active cfg 0? assuming unconfigured device");
-               priv->active_config = -1;
-               r = LIBUSB_SUCCESS;
-       } else if (r == LIBUSB_ERROR_IO) {
-               /* buggy devices sometimes fail to report their active config.
-                * assume unconfigured and continue the probing */
-               usbi_warn(ctx, "couldn't query active configuration, assuming"
-                              " unconfigured");
-               priv->active_config = -1;
-               r = LIBUSB_SUCCESS;
-       } /* else r < 0, just return the error code */
+       if (fd != wrapped_fd)
+               close(fd);
 
-       close(fd);
        return r;
 }
 
@@ -969,57 +1027,62 @@ static int linux_get_parent_info(struct libusb_device *dev, const char *sysfs_di
        int ret, add_parent = 1;
 
        /* XXX -- can we figure out the topology when using usbfs? */
-       if (NULL == sysfs_dir || 0 == strncmp(sysfs_dir, "usb", 3)) {
+       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 (NULL != (tmp = strrchr(parent_sysfs_dir, '.')) ||
-           NULL != (tmp = strrchr(parent_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);
+               free(parent_sysfs_dir);
                return LIBUSB_SUCCESS;
        }
 
        /* is the parent a root hub? */
-       if (NULL == strchr(parent_sysfs_dir, '-')) {
+       if (!strchr(parent_sysfs_dir, '-')) {
                tmp = parent_sysfs_dir;
-               ret = asprintf (&parent_sysfs_dir, "usb%s", tmp);
-               free (tmp);
-               if (0 > ret) {
+               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);
-       list_for_each_entry(it, &ctx->usb_devs, list, struct libusb_device) {
-               struct linux_device_priv *priv = _device_priv(it);
-               if (0 == strcmp (priv->sysfs_dir, parent_sysfs_dir)) {
-                       dev->parent_dev = libusb_ref_device(it);
-                       break;
+       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("parent_dev %s not enumerated yet, enumerating now",
+               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("Dev %p (%s) has parent %p (%s) port %d", dev, sysfs_dir,
+       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);
+       free(parent_sysfs_dir);
 
        return LIBUSB_SUCCESS;
 }
@@ -1029,28 +1092,29 @@ int linux_enumerate_device(struct libusb_context *ctx,
 {
        unsigned long session_id;
        struct libusb_device *dev;
-       int r = 0;
+       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("busnum %d devaddr %d session_id %ld", busnum, devaddr,
-               session_id);
+       usbi_dbg(ctx, "busnum %u devaddr %u session_id %lu", busnum, devaddr, session_id);
 
-       if (usbi_get_device_by_session_id(ctx, session_id)) {
+       dev = usbi_get_device_by_session_id(ctx, session_id);
+       if (dev) {
                /* device already exists in the context */
-               usbi_dbg("session_id %ld already exists", session_id);
+               usbi_dbg(ctx, "session_id %lu already exists", session_id);
+               libusb_unref_device(dev);
                return LIBUSB_SUCCESS;
        }
 
-       usbi_dbg("allocating new device for %d/%d (session %ld)",
+       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);
+       r = initialize_device(dev, busnum, devaddr, sysfs_dir, -1);
        if (r < 0)
                goto out;
        r = usbi_sanitize_device(dev);
@@ -1074,41 +1138,58 @@ void linux_hotplug_enumerate(uint8_t busnum, uint8_t devaddr, const char *sys_na
        struct libusb_context *ctx;
 
        usbi_mutex_static_lock(&active_contexts_lock);
-       list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) {
+       for_each_context(ctx) {
                linux_enumerate_device(ctx, busnum, devaddr, sys_name);
        }
        usbi_mutex_static_unlock(&active_contexts_lock);
 }
 
-void linux_hotplug_disconnected(uint8_t busnum, uint8_t devaddr, const char *sys_name)
+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);
-       list_for_each_entry(ctx, &active_contexts_list, list, struct libusb_context) {
-               dev = usbi_get_device_by_session_id (ctx, session_id);
-               if (NULL != dev) {
-                       usbi_disconnect_device (dev);
+       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("device not found for session %x", session_id);
+                       usbi_dbg(ctx, "device not found for session %lx", session_id);
                }
        }
        usbi_mutex_static_unlock(&active_contexts_lock);
 }
 
-#if !defined(USE_UDEV)
+#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[PATH_MAX];
+       char dirpath[20];
        struct dirent *entry;
        int r = LIBUSB_ERROR_IO;
 
-       snprintf(dirpath, PATH_MAX, "%s/%03d", usbfs_path, busnum);
-       usbi_dbg("%s", dirpath);
+       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);
@@ -1118,19 +1199,18 @@ static int usbfs_scan_busdir(struct libusb_context *ctx, uint8_t busnum)
        }
 
        while ((entry = readdir(dir))) {
-               int devaddr;
+               uint8_t devaddr;
 
                if (entry->d_name[0] == '.')
                        continue;
 
-               devaddr = atoi(entry->d_name);
-               if (devaddr == 0) {
-                       usbi_dbg("unknown dir entry %s", entry->d_name);
+               if (!parse_u8(entry->d_name, &devaddr)) {
+                       usbi_dbg(ctx, "unknown dir entry %s", entry->d_name);
                        continue;
                }
 
-               if (linux_enumerate_device(ctx, busnum, (uint8_t) devaddr, NULL)) {
-                       usbi_dbg("failed to enumerate dir entry %s", entry->d_name);
+               if (linux_enumerate_device(ctx, busnum, devaddr, NULL)) {
+                       usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name);
                        continue;
                }
 
@@ -1144,34 +1224,36 @@ static int usbfs_scan_busdir(struct libusb_context *ctx, uint8_t busnum)
 static int usbfs_get_device_list(struct libusb_context *ctx)
 {
        struct dirent *entry;
-       DIR *buses = opendir(usbfs_path);
+       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);
+               usbi_err(ctx, "opendir buses failed, errno=%d", errno);
                return LIBUSB_ERROR_IO;
        }
 
        while ((entry = readdir(buses))) {
-               int busnum;
-
                if (entry->d_name[0] == '.')
                        continue;
 
                if (usbdev_names) {
-                       int devaddr;
-                       if (!_is_usbdev_entry(entry, &busnum, &devaddr))
+                       if (!is_usbdev_entry(entry->d_name, &busnum, &devaddr))
                                continue;
 
-                       r = linux_enumerate_device(ctx, busnum, (uint8_t) devaddr, NULL);
+                       r = linux_enumerate_device(ctx, busnum, devaddr, NULL);
                        if (r < 0) {
-                               usbi_dbg("failed to enumerate dir entry %s", entry->d_name);
+                               usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name);
                                continue;
                        }
                } else {
-                       busnum = atoi(entry->d_name);
-                       if (busnum == 0) {
-                               usbi_dbg("unknown dir entry %s", entry->d_name);
+                       if (!parse_u8(entry->d_name, &busnum)) {
+                               usbi_dbg(ctx, "unknown dir entry %s", entry->d_name);
                                continue;
                        }
 
@@ -1185,130 +1267,201 @@ static int usbfs_get_device_list(struct libusb_context *ctx)
        return r;
 
 }
-#endif
-
-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);
-       if (LIBUSB_SUCCESS != ret) {
-               return ret;
-       }
-
-       return linux_enumerate_device(ctx, busnum & 0xff, devaddr & 0xff,
-               devname);
-}
 
-#if !defined(USE_UDEV)
 static int sysfs_get_device_list(struct libusb_context *ctx)
 {
        DIR *devices = opendir(SYSFS_DEVICE_PATH);
        struct dirent *entry;
-       int r = LIBUSB_ERROR_IO;
+       int num_devices = 0;
+       int num_enumerated = 0;
 
        if (!devices) {
-               usbi_err(ctx, "opendir devices failed errno=%d", errno);
-               return r;
+               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, ':'))
+                   || strchr(entry->d_name, ':'))
                        continue;
 
+               num_devices++;
+
                if (sysfs_scan_device(ctx, entry->d_name)) {
-                       usbi_dbg("failed to enumerate dir entry %s", entry->d_name);
+                       usbi_dbg(ctx, "failed to enumerate dir entry %s", entry->d_name);
                        continue;
                }
 
-               r = 0;
+               num_enumerated++;
        }
 
        closedir(devices);
-       return r;
+
+       /* 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)
+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.
-        *
-        * as described in the "sysfs vs usbfs" comment at the top of this
-        * file, sometimes we have sysfs but not enough information to
-        * relate sysfs devices to usbfs nodes.  op_init() determines the
-        * adequacy of sysfs and sets sysfs_can_relate_devices.
         */
-       if (sysfs_can_relate_devices != 0)
+       if (sysfs_available)
                return sysfs_get_device_list(ctx);
        else
                return usbfs_get_device_list(ctx);
 }
 #endif
 
-static int op_open(struct libusb_device_handle *handle)
+static int initialize_handle(struct libusb_device_handle *handle, int fd)
 {
-       struct linux_device_handle_priv *hpriv = _device_handle_priv(handle);
+       struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);
        int r;
 
-       hpriv->fd = _get_usbfs_fd(handle->dev, O_RDWR, 0);
-       if (hpriv->fd < 0)
-               return hpriv->fd;
+       hpriv->fd = fd;
 
-       r = ioctl(hpriv->fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps);
+       r = ioctl(fd, IOCTL_USBFS_GET_CAPABILITIES, &hpriv->caps);
        if (r < 0) {
                if (errno == ENOTTY)
-                       usbi_dbg("getcap not available");
+                       usbi_dbg(HANDLE_CTX(handle), "getcap not available");
                else
-                       usbi_err(HANDLE_CTX(handle), "getcap failed (%d)", errno);
-               hpriv->caps = 0;
-               if (supports_flag_zero_packet)
-                       hpriv->caps |= USBFS_CAP_ZERO_PACKET;
-               if (supports_flag_bulk_continuation)
-                       hpriv->caps |= USBFS_CAP_BULK_CONTINUATION;
+                       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;
        }
 
-       return usbi_add_pollfd(HANDLE_CTX(handle), hpriv->fd, POLLOUT);
+       r = initialize_handle(handle, fd);
+       if (r < 0)
+               close(fd);
+
+       return r;
 }
 
 static void op_close(struct libusb_device_handle *dev_handle)
 {
-       int fd = _device_handle_priv(dev_handle)->fd;
-       usbi_remove_pollfd(HANDLE_CTX(dev_handle), fd);
-       close(fd);
+       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,
-       int *config)
+       uint8_t *config)
 {
+       struct linux_device_priv *priv = usbi_get_device_priv(handle->dev);
+       int active_config;
        int r;
 
-       if (sysfs_can_relate_devices) {
-               r = sysfs_get_active_config(handle->dev, config);
+       if (priv->sysfs_dir) {
+               r = sysfs_get_active_config(handle->dev, &active_config);
        } else {
-               r = usbfs_get_active_config(handle->dev,
-                                           _device_handle_priv(handle)->fd);
+               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 (*config == -1) {
-               usbi_err(HANDLE_CTX(handle), "device unconfigured");
-               *config = 0;
+       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 = _device_priv(handle->dev);
-       int fd = _device_handle_priv(handle)->fd;
-       int r = ioctl(fd, IOCTL_USBFS_SETCONFIG, &config);
-       if (r) {
+       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)
@@ -1316,21 +1469,28 @@ static int op_set_configuration(struct libusb_device_handle *handle, int config)
                else if (errno == ENODEV)
                        return LIBUSB_ERROR_NO_DEVICE;
 
-               usbi_err(HANDLE_CTX(handle), "failed, error %d errno %d", r, errno);
+               usbi_err(HANDLE_CTX(handle), "set configuration failed, errno=%d", errno);
                return LIBUSB_ERROR_OTHER;
        }
 
-       /* update our cached active config descriptor */
-       priv->active_config = config;
+       /* 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, int iface)
+static int claim_interface(struct libusb_device_handle *handle, unsigned int iface)
 {
-       int fd = _device_handle_priv(handle)->fd;
-       int r = ioctl(fd, IOCTL_USBFS_CLAIMINTF, &iface);
-       if (r) {
+       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)
@@ -1338,46 +1498,46 @@ static int claim_interface(struct libusb_device_handle *handle, int iface)
                else if (errno == ENODEV)
                        return LIBUSB_ERROR_NO_DEVICE;
 
-               usbi_err(HANDLE_CTX(handle),
-                       "claim interface failed, error %d errno %d", r, errno);
+               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, int iface)
+static int release_interface(struct libusb_device_handle *handle, unsigned int iface)
 {
-       int fd = _device_handle_priv(handle)->fd;
-       int r = ioctl(fd, IOCTL_USBFS_RELEASEINTF, &iface);
-       if (r) {
+       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, error %d errno %d", r, errno);
+               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, int iface,
-       int altsetting)
+static int op_set_interface(struct libusb_device_handle *handle, uint8_t interface,
+       uint8_t altsetting)
 {
-       int fd = _device_handle_priv(handle)->fd;
+       struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);
+       int fd = hpriv->fd;
        struct usbfs_setinterface setintf;
        int r;
 
-       setintf.interface = iface;
+       setintf.interface = interface;
        setintf.altsetting = altsetting;
-       r = ioctl(fd, IOCTL_USBFS_SETINTF, &setintf);
-       if (r) {
+       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),
-                       "setintf failed error %d errno %d", r, errno);
+               usbi_err(HANDLE_CTX(handle), "set interface failed, errno=%d", errno);
                return LIBUSB_ERROR_OTHER;
        }
 
@@ -1387,17 +1547,18 @@ static int op_set_interface(struct libusb_device_handle *handle, int iface,
 static int op_clear_halt(struct libusb_device_handle *handle,
        unsigned char endpoint)
 {
-       int fd = _device_handle_priv(handle)->fd;
+       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) {
+
+       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 error %d errno %d", r, errno);
+               usbi_err(HANDLE_CTX(handle), "clear halt failed, errno=%d", errno);
                return LIBUSB_ERROR_OTHER;
        }
 
@@ -1406,50 +1567,49 @@ static int op_clear_halt(struct libusb_device_handle *handle,
 
 static int op_reset_device(struct libusb_device_handle *handle)
 {
-       int fd = _device_handle_priv(handle)->fd;
-       int i, r, ret = 0;
+       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). */
+        * 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 & (1L << 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) {
+       if (r < 0) {
                if (errno == ENODEV) {
                        ret = LIBUSB_ERROR_NOT_FOUND;
                        goto out;
                }
 
-               usbi_err(HANDLE_CTX(handle),
-                       "reset failed error %d errno %d", r, errno);
+               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 & (1L << i)) {
-                       /*
-                        * 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 %d after reset: %s",
-                                       i, libusb_error_name(r));
-                               handle->claimed_interfaces &= ~(1L << i);
-                               ret = LIBUSB_ERROR_NOT_FOUND;
-                       }
+               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:
@@ -1457,33 +1617,108 @@ out:
        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,
-       int interface)
+       uint8_t interface)
 {
-       int fd = _device_handle_priv(handle)->fd;
+       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) {
+       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 error %d errno %d", r, errno);
+               usbi_err(HANDLE_CTX(handle), "get driver failed, errno=%d", errno);
                return LIBUSB_ERROR_OTHER;
        }
 
-       return (strcmp(getdrv.driver, "usbfs") == 0) ? 0 : 1;
+       return strcmp(getdrv.driver, "usbfs") != 0;
 }
 
 static int op_detach_kernel_driver(struct libusb_device_handle *handle,
-       int interface)
+       uint8_t interface)
 {
-       int fd = _device_handle_priv(handle)->fd;
+       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;
@@ -1494,11 +1729,11 @@ static int op_detach_kernel_driver(struct libusb_device_handle *handle,
 
        getdrv.interface = interface;
        r = ioctl(fd, IOCTL_USBFS_GETDRIVER, &getdrv);
-       if (r == 0 && strcmp(getdrv.driver, "usbfs") == 0)
+       if (r == 0 && !strcmp(getdrv.driver, "usbfs"))
                return LIBUSB_ERROR_NOT_FOUND;
 
        r = ioctl(fd, IOCTL_USBFS_IOCTL, &command);
-       if (r) {
+       if (r < 0) {
                if (errno == ENODATA)
                        return LIBUSB_ERROR_NOT_FOUND;
                else if (errno == EINVAL)
@@ -1506,8 +1741,7 @@ static int op_detach_kernel_driver(struct libusb_device_handle *handle,
                else if (errno == ENODEV)
                        return LIBUSB_ERROR_NO_DEVICE;
 
-               usbi_err(HANDLE_CTX(handle),
-                       "detach failed error %d errno %d", r, errno);
+               usbi_err(HANDLE_CTX(handle), "detach failed, errno=%d", errno);
                return LIBUSB_ERROR_OTHER;
        }
 
@@ -1515,9 +1749,10 @@ static int op_detach_kernel_driver(struct libusb_device_handle *handle,
 }
 
 static int op_attach_kernel_driver(struct libusb_device_handle *handle,
-       int interface)
+       uint8_t interface)
 {
-       int fd = _device_handle_priv(handle)->fd;
+       struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);
+       int fd = hpriv->fd;
        struct usbfs_ioctl command;
        int r;
 
@@ -1536,8 +1771,7 @@ static int op_attach_kernel_driver(struct libusb_device_handle *handle,
                else if (errno == EBUSY)
                        return LIBUSB_ERROR_BUSY;
 
-               usbi_err(HANDLE_CTX(handle),
-                       "attach failed error %d errno %d", r, errno);
+               usbi_err(HANDLE_CTX(handle), "attach failed, errno=%d", errno);
                return LIBUSB_ERROR_OTHER;
        } else if (r == 0) {
                return LIBUSB_ERROR_NOT_FOUND;
@@ -1547,29 +1781,29 @@ static int op_attach_kernel_driver(struct libusb_device_handle *handle,
 }
 
 static int detach_kernel_driver_and_claim(struct libusb_device_handle *handle,
-       int interface)
+       uint8_t interface)
 {
+       struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);
        struct usbfs_disconnect_claim dc;
-       int r, fd = _device_handle_priv(handle)->fd;
+       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 || (r != 0 && errno != ENOTTY)) {
-               if (r == 0)
-                       return 0;
-
-               switch (errno) {
-               case EBUSY:
-                       return LIBUSB_ERROR_BUSY;
-               case EINVAL:
-                       return LIBUSB_ERROR_INVALID_PARAM;
-               case ENODEV:
-                       return LIBUSB_ERROR_NO_DEVICE;
-               }
-               usbi_err(HANDLE_CTX(handle),
-                       "disconnect-and-claim failed errno %d", errno);
+       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;
        }
 
@@ -1582,35 +1816,35 @@ static int detach_kernel_driver_and_claim(struct libusb_device_handle *handle,
        return claim_interface(handle, interface);
 }
 
-static int op_claim_interface(struct libusb_device_handle *handle, int iface)
+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, iface);
+               return detach_kernel_driver_and_claim(handle, interface);
        else
-               return claim_interface(handle, iface);
+               return claim_interface(handle, interface);
 }
 
-static int op_release_interface(struct libusb_device_handle *handle, int iface)
+static int op_release_interface(struct libusb_device_handle *handle, uint8_t interface)
 {
        int r;
 
-       r = release_interface(handle, iface);
+       r = release_interface(handle, interface);
        if (r)
                return r;
 
        if (handle->auto_detach_kernel_driver)
-               op_attach_kernel_driver(handle, iface);
+               op_attach_kernel_driver(handle, interface);
 
        return 0;
 }
 
 static void op_destroy_device(struct libusb_device *dev)
 {
-       struct linux_device_priv *priv = _device_priv(dev);
-       if (priv->descriptors)
-               free(priv->descriptors);
-       if (priv->sysfs_dir)
-               free(priv->sysfs_dir);
+       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. */
@@ -1618,32 +1852,30 @@ static int discard_urbs(struct usbi_transfer *itransfer, int first, int last_plu
 {
        struct libusb_transfer *transfer =
                USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-       struct linux_transfer_priv *tpriv =
-               usbi_transfer_get_os_priv(itransfer);
-       struct linux_device_handle_priv *dpriv =
-               _device_handle_priv(transfer->dev_handle);
+       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 (LIBUSB_TRANSFER_TYPE_ISOCHRONOUS == transfer->type)
+               if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS)
                        urb = tpriv->iso_urbs[i];
                else
                        urb = &tpriv->urbs[i];
 
-               if (0 == ioctl(dpriv->fd, IOCTL_USBFS_DISCARDURB, urb))
+               if (ioctl(hpriv->fd, IOCTL_USBFS_DISCARDURB, urb) == 0)
                        continue;
 
-               if (EINVAL == errno) {
-                       usbi_dbg("URB not found --> assuming ready to be reaped");
+               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 (ENODEV == errno) {
-                       usbi_dbg("Device not found for URB --> assuming ready to be reaped");
+               } 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);
+                       usbi_warn(TRANSFER_CTX(transfer), "unrecognised discard errno %d", errno);
                        ret = LIBUSB_ERROR_OTHER;
                }
        }
@@ -1653,8 +1885,10 @@ static int discard_urbs(struct usbi_transfer *itransfer, int first, int last_plu
 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);
@@ -1664,28 +1898,20 @@ static void free_iso_urbs(struct linux_transfer_priv *tpriv)
        tpriv->iso_urbs = NULL;
 }
 
-static int submit_bulk_transfer(struct usbi_transfer *itransfer,
-       unsigned char urb_type)
+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_transfer_get_os_priv(itransfer);
-       struct linux_device_handle_priv *dpriv =
-               _device_handle_priv(transfer->dev_handle);
+       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 = (transfer->endpoint & LIBUSB_ENDPOINT_DIR_MASK)
-               == LIBUSB_ENDPOINT_OUT;
+       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;
-       size_t alloc_size;
-
-       if (tpriv->urbs)
-               return LIBUSB_ERROR_BUSY;
-
-       if (is_out && (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) &&
-                       !(dpriv->caps & USBFS_CAP_ZERO_PACKET))
-               return LIBUSB_ERROR_NOT_SUPPORTED;
 
        /*
         * Older versions of usbfs place a 16kb limit on bulk URBs. We work
@@ -1694,7 +1920,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
         * 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 arbritary large transfers can still be a bad idea though, as
+        * 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.
         *
@@ -1706,16 +1932,16 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
         * short split-transfers to work reliable USBFS_CAP_BULK_CONTINUATION
         * is needed, but this is not always available.
         */
-       if (dpriv->caps & USBFS_CAP_BULK_SCATTER_GATHER) {
+       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 (dpriv->caps & USBFS_CAP_BULK_CONTINUATION) {
+       } 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 (dpriv->caps & USBFS_CAP_NO_PACKET_SIZE_LIM) {
+       } 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;
@@ -1729,8 +1955,7 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
                use_bulk_continuation = 0;
        }
 
-       int num_urbs = transfer->length / bulk_buffer_len;
-       int last_urb_partial = 0;
+       num_urbs = transfer->length / bulk_buffer_len;
 
        if (transfer->length == 0) {
                num_urbs = 1;
@@ -1738,10 +1963,8 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
                last_urb_partial = 1;
                num_urbs++;
        }
-       usbi_dbg("need %d urbs for new transfer with length %d", num_urbs,
-               transfer->length);
-       alloc_size = num_urbs * sizeof(struct usbfs_urb);
-       urbs = calloc(1, alloc_size);
+       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;
@@ -1752,13 +1975,28 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
 
        for (i = 0; i < num_urbs; i++) {
                struct usbfs_urb *urb = &urbs[i];
+
                urb->usercontext = itransfer;
-               urb->type = urb_type;
+               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)
@@ -1771,60 +2009,62 @@ static int submit_bulk_transfer(struct usbi_transfer *itransfer,
 
                /* we have already checked that the flag is supported */
                if (is_out && i == num_urbs - 1 &&
-                   transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)
+                   (transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET))
                        urb->flags |= USBFS_URB_ZERO_PACKET;
 
-               r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
-               if (r < 0) {
-                       if (errno == ENODEV) {
-                               r = LIBUSB_ERROR_NO_DEVICE;
-                       } else {
-                               usbi_err(TRANSFER_CTX(transfer),
-                                       "submiturb failed error %d errno=%d", r, 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("first URB failed, easy peasy");
-                               free(urbs);
-                               tpriv->urbs = NULL;
-                               return r;
-                       }
+               r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
+               if (r == 0)
+                       continue;
 
-                       /* 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 libusbx 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 = EREMOTEIO == errno ? COMPLETED_EARLY : SUBMIT_FAILED;
+               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;
+               }
 
-                       /* The URBs we haven't submitted yet we count as already
-                        * retired. */
-                       tpriv->num_retired += num_urbs - i;
+               /* 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 we completed short then don't try to discard. */
-                       if (COMPLETED_EARLY == tpriv->reap_action)
-                               return 0;
+               /* 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;
 
-                       discard_urbs(itransfer, 0, i);
+               /* The URBs we haven't submitted yet we count as already
+                * retired. */
+               tpriv->num_retired += num_urbs - i;
 
-                       usbi_dbg("reporting successful submission but waiting for %d "
-                               "discards before reporting error", 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;
@@ -1834,49 +2074,48 @@ 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_transfer_get_os_priv(itransfer);
-       struct linux_device_handle_priv *dpriv =
-               _device_handle_priv(transfer->dev_handle);
+       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;
-       size_t alloc_size;
        int num_packets = transfer->num_iso_packets;
-       int i;
-       int this_urb_len = 0;
-       int num_urbs = 1;
-       int packet_offset = 0;
+       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 (tpriv->iso_urbs)
-               return LIBUSB_ERROR_BUSY;
+       if (num_packets < 1)
+               return LIBUSB_ERROR_INVALID_PARAM;
 
-       /* usbfs places a 32kb limit on iso URBs. we divide up larger requests
-        * into smaller units to meet such restriction, then fire off all the
-        * units at once. it would be simpler if we just fired one unit at a time,
-        * but there is a big performance gain through doing it this way.
-        *
-        * Newer kernels lift the 32k limit (USBFS_CAP_NO_PACKET_SIZE_LIM),
-        * using arbritary large transfers is still be a bad idea though, as
-        * the kernel needs to allocate physical contiguous memory for this,
-        * which may fail for large buffers.
+       /* 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.
         */
-
-       /* calculate how many URBs we need */
        for (i = 0; i < num_packets; i++) {
-               unsigned int space_remaining = MAX_ISO_BUFFER_LENGTH - this_urb_len;
                packet_len = transfer->iso_packet_desc[i].length;
 
-               if (packet_len > space_remaining) {
-                       num_urbs++;
-                       this_urb_len = packet_len;
-               } else {
-                       this_urb_len += packet_len;
+               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;
        }
-       usbi_dbg("need %d 32k URBs for transfer", num_urbs);
 
-       alloc_size = num_urbs * sizeof(*urbs);
-       urbs = calloc(1, alloc_size);
+       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;
 
@@ -1887,31 +2126,15 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer)
        tpriv->iso_packet_offset = 0;
 
        /* allocate + initialize each URB with the correct number of packets */
-       for (i = 0; i < num_urbs; i++) {
+       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;
-               unsigned int space_remaining_in_urb = MAX_ISO_BUFFER_LENGTH;
-               int urb_packet_offset = 0;
-               unsigned char *urb_buffer_orig = urb_buffer;
-               int j;
+               size_t alloc_size;
                int k;
 
-               /* swallow up all the packets we can fit into this URB */
-               while (packet_offset < transfer->num_iso_packets) {
-                       packet_len = transfer->iso_packet_desc[packet_offset].length;
-                       if (packet_len <= space_remaining_in_urb) {
-                               /* throw it in */
-                               urb_packet_offset++;
-                               packet_offset++;
-                               space_remaining_in_urb -= packet_len;
-                               urb_buffer += packet_len;
-                       } else {
-                               /* it can't fit, save it for the next URB */
-                               break;
-                       }
-               }
-
                alloc_size = sizeof(*urb)
-                       + (urb_packet_offset * sizeof(struct usbfs_iso_packet_desc));
+                       + (num_packets_in_urb * sizeof(struct usbfs_iso_packet_desc));
                urb = calloc(1, alloc_size);
                if (!urb) {
                        free_iso_urbs(tpriv);
@@ -1920,10 +2143,10 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer)
                urbs[i] = urb;
 
                /* populate packet lengths */
-               for (j = 0, k = packet_offset - urb_packet_offset;
-                               k < packet_offset; k++, j++) {
-                       packet_len = transfer->iso_packet_desc[k].length;
-                       urb->iso_frame_desc[j].length = packet_len;
+               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;
@@ -1931,54 +2154,64 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer)
                /* FIXME: interface for non-ASAP data? */
                urb->flags = USBFS_URB_ISO_ASAP;
                urb->endpoint = transfer->endpoint;
-               urb->number_of_packets = urb_packet_offset;
-               urb->buffer = urb_buffer_orig;
+               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(dpriv->fd, IOCTL_USBFS_SUBMITURB, urbs[i]);
-               if (r < 0) {
-                       if (errno == ENODEV) {
-                               r = LIBUSB_ERROR_NO_DEVICE;
-                       } else {
-                               usbi_err(TRANSFER_CTX(transfer),
-                                       "submiturb failed error %d errno=%d", r, errno);
-                               r = LIBUSB_ERROR_IO;
-                       }
+               int r = ioctl(hpriv->fd, IOCTL_USBFS_SUBMITURB, urbs[i]);
 
-                       /* if the first URB submission fails, we can simply free up and
-                        * return failure immediately. */
-                       if (i == 0) {
-                               usbi_dbg("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 libusbx 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;
+               if (r == 0)
+                       continue;
 
-                       /* The URBs we haven't submitted yet we count as already
-                        * retired. */
-                       tpriv->num_retired = num_urbs - i;
-                       discard_urbs(itransfer, 0, i);
+               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;
+               }
 
-                       usbi_dbg("reporting successful submission but waiting for %d "
-                               "discards before reporting error", i);
-                       return 0;
+               /* 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;
@@ -1986,21 +2219,18 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer)
 
 static int submit_control_transfer(struct usbi_transfer *itransfer)
 {
-       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(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 *dpriv =
-               _device_handle_priv(transfer->dev_handle);
+       struct linux_device_handle_priv *hpriv =
+               usbi_get_device_handle_priv(transfer->dev_handle);
        struct usbfs_urb *urb;
        int r;
 
-       if (tpriv->urbs)
-               return LIBUSB_ERROR_BUSY;
-
        if (transfer->length - LIBUSB_CONTROL_SETUP_SIZE > MAX_CTRL_BUFFER_LENGTH)
                return LIBUSB_ERROR_INVALID_PARAM;
 
-       urb = calloc(1, sizeof(struct usbfs_urb));
+       urb = calloc(1, sizeof(*urb));
        if (!urb)
                return LIBUSB_ERROR_NO_MEM;
        tpriv->urbs = urb;
@@ -2013,15 +2243,14 @@ static int submit_control_transfer(struct usbi_transfer *itransfer)
        urb->buffer = transfer->buffer;
        urb->buffer_length = transfer->length;
 
-       r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);
+       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 error %d errno=%d", r, errno);
+               usbi_err(TRANSFER_CTX(transfer), "submiturb failed, errno=%d", errno);
                return LIBUSB_ERROR_IO;
        }
        return 0;
@@ -2036,91 +2265,88 @@ static int op_submit_transfer(struct usbi_transfer *itransfer)
        case LIBUSB_TRANSFER_TYPE_CONTROL:
                return submit_control_transfer(itransfer);
        case LIBUSB_TRANSFER_TYPE_BULK:
-               return submit_bulk_transfer(itransfer, USBFS_URB_TYPE_BULK);
+       case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
+               return submit_bulk_transfer(itransfer);
        case LIBUSB_TRANSFER_TYPE_INTERRUPT:
-               return submit_bulk_transfer(itransfer, USBFS_URB_TYPE_INTERRUPT);
+               return submit_bulk_transfer(itransfer);
        case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
                return submit_iso_transfer(itransfer);
        default:
-               usbi_err(TRANSFER_CTX(transfer),
-                       "unknown endpoint type %d", transfer->type);
+               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_transfer_get_os_priv(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 */
-       case LIBUSB_TRANSFER_TYPE_CONTROL:
-       case LIBUSB_TRANSFER_TYPE_INTERRUPT:
-       case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
-               tpriv->reap_action = CANCELLED;
-               break;
        default:
-               usbi_err(TRANSFER_CTX(transfer),
-                       "unknown endpoint type %d", transfer->type);
-               return LIBUSB_ERROR_INVALID_PARAM;
+               tpriv->reap_action = CANCELLED;
        }
 
-       if (!tpriv->urbs)
-               return LIBUSB_ERROR_NOT_FOUND;
-
-       return discard_urbs(itransfer, 0, tpriv->num_urbs);
+       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_transfer_get_os_priv(itransfer);
+       struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer);
 
-       /* urbs can be freed also in submit_transfer so lock mutex first */
        switch (transfer->type) {
        case LIBUSB_TRANSFER_TYPE_CONTROL:
        case LIBUSB_TRANSFER_TYPE_BULK:
+       case LIBUSB_TRANSFER_TYPE_BULK_STREAM:
        case LIBUSB_TRANSFER_TYPE_INTERRUPT:
-               usbi_mutex_lock(&itransfer->lock);
-               if (tpriv->urbs)
+               if (tpriv->urbs) {
                        free(tpriv->urbs);
-               tpriv->urbs = NULL;
-               usbi_mutex_unlock(&itransfer->lock);
+                       tpriv->urbs = NULL;
+               }
                break;
        case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS:
-               usbi_mutex_lock(&itransfer->lock);
-               if (tpriv->iso_urbs)
+               if (tpriv->iso_urbs) {
                        free_iso_urbs(tpriv);
-               usbi_mutex_unlock(&itransfer->lock);
+                       tpriv->iso_urbs = NULL;
+               }
                break;
        default:
-               usbi_err(TRANSFER_CTX(transfer),
-                       "unknown endpoint type %d", transfer->type);
+               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_transfer_get_os_priv(itransfer);
+       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("handling completion status %d of bulk urb %d/%d", urb->status,
-               urb_idx + 1, tpriv->num_urbs);
+       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("abnormal reap: urb status %d", urb->status);
+               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.
@@ -2135,23 +2361,24 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer,
                 *
                 * 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 libusbx reports the total amount of
+                * (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("received %d bytes of surplus data", urb->actual_length);
+
+                       usbi_dbg(TRANSFER_CTX(transfer), "received %d bytes of surplus data", urb->actual_length);
                        if (urb->buffer != target) {
-                               usbi_dbg("moving surplus data from offset %d to offset %d",
-                                       (unsigned char *) urb->buffer - transfer->buffer,
-                                       target - transfer->buffer);
+                               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("abnormal reap: last URB handled, reporting");
+                       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;
@@ -2175,17 +2402,17 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer,
                break;
        case -ENODEV:
        case -ESHUTDOWN:
-               usbi_dbg("device removed");
+               usbi_dbg(TRANSFER_CTX(transfer), "device removed");
                tpriv->reap_status = LIBUSB_TRANSFER_NO_DEVICE;
                goto cancel_remaining;
        case -EPIPE:
-               usbi_dbg("detected endpoint stall");
+               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("overflow, actual_length=%d", urb->actual_length);
+               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;
@@ -2194,31 +2421,31 @@ static int handle_bulk_completion(struct usbi_transfer *itransfer,
        case -EILSEQ:
        case -ECOMM:
        case -ENOSR:
-               usbi_dbg("low level error %d", urb->status);
+               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);
+               usbi_warn(ITRANSFER_CTX(itransfer), "unrecognised urb status %d", urb->status);
                tpriv->reap_action = ERROR;
                goto cancel_remaining;
        }
 
-       /* if we're the last urb or we got less data than requested then we're
+       /* if we've reaped all urbs or we got less data than requested then we're
         * done */
-       if (urb_idx == tpriv->num_urbs - 1) {
-               usbi_dbg("last URB in transfer --> complete!");
+       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("short transfer %d/%d --> complete!",
-                       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
+       } else {
                goto out_unlock;
+       }
 
 cancel_remaining:
-       if (ERROR == tpriv->reap_action && LIBUSB_TRANSFER_COMPLETED == tpriv->reap_status)
+       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 */
@@ -2236,7 +2463,7 @@ completed:
        free(tpriv->urbs);
        tpriv->urbs = NULL;
        usbi_mutex_unlock(&itransfer->lock);
-       return CANCELLED == tpriv->reap_action ?
+       return tpriv->reap_action == CANCELLED ?
                usbi_handle_transfer_cancellation(itransfer) :
                usbi_handle_transfer_completion(itransfer, tpriv->reap_status);
 }
@@ -2246,7 +2473,7 @@ static int handle_iso_completion(struct usbi_transfer *itransfer,
 {
        struct libusb_transfer *transfer =
                USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
-       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
+       struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer);
        int num_urbs = tpriv->num_urbs;
        int urb_idx = 0;
        int i;
@@ -2265,8 +2492,8 @@ static int handle_iso_completion(struct usbi_transfer *itransfer,
                return LIBUSB_ERROR_NOT_FOUND;
        }
 
-       usbi_dbg("handling completion status %d of iso urb %d/%d", urb->status,
-               urb_idx, num_urbs);
+       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 */
 
@@ -2274,6 +2501,7 @@ static int handle_iso_completion(struct usbi_transfer *itransfer,
                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:
@@ -2283,15 +2511,15 @@ static int handle_iso_completion(struct usbi_transfer *itransfer,
                        break;
                case -ENODEV:
                case -ESHUTDOWN:
-                       usbi_dbg("device removed");
+                       usbi_dbg(TRANSFER_CTX(transfer), "packet %d - device removed", i);
                        lib_desc->status = LIBUSB_TRANSFER_NO_DEVICE;
                        break;
                case -EPIPE:
-                       usbi_dbg("detected endpoint stall");
+                       usbi_dbg(TRANSFER_CTX(transfer), "packet %d - detected endpoint stall", i);
                        lib_desc->status = LIBUSB_TRANSFER_STALL;
                        break;
                case -EOVERFLOW:
-                       usbi_dbg("overflow error");
+                       usbi_dbg(TRANSFER_CTX(transfer), "packet %d - overflow error", i);
                        lib_desc->status = LIBUSB_TRANSFER_OVERFLOW;
                        break;
                case -ETIME:
@@ -2300,12 +2528,12 @@ static int handle_iso_completion(struct usbi_transfer *itransfer,
                case -ECOMM:
                case -ENOSR:
                case -EXDEV:
-                       usbi_dbg("low-level USB error %d", urb_desc->status);
+                       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),
-                               "unrecognised urb status %d", urb_desc->status);
+                       usbi_warn(TRANSFER_CTX(transfer), "packet %d - unrecognised urb status %d",
+                                 i, urb_desc->status);
                        lib_desc->status = LIBUSB_TRANSFER_ERROR;
                        break;
                }
@@ -2315,18 +2543,17 @@ static int handle_iso_completion(struct usbi_transfer *itransfer,
        tpriv->num_retired++;
 
        if (tpriv->reap_action != NORMAL) { /* cancelled or submit_fail */
-               usbi_dbg("CANCEL: urb status %d", urb->status);
+               usbi_dbg(TRANSFER_CTX(transfer), "CANCEL: urb status %d", urb->status);
 
                if (tpriv->num_retired == num_urbs) {
-                       usbi_dbg("CANCEL: last URB handled, reporting");
+                       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);
+                               return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_ERROR);
                        }
                }
                goto out;
@@ -2339,19 +2566,18 @@ static int handle_iso_completion(struct usbi_transfer *itransfer,
        case -ECONNRESET:
                break;
        case -ESHUTDOWN:
-               usbi_dbg("device removed");
+               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);
+               usbi_warn(TRANSFER_CTX(transfer), "unrecognised urb status %d", urb->status);
                status = LIBUSB_TRANSFER_ERROR;
                break;
        }
 
-       /* if we're the last urb then we're done */
-       if (urb_idx == num_urbs) {
-               usbi_dbg("last URB in transfer --> complete!");
+       /* 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);
@@ -2365,18 +2591,18 @@ out:
 static int handle_control_completion(struct usbi_transfer *itransfer,
        struct usbfs_urb *urb)
 {
-       struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
+       struct linux_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer);
        int status;
 
        usbi_mutex_lock(&itransfer->lock);
-       usbi_dbg("handling completion status %d", urb->status);
+       usbi_dbg(ITRANSFER_CTX(itransfer), "handling completion status %d", urb->status);
 
        itransfer->transferred += urb->actual_length;
 
        if (tpriv->reap_action == CANCELLED) {
-               if (urb->status != 0 && urb->status != -ENOENT)
-                       usbi_warn(ITRANSFER_CTX(itransfer),
-                               "cancel: unrecognised urb status %d", urb->status);
+               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);
@@ -2392,15 +2618,15 @@ static int handle_control_completion(struct usbi_transfer *itransfer,
                break;
        case -ENODEV:
        case -ESHUTDOWN:
-               usbi_dbg("device removed");
+               usbi_dbg(ITRANSFER_CTX(itransfer), "device removed");
                status = LIBUSB_TRANSFER_NO_DEVICE;
                break;
        case -EPIPE:
-               usbi_dbg("unsupported control request");
+               usbi_dbg(ITRANSFER_CTX(itransfer), "unsupported control request");
                status = LIBUSB_TRANSFER_STALL;
                break;
        case -EOVERFLOW:
-               usbi_dbg("control overflow error");
+               usbi_dbg(ITRANSFER_CTX(itransfer), "overflow, actual_length=%d", urb->actual_length);
                status = LIBUSB_TRANSFER_OVERFLOW;
                break;
        case -ETIME:
@@ -2408,12 +2634,11 @@ static int handle_control_completion(struct usbi_transfer *itransfer,
        case -EILSEQ:
        case -ECOMM:
        case -ENOSR:
-               usbi_dbg("low-level bus error occurred");
+               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);
+               usbi_warn(ITRANSFER_CTX(itransfer), "unrecognised urb status %d", urb->status);
                status = LIBUSB_TRANSFER_ERROR;
                break;
        }
@@ -2426,76 +2651,103 @@ static int handle_control_completion(struct usbi_transfer *itransfer,
 
 static int reap_for_handle(struct libusb_device_handle *handle)
 {
-       struct linux_device_handle_priv *hpriv = _device_handle_priv(handle);
+       struct linux_device_handle_priv *hpriv = usbi_get_device_handle_priv(handle);
        int r;
-       struct usbfs_urb *urb;
+       struct usbfs_urb *urb = NULL;
        struct usbi_transfer *itransfer;
        struct libusb_transfer *transfer;
 
        r = ioctl(hpriv->fd, IOCTL_USBFS_REAPURBNDELAY, &urb);
-       if (r == -1 && errno == EAGAIN)
-               return 1;
        if (r < 0) {
+               if (errno == EAGAIN)
+                       return 1;
                if (errno == ENODEV)
                        return LIBUSB_ERROR_NO_DEVICE;
 
-               usbi_err(HANDLE_CTX(handle), "reap failed error %d errno=%d",
-                       r, errno);
+               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("urb type=%d status=%d transferred=%d", urb->type, urb->status,
-               urb->actual_length);
+       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 endpoint type %x",
-                       transfer->type);
+               usbi_err(HANDLE_CTX(handle), "unrecognised transfer type %u", transfer->type);
                return LIBUSB_ERROR_OTHER;
        }
 }
 
 static int op_handle_events(struct libusb_context *ctx,
-       struct pollfd *fds, POLL_NFDS_TYPE nfds, int num_ready)
+       void *event_data, unsigned int count, unsigned int num_ready)
 {
+       struct pollfd *fds = event_data;
+       unsigned int n;
        int r;
-       unsigned int i = 0;
 
        usbi_mutex_lock(&ctx->open_devs_lock);
-       for (i = 0; i < nfds && num_ready > 0; i++) {
-               struct pollfd *pollfd = &fds[i];
+       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--;
-               list_for_each_entry(handle, &ctx->open_devs, list, struct libusb_device_handle) {
-                       hpriv = _device_handle_priv(handle);
+               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) {
-                       usbi_remove_pollfd(HANDLE_CTX(handle), hpriv->fd);
+                       /* 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);
+               } while (r == 0 && ++reap_count <= 25);
+
                if (r == 1 || r == LIBUSB_ERROR_NO_DEVICE)
                        continue;
                else if (r < 0)
@@ -2508,38 +2760,18 @@ out:
        return r;
 }
 
-static int op_clock_gettime(int clk_id, struct timespec *tp)
-{
-       switch (clk_id) {
-       case USBI_CLOCK_MONOTONIC:
-               return clock_gettime(monotonic_clkid, tp);
-       case USBI_CLOCK_REALTIME:
-               return clock_gettime(CLOCK_REALTIME, tp);
-       default:
-               return LIBUSB_ERROR_INVALID_PARAM;
-  }
-}
-
-#ifdef USBI_TIMERFD_AVAILABLE
-static clockid_t op_get_timerfd_clockid(void)
-{
-       return monotonic_clkid;
-
-}
-#endif
-
-const struct usbi_os_backend linux_usbfs_backend = {
+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,
-       .get_device_list = NULL,
+       .set_option = op_set_option,
        .hotplug_poll = op_hotplug_poll,
-       .get_device_descriptor = op_get_device_descriptor,
        .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,
@@ -2551,6 +2783,12 @@ const struct usbi_os_backend linux_usbfs_backend = {
        .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,
@@ -2563,14 +2801,7 @@ const struct usbi_os_backend linux_usbfs_backend = {
 
        .handle_events = op_handle_events,
 
-       .clock_gettime = op_clock_gettime,
-
-#ifdef USBI_TIMERFD_AVAILABLE
-       .get_timerfd_clockid = op_get_timerfd_clockid,
-#endif
-
        .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),
-       .add_iso_packet_size = 0,
 };