diff options
author | Daniel Drake <dsd@gentoo.org> | 2008-05-09 14:34:31 +0100 |
---|---|---|
committer | Daniel Drake <dsd@gentoo.org> | 2008-05-09 19:33:33 +0100 |
commit | fe4adcc99e30115204ab832ad3e0170c9aca7629 (patch) | |
tree | c439121d3e729bb87f239c4d5b0da205b741174d | |
parent | 5741bfe01a2481b8c3830c80edc3637bf62a7e16 (diff) | |
download | libusb-fe4adcc99e30115204ab832ad3e0170c9aca7629.tar.gz |
Rework configuration handling
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.
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | examples/lsusb.c | 11 | ||||
-rw-r--r-- | libusb/core.c | 133 | ||||
-rw-r--r-- | libusb/descriptor.c | 153 | ||||
-rw-r--r-- | libusb/libusb.h | 12 | ||||
-rw-r--r-- | libusb/libusbi.h | 17 | ||||
-rw-r--r-- | libusb/os/linux_usbfs.c | 369 |
7 files changed, 513 insertions, 183 deletions
@@ -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 ========================================= diff --git a/examples/lsusb.c b/examples/lsusb.c index 509e0bd..5a83020 100644 --- a/examples/lsusb.c +++ b/examples/lsusb.c @@ -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)); } } diff --git a/libusb/core.c b/libusb/core.c index f83305e..43491ad 100644 --- a/libusb/core.c +++ b/libusb/core.c @@ -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; } diff --git a/libusb/descriptor.c b/libusb/descriptor.c index 822306d..e07711a 100644 --- a/libusb/descriptor.c +++ b/libusb/descriptor.c @@ -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 diff --git a/libusb/libusb.h b/libusb/libusb.h index 07847bc..62ed0ea 100644 --- a/libusb/libusb.h +++ b/libusb/libusb.h @@ -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); diff --git a/libusb/libusbi.h b/libusb/libusbi.h index ca44512..e596b86 100644 --- a/libusb/libusbi.h +++ b/libusb/libusbi.h @@ -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); diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index 820100f..6efaaa1 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -41,9 +41,31 @@ 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, |