diff --git a/lib/libusb/libusb10.h b/lib/libusb/libusb10.h --- a/lib/libusb/libusb10.h +++ b/lib/libusb/libusb10.h @@ -30,6 +30,12 @@ #ifndef LIBUSB_GLOBAL_INCLUDE_FILE #include +#include +#include +#include +#include +#include + #endif #define GET_CONTEXT(ctx) (((ctx) == NULL) ? usbi_default_context : (ctx)) @@ -90,12 +96,22 @@ TAILQ_HEAD(libusb_device_head, libusb_device); +typedef enum { + usb_event_none, + usb_event_scan, + usb_event_devd, + usb_event_netlink +} usb_event_mode_t; + struct libusb_context { int debug; int debug_fixed; int ctrl_pipe[2]; int tr_done_ref; int tr_done_gen; + usb_event_mode_t usb_event_mode; + int devd_pipe; + struct snl_state ss; pthread_mutex_t ctx_lock; pthread_mutex_t hotplug_lock; diff --git a/lib/libusb/libusb10.c b/lib/libusb/libusb10.c --- a/lib/libusb/libusb10.c +++ b/lib/libusb/libusb10.c @@ -155,6 +155,7 @@ return (LIBUSB_ERROR_INVALID_PARAM); memset(ctx, 0, sizeof(*ctx)); + ctx->devd_pipe = -1; debug = getenv("LIBUSB_DEBUG"); if (debug != NULL) { diff --git a/lib/libusb/libusb10_hotplug.c b/lib/libusb/libusb10_hotplug.c --- a/lib/libusb/libusb10_hotplug.c +++ b/lib/libusb/libusb10_hotplug.c @@ -23,6 +23,7 @@ * SUCH DAMAGE. */ +#include #ifdef LIBUSB_GLOBAL_INCLUDE_FILE #include LIBUSB_GLOBAL_INCLUDE_FILE #else @@ -39,6 +40,10 @@ #include #include #include +#include +#include +#include +#include #endif #define libusb_device_handle libusb20_device @@ -49,6 +54,61 @@ #include "libusb.h" #include "libusb10.h" +#define DEVDPIPE "/var/run/devd.pipe" + +static bool +netlink_init(libusb_context *ctx) +{ + struct _getfamily_attrs attrs; + + if (modfind("nlsysevent") < 0) + kldload("nlsysevent"); + if (modfind("nlsysevent") < 0) + return (false); + if (!snl_init(&ctx->ss, NETLINK_GENERIC)) + return (false); + + if (!snl_get_genl_family_info(&ctx->ss, "nlsysevent", &attrs)) + return (false); + + for (unsigned int i = 0; i < attrs.mcast_groups.num_groups; i++) { + if (strcmp(attrs.mcast_groups.groups[i]->mcast_grp_name, + "USB") == 0) { + if (setsockopt(ctx->ss.fd, SOL_NETLINK, + NETLINK_ADD_MEMBERSHIP, + &attrs.mcast_groups.groups[i]->mcast_grp_id, + sizeof(attrs.mcast_groups.groups[i]->mcast_grp_id)) + == -1) { + return (false); + } + } + } + ctx->usb_event_mode = usb_event_netlink; + return (true); +} + +static bool +devd_init(libusb_context *ctx) +{ + struct sockaddr_un devd_addr; + + bzero(&devd_addr, sizeof(devd_addr)); + if ((ctx->devd_pipe = socket(PF_LOCAL, SOCK_STREAM|SOCK_NONBLOCK, 0)) < 0) + return (false); + + devd_addr.sun_family = PF_LOCAL; + strlcpy(devd_addr.sun_path, DEVDPIPE, sizeof(devd_addr.sun_path)); + if (connect(ctx->devd_pipe, (struct sockaddr *)&devd_addr, + sizeof(devd_addr)) == -1) { + close(ctx->devd_pipe); + ctx->devd_pipe = -1; + return (false); + } + + ctx->usb_event_mode = usb_event_devd; + return (true); +} + static int libusb_hotplug_equal(libusb_device *_adev, libusb_device *_bdev) { @@ -112,12 +172,39 @@ libusb_device *temp; libusb_device *adev; libusb_device *bdev; - unsigned do_loop = 1; + struct pollfd pfd[1]; + int timeout = 4000; + int nfds = 0; - while (do_loop) { - usleep(4000000); + if (ctx->usb_event_mode == usb_event_devd) { + pfd[0].fd = ctx->devd_pipe; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + nfds = 1; + } else if (ctx->usb_event_mode == usb_event_netlink) { + pfd[0].fd = ctx->ss.fd; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + nfds = 1; + } else { + nfds = 0; + } + for (;;) { + poll(pfd, nfds, timeout); /* ignore any error */ HOTPLUG_LOCK(ctx); + if (ctx->hotplug_handler == NO_THREAD) { + while ((adev = TAILQ_FIRST(&ctx->hotplug_devs)) != NULL) { + TAILQ_REMOVE(&ctx->hotplug_devs, adev, hotplug_entry); + libusb_unref_device(adev); + } + if (ctx->usb_event_mode == usb_event_devd) + close(ctx->devd_pipe); + else if (ctx->usb_event_mode == usb_event_netlink) + close(ctx->ss.fd); + HOTPLUG_UNLOCK(ctx); + break; + } TAILQ_INIT(&hotplug_devs); @@ -127,7 +214,7 @@ continue; } } else { - do_loop = 0; + break; } /* figure out which devices are gone */ @@ -189,6 +276,13 @@ ctx = GET_CONTEXT(ctx); + if (ctx->usb_event_mode == usb_event_none) { + HOTPLUG_LOCK(ctx); + if (!netlink_init(ctx) && !devd_init(ctx)) + ctx->usb_event_mode = usb_event_scan; + HOTPLUG_UNLOCK(ctx); + } + if (ctx == NULL || cb_fn == NULL || events == 0 || vendor_id < -1 || vendor_id > 0xffff || product_id < -1 || product_id > 0xffff ||