Rework configuration handling
authorDaniel Drake <dsd@gentoo.org>
Fri, 9 May 2008 13:34:31 +0000 (14:34 +0100)
committerDaniel Drake <dsd@gentoo.org>
Fri, 9 May 2008 18:33:33 +0000 (19:33 +0100)
libusb no longer caches descriptors in libusb_device but backends are
intended to be able to provide copies from memory. In the common linux
case we can use sysfs.

TODO
examples/lsusb.c
libusb/core.c
libusb/descriptor.c
libusb/libusb.h
libusb/libusbi.h
libusb/os/linux_usbfs.c

diff --git a/TODO b/TODO
index 478e43e..2b53246 100644 (file)
--- a/TODO
+++ b/TODO
@@ -7,6 +7,7 @@ serialization of handle_events
 internal docs for OS porters
 configuration handling
 check which messages are sent during open, claim interface, close, release
+unconfigured devices
 
 1.0 API style/naming points to reconsider
 =========================================
index 509e0bd..5a83020 100644 (file)
@@ -28,10 +28,15 @@ void print_devs(libusb_device **devs)
        int i = 0;
 
        while ((dev = devs[i++]) != NULL) {
-               const struct libusb_device_descriptor *desc =
-                       libusb_get_device_descriptor(dev);
+               struct libusb_device_descriptor desc;
+               int r = libusb_get_device_descriptor(dev, &desc);
+               if (r < 0) {
+                       fprintf(stderr, "failed to get device descriptor");
+                       return;
+               }
+
                printf("%04x:%04x (bus %d, device %d)\n",
-                       desc->idVendor, desc->idProduct,
+                       desc.idVendor, desc.idProduct,
                        libusb_get_bus_number(dev), libusb_get_device_address(dev));
        }
 }
index f83305e..43491ad 100644 (file)
@@ -304,95 +304,28 @@ struct libusb_device *usbi_alloc_device(unsigned long session_id)
        return dev;
 }
 
-/* call the OS discovery routines to populate descriptors etc */
-int usbi_discover_device(struct libusb_device *dev)
+/* to be called by OS implementations when a new device is ready for final
+ * sanitization and checking before being returned in a device list. */
+int usbi_sanitize_device(struct libusb_device *dev)
 {
        int r;
-       int i;
-       void *user_data;
        unsigned char raw_desc[DEVICE_DESC_LENGTH];
-       size_t alloc_size;
+       uint8_t num_configurations;
 
-       dev->config = NULL;
-
-       r = usbi_backend->begin_discovery(dev, &user_data);
+       r = usbi_backend->get_device_descriptor(dev, raw_desc);
        if (r < 0)
                return r;
-       
-       r = usbi_backend->get_device_descriptor(dev, raw_desc, user_data);
-       if (r < 0)
-               goto err;
 
-       usbi_parse_descriptor(raw_desc, "bbWbbbbWWWbbbb", &dev->desc);
-
-       if (dev->desc.bNumConfigurations > USB_MAXCONFIG) {
+       num_configurations = raw_desc[DEVICE_DESC_LENGTH - 1];
+       if (num_configurations > USB_MAXCONFIG) {
                usbi_err("too many configurations");
-               r = LIBUSB_ERROR_IO;
-               goto err;
-       }
-
-       if (dev->desc.bNumConfigurations < 1) {
+               return LIBUSB_ERROR_IO;
+       } else if (num_configurations < 1) {
                usbi_dbg("no configurations?");
-               r = LIBUSB_ERROR_IO;
-               goto err;
-       }
-
-       alloc_size = dev->desc.bNumConfigurations
-               * sizeof(struct libusb_config_descriptor);
-       dev->config = malloc(alloc_size);
-       if (!dev->config) {
-               r = LIBUSB_ERROR_NO_MEM;
-               goto err;
-       }
-
-       memset(dev->config, 0, alloc_size);
-       for (i = 0; i < dev->desc.bNumConfigurations; i++) {
-               unsigned char tmp[8];
-               unsigned char *bigbuffer;
-               struct libusb_config_descriptor config;
-
-               r = usbi_backend->get_config_descriptor(dev, i, tmp, sizeof(tmp),
-                       user_data);
-               if (r < 0)
-                       goto err;
-
-               usbi_parse_descriptor(tmp, "bbw", &config);
-
-               bigbuffer = malloc(config.wTotalLength);
-               if (!bigbuffer) {
-                       r = LIBUSB_ERROR_NO_MEM;
-                       goto err;
-               }
-
-               r = usbi_backend->get_config_descriptor(dev, i, bigbuffer,
-                       config.wTotalLength, user_data);
-               if (r < 0) {
-                       free(bigbuffer);
-                       goto err;
-               }
-
-               r = usbi_parse_configuration(&dev->config[i], bigbuffer);
-               free(bigbuffer);
-               if (r < 0) {
-                       usbi_err("parse_configuration failed with code %d", r);
-                       goto err;
-               } else if (r > 0) {
-                       usbi_warn("descriptor data still left\n");
-               }
+               return LIBUSB_ERROR_IO;
        }
 
-       usbi_backend->end_discovery(dev, user_data);
        return 0;
-
-err:
-       if (dev->config) {
-               usbi_clear_configurations(dev);
-               free(dev->config);
-               dev->config = NULL;
-       }
-
-       usbi_backend->end_discovery(dev, user_data);
-       return r;
 }
 
 struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id)
@@ -513,19 +446,27 @@ API_EXPORTED uint8_t libusb_get_device_address(libusb_device *dev)
 
 /** \ingroup dev
  * Convenience function to retrieve the wMaxPacketSize value for a particular
- * endpoint. This is useful for setting up isochronous transfers.
+ * endpoint in the active device configuration. This is useful for setting up
+ * isochronous transfers.
  *
  * \param dev a device
  * \param endpoint address of the endpoint in question
- * \returns the wMaxPacketSize value, or LIBUSB_ERROR_NOT_FOUND if the endpoint
- * does not exist.
+ * \returns the wMaxPacketSize value
+ * \returns LIBUSB_ERROR_NOT_FOUND if the endpoint does not exist
+ * \returns LIBUSB_ERROR_OTHER on other failure
  */
 API_EXPORTED int libusb_get_max_packet_size(libusb_device *dev,
        unsigned char endpoint)
 {
        int iface_idx;
-       /* FIXME: active config considerations? */
-       struct libusb_config_descriptor *config = dev->config;
+       struct libusb_config_descriptor *config =
+               libusb_get_active_config_descriptor(dev);
+       int r = LIBUSB_ERROR_NOT_FOUND;
+
+       if (!config) {
+               usbi_err("could not retrieve active config descriptor");
+               return LIBUSB_ERROR_OTHER;
+       }
 
        for (iface_idx = 0; iface_idx < config->bNumInterfaces; iface_idx++) {
                const struct libusb_interface *iface = &config->interface[iface_idx];
@@ -540,13 +481,17 @@ API_EXPORTED int libusb_get_max_packet_size(libusb_device *dev,
                        for (ep_idx = 0; ep_idx < altsetting->bNumEndpoints; ep_idx++) {
                                const struct libusb_endpoint_descriptor *ep =
                                        &altsetting->endpoint[ep_idx];
-                               if (ep->bEndpointAddress == endpoint)
-                                       return ep->wMaxPacketSize;
+                               if (ep->bEndpointAddress == endpoint) {
+                                       r = ep->wMaxPacketSize;
+                                       goto out;
+                               }
                        }
                }
        }
 
-       return LIBUSB_ERROR_NOT_FOUND;
+out:
+       libusb_free_config_descriptor(config);
+       return r;
 }
 
 /** \ingroup dev
@@ -579,8 +524,7 @@ API_EXPORTED void libusb_unref_device(libusb_device *dev)
        pthread_mutex_unlock(&dev->lock);
 
        if (refcnt == 0) {
-               usbi_dbg("destroy device %04x:%04x", dev->desc.idVendor,
-                       dev->desc.idProduct);
+               usbi_dbg("destroy device %d.%d", dev->bus_number, dev->device_address);
 
                if (usbi_backend->destroy_device)
                        usbi_backend->destroy_device(dev);
@@ -589,10 +533,6 @@ API_EXPORTED void libusb_unref_device(libusb_device *dev)
                list_del(&dev->list);
                pthread_mutex_unlock(&usb_devs_lock);
 
-               if (dev->config) {
-                       usbi_clear_configurations(dev);
-                       free(dev->config);
-               }
                free(dev);
        }
 }
@@ -615,7 +555,7 @@ API_EXPORTED libusb_device_handle *libusb_open(libusb_device *dev)
        struct libusb_device_handle *handle;
        size_t priv_size = usbi_backend->device_handle_priv_size;
        int r;
-       usbi_dbg("open %04x:%04x", dev->desc.idVendor, dev->desc.idProduct);
+       usbi_dbg("open %d.%d", dev->bus_number, dev->device_address);
 
        handle = malloc(sizeof(*handle) + priv_size);
        if (!handle)
@@ -670,9 +610,11 @@ API_EXPORTED libusb_device_handle *libusb_open_device_with_vid_pid(
                return NULL;
 
        while ((dev = devs[i++]) != NULL) {
-               const struct libusb_device_descriptor *desc =
-                       libusb_get_device_descriptor(dev);
-               if (desc->idVendor == vendor_id && desc->idProduct == product_id) {
+               struct libusb_device_descriptor desc;
+               int r = libusb_get_device_descriptor(dev, &desc);
+               if (r < 0)
+                       goto out;
+               if (desc.idVendor == vendor_id && desc.idProduct == product_id) {
                        found = dev;
                        break;
                }
@@ -681,6 +623,7 @@ API_EXPORTED libusb_device_handle *libusb_open_device_with_vid_pid(
        if (found)
                handle = libusb_open(found);
 
+out:
        libusb_free_device_list(devs, 1);
        return handle;
 }
index 822306d..e07711a 100644 (file)
@@ -325,14 +325,7 @@ static void clear_configuration(struct libusb_config_descriptor *config)
                free((void *) config->extra);
 }
 
-void usbi_clear_configurations(struct libusb_device *dev)
-{
-       int i;
-       for (i = 0; i < dev->desc.bNumConfigurations; i++)
-               clear_configuration(dev->config + i);
-}
-
-int usbi_parse_configuration(struct libusb_config_descriptor *config,
+static int parse_configuration(struct libusb_config_descriptor *config,
        unsigned char *buffer)
 {
        int i;
@@ -430,23 +423,149 @@ err:
  * This is a non-blocking function; the device descriptor is cached in memory.
  *
  * \param dev the device
- * \returns the USB device descriptor
+ * \param desc output location for the descriptor data
+ * \returns 0 on success or a LIBUSB_ERROR code on failure
  */
-API_EXPORTED const struct libusb_device_descriptor *libusb_get_device_descriptor(
-       libusb_device *dev)
+API_EXPORTED int libusb_get_device_descriptor(libusb_device *dev,
+       struct libusb_device_descriptor *desc)
 {
-       return &dev->desc;
+       unsigned char raw_desc[DEVICE_DESC_LENGTH];
+       int r;
+
+       r = usbi_backend->get_device_descriptor(dev, raw_desc);
+       if (r < 0)
+               return r;
+
+       usbi_parse_descriptor(raw_desc, "bbWbbbbWWWbbbb", desc);
+       return 0;
 }
 
 /** \ingroup desc
- * Get the USB configuration descriptor for a given device.
- * \param dev the device
- * \returns the USB configuration descriptor
+ * Get the USB configuration descriptor for the currently active configuration.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \returns the USB configuration descriptor which must be freed with
+ * libusb_free_config_descriptor() when done
+ * \returns NULL on error
+ * \see libusb_get_config_descriptor
  */
-API_EXPORTED const struct libusb_config_descriptor *libusb_get_config_descriptor(
+API_EXPORTED
+struct libusb_config_descriptor *libusb_get_active_config_descriptor(
        libusb_device *dev)
 {
-       return dev->config;
+       struct libusb_config_descriptor *config = malloc(sizeof(*config));
+       unsigned char tmp[8];
+       unsigned char *buf = NULL;
+       int r;
+
+       if (!config)
+               return NULL;
+
+       r = usbi_backend->get_active_config_descriptor(dev, tmp, sizeof(tmp));
+       if (r < 0)
+               goto err;
+
+       usbi_parse_descriptor(tmp, "bbw", &config);
+       buf = malloc(config->wTotalLength);
+       if (!buf)
+               goto err;
+
+       r = usbi_backend->get_active_config_descriptor(dev, buf,
+               config->wTotalLength);
+       if (r < 0)
+               goto err;
+
+       r = parse_configuration(config, buf);
+       if (r < 0) {
+               usbi_err("parse_configuration failed with error %d", r);
+               goto err;
+       } else if (r > 0) {
+               usbi_warn("descriptor data still left");
+       }
+
+       return config;
+
+err:
+       free(config);
+       if (buf)
+               free(buf);
+       return NULL;
+}
+
+/** \ingroup desc
+ * Get the USB configuration descriptor for the currently active configuration.
+ * This is a non-blocking function which does not involve any requests being
+ * sent to the device.
+ *
+ * \param dev a device
+ * \param bConfigurationValue the bConfigurationValue of the configuration
+ * you wish to retreive
+ * \returns the USB configuration descriptor which must be freed with
+ * libusb_free_config_descriptor() when done
+ * \returns NULL on error
+ * \see libusb_get_active_config_descriptor()
+ */
+API_EXPORTED struct libusb_config_descriptor *libusb_get_config_descriptor(
+       libusb_device *dev, uint8_t bConfigurationValue)
+{
+       struct libusb_config_descriptor *config = malloc(sizeof(*config));
+       unsigned char tmp[8];
+       unsigned char *buf = NULL;
+       int r;
+
+       if (!config)
+               return NULL;
+
+       r = usbi_backend->get_config_descriptor(dev, bConfigurationValue, tmp,
+               sizeof(tmp));
+       if (r < 0)
+               goto err;
+
+       usbi_parse_descriptor(tmp, "bbw", &config);
+       buf = malloc(config->wTotalLength);
+       if (!buf)
+               goto err;
+
+       r = usbi_backend->get_config_descriptor(dev, bConfigurationValue, buf,
+               config->wTotalLength);
+       if (r < 0)
+               goto err;
+
+       r = parse_configuration(config, buf);
+       if (r < 0) {
+               usbi_err("parse_configuration failed with error %d", r);
+               goto err;
+       } else if (r > 0) {
+               usbi_warn("descriptor data still left");
+       }
+
+       return config;
+
+err:
+       free(config);
+       if (buf)
+               free(buf);
+       return NULL;
+}
+
+/** \ingroup desc
+ * Free a configuration descriptor obtained from
+ * libusb_get_active_config_descriptor() or libusb_get_config_descriptor().
+ * It is safe to call this function with a NULL config parameter, in which
+ * case the function simply returns.
+ *
+ * \param config the configuration descriptor to free
+ */
+API_EXPORTED void libusb_free_config_descriptor(
+       struct libusb_config_descriptor *config)
+{
+       if (!config)
+               return;
+
+       clear_configuration(config);
+       free(config);
 }
 
 /** \ingroup desc
index 07847bc..62ed0ea 100644 (file)
@@ -662,12 +662,16 @@ void libusb_exit(void);
 
 ssize_t libusb_get_device_list(libusb_device ***list);
 void libusb_free_device_list(libusb_device **list, int unref_devices);
-const struct libusb_device_descriptor *libusb_get_device_descriptor(
-       libusb_device *dev);
-const struct libusb_config_descriptor *libusb_get_config_descriptor(
-       libusb_device *dev);
 libusb_device *libusb_ref_device(libusb_device *dev);
 void libusb_unref_device(libusb_device *dev);
+
+int libusb_get_device_descriptor(libusb_device *dev,
+       struct libusb_device_descriptor *desc);
+struct libusb_config_descriptor *libusb_get_active_config_descriptor(
+       libusb_device *dev);
+struct libusb_config_descriptor *libusb_get_config_descriptor(
+       libusb_device *dev, uint8_t config);
+void libusb_free_config_descriptor(struct libusb_config_descriptor *config);
 uint8_t libusb_get_bus_number(libusb_device *dev);
 uint8_t libusb_get_device_address(libusb_device *dev);
 int libusb_get_max_packet_size(libusb_device *dev, unsigned char endpoint);
index ca44512..e596b86 100644 (file)
@@ -153,8 +153,6 @@ struct libusb_device {
 
        struct list_head list;
        unsigned long session_data;
-       struct libusb_device_descriptor desc;
-       struct libusb_config_descriptor *config;
        unsigned char os_priv[0];
 };
 
@@ -223,16 +221,13 @@ void usbi_io_init(void);
 
 struct libusb_device *usbi_alloc_device(unsigned long session_id);
 struct libusb_device *usbi_get_device_by_session_id(unsigned long session_id);
-int usbi_discover_device(struct libusb_device *dev);
+int usbi_sanitize_device(struct libusb_device *dev);
 
 void usbi_handle_transfer_completion(struct usbi_transfer *itransfer,
        enum libusb_transfer_status status);
 void usbi_handle_transfer_cancellation(struct usbi_transfer *transfer);
 
 int usbi_parse_descriptor(unsigned char *source, char *descriptor, void *dest);
-int usbi_parse_configuration(struct libusb_config_descriptor *config,
-               unsigned char *buffer);
-void usbi_clear_configurations(struct libusb_device *dev);
 
 /* polling */
 
@@ -274,12 +269,12 @@ struct usbi_os_backend {
        int (*open)(struct libusb_device_handle *handle);
        void (*close)(struct libusb_device_handle *handle);
 
-       int (*begin_discovery)(struct libusb_device *device, void **user_data);
        int (*get_device_descriptor)(struct libusb_device *device,
-               unsigned char *buffer, void *user_data);
-       int (*get_config_descriptor)(struct libusb_device *device, int index,
-               unsigned char *buffer, size_t len, void *user_data);
-       void (*end_discovery)(struct libusb_device *device, void *user_data);
+               unsigned char *buffer);
+       int (*get_active_config_descriptor)(struct libusb_device *device,
+               unsigned char *buffer, size_t len);
+       int (*get_config_descriptor)(struct libusb_device *device, uint8_t config,
+               unsigned char *buffer, size_t len);
 
        int (*set_configuration)(struct libusb_device_handle *handle, int config);
 
index 820100f..6efaaa1 100644 (file)
 static const char *usbfs_path = NULL;
 static int have_sysfs;
 
+/* sysfs vs usbfs:
+ * opening a usbfs node causes the device to be resumed, so we attempt to
+ * avoid this during enumeration.
+ *
+ * sysfs allows us to read the kernel's in-memory copies of device descriptors
+ * and so forth, avoiding the need to open the device:
+ *  - The binary "descriptors" file was added in 2.6.23.
+ *  - The "busnum" file was added in 2.6.22
+ *  - The "devnum" file has been present since pre-2.6.18
+ * Hence we check for the existance of a descriptors file to determine whether
+ * sysfs provides all the information we need. We effectively require 2.6.23
+ * in order to avoid waking suspended devices during enumeration.
+ */
+
 struct linux_device_priv {
+       /* FIXME remove this, infer from dev->busnum etc */
        char *nodepath;
-       char sysfs_dir[SYSFS_DIR_LENGTH];
+
+       union {
+               char sysfs_dir[SYSFS_DIR_LENGTH];
+               struct {
+                       unsigned char *dev_descriptor;
+                       unsigned char *config_descriptor;
+               };
+       };
 };
 
 struct linux_device_handle_priv {
@@ -149,71 +171,167 @@ static int op_init(void)
        return 0;
 }
 
-struct discovery_data {
-       int fd;
-};
+static int usbfs_get_device_descriptor(struct libusb_device *dev,
+       unsigned char *buffer)
+{
+       struct linux_device_priv *priv = __device_priv(dev);
 
-static int op_begin_discovery(struct libusb_device *dev, void **user_data)
+       /* return cached copy */
+       memcpy(buffer, priv->dev_descriptor, DEVICE_DESC_LENGTH);
+       return 0;
+}
+
+static int open_sysfs_descriptors(struct libusb_device *dev)
 {
        struct linux_device_priv *priv = __device_priv(dev);
-       struct discovery_data *ddata = malloc(sizeof(*ddata));
-       if (!ddata)
-               return LIBUSB_ERROR_NO_MEM;
+       char filename[PATH_MAX + 1];
+       int fd;
 
-       if (have_sysfs) {
-               char filename[PATH_MAX + 1];
-               snprintf(filename, PATH_MAX, "%s/%s/descriptors",
-                       SYSFS_DEVICE_PATH, priv->sysfs_dir);
-               ddata->fd = open(filename, O_RDONLY);
-       } else {
-               ddata->fd = open(priv->nodepath, O_RDONLY);
+       snprintf(filename, PATH_MAX, "%s/%s/descriptors", SYSFS_DEVICE_PATH,
+               priv->sysfs_dir);
+       fd = open(filename, O_RDONLY);
+       if (fd < 0) {
+               usbi_err("open '%s' failed, ret=%d errno=%d", filename, fd, errno);
+               return LIBUSB_ERROR_IO;
        }
 
-       if (ddata->fd < 0) {
-               usbi_dbg("open '%s' failed, ret=%d errno=%d", priv->nodepath,
-                       ddata->fd, errno);
-               free(ddata);
+       return fd;
+}
+
+static int sysfs_get_device_descriptor(struct libusb_device *dev,
+       unsigned char *buffer)
+{
+       int fd;
+       ssize_t r;
+
+       /* sysfs provides access to an in-memory copy of the device descriptor,
+        * so we use that rather than keeping our own copy */
+
+       fd = open_sysfs_descriptors(dev);
+       if (fd < 0)
+               return fd;
+
+       r = read(fd, buffer, DEVICE_DESC_LENGTH);;
+       close(fd);
+       if (r < 0) {
+               usbi_err("read failed, ret=%d errno=%d", fd, errno);
+               return LIBUSB_ERROR_IO;
+       } else if (r < DEVICE_DESC_LENGTH) {
+               usbi_err("short read %d/%d", r, DEVICE_DESC_LENGTH);
                return LIBUSB_ERROR_IO;
        }
 
-       *user_data = ddata;
        return 0;
 }
 
-static int op_get_device_descriptor(struct libusb_device *device,
-       unsigned char *buffer, void *user_data)
+static int op_get_device_descriptor(struct libusb_device *dev,
+       unsigned char *buffer)
+{
+       if (have_sysfs)
+               return sysfs_get_device_descriptor(dev, buffer);
+       else
+               return usbfs_get_device_descriptor(dev, buffer);
+}
+
+static int usbfs_get_active_config_descriptor(struct libusb_device *dev,
+       unsigned char *buffer, size_t len)
+{
+       struct linux_device_priv *priv = __device_priv(dev);
+       /* retrieve cached copy */
+       memcpy(buffer, priv->config_descriptor, len);
+       return 0;
+}
+
+static int sysfs_get_active_config_descriptor(struct libusb_device *dev,
+       unsigned char *buffer, size_t len)
 {
-       struct discovery_data *ddata = user_data;
-       int r = read(ddata->fd, buffer, DEVICE_DESC_LENGTH);
+       int fd;
+       ssize_t r;
+       off_t off;
+
+       /* sysfs provides access to an in-memory copy of the device descriptor,
+        * so we use that rather than keeping our own copy */
+
+       fd = open_sysfs_descriptors(dev);
+       if (fd < 0)
+               return fd;
+
+       off = lseek(fd, DEVICE_DESC_LENGTH, SEEK_SET);
+       if (off < 0) {
+               usbi_err("seek failed, ret=%d errno=%d", off, errno);
+               close(fd);
+               return LIBUSB_ERROR_IO;
+       }
+
+       r = read(fd, buffer, len);
+       close(fd);
        if (r < 0) {
-               usbi_err("read failed ret=%d errno=%d", r, errno);
+               usbi_err("read failed, ret=%d errno=%d", fd, errno);
                return LIBUSB_ERROR_IO;
-       } else if (r < DEVICE_DESC_LENGTH) {
-               usbi_err("short descriptor read %d/%d", r, DEVICE_DESC_LENGTH);
+       } else if (r < len) {
+               usbi_err("short read %d/%d", r, len);
                return LIBUSB_ERROR_IO;
        }
 
        return 0;
 }
 
-static int op_get_config_descriptor(struct libusb_device *device,
-       int config_index, unsigned char *buffer, size_t len, void *user_data)
+static int op_get_active_config_descriptor(struct libusb_device *dev,
+       unsigned char *buffer, size_t len)
+{
+       if (have_sysfs)
+               return sysfs_get_active_config_descriptor(dev, buffer, len);
+       else
+               return usbfs_get_active_config_descriptor(dev, buffer, len);
+}
+
+
+/* takes a usbfs fd, attempts to find the requested config and copy a certain
+ * amount of it into an output buffer. a bConfigurationValue of -1 indicates
+ * that the first config should be retreived. */
+static int get_config_descriptor(int fd, int bConfigurationValue,
+       unsigned char *buffer, size_t len)
 {
-       struct discovery_data *ddata = user_data;
+       unsigned char tmp[8];
+       uint8_t num_configurations;
        off_t off;
        ssize_t r;
-       int fd = ddata->fd;
 
-       off = lseek(fd, DEVICE_DESC_LENGTH, SEEK_SET);
+       if (bConfigurationValue == -1) {
+               /* read first configuration */
+               off = lseek(fd, DEVICE_DESC_LENGTH, SEEK_SET);
+               if (off < 0) {
+                       usbi_err("seek failed, ret=%d errno=%d", off, errno);
+                       return LIBUSB_ERROR_IO;
+               }
+               r = read(fd, buffer, len);
+               if (r < 0) {
+                       usbi_err("read failed ret=%d errno=%d", r, errno);
+                       return LIBUSB_ERROR_IO;
+               } else if (r < len) {
+                       usbi_err("short output read %d/%d", r, len);
+                       return LIBUSB_ERROR_IO;
+               }
+               return 0;
+       }
+
+       /* seek to last byte of device descriptor to determine number of
+        * configurations */
+       off = lseek(fd, DEVICE_DESC_LENGTH - 1, SEEK_SET);
        if (off < 0) {
-               usbi_err("seek failed ret=%d errno=%d", off, errno);
+               usbi_err("seek failed, ret=%d errno=%d", off, errno);
+               return LIBUSB_ERROR_IO;
+       }
+
+       r = read(fd, &num_configurations, 1);
+       if (r < 0) {
+               usbi_err("read num_configurations failed, ret=%d errno=%d", off, errno);
                return LIBUSB_ERROR_IO;
        }
 
        /* might need to skip some configuration descriptors to reach the
-        * requested index */
-       while (config_index > 0) {
-               unsigned char tmp[8];
+        * requested configuration */
+       while (num_configurations) {
                struct libusb_config_descriptor config;
 
                /* read first 8 bytes of descriptor */
@@ -225,8 +343,10 @@ static int op_get_config_descriptor(struct libusb_device *device,
                        usbi_err("short descriptor read %d/%d", r, sizeof(tmp));
                        return LIBUSB_ERROR_IO;
                }
-       
-               usbi_parse_descriptor(buffer, "bbw", &config);
+
+               usbi_parse_descriptor(tmp, "bbwbb", &config);
+               if (config.bConfigurationValue == bConfigurationValue)
+                       break;
 
                /* seek forward to end of config */
                off = lseek(fd, config.wTotalLength - sizeof(tmp), SEEK_CUR);
@@ -235,27 +355,80 @@ static int op_get_config_descriptor(struct libusb_device *device,
                        return LIBUSB_ERROR_IO;
                }
 
-               config_index--;
+               num_configurations--;
        }
 
-       /* read the actual config */
-       r = read(fd, buffer, len);
+       if (num_configurations == 0)
+               return LIBUSB_ERROR_NOT_FOUND;
+
+       /* copy config-so-far */
+       memcpy(buffer, tmp, sizeof(tmp));
+
+       /* read the rest of the descriptor */
+       r = read(fd, buffer + sizeof(tmp), len - sizeof(tmp));
        if (r < 0) {
                usbi_err("read failed ret=%d errno=%d", r, errno);
                return LIBUSB_ERROR_IO;
-       } else if (r < len) {
-               usbi_err("short descriptor read %d/%d", r, len);
+       } else if (r < (len - sizeof(tmp))) {
+               usbi_err("short output read %d/%d", r, len);
                return LIBUSB_ERROR_IO;
        }
 
        return 0;
 }
 
-static void op_end_discovery(struct libusb_device *device, void *user_data)
+static int op_get_config_descriptor(struct libusb_device *dev, uint8_t config,
+       unsigned char *buffer, size_t len)
 {
-       struct discovery_data *ddata = user_data;
-       close(ddata->fd);
-       free(ddata);
+       struct linux_device_priv *priv = __device_priv(dev);
+       int fd;
+       int r;
+
+       /* always read from usbfs: sysfs only has the active descriptor
+        * this will involve waking the device up, but oh well! */
+
+       fd = open(priv->nodepath, O_RDONLY);
+       if (fd < 0) {
+               usbi_err("open '%s' failed, ret=%d errno=%d",
+                       priv->nodepath, fd, errno);
+               return LIBUSB_ERROR_IO;
+       }
+
+       r = get_config_descriptor(fd, config, buffer, len);
+       close(fd);
+       return r;
+}
+
+static int cache_active_config(struct libusb_device *dev, int fd,
+       int active_config)
+{
+       struct linux_device_priv *priv = __device_priv(dev);
+       struct libusb_config_descriptor config;
+       unsigned char tmp[8];
+       unsigned char *buf;
+       int r;
+
+       r = get_config_descriptor(fd, active_config, tmp, sizeof(tmp));
+       if (r < 0) {
+               usbi_err("first read error %d", r);
+               return r;
+       }
+
+       usbi_parse_descriptor(tmp, "bbw", &config);
+       buf = malloc(config.wTotalLength);
+       if (!buf)
+               return LIBUSB_ERROR_NO_MEM;
+
+       r = get_config_descriptor(fd, active_config, buf, config.wTotalLength);
+       if (r < 0) {
+               free(buf);
+               return r;
+       }
+
+       if (priv->config_descriptor)
+               free(priv->config_descriptor);
+       priv->config_descriptor = buf;
+       return 0;
 }
 
 static int initialize_device(struct libusb_device *dev, uint8_t busnum,
@@ -271,6 +444,82 @@ static int initialize_device(struct libusb_device *dev, uint8_t busnum,
        snprintf(path, PATH_MAX, "%s/%03d/%03d", usbfs_path, busnum, devaddr);
        usbi_dbg("%s", path);
 
+       if (!have_sysfs) {
+               /* cache device descriptor in memory so that we can retrieve it later
+                * without waking the device up (op_get_device_descriptor) */
+               unsigned char *dev_buf = malloc(DEVICE_DESC_LENGTH);
+               int fd;
+               ssize_t r;
+               int tmp;
+               int active_config = 0;
+
+               struct usbfs_ctrltransfer ctrl = {
+                       .bmRequestType = LIBUSB_ENDPOINT_IN,
+                       .bRequest = LIBUSB_REQUEST_GET_CONFIGURATION,
+                       .wValue = 0,
+                       .wIndex = 0,
+                       .wLength = 1,
+                       .timeout = 1000,
+                       .data = &active_config
+               };
+               
+               priv->dev_descriptor = NULL;
+               priv->config_descriptor = NULL;
+               if (!dev_buf)
+                       return LIBUSB_ERROR_NO_MEM;
+
+               fd = open(path, O_RDWR);
+               if (fd < 0 && errno == EACCES) {
+                       usbi_dbg("sysfs unavailable and read-only access to usbfs --> "
+                               "cannot determine which configuration is active");
+                       fd = open(path, O_RDONLY);
+                       /* if we only have read-only access to the device, we cannot
+                        * send a control message to determine the active config. just
+                        * assume the first one is active. */
+                       active_config = -1;
+               }
+
+               if (fd < 0) {
+                       usbi_err("open failed, ret=%d errno=%d", fd, errno);
+                       free(dev_buf);
+                       return LIBUSB_ERROR_IO;
+               }
+
+               r = read(fd, dev_buf, DEVICE_DESC_LENGTH);
+               if (r < 0) {
+                       usbi_err("read descriptor failed ret=%d errno=%d", fd, errno);
+                       free(dev_buf);
+                       close(fd);
+                       return LIBUSB_ERROR_IO;
+               } else if (r < DEVICE_DESC_LENGTH) {
+                       usbi_err("short descriptor read (%d)", r);
+                       free(dev_buf);
+                       close(fd);
+                       return LIBUSB_ERROR_IO;
+               }
+
+               if (active_config == 0) {
+                       /* determine active configuration and cache the descriptor */
+                       tmp = ioctl(fd, IOCTL_USBFS_CONTROL, &ctrl);
+                       if (tmp < 0) {
+                               usbi_err("get_configuration failed ret=%d errno=%d", tmp, errno);
+                               free(dev_buf);
+                               close(fd);
+                               return LIBUSB_ERROR_IO;
+                       }
+               }
+
+               r = cache_active_config(dev, fd, active_config);
+               if (r < 0) {
+                       free(dev_buf);
+                       close(fd);
+                       return r;
+               }
+
+               priv->dev_descriptor = dev_buf;
+               close(fd);
+       }
+
        if (sysfs_dir)
                strncpy(priv->sysfs_dir, sysfs_dir, SYSFS_DIR_LENGTH);
 
@@ -311,7 +560,7 @@ static int enumerate_device(struct discovered_devs **_discdevs,
                r = initialize_device(dev, busnum, devaddr, sysfs_dir);
                if (r < 0)
                        goto out;
-               r = usbi_discover_device(dev);
+               r = usbi_sanitize_device(dev);
                if (r < 0)
                        goto out;
        }
@@ -542,6 +791,14 @@ static int op_set_configuration(struct libusb_device_handle *handle, int config)
                usbi_err("failed, error %d errno %d", r, errno);
                return LIBUSB_ERROR_OTHER;
        }
+
+       if (!have_sysfs) {
+               /* update our cached active config descriptor */
+               r = cache_active_config(handle->dev, fd, config);
+               if (r < 0)
+                       usbi_warn("failed to update cached config descriptor, error %d", r);
+       }
+
        return 0;
 }
 
@@ -672,9 +929,16 @@ static int op_detach_kernel_driver(struct libusb_device_handle *handle,
 
 static void op_destroy_device(struct libusb_device *dev)
 {
-       unsigned char *nodepath = __device_priv(dev)->nodepath;
-       if (nodepath)
-               free(nodepath);
+       struct linux_device_priv *priv = __device_priv(dev);
+       if (priv->nodepath)
+               free(priv->nodepath);
+
+       if (!have_sysfs) {
+               if (priv->dev_descriptor)
+                       free(priv->dev_descriptor);
+               if (priv->config_descriptor)
+                       free(priv->config_descriptor);
+       }
 }
 
 static void free_iso_urbs(struct linux_transfer_priv *tpriv)
@@ -1321,10 +1585,9 @@ const struct usbi_os_backend linux_usbfs_backend = {
        .init = op_init,
        .exit = NULL,
        .get_device_list = op_get_device_list,
-       .begin_discovery = op_begin_discovery,
        .get_device_descriptor = op_get_device_descriptor,
+       .get_active_config_descriptor = op_get_active_config_descriptor,
        .get_config_descriptor = op_get_config_descriptor,
-       .end_discovery = op_end_discovery,
 
        .open = op_open,
        .close = op_close,