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>
This commit is contained in:
Nathan Hjelm 2012-11-29 14:23:26 -07:00 committed by Hans de Goede
parent 2948008951
commit 7801ff94fa
13 changed files with 773 additions and 37 deletions

1
.gitignore vendored
View file

@ -29,6 +29,7 @@ examples/xusb
examples/dpfp
examples/dpfp_threaded
examples/fxload
examples/hotplugtest
tests/stress
*.exe
*.pc

View file

@ -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

View file

@ -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

96
examples/hotplugtest.c Normal file
View file

@ -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);
}

View file

@ -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

View file

@ -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,12 +515,65 @@ struct libusb_device *usbi_alloc_device(struct libusb_context *ctx,
dev->speed = LIBUSB_SPEED_UNKNOWN;
memset(&dev->os_priv, 0, priv_size);
usbi_mutex_lock(&ctx->usb_devs_lock);
list_add(&dev->list, &ctx->usb_devs);
usbi_mutex_unlock(&ctx->usb_devs_lock);
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_del(&dev->list);
usbi_mutex_unlock(&ctx->usb_devs_lock);
}
/* Perform some final sanity checks on a newly discovered device. If this
* function fails (negative return code), the device should not be added
* to the discovered device list. */
@ -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:

302
libusb/hotplug.c Normal file
View file

@ -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);
}

81
libusb/hotplug.h Normal file
View file

@ -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

View file

@ -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--;
}
}

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -1 +1 @@
#define LIBUSB_NANO 10651
#define LIBUSB_NANO 10652