Refine libusb_set_configuration() semantics
authorDaniel Drake <dsd@gentoo.org>
Thu, 29 May 2008 11:35:01 +0000 (12:35 +0100)
committerDaniel Drake <dsd@gentoo.org>
Thu, 29 May 2008 11:35:01 +0000 (12:35 +0100)
Applications will generally want to set a configuration before claiming
interfaces. The problem is that the interface may already be set, and
someone else may have claimed an interface (meaning that all calls to
set_configuration will fail, even if it's for the same configuration).

There are now 2 options:
1. Use the new libusb_get_configuration() to determine active
   configuration before calling libusb_set_configuration()
2. Or just call libusb_set_configuration() as usual, which will do
   nothing if that configuration is already active.

libusb/core.c
libusb/libusbi.h
libusb/os/linux_usbfs.c

index 292e3ab..a4668f0 100644 (file)
@@ -764,6 +764,57 @@ API_EXPORTED libusb_device *libusb_get_device(libusb_device_handle *dev_handle)
 }
 
 /** \ingroup dev
+ * Determine the bConfigurationValue of the currently active configuration.
+ *
+ * You could formulate your own control request to obtain this information,
+ * but this function has the advantage that it may be able to retrieve the
+ * information from operating system caches (no I/O involved).
+ *
+ * If the OS does not cache this information, then this function will block
+ * while a control transfer is submitted to retrieve the information.
+ *
+ * This function will return a value of 0 in the <tt>config</tt> output
+ * parameter if the device is in unconfigured state.
+ *
+ * \param dev a device handle
+ * \param config output location for the bConfigurationValue of the active
+ * configuration (only valid for return code 0)
+ * \returns 0 on success
+ * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
+ * \returns another LIBUSB_ERROR code on other failure
+ */
+API_EXPORTED int libusb_get_configuration(libusb_device_handle *dev,
+       int *config)
+{
+       int r = LIBUSB_ERROR_NOT_SUPPORTED;
+
+       usbi_dbg("");
+       if (usbi_backend->get_configuration)
+               r = usbi_backend->get_configuration(dev, config);
+
+       if (r == LIBUSB_ERROR_NOT_SUPPORTED) {
+               uint8_t tmp = 0;
+               usbi_dbg("falling back to control message");
+               r = libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN,
+                       LIBUSB_REQUEST_GET_CONFIGURATION, 0, 0, &tmp, 1, 1000);
+               if (r == 0) {
+                       usbi_err("zero bytes returned in ctrl transfer?");
+                       r = LIBUSB_ERROR_IO;
+               } else if (r == 1) {
+                       r = 0;
+                       *config = tmp;
+               } else {
+                       usbi_dbg("control failed, error %d", r);
+               }
+       }
+
+       if (r == 0)
+               usbi_dbg("active config %d", *config);
+
+       return r;
+}
+
+/** \ingroup dev
  * Set the active configuration for a device. The operating system may have
  * already set an active configuration on the device, but for portability
  * reasons you should use this function to select the configuration you want
@@ -771,12 +822,27 @@ API_EXPORTED libusb_device *libusb_get_device(libusb_device_handle *dev_handle)
  *
  * If you wish to change to another configuration at some later time, you
  * must release all claimed interfaces using libusb_release_interface() before
- * setting a new active configuration.
+ * setting a new active configuration. Also, consider that other applications
+ * or drivers may have claimed interfaces, in which case you are unable to
+ * change the configuration.
  *
  * A configuration value of -1 will put the device in unconfigured state.
  * The USB specifications state that a configuration value of 0 does this,
  * however buggy devices exist which actually have a configuration 0.
  *
+ * This function checks the current active configuration before setting the
+ * new one. If the requested configuration is already active, this function
+ * does nothing more.
+ *
+ * This function is inherently racy: there is a small chance that someone may
+ * change the configuration after libusb has determined the active
+ * configuration but before the new one has been applied (or not applied, if
+ * libusb thinks the specified configuration is already active). After changing
+ * configuration, you may choose to claim an interface and then call
+ * libusb_get_configuration() to ensure that the requested change actually took
+ * place. The fact that you have now claimed an interface means that nobody
+ * else can change the configuration.
+ *
  * You should always use this function rather than formulating your own
  * SET_CONFIGURATION control request. This is because the underlying operating
  * system needs to know when such changes happen.
@@ -786,7 +852,8 @@ API_EXPORTED libusb_device *libusb_get_device(libusb_device_handle *dev_handle)
  * \param dev a device handle
  * \param configuration the bConfigurationValue of the configuration you
  * wish to activate, or -1 if you wish to put the device in unconfigured state
- * \returns 0 on success
+ * \returns 1 if the configuration was changed
+ * \returns 0 if the configuration was already active
  * \returns LIBUSB_ERROR_NOT_FOUND if the requested configuration does not exist
  * \returns LIBUSB_ERROR_BUSY if interfaces are currently claimed
  * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
@@ -795,8 +862,24 @@ API_EXPORTED libusb_device *libusb_get_device(libusb_device_handle *dev_handle)
 API_EXPORTED int libusb_set_configuration(libusb_device_handle *dev,
        int configuration)
 {
+       int r;
+       int active;
+
        usbi_dbg("configuration %d", configuration);
-       return usbi_backend->set_configuration(dev, configuration);
+       r = libusb_get_configuration(dev, &active);
+       if (r < 0)
+               return r;
+       if (active == 0)
+               active = -1;
+       if (active == configuration) {
+               usbi_dbg("already active");
+               return 0;
+       }
+
+       r = usbi_backend->set_configuration(dev, configuration);
+       if (r == 0)
+               r = 1;
+       return r;
 }
 
 /** \ingroup dev
index 41c8097..5c00a63 100644 (file)
@@ -436,6 +436,25 @@ struct usbi_os_backend {
                uint8_t config_index, unsigned char *buffer, size_t len,
                int *host_endian);
 
+       /* Get the bConfigurationValue for the active configuration for a device.
+        * Optional. This should only be implemented if you can retrieve it from
+        * cache (don't generate I/O).
+        *
+        * If you cannot retrieve this from cache, either do not implement this
+        * function, or return LIBUSB_ERROR_NOT_SUPPORTED. This will cause
+        * libusb to retrieve the information through a standard control transfer.
+        *
+        * This function must be non-blocking.
+        * Return:
+        * - 0 on success
+        * - LIBUSB_ERROR_NO_DEVICE if the device has been disconnected since it
+        *   was opened
+        * - LIBUSB_ERROR_NOT_SUPPORTED if the value cannot be retrieved without
+        *   blocking
+        * - another LIBUSB_ERROR code on other failure.
+        */
+       int (*get_configuration)(struct libusb_device_handle *handle, int *config);
+
        /* Set the active configuration for a device.
         *
         * A configuration value of -1 should put the device in unconfigured state.
index 7dc6b6c..2e9e0e2 100644 (file)
@@ -904,6 +904,20 @@ static void op_close(struct libusb_device_handle *dev_handle)
        close(fd);
 }
 
+static int op_get_configuration(struct libusb_device_handle *handle,
+       int *config)
+{
+       int r;
+       if (sysfs_can_relate_devices != 1)
+               return LIBUSB_ERROR_NOT_SUPPORTED;
+
+       r = sysfs_get_active_config(handle->dev, config);
+       if (*config == -1)
+               *config = 0;
+
+       return 0;
+}
+
 static int op_set_configuration(struct libusb_device_handle *handle, int config)
 {
        struct linux_device_priv *priv = __device_priv(handle->dev);
@@ -1827,6 +1841,7 @@ const struct usbi_os_backend linux_usbfs_backend = {
 
        .open = op_open,
        .close = op_close,
+       .get_configuration = op_get_configuration,
        .set_configuration = op_set_configuration,
        .claim_interface = op_claim_interface,
        .release_interface = op_release_interface,