aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNathan Hjelm <hjelmn@me.com>2012-11-29 14:23:26 -0700
committerHans de Goede <hdegoede@redhat.com>2013-05-15 17:28:06 +0200
commit7801ff94fa6e49fe98433eccc7f2e461590a6f7c (patch)
tree7d75d9c8f40471b45bbd77e8352ff602d5497c6f
parent29480089519f0b52da7697c34a3bbb67d3e74a3f (diff)
downloadandroid_external_libusbx-7801ff94fa6e49fe98433eccc7f2e461590a6f7c.tar.gz
android_external_libusbx-7801ff94fa6e49fe98433eccc7f2e461590a6f7c.tar.bz2
android_external_libusbx-7801ff94fa6e49fe98433eccc7f2e461590a6f7c.zip
Add hotplug support.
The internal API is changing as follows: - Adding two new functions. usbi_connect_device, and usbi_disconnect_device. Backends must call these functions to add them to the context's device list at one of two places: initial enumeration (done at init), and on device attach and removal. These functions need to be called once per context. - Backends that support hotplug should not provide a get_device_list funtion. This function is now deprecated and will likely be removed once all backends support hotplug. The external API is changing as follows: - Two new functions have been added to register and deregister callbacks for hotplug notification: libusb_hotplug_register_callback(), libusb_hotplug_deregister_callback(). Hotplug callbacks are called by libusb_handle_events(). Details of the new API can be found in libusb.h. - A new capability check has been added to check for hotplug support. See LIBUSB_CAP_HAS_HOTPLUG. Aa suggested by Xiaofan add new example has been added to show how to use the new external hotplug API. See examples/hotplugtest.c. Signed-off-by: Hans de Goede <hdegoede@redhat.com>
-rw-r--r--.gitignore1
-rw-r--r--doc/doxygen.cfg.in2
-rw-r--r--examples/Makefile.am2
-rw-r--r--examples/hotplugtest.c96
-rw-r--r--libusb/Makefile.am6
-rw-r--r--libusb/core.c147
-rw-r--r--libusb/hotplug.c302
-rw-r--r--libusb/hotplug.h81
-rw-r--r--libusb/io.c55
-rw-r--r--libusb/libusb.h103
-rw-r--r--libusb/libusbi.h9
-rw-r--r--libusb/version.h2
-rw-r--r--libusb/version_nano.h2
13 files changed, 772 insertions, 36 deletions
diff --git a/.gitignore b/.gitignore
index bf7ecb8..76f2613 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ examples/xusb
examples/dpfp
examples/dpfp_threaded
examples/fxload
+examples/hotplugtest
tests/stress
*.exe
*.pc
diff --git a/doc/doxygen.cfg.in b/doc/doxygen.cfg.in
index 5bb13da..05f984a 100644
--- a/doc/doxygen.cfg.in
+++ b/doc/doxygen.cfg.in
@@ -505,7 +505,7 @@ RECURSIVE = NO
# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
-EXCLUDE = @top_srcdir@/libusb/libusbi.h
+EXCLUDE = @top_srcdir@/libusb/libusbi.h @top_srcdir@/libusb/hotplug.h
# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
# directories that are symbolic links (a Unix filesystem feature) are excluded
diff --git a/examples/Makefile.am b/examples/Makefile.am
index a28a300..380e13f 100644
--- a/examples/Makefile.am
+++ b/examples/Makefile.am
@@ -1,7 +1,7 @@
AM_CPPFLAGS = -I$(top_srcdir)/libusb
LDADD = ../libusb/libusb-1.0.la
-noinst_PROGRAMS = listdevs xusb fxload
+noinst_PROGRAMS = listdevs xusb fxload hotplugtest
if HAVE_SIGACTION
noinst_PROGRAMS += dpfp
diff --git a/examples/hotplugtest.c b/examples/hotplugtest.c
new file mode 100644
index 0000000..6f16827
--- /dev/null
+++ b/examples/hotplugtest.c
@@ -0,0 +1,96 @@
+/*
+ * libusb example program for hotplug API
+ * Copyright © 2012-2013 Nathan Hjelm <hjelmn@mac.ccom>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "libusb.h"
+
+int done = 0;
+libusb_device_handle *handle;
+
+static int LIBUSB_CALL hotplug_callback(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data)
+{
+ struct libusb_device_descriptor desc;
+ int rc;
+
+ rc = libusb_get_device_descriptor(dev, &desc);
+ if (LIBUSB_SUCCESS != rc) {
+ fprintf (stderr, "Error getting device descriptor\n");
+ }
+
+ printf ("Device attached: %04x:%04x\n", desc.idVendor, desc.idProduct);
+
+ libusb_open (dev, &handle);
+
+ done++;
+
+ return 0;
+}
+
+static int LIBUSB_CALL hotplug_callback_detach(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data)
+{
+ printf ("Device detached\n");
+
+ libusb_close (handle);
+
+ done++;
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ libusb_hotplug_callback_handle hp[2];
+ int product_id, vendor_id, class_id;
+ int rc;
+
+ vendor_id = (argc > 1) ? strtol (argv[1], NULL, 0) : 0x045a;
+ product_id = (argc > 2) ? strtol (argv[2], NULL, 0) : 0x5005;
+ class_id = (argc > 3) ? strtol (argv[3], NULL, 0) : LIBUSB_HOTPLUG_MATCH_ANY;
+
+ libusb_init (NULL);
+
+ if (!libusb_has_capability (LIBUSB_CAP_HAS_HOTPLUG)) {
+ printf ("Hotplug capabilites are not supported on this platform\n");
+ libusb_exit (NULL);
+ return EXIT_FAILURE;
+ }
+
+ rc = libusb_hotplug_register_callback (NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, 0, vendor_id,
+ product_id, class_id, hotplug_callback, NULL, &hp[0]);
+ if (LIBUSB_SUCCESS != rc) {
+ fprintf (stderr, "Error registering callback 0\n");
+ libusb_exit (NULL);
+ return EXIT_FAILURE;
+ }
+
+ rc = libusb_hotplug_register_callback (NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, vendor_id,
+ product_id,class_id, hotplug_callback_detach, NULL, &hp[1]);
+ if (LIBUSB_SUCCESS != rc) {
+ fprintf (stderr, "Error registering callback 1\n");
+ libusb_exit (NULL);
+ return EXIT_FAILURE;
+ }
+
+ while (done < 2) {
+ libusb_handle_events (NULL);
+ }
+
+ libusb_exit (NULL);
+}
diff --git a/libusb/Makefile.am b/libusb/Makefile.am
index 78a19c3..6950784 100644
--- a/libusb/Makefile.am
+++ b/libusb/Makefile.am
@@ -50,10 +50,8 @@ endif
libusb_1_0_la_CFLAGS = $(AM_CFLAGS)
libusb_1_0_la_LDFLAGS = $(LTLDFLAGS)
libusb_1_0_la_SOURCES = libusbi.h core.c descriptor.c io.c sync.c $(OS_SRC) \
- os/linux_usbfs.h os/darwin_usb.h os/windows_usb.h \
- $(THREADS_SRC) \
- os/poll_posix.h os/poll_windows.h \
- os/windows_common.h
+ os/linux_usbfs.h os/darwin_usb.h os/windows_usb.h os/windows_common.h \
+ hotplug.h hotplug.c $(THREADS_SRC) os/poll_posix.h os/poll_windows.h
hdrdir = $(includedir)/libusb-1.0
hdr_HEADERS = libusb.h
diff --git a/libusb/core.c b/libusb/core.c
index 9a2d1b7..733d1a6 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -1,5 +1,6 @@
/*
* Core functions for libusbx
+ * Copyright © 2012-2013 Nathan Hjelm <hjelmn@cs.unm.edu>
* Copyright © 2007-2008 Daniel Drake <dsd@gentoo.org>
* Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com>
*
@@ -33,6 +34,7 @@
#endif
#include "libusbi.h"
+#include "hotplug.h"
#if defined(OS_LINUX)
const struct usbi_os_backend * const usbi_backend = &linux_usbfs_backend;
@@ -90,6 +92,7 @@ struct list_head active_contexts_list;
* usually won't need to thread)
* - Lightweight with lean API
* - Compatible with libusb-0.1 through the libusb-compat-0.1 translation layer
+ * - Hotplug support (see \ref hotplug)
*
* \section gettingstarted Getting Started
*
@@ -191,19 +194,6 @@ struct list_head active_contexts_list;
* - Clearing of halt/stall condition (libusb_clear_halt())
* - Device resets (libusb_reset_device())
*
- * \section nohotplug No hotplugging
- *
- * libusbx-1.0 lacks functionality for providing notifications of when devices
- * are added or removed. This functionality is planned to be implemented
- * in a later version of libusbx.
- *
- * That said, there is basic disconnection handling for open device handles:
- * - If there are ongoing transfers, libusbx's handle_events loop will detect
- * disconnections and complete ongoing transfers with the
- * LIBUSB_TRANSFER_NO_DEVICE status code.
- * - Many functions such as libusb_set_configuration() return the special
- * LIBUSB_ERROR_NO_DEVICE error code when the device has been disconnected.
- *
* \section configsel Configuration selection and handling
*
* When libusbx presents a device handle to an application, there is a chance
@@ -525,10 +515,63 @@ struct libusb_device *usbi_alloc_device(struct libusb_context *ctx,
dev->speed = LIBUSB_SPEED_UNKNOWN;
memset(&dev->os_priv, 0, priv_size);
+ if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
+ usbi_connect_device (dev);
+ }
+
+ return dev;
+}
+
+void usbi_connect_device(struct libusb_device *dev)
+{
+ libusb_hotplug_message message;
+ ssize_t ret;
+
+ message.event = LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED;
+ message.device = dev;
+ dev->attached = 1;
+
+ usbi_mutex_lock(&dev->ctx->usb_devs_lock);
+ list_add(&dev->list, &dev->ctx->usb_devs);
+ usbi_mutex_unlock(&dev->ctx->usb_devs_lock);
+
+ /* Signal that an event has occurred for this device if we support hotplug AND
+ * the hotplug pipe is ready. This prevents an event from getting raised during
+ * initial enumeration. */
+ if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_pipe[1] > 0) {
+ ret = usbi_write(dev->ctx->hotplug_pipe[1], &message, sizeof(message));
+ if (sizeof (message) != ret) {
+ usbi_err(DEVICE_CTX(dev), "error writing hotplug message");
+ }
+ }
+}
+
+void usbi_disconnect_device(struct libusb_device *dev)
+{
+ libusb_hotplug_message message;
+ struct libusb_context *ctx = dev->ctx;
+ ssize_t ret;
+
+ message.event = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT;
+ message.device = dev;
+ usbi_mutex_lock(&dev->lock);
+ dev->attached = 0;
+ usbi_mutex_unlock(&dev->lock);
+
+ /* Signal that an event has occurred for this device if we support hotplug AND
+ * the hotplug pipe is ready. This prevents an event from getting raised during
+ * initial enumeration. libusb_handle_events will take care of dereferencing the
+ * device. */
+ if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && dev->ctx->hotplug_pipe[1] > 0) {
+ ret = usbi_write(dev->ctx->hotplug_pipe[1], &message, sizeof(message));
+ if (sizeof(message) != ret) {
+ usbi_err(DEVICE_CTX(dev), "error writing hotplug message");
+ }
+ }
+
usbi_mutex_lock(&ctx->usb_devs_lock);
- list_add(&dev->list, &ctx->usb_devs);
+ list_del(&dev->list);
usbi_mutex_unlock(&ctx->usb_devs_lock);
- return dev;
}
/* Perform some final sanity checks on a newly discovered device. If this
@@ -607,7 +650,25 @@ ssize_t API_EXPORTED libusb_get_device_list(libusb_context *ctx,
if (!discdevs)
return LIBUSB_ERROR_NO_MEM;
- r = usbi_backend->get_device_list(ctx, &discdevs);
+ if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
+ /* backend provides hotplug support */
+ struct libusb_device *dev;
+
+ usbi_mutex_lock(&ctx->usb_devs_lock);
+ list_for_each_entry(dev, &ctx->usb_devs, list, struct libusb_device) {
+ discdevs = discovered_devs_append(discdevs, dev);
+
+ if (!discdevs) {
+ r = LIBUSB_ERROR_NO_MEM;
+ break;
+ }
+ }
+ usbi_mutex_unlock(&ctx->usb_devs_lock);
+ } else {
+ /* backend does not provide hotplug support */
+ r = usbi_backend->get_device_list(ctx, &discdevs);
+ }
+
if (r < 0) {
len = r;
goto out;
@@ -910,9 +971,10 @@ void API_EXPORTED libusb_unref_device(libusb_device *dev)
if (usbi_backend->destroy_device)
usbi_backend->destroy_device(dev);
- usbi_mutex_lock(&dev->ctx->usb_devs_lock);
- list_del(&dev->list);
- usbi_mutex_unlock(&dev->ctx->usb_devs_lock);
+ if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
+ /* backend does not support hotplug */
+ usbi_disconnect_device(dev);
+ }
usbi_mutex_destroy(&dev->lock);
free(dev);
@@ -991,6 +1053,10 @@ int API_EXPORTED libusb_open(libusb_device *dev,
int r;
usbi_dbg("open %d.%d", dev->bus_number, dev->device_address);
+ if (!dev->attached) {
+ return LIBUSB_ERROR_NO_DEVICE;
+ }
+
_handle = malloc(sizeof(*_handle) + priv_size);
if (!_handle)
return LIBUSB_ERROR_NO_MEM;
@@ -1347,6 +1413,9 @@ int API_EXPORTED libusb_claim_interface(libusb_device_handle *dev,
if (interface_number >= USB_MAXINTERFACES)
return LIBUSB_ERROR_INVALID_PARAM;
+ if (!dev->dev->attached)
+ return LIBUSB_ERROR_NO_DEVICE;
+
usbi_mutex_lock(&dev->lock);
if (dev->claimed_interfaces & (1 << interface_number))
goto out;
@@ -1429,6 +1498,11 @@ int API_EXPORTED libusb_set_interface_alt_setting(libusb_device_handle *dev,
return LIBUSB_ERROR_INVALID_PARAM;
usbi_mutex_lock(&dev->lock);
+ if (!dev->dev->attached) {
+ usbi_mutex_unlock(&dev->lock);
+ return LIBUSB_ERROR_NO_DEVICE;
+ }
+
if (!(dev->claimed_interfaces & (1 << interface_number))) {
usbi_mutex_unlock(&dev->lock);
return LIBUSB_ERROR_NOT_FOUND;
@@ -1459,6 +1533,9 @@ int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev,
unsigned char endpoint)
{
usbi_dbg("endpoint %x", endpoint);
+ if (!dev->dev->attached)
+ return LIBUSB_ERROR_NO_DEVICE;
+
return usbi_backend->clear_halt(dev, endpoint);
}
@@ -1484,6 +1561,9 @@ int API_EXPORTED libusb_clear_halt(libusb_device_handle *dev,
int API_EXPORTED libusb_reset_device(libusb_device_handle *dev)
{
usbi_dbg("");
+ if (!dev->dev->attached)
+ return LIBUSB_ERROR_NO_DEVICE;
+
return usbi_backend->reset_device(dev);
}
@@ -1508,6 +1588,10 @@ int API_EXPORTED libusb_kernel_driver_active(libusb_device_handle *dev,
int interface_number)
{
usbi_dbg("interface %d", interface_number);
+
+ if (!dev->dev->attached)
+ return LIBUSB_ERROR_NO_DEVICE;
+
if (usbi_backend->kernel_driver_active)
return usbi_backend->kernel_driver_active(dev, interface_number);
else
@@ -1539,6 +1623,10 @@ int API_EXPORTED libusb_detach_kernel_driver(libusb_device_handle *dev,
int interface_number)
{
usbi_dbg("interface %d", interface_number);
+
+ if (!dev->dev->attached)
+ return LIBUSB_ERROR_NO_DEVICE;
+
if (usbi_backend->detach_kernel_driver)
return usbi_backend->detach_kernel_driver(dev, interface_number);
else
@@ -1569,6 +1657,10 @@ int API_EXPORTED libusb_attach_kernel_driver(libusb_device_handle *dev,
int interface_number)
{
usbi_dbg("interface %d", interface_number);
+
+ if (!dev->dev->attached)
+ return LIBUSB_ERROR_NO_DEVICE;
+
if (usbi_backend->attach_kernel_driver)
return usbi_backend->attach_kernel_driver(dev, interface_number);
else
@@ -1667,17 +1759,19 @@ int API_EXPORTED libusb_init(libusb_context **context)
usbi_dbg("libusbx v%d.%d.%d.%d", libusb_version_internal.major, libusb_version_internal.minor,
libusb_version_internal.micro, libusb_version_internal.nano);
+ usbi_mutex_init(&ctx->usb_devs_lock, NULL);
+ usbi_mutex_init(&ctx->open_devs_lock, NULL);
+ usbi_mutex_init(&ctx->hotplug_cbs_lock, NULL);
+ list_init(&ctx->usb_devs);
+ list_init(&ctx->open_devs);
+ list_init(&ctx->hotplug_cbs);
+
if (usbi_backend->init) {
r = usbi_backend->init(ctx);
if (r)
goto err_free_ctx;
}
- usbi_mutex_init(&ctx->usb_devs_lock, NULL);
- usbi_mutex_init(&ctx->open_devs_lock, NULL);
- list_init(&ctx->usb_devs);
- list_init(&ctx->open_devs);
-
r = usbi_io_init(ctx);
if (r < 0) {
if (usbi_backend->exit)
@@ -1740,6 +1834,8 @@ void API_EXPORTED libusb_exit(struct libusb_context *ctx)
list_del (&ctx->list);
usbi_mutex_static_unlock(&active_contexts_lock);
+ usbi_hotplug_deregister_all(ctx);
+
/* a little sanity check. doesn't bother with open_devs locking because
* unless there is an application bug, nobody will be accessing this. */
if (!list_empty(&ctx->open_devs))
@@ -1751,6 +1847,7 @@ void API_EXPORTED libusb_exit(struct libusb_context *ctx)
usbi_mutex_destroy(&ctx->open_devs_lock);
usbi_mutex_destroy(&ctx->usb_devs_lock);
+ usbi_mutex_destroy(&ctx->hotplug_cbs_lock);
free(ctx);
}
@@ -1767,6 +1864,8 @@ int API_EXPORTED libusb_has_capability(uint32_t capability)
switch (capability) {
case LIBUSB_CAP_HAS_CAPABILITY:
return 1;
+ case LIBUSB_CAP_HAS_HOTPLUG:
+ return !(usbi_backend->get_device_list);
case LIBUSB_CAP_HAS_HID_ACCESS:
return (usbi_backend->caps & USBI_CAP_HAS_HID_ACCESS);
case LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER:
diff --git a/libusb/hotplug.c b/libusb/hotplug.c
new file mode 100644
index 0000000..e8f1e52
--- /dev/null
+++ b/libusb/hotplug.c
@@ -0,0 +1,302 @@
+/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */
+/*
+ * Hotplug functions for libusbx
+ * Copyright © 2012-2013 Nathan Hjelm <hjelmn@mac.com>
+ * Copyright © 2012-2013 Peter Stuge <peter@stuge.se>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#include "libusbi.h"
+#include "hotplug.h"
+
+/**
+ * @defgroup hotplug Device hotplug event notification
+ * This page details how to use the libusb hotplug interface.
+ *
+ * \page hotplug Device hotplug event notification
+ *
+ * \section intro Introduction
+ *
+ * Releases of libusb 1.0 newer than 1.X have added support for hotplug
+ * events. This interface allows you to request notification for the
+ * arrival and departure of matching USB devices.
+ *
+ * To receive hotplug notification you register a callback by calling
+ * libusb_hotplug_register_callback(). This function will optionally return
+ * a handle that can be passed to libusb_hotplug_deregister_callback().
+ *
+ * A callback function must return an int (0 or 1) indicating whether the callback is
+ * expecting additional events. Returning 0 will rearm the callback and 1 will cause
+ * the callback to be deregistered.
+ *
+ * Callbacks for a particulat context are automatically deregistered by libusb_exit().
+ *
+ * As of 1.X there are two supported hotplug events:
+ * - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: A device has arrived and is ready to use
+ * - LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: A device has left and is no longer available
+ *
+ * A hotplug event can listen for either or both of these events.
+ *
+ * Note: If you receive notification that a device has left and you have any
+ * a libusb_device_handles for the device it is up to you to call libusb_close()
+ * on each handle to free up any remaining resources associated with the device.
+ * Once a device has left any libusb_device_handle associated with the device
+ * are invalid and will remain so even if the device comes back.
+ *
+ * When handling a LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED event it is considered
+ * safe to call any libusbx function that takes a libusb_device. On the other hand,
+ * when handling a LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT event the only safe function
+ * is libusb_get_device_descriptor().
+ *
+ * The following code provides an example of the usage of the hotplug interface:
+\code
+static int count = 0;
+
+int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev,
+ libusb_hotplug_event event, void *user_data) {
+ static libusb_device_handle *handle = NULL;
+ struct libusb_device_descriptor desc;
+ int rc;
+
+ (void)libusb_get_device_descriptor(dev, &desc);
+
+ if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) {
+ rc = libusb_open(dev, &handle);
+ if (LIBUSB_SUCCESS != rc) {
+ printf("Could not open USB device\n");
+ }
+ } else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) {
+ if (handle) {
+ libusb_close(handle);
+ handle = NULL;
+ }
+ } else {
+ printf("Unhandled event %d\n", event);
+ }
+ count++;
+
+ return 0;
+}
+
+int main (void) {
+ libusb_hotplug_callback_handle handle;
+ int rc;
+
+ libusb_init(NULL);
+
+ rc = libusb_hotplug_register_callback(NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
+ LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 0, 0x045a, 0x5005,
+ LIBUSB_HOTPLUG_MATCH_ANY, hotplug_callback, NULL,
+ &handle);
+ if (LIBUSB_SUCCESS != rc) {
+ printf("Error creating a hotplug callback\n");
+ libusb_exit(NULL);
+ return EXIT_FAILURE;
+ }
+
+ while (count < 2) {
+ usleep(10000);
+ }
+
+ libusb_hotplug_deregister_callback(handle);
+ libusb_exit(NULL);
+
+ return 0;
+}
+\endcode
+ */
+
+static int usbi_hotplug_match_cb (struct libusb_device *dev, libusb_hotplug_event event,
+ struct libusb_hotplug_callback *hotplug_cb)
+{
+ struct libusb_context *ctx = dev->ctx;
+
+ /* Handle lazy deregistration of callback */
+ if (hotplug_cb->needs_free) {
+ /* Free callback */
+ return 1;
+ }
+
+ if (!(hotplug_cb->events & event)) {
+ return 0;
+ }
+
+ if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->vendor_id &&
+ hotplug_cb->vendor_id != dev->device_descriptor.idVendor) {
+ return 0;
+ }
+
+ if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->product_id &&
+ hotplug_cb->product_id != dev->device_descriptor.idProduct) {
+ return 0;
+ }
+
+ if (LIBUSB_HOTPLUG_MATCH_ANY != hotplug_cb->dev_class &&
+ hotplug_cb->dev_class != dev->device_descriptor.bDeviceClass) {
+ return 0;
+ }
+
+ return hotplug_cb->cb (ctx == usbi_default_context ? NULL : ctx,
+ dev, event, hotplug_cb->user_data);
+}
+
+void usbi_hotplug_match(struct libusb_device *dev, libusb_hotplug_event event)
+{
+ struct libusb_hotplug_callback *hotplug_cb, *next;
+ struct libusb_context *ctx = dev->ctx;
+ int ret;
+
+ usbi_mutex_lock(&ctx->hotplug_cbs_lock);
+
+ list_for_each_entry_safe(hotplug_cb, next, &ctx->hotplug_cbs, list, struct libusb_hotplug_callback) {
+ usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
+ ret = usbi_hotplug_match_cb (dev, event, hotplug_cb);
+ usbi_mutex_lock(&ctx->hotplug_cbs_lock);
+
+ if (ret) {
+ list_del(&hotplug_cb->list);
+ free(hotplug_cb);
+ }
+ }
+
+ usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
+
+ /* loop through and disconnect all open handles for this device */
+ if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == event) {
+ struct libusb_device_handle *handle;
+
+ usbi_mutex_lock(&ctx->open_devs_lock);
+ list_for_each_entry(handle, &ctx->open_devs, list, struct libusb_device_handle) {
+ if (dev == handle->dev) {
+ usbi_handle_disconnect (handle);
+ }
+ }
+ usbi_mutex_unlock(&ctx->open_devs_lock);
+ }
+}
+
+int API_EXPORTED libusb_hotplug_register_callback(libusb_context *ctx,
+ libusb_hotplug_event events, libusb_hotplug_flag flags,
+ int vendor_id, int product_id, int dev_class,
+ libusb_hotplug_callback_fn cb_fn, void *user_data,
+ libusb_hotplug_callback_handle *handle)
+{
+ libusb_hotplug_callback *new_callback;
+ static int handle_id = 1;
+
+ /* check for hotplug support */
+ if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
+ return LIBUSB_ERROR_NOT_SUPPORTED;
+ }
+
+ /* check for sane values */
+ if ((LIBUSB_HOTPLUG_MATCH_ANY != vendor_id && (~0xffff & vendor_id)) ||
+ (LIBUSB_HOTPLUG_MATCH_ANY != product_id && (~0xffff & product_id)) ||
+ (LIBUSB_HOTPLUG_MATCH_ANY != dev_class && (~0xff & dev_class)) ||
+ !cb_fn) {
+ return LIBUSB_ERROR_INVALID_PARAM;
+ }
+
+ USBI_GET_CONTEXT(ctx);
+
+ new_callback = (libusb_hotplug_callback *)calloc(1, sizeof (*new_callback));
+ if (!new_callback) {
+ return LIBUSB_ERROR_NO_MEM;
+ }
+
+ new_callback->ctx = ctx;
+ new_callback->vendor_id = vendor_id;
+ new_callback->product_id = product_id;
+ new_callback->dev_class = dev_class;
+ new_callback->flags = flags;
+ new_callback->events = events;
+ new_callback->cb = cb_fn;
+ new_callback->user_data = user_data;
+ new_callback->needs_free = 0;
+
+ usbi_mutex_lock(&ctx->hotplug_cbs_lock);
+
+ /* protect the handle by the context hotplug lock. it doesn't matter if the same handle
+ * is used for different contexts only that the handle is unique for this context */
+ new_callback->handle = handle_id++;
+
+ list_add(&new_callback->list, &ctx->hotplug_cbs);
+
+ if (flags & LIBUSB_HOTPLUG_ENUMERATE) {
+ struct libusb_device *dev;
+
+ usbi_mutex_lock(&ctx->usb_devs_lock);
+
+ list_for_each_entry(dev, &ctx->usb_devs, list, struct libusb_device) {
+ (void) usbi_hotplug_match_cb (dev, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, new_callback);
+ }
+
+ usbi_mutex_unlock(&ctx->usb_devs_lock);
+ }
+
+ usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
+
+ if (handle) {
+ *handle = new_callback->handle;
+ }
+
+ return LIBUSB_SUCCESS;
+}
+
+void API_EXPORTED libusb_hotplug_deregister_callback (struct libusb_context *ctx,
+ libusb_hotplug_callback_handle handle)
+{
+ struct libusb_hotplug_callback *hotplug_cb;
+
+ /* check for hotplug support */
+ if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
+ return;
+ }
+
+ USBI_GET_CONTEXT(ctx);
+
+ usbi_mutex_lock(&ctx->hotplug_cbs_lock);
+ list_for_each_entry(hotplug_cb, &ctx->hotplug_cbs, list,
+ struct libusb_hotplug_callback) {
+ if (handle == hotplug_cb->handle) {
+ /* Mark this callback for deregistration */
+ hotplug_cb->needs_free = 1;
+ }
+ }
+ usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
+}
+
+void usbi_hotplug_deregister_all(struct libusb_context *ctx) {
+ struct libusb_hotplug_callback *hotplug_cb, *next;
+
+ usbi_mutex_lock(&ctx->hotplug_cbs_lock);
+ list_for_each_entry_safe(hotplug_cb, next, &ctx->hotplug_cbs, list,
+ struct libusb_hotplug_callback) {
+ list_del(&hotplug_cb->list);
+ free(hotplug_cb);
+ }
+
+ usbi_mutex_unlock(&ctx->hotplug_cbs_lock);
+}
diff --git a/libusb/hotplug.h b/libusb/hotplug.h
new file mode 100644
index 0000000..12c120e
--- /dev/null
+++ b/libusb/hotplug.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C; indent-tabs-mode:t ; c-basic-offset:8 -*- */
+/*
+ * Hotplug support for libusbx
+ * Copyright © 2012-2013 Nathan Hjelm <hjelmn@mac.com>
+ * Copyright © 2012-2013 Peter Stuge <peter@stuge.se>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#if !defined(USBI_HOTPLUG_H)
+#define USBI_HOTPLUG_H
+
+#ifndef LIBUSBI_H
+#include "libusbi.h"
+#endif
+
+/** \ingroup hotplug
+ * The hotplug callback structure. The user populates this structure with
+ * libusb_hotplug_prepare_callback() and then calls libusb_hotplug_register_callback()
+ * to receive notification of hotplug events.
+ */
+struct libusb_hotplug_callback {
+ /** Context this callback is associated with */
+ struct libusb_context *ctx;
+
+ /** Vendor ID to match or LIBUSB_HOTPLUG_MATCH_ANY */
+ int vendor_id;
+
+ /** Product ID to match or LIBUSB_HOTPLUG_MATCH_ANY */
+ int product_id;
+
+ /** Device class to match or LIBUSB_HOTPLUG_MATCH_ANY */
+ int dev_class;
+
+ /** Hotplug callback flags */
+ libusb_hotplug_flag flags;
+
+ /** Event(s) that will trigger this callback */
+ libusb_hotplug_event events;
+
+ /** Callback function to invoke for matching event/device */
+ libusb_hotplug_callback_fn cb;
+
+ /** Handle for this callback (used to match on deregister) */
+ libusb_hotplug_callback_handle handle;
+
+ /** User data that will be passed to the callback function */
+ void *user_data;
+
+ /** Callback is marked for deletion */
+ int needs_free;
+
+ /** List this callback is registered in (ctx->hotplug_cbs) */
+ struct list_head list;
+};
+
+typedef struct libusb_hotplug_callback libusb_hotplug_callback;
+
+struct libusb_hotplug_message {
+ libusb_hotplug_event event;
+ struct libusb_device *device;
+};
+
+typedef struct libusb_hotplug_message libusb_hotplug_message;
+
+void usbi_hotplug_deregister_all(struct libusb_context *ctx);
+void usbi_hotplug_match(struct libusb_device *dev, libusb_hotplug_event event);
+
+#endif
diff --git a/libusb/io.c b/libusb/io.c
index 2539b26..4495b23 100644
--- a/libusb/io.c
+++ b/libusb/io.c
@@ -24,6 +24,9 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
+#ifndef OS_WINDOWS
+#include <fcntl.h>
+#endif
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
@@ -35,6 +38,7 @@
#endif
#include "libusbi.h"
+#include "hotplug.h"
/**
* \page io Synchronous and asynchronous device I/O
@@ -1070,6 +1074,20 @@ int usbi_io_init(struct libusb_context *ctx)
if (r < 0)
goto err_close_pipe;
+ /* create hotplug pipe */
+ r = usbi_pipe(ctx->hotplug_pipe);
+ if (r < 0) {
+ r = LIBUSB_ERROR_OTHER;
+ goto err;
+ }
+
+#ifndef OS_WINDOWS
+ fcntl(ctx->hotplug_pipe[1], F_SETFD, O_NONBLOCK);
+#endif
+ r = usbi_add_pollfd(ctx, ctx->hotplug_pipe[0], POLLIN);
+ if (r < 0)
+ goto err_close_hp_pipe;
+
#ifdef USBI_TIMERFD_AVAILABLE
ctx->timerfd = timerfd_create(usbi_backend->get_timerfd_clockid(),
TFD_NONBLOCK);
@@ -1079,7 +1097,7 @@ int usbi_io_init(struct libusb_context *ctx)
if (r < 0) {
usbi_remove_pollfd(ctx, ctx->ctrl_pipe[0]);
close(ctx->timerfd);
- goto err_close_pipe;
+ goto err_close_hp_pipe;
}
} else {
usbi_dbg("timerfd not available (code %d error %d)", ctx->timerfd, errno);
@@ -1089,6 +1107,9 @@ int usbi_io_init(struct libusb_context *ctx)
return 0;
+err_close_hp_pipe:
+ usbi_close(ctx->hotplug_pipe[0]);
+ usbi_close(ctx->hotplug_pipe[1]);
err_close_pipe:
usbi_close(ctx->ctrl_pipe[0]);
usbi_close(ctx->ctrl_pipe[1]);
@@ -1107,6 +1128,9 @@ void usbi_io_exit(struct libusb_context *ctx)
usbi_remove_pollfd(ctx, ctx->ctrl_pipe[0]);
usbi_close(ctx->ctrl_pipe[0]);
usbi_close(ctx->ctrl_pipe[1]);
+ usbi_remove_pollfd(ctx, ctx->hotplug_pipe[0]);
+ usbi_close(ctx->hotplug_pipe[0]);
+ usbi_close(ctx->hotplug_pipe[1]);
#ifdef USBI_TIMERFD_AVAILABLE
if (usbi_using_timerfd(ctx)) {
usbi_remove_pollfd(ctx, ctx->timerfd);
@@ -1913,9 +1937,32 @@ static int handle_events(struct libusb_context *ctx, struct timeval *tv)
}
}
+ /* fd[1] is always the hotplug pipe */
+ if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG) && fds[1].revents) {
+ libusb_hotplug_message message;
+ ssize_t ret;
+
+ /* read the message from the hotplug thread */
+ ret = usbi_read(ctx->hotplug_pipe[0], &message, sizeof (message));
+ if (ret < sizeof(message)) {
+ ret = LIBUSB_ERROR_OTHER;
+ goto handled;
+ }
+
+ usbi_hotplug_match(message.device, message.event);
+
+ /* the device left. dereference the device */
+ if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT == message.event)
+ libusb_unref_device(message.device);
+
+ fds[1].revents = 0;
+ if (1 == r--)
+ goto handled;
+ } /* else there shouldn't be anything on this pipe */
+
#ifdef USBI_TIMERFD_AVAILABLE
- /* on timerfd configurations, fds[1] is the timerfd */
- if (usbi_using_timerfd(ctx) && fds[1].revents) {
+ /* on timerfd configurations, fds[2] is the timerfd */
+ if (usbi_using_timerfd(ctx) && fds[2].revents) {
/* timerfd indicates that a timeout has expired */
int ret;
usbi_dbg("timerfd triggered");
@@ -1932,7 +1979,7 @@ static int handle_events(struct libusb_context *ctx, struct timeval *tv)
} else {
/* more events pending...
* prevent OS backend from trying to handle events on timerfd */
- fds[1].revents = 0;
+ fds[2].revents = 0;
r--;
}
}
diff --git a/libusb/libusb.h b/libusb/libusb.h
index 13fb9a6..75613e6 100644
--- a/libusb/libusb.h
+++ b/libusb/libusb.h
@@ -3,6 +3,7 @@
* Copyright © 2001 Johannes Erdfelt <johannes@erdfelt.com>
* Copyright © 2007-2008 Daniel Drake <dsd@gentoo.org>
* Copyright © 2012 Pete Batard <pete@akeo.ie>
+ * Copyright © 2012 Nathan Hjelm <hjelmn@cs.unm.edu>
* For more information, please visit: http://libusbx.org
*
* This library is free software; you can redistribute it and/or
@@ -682,6 +683,7 @@ struct libusb_control_setup {
struct libusb_context;
struct libusb_device;
struct libusb_device_handle;
+struct libusb_hotplug_callback;
/** \ingroup lib
* Structure providing the version of the libusbx runtime
@@ -1515,6 +1517,107 @@ void LIBUSB_CALL libusb_set_pollfd_notifiers(libusb_context *ctx,
libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb,
void *user_data);
+/** \ingroup hotplug
+ * Callback handle.
+ *
+ * Callbacks handles are generated by libusb_hotplug_register_callback()
+ * and can be used to deregister callbacks. Callback handles are unique
+ * per libusb_context and it is safe to call libusb_hotplug_deregister_callback()
+ * on an already deregisted callback.
+ *
+ * For more information, see \ref hotplug.
+ */
+typedef int libusb_hotplug_callback_handle;
+
+/** \ingroup hotplug
+ * Flags for hotplug events */
+typedef enum {
+ /** Arm the callback and fire it for all matching currently attached devices. */
+ LIBUSB_HOTPLUG_ENUMERATE = 1,
+} libusb_hotplug_flag;
+
+/** \ingroup hotplug
+ * Hotplug events */
+typedef enum {
+ /** A device has been plugged in and is ready to use */
+ LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED = 0x01,
+
+ /** A device has left and is no longer available.
+ * It is the user's responsibility to call libusb_close on any handle associated with a disconnected device.
+ * It is safe to call libusb_get_device_descriptor on a device that has left */
+ LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT = 0x02,
+} libusb_hotplug_event;
+
+/** \ingroup hotplug
+ * Wildcard matching for hotplug events */
+#define LIBUSB_HOTPLUG_MATCH_ANY -1
+
+/** \ingroup hotplug
+ * Hotplug callback function type. When requesting hotplug event notifications,
+ * you pass a pointer to a callback function of this type.
+ *
+ * This callback may be called by an internal event thread and as such it is
+ * recommended the callback do minimal processing before returning.
+ *
+ * libusbx will call this function later, when a matching event had happened on
+ * a matching device. See \ref hotplug for more information.
+ *
+ * It is safe to call either libusb_hotplug_register_callback() or
+ * libusb_hotplug_deregister_callback() from within a callback function.
+ *
+ * \param libusb_context context of this notification
+ * \param device libusb_device this event occurred on
+ * \param event event that occurred
+ * \param user_data user data provided when this callback was registered
+ * \returns bool whether this callback is finished processing events.
+ * returning 1 will cause this callback to be deregistered
+ */
+typedef int (LIBUSB_CALL *libusb_hotplug_callback_fn)(libusb_context *ctx,
+ libusb_device *device,
+ libusb_hotplug_event event,
+ void *user_data);
+
+/** \ingroup hotplug
+ * Register a hotplug callback function
+ *
+ * Register a callback with the libusb_context. The callback will fire
+ * when a matching event occurs on a matching device. The callback is
+ * armed until either it is deregistered with libusb_hotplug_deregister_callback()
+ * or the supplied callback returns 1 to indicate it is finished processing events.
+ *
+ * \param[in] ctx context to register this callback with
+ * \param[in] events bitwise or of events that will trigger this callback. See \ref
+ * libusb_hotplug_event
+ * \param[in] flags hotplug callback flags. See \ref libusb_hotplug_flag
+ * \param[in] vendor_id the vendor id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY
+ * \param[in] product_id the product id to match or \ref LIBUSB_HOTPLUG_MATCH_ANY
+ * \param[in] dev_class the device class to match or \ref LIBUSB_HOTPLUG_MATCH_ANY
+ * \param[in] cb_fn the function to be invoked on a matching event/device
+ * \param[in] user_data user data to pass to the callback function
+ * \param[out] handle pointer to store the handle of the allocated callback (can be NULL)
+ * \returns LIBUSB_SUCCESS on success LIBUSB_ERROR code on failure
+ */
+int LIBUSB_CALL libusb_hotplug_register_callback(libusb_context *ctx,
+ libusb_hotplug_event events,
+ libusb_hotplug_flag flags,
+ int vendor_id, int product_id,
+ int dev_class,
+ libusb_hotplug_callback_fn cb_fn,
+ void *user_data,
+ libusb_hotplug_callback_handle *handle);
+
+/** \ingroup hotplug
+ * Deregisters a hotplug callback.
+ *
+ * Deregister a callback from a libusb_context. This function is safe to call from within
+ * a hotplug callback.
+ *
+ * \param[in] ctx context this callback is registered with
+ * \param[in] handle the handle of the callback to deregister
+ */
+void LIBUSB_CALL libusb_hotplug_deregister_callback(libusb_context *ctx,
+ libusb_hotplug_callback_handle handle);
+
#ifdef __cplusplus
}
#endif
diff --git a/libusb/libusbi.h b/libusb/libusbi.h
index b0cc53e..1b59175 100644
--- a/libusb/libusbi.h
+++ b/libusb/libusbi.h
@@ -224,6 +224,11 @@ struct libusb_context {
struct list_head open_devs;
usbi_mutex_t open_devs_lock;
+ /* A list of registered hotplug callbacks */
+ struct list_head hotplug_cbs;
+ usbi_mutex_t hotplug_cbs_lock;
+ int hotplug_pipe[2];
+
/* this is a list of in-flight transfer handles, sorted by timeout
* expiration. URBs to timeout the soonest are placed at the beginning of
* the list, URBs that will time out later are placed after, and urbs with
@@ -290,6 +295,7 @@ struct libusb_device {
unsigned long session_data;
struct libusb_device_descriptor device_descriptor;
+ int attached;
unsigned char os_priv[0];
};
@@ -401,6 +407,9 @@ int usbi_device_cache_descriptor(libusb_device *dev);
int usbi_get_config_index_by_value(struct libusb_device *dev,
uint8_t bConfigurationValue, int *idx);
+void usbi_connect_device (struct libusb_device *dev);
+void usbi_disconnect_device (struct libusb_device *dev);
+
/* Internal abstraction for poll (needs struct usbi_transfer on Windows) */
#if defined(OS_LINUX) || defined(OS_DARWIN) || defined(OS_OPENBSD)
#include <unistd.h>
diff --git a/libusb/version.h b/libusb/version.h
index 3736ab3..cf37de9 100644
--- a/libusb/version.h
+++ b/libusb/version.h
@@ -7,7 +7,7 @@
#define LIBUSB_MINOR 0
#endif
#ifndef LIBUSB_MICRO
-#define LIBUSB_MICRO 15
+#define LIBUSB_MICRO 16
#endif
#ifndef LIBUSB_NANO
#define LIBUSB_NANO 0
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index 1677e61..eae7c6a 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 10651
+#define LIBUSB_NANO 10652