diff options
author | Nathan Hjelm <hjelmn@me.com> | 2012-11-29 14:23:26 -0700 |
---|---|---|
committer | Hans de Goede <hdegoede@redhat.com> | 2013-05-15 17:28:06 +0200 |
commit | 7801ff94fa6e49fe98433eccc7f2e461590a6f7c (patch) | |
tree | 7d75d9c8f40471b45bbd77e8352ff602d5497c6f | |
parent | 29480089519f0b52da7697c34a3bbb67d3e74a3f (diff) | |
download | android_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-- | .gitignore | 1 | ||||
-rw-r--r-- | doc/doxygen.cfg.in | 2 | ||||
-rw-r--r-- | examples/Makefile.am | 2 | ||||
-rw-r--r-- | examples/hotplugtest.c | 96 | ||||
-rw-r--r-- | libusb/Makefile.am | 6 | ||||
-rw-r--r-- | libusb/core.c | 147 | ||||
-rw-r--r-- | libusb/hotplug.c | 302 | ||||
-rw-r--r-- | libusb/hotplug.h | 81 | ||||
-rw-r--r-- | libusb/io.c | 55 | ||||
-rw-r--r-- | libusb/libusb.h | 103 | ||||
-rw-r--r-- | libusb/libusbi.h | 9 | ||||
-rw-r--r-- | libusb/version.h | 2 | ||||
-rw-r--r-- | libusb/version_nano.h | 2 |
13 files changed, 772 insertions, 36 deletions
@@ -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 |