diff options
author | Hans de Goede <hdegoede@redhat.com> | 2013-06-13 21:21:14 +0200 |
---|---|---|
committer | Hans de Goede <hdegoede@redhat.com> | 2013-06-19 11:04:14 +0200 |
commit | fc51f19341af70508b94c06ff763842b396ea72e (patch) | |
tree | 0cb2d75131893603a3e4c0c456f4cefe060599ca | |
parent | 6b3931526db981e3a298a226e5d0b4e20c04913d (diff) | |
download | libusb-fc51f19341af70508b94c06ff763842b396ea72e.tar.gz |
libusb: Add auto-detach-kernel-driver functionality
Add auto-detach-kernel-driver functionality, and a
libusb_set_auto_detach_kernel_driver() function.
Note that I went with a libusb_set_auto_detach_kernel_driver() function,
rather then with a libusb_enable_auto_detach_kernel_driver(), so that apps
can also disable it again. This is necessary to handle 2 corner cases:
1) When an app wants to do a libusb_set_configuration after claiming 1 or
more interfaces, it needs to first release the interface(s), and in this
case libusb_release_interface() should *not* (re-)attach the kernel driver
2) Some usb classes use multiple interfaces for one function, ie usb-audio
devices do this. In this case attaching the driver will fail until all
interfaces are released, so the app should first release all interfaces, and
only then (re-)attach the kernel driver.
auto-detach-kernel-driver functionality is still useful for these apps, since
doing libusb_detach_kernel_driver() followed by libusb_claim_interface() in
2 separate calls is inherently racy, but they need to be able to disable the
auto-detach functionality before releasing interfaces to be able to properly
handle the 2 described corner cases.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
-rw-r--r-- | libusb/core.c | 50 | ||||
-rw-r--r-- | libusb/libusb-1.0.def | 2 | ||||
-rw-r--r-- | libusb/libusb.h | 2 | ||||
-rw-r--r-- | libusb/libusbi.h | 1 | ||||
-rw-r--r-- | libusb/os/linux_usbfs.c | 42 | ||||
-rw-r--r-- | libusb/version_nano.h | 2 |
6 files changed, 93 insertions, 6 deletions
diff --git a/libusb/core.c b/libusb/core.c index 1c3748d..c902747 100644 --- a/libusb/core.c +++ b/libusb/core.c @@ -1361,7 +1361,14 @@ int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev, * endpoint halts cleared, toggles reset). * * You cannot change/reset configuration if your application has claimed - * interfaces - you should free them with libusb_release_interface() first. + * interfaces. It is advised to set the desired configuration before claiming + * interfaces. + * + * Alternatively you can call libusb_release_interface() first. Note if you + * do things this way you must ensure that auto_detach_kernel_driver for + * <tt>dev</tt> is 0, otherwise the kernel driver will be re-attached when you + * release the interface(s). + * * You cannot change/reset configuration if other applications or drivers have * claimed interfaces. * @@ -1383,6 +1390,7 @@ int API_EXPORTED libusb_get_configuration(libusb_device_handle *dev, * \returns LIBUSB_ERROR_BUSY if interfaces are currently claimed * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected * \returns another LIBUSB_ERROR code on other failure + * \see libusb_set_auto_detach_kernel_driver() */ int API_EXPORTED libusb_set_configuration(libusb_device_handle *dev, int configuration) @@ -1398,6 +1406,9 @@ int API_EXPORTED libusb_set_configuration(libusb_device_handle *dev, * It is legal to attempt to claim an already-claimed interface, in which * case libusbx just returns 0 without doing anything. * + * If auto_detach_kernel_driver is set to 1 for <tt>dev</tt>, the kernel driver + * will be detached if necessary, on failure the detach error is returned. + * * Claiming of interfaces is a purely logical operation; it does not cause * any requests to be sent over the bus. Interface claiming is used to * instruct the underlying operating system that your application wishes @@ -1414,6 +1425,7 @@ int API_EXPORTED libusb_set_configuration(libusb_device_handle *dev, * interface * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected * \returns a LIBUSB_ERROR code on other failure + * \see libusb_set_auto_detach_kernel_driver() */ int API_EXPORTED libusb_claim_interface(libusb_device_handle *dev, int interface_number) @@ -1447,6 +1459,9 @@ out: * This is a blocking function. A SET_INTERFACE control request will be sent * to the device, resetting interface state to the first alternate setting. * + * If auto_detach_kernel_driver is set to 1 for <tt>dev</tt>, the kernel + * driver will be re-attached after releasing the interface. + * * \param dev a device handle * \param interface_number the <tt>bInterfaceNumber</tt> of the * previously-claimed interface @@ -1454,6 +1469,7 @@ out: * \returns LIBUSB_ERROR_NOT_FOUND if the interface was not claimed * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected * \returns another LIBUSB_ERROR code on other failure + * \see libusb_set_auto_detach_kernel_driver() */ int API_EXPORTED libusb_release_interface(libusb_device_handle *dev, int interface_number) @@ -1678,6 +1694,38 @@ int API_EXPORTED libusb_attach_kernel_driver(libusb_device_handle *dev, return LIBUSB_ERROR_NOT_SUPPORTED; } +/** \ingroup dev + * Enable/disable libusbx's automatic kernel driver detachment. When this is + * enabled libusbx will automatically detach the kernel driver on an interface + * when claiming the interface, and attach it when releasing the interface. + * + * Automatic kernel driver detachment is disabled on newly opened device + * handles by default. + * + * On platforms which do not have LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER + * this function will return LIBUSB_ERROR_NOT_SUPPORTED, and libusbx will + * continue as if this function was never called. + * + * \param dev a device handle + * \param enable whether to enable or disable auto kernel driver detachment + * + * \returns LIBUSB_SUCCESS on success + * \returns LIBUSB_ERROR_NOT_SUPPORTED on platforms where the functionality + * is not available + * \see libusb_claim_interface() + * \see libusb_release_interface() + * \see libusb_set_configuration() + */ +int API_EXPORTED libusb_set_auto_detach_kernel_driver( + libusb_device_handle *dev, int enable) +{ + if (!(usbi_backend->caps & USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER)) + return LIBUSB_ERROR_NOT_SUPPORTED; + + dev->auto_detach_kernel_driver = enable; + return LIBUSB_SUCCESS; +} + /** \ingroup lib * Set log message verbosity. * diff --git a/libusb/libusb-1.0.def b/libusb/libusb-1.0.def index e6c46fd..cb0e32a 100644 --- a/libusb/libusb-1.0.def +++ b/libusb/libusb-1.0.def @@ -130,6 +130,8 @@ EXPORTS libusb_release_interface@8 = libusb_release_interface libusb_reset_device libusb_reset_device@4 = libusb_reset_device + libusb_set_auto_detach_kernel_driver + libusb_set_auto_detach_kernel_driver@8 = libusb_set_auto_detach_kernel_driver libusb_set_configuration libusb_set_configuration@8 = libusb_set_configuration libusb_set_debug diff --git a/libusb/libusb.h b/libusb/libusb.h index a56a687..d7cc40f 100644 --- a/libusb/libusb.h +++ b/libusb/libusb.h @@ -1390,6 +1390,8 @@ int LIBUSB_CALL libusb_detach_kernel_driver(libusb_device_handle *dev, int interface_number); int LIBUSB_CALL libusb_attach_kernel_driver(libusb_device_handle *dev, int interface_number); +int LIBUSB_CALL libusb_set_auto_detach_kernel_driver( + libusb_device_handle *dev, int enable); /* async I/O */ diff --git a/libusb/libusbi.h b/libusb/libusbi.h index d5e5c59..c9b402d 100644 --- a/libusb/libusbi.h +++ b/libusb/libusbi.h @@ -317,6 +317,7 @@ struct libusb_device_handle { struct list_head list; struct libusb_device *dev; + int auto_detach_kernel_driver; unsigned char os_priv #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) [] /* valid C99 code */ diff --git a/libusb/os/linux_usbfs.c b/libusb/os/linux_usbfs.c index a27ed44..e4ff79d 100644 --- a/libusb/os/linux_usbfs.c +++ b/libusb/os/linux_usbfs.c @@ -1317,7 +1317,7 @@ static int op_set_configuration(struct libusb_device_handle *handle, int config) return LIBUSB_SUCCESS; } -static int op_claim_interface(struct libusb_device_handle *handle, int iface) +static int claim_interface(struct libusb_device_handle *handle, int iface) { int fd = _device_handle_priv(handle)->fd; int r = ioctl(fd, IOCTL_USBFS_CLAIMINTF, &iface); @@ -1336,7 +1336,7 @@ static int op_claim_interface(struct libusb_device_handle *handle, int iface) return 0; } -static int op_release_interface(struct libusb_device_handle *handle, int iface) +static int release_interface(struct libusb_device_handle *handle, int iface) { int fd = _device_handle_priv(handle)->fd; int r = ioctl(fd, IOCTL_USBFS_RELEASEINTF, &iface); @@ -1407,7 +1407,7 @@ static int op_reset_device(struct libusb_device_handle *handle) getting bound to the in kernel driver if any). */ for (i = 0; i < USB_MAXINTERFACES; i++) { if (handle->claimed_interfaces & (1L << i)) { - op_release_interface(handle, i); + release_interface(handle, i); } } @@ -1428,7 +1428,7 @@ static int op_reset_device(struct libusb_device_handle *handle) /* And re-claim any interfaces which were claimed before the reset */ for (i = 0; i < USB_MAXINTERFACES; i++) { if (handle->claimed_interfaces & (1L << i)) { - r = op_claim_interface(handle, i); + r = claim_interface(handle, i); if (r) { usbi_warn(HANDLE_CTX(handle), "failed to re-claim interface %d after reset", i); @@ -1530,6 +1530,40 @@ static int op_attach_kernel_driver(struct libusb_device_handle *handle, return 0; } +static int detach_kernel_driver_and_claim(struct libusb_device_handle *handle, + int interface) +{ + int r; + + r = op_detach_kernel_driver(handle, interface); + if (r != 0 && r != LIBUSB_ERROR_NOT_FOUND) + return r; + + return claim_interface(handle, interface); +} + +static int op_claim_interface(struct libusb_device_handle *handle, int iface) +{ + if (handle->auto_detach_kernel_driver) + return detach_kernel_driver_and_claim(handle, iface); + else + return claim_interface(handle, iface); +} + +static int op_release_interface(struct libusb_device_handle *handle, int iface) +{ + int r; + + r = release_interface(handle, iface); + if (r) + return r; + + if (handle->auto_detach_kernel_driver) + op_attach_kernel_driver(handle, iface); + + return 0; +} + static void op_destroy_device(struct libusb_device *dev) { struct linux_device_priv *priv = _device_priv(dev); diff --git a/libusb/version_nano.h b/libusb/version_nano.h index 5a98334..60306bf 100644 --- a/libusb/version_nano.h +++ b/libusb/version_nano.h @@ -1 +1 @@ -#define LIBUSB_NANO 10741 +#define LIBUSB_NANO 10742 |