From cfcd7dea5c4df7be3bae5a536754493ae6bc8616 Mon Sep 17 00:00:00 2001 From: Sean McBride Date: Mon, 20 Dec 2021 21:20:35 -0500 Subject: [PATCH] Added hotplug support Added several API to support hotplug notifations. That is, when a device is connected/disconnected, a callback can be invoked to notify the application. Implemented using libusb's hotplug support. --- liblabjackusb/labjackusb.c | 150 +++++++++++++++++++++++++++++++++++++ liblabjackusb/labjackusb.h | 51 ++++++++++++- 2 files changed, 199 insertions(+), 2 deletions(-) diff --git a/liblabjackusb/labjackusb.c b/liblabjackusb/labjackusb.c index e57251d..e062802 100644 --- a/liblabjackusb/labjackusb.c +++ b/liblabjackusb/labjackusb.c @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -48,6 +49,10 @@ static bool gIsLibUSBInitialized = false; static struct libusb_context *gLJContext = NULL; +static libusb_hotplug_callback_handle gHotPlugCallbackHandle = 0; +static LJUSB_HotPlugConnectedCallback gHotPlugConnectedCallback = NULL; +static LJUSB_HotPlugDisconnectedCallback gHotPlugDisconnectedCallback = NULL; +static void *gHotPlugUserContext = NULL; enum LJUSB_TRANSFER_OPERATION { LJUSB_WRITE, LJUSB_READ, LJUSB_STREAM }; @@ -717,6 +722,151 @@ int LJUSB_OpenAllDevicesOfProductId(UINT productId, HANDLE **devHandles) return successCount; } +static int LJUSB_HotPlugCallback(libusb_context *ctx, + libusb_device *dev, + libusb_hotplug_event event, + void *user_data) +{ + assert(ctx == gLJContext); + (void)ctx; + assert(dev); + (void)user_data; + + // NB: this callback may, or may not, be called from a private libusb thread! + + if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + fprintf(stderr, "LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED occurred\n"); + + HANDLE handle = NULL; + + struct libusb_device_descriptor desc; + int r = libusb_get_device_descriptor(dev, &desc); + if (r < 0) { + fprintf(stderr, "LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED failed to get device descriptor due to:\n"); + LJUSB_libusbError(r); + } + else { + handle = LJUSB_OpenSpecificDevice(dev, &desc); + if (!handle) { + fprintf(stderr, "LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED failed to open device, errno: %i\n", errno); + } + } + gHotPlugConnectedCallback(dev, handle, gHotPlugUserContext); + } + else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { + fprintf(stderr, "LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT occurred\n"); + + gHotPlugDisconnectedCallback(dev, gHotPlugUserContext); + } + else { + fprintf(stderr, "unknown hotplug event type received\n"); + } + + // Always return 0 to indicate we want additional events. + return 0; +} + +bool LJUSB_RegisterHotPlug(unsigned long productID, + LJUSB_HotPlugConnectedCallback connectedCallback, + LJUSB_HotPlugDisconnectedCallback disconnectedCallback, + void *context) +{ + assert(connectedCallback); + assert(disconnectedCallback); + + int r = 1; + + if (!LJUSB_libusb_initialize()) { + return false; + } + + if (!libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + fprintf(stderr, "libusb hotplug not supported on this platform\n"); + LJUSB_libusbError(LIBUSB_ERROR_NOT_SUPPORTED); + return false; + } + + int actualProductID = (productID == 0) ? LIBUSB_HOTPLUG_MATCH_ANY : (int)productID; + + // Store the callbacks now, since LIBUSB_HOTPLUG_ENUMERATE below could result in them being call before libusb_hotplug_register_callback() even returns. + gHotPlugConnectedCallback = connectedCallback; + gHotPlugDisconnectedCallback = disconnectedCallback; + gHotPlugUserContext = context; + + r = libusb_hotplug_register_callback(gLJContext, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_ENUMERATE, + LJ_VENDOR_ID, + actualProductID, + LIBUSB_HOTPLUG_MATCH_ANY, + &LJUSB_HotPlugCallback, + NULL, + &gHotPlugCallbackHandle); + + if (r < 0) { + gHotPlugConnectedCallback = NULL; + gHotPlugDisconnectedCallback = NULL; + gHotPlugUserContext = NULL; + gHotPlugCallbackHandle = 0; + + fprintf(stderr, "failed to register hot plug callback due to:\n"); + LJUSB_libusbError(r); + return false; + } + + return true; +} + + +void LJUSB_DeregisterHotPlug(void) +{ + assert(gIsLibUSBInitialized); + + if (gLJContext && gHotPlugCallbackHandle) { + libusb_hotplug_deregister_callback(gLJContext, gHotPlugCallbackHandle); + + gHotPlugConnectedCallback = NULL; + gHotPlugDisconnectedCallback = NULL; + gHotPlugUserContext = NULL; + gHotPlugCallbackHandle = 0; + } + else { + fprintf(stderr, "LJUSB_DeregisterHotPlug: nothing to do\n"); + } +} + + +int LJUSB_HandleEventsTimeoutCompleted(struct timeval *tv, int *completed) +{ + if (!LJUSB_libusb_initialize()) { + return false; + } + + return libusb_handle_events_timeout_completed(gLJContext, tv, completed); +} + +DEVICE LJUSB_DeviceFromHandle(HANDLE hDevice) +{ + if (!hDevice) { + return NULL; + } + + libusb_device *device = libusb_get_device(hDevice); + return device; +} + +void LJUSB_RefCountIncrement(DEVICE device) +{ + assert(device); + libusb_ref_device(device); +} + +void LJUSB_RefCountDecrement(DEVICE device) +{ + assert(device); + libusb_unref_device(device); +} + bool LJUSB_ResetConnection(HANDLE hDevice) { int r; diff --git a/liblabjackusb/labjackusb.h b/liblabjackusb/labjackusb.h index 0d784c1..8aa618e 100644 --- a/liblabjackusb/labjackusb.h +++ b/liblabjackusb/labjackusb.h @@ -78,8 +78,10 @@ #define LJUSB_LIBRARY_VERSION 2.0700f #include +#include -typedef void * HANDLE; +typedef void * HANDLE; // Really libusb_device_handle*. +typedef void * DEVICE; // Really libusb_device*. typedef unsigned int UINT; typedef unsigned char BYTE; @@ -212,6 +214,51 @@ HANDLE LJUSB_OpenDevice(UINT DevNum, unsigned int dwReserved, unsigned long Prod // dwReserved = Not used, set to 0. // ProductID = The product ID of the LabJack USB device. + +typedef void (*LJUSB_HotPlugConnectedCallback)(DEVICE hDevice, HANDLE hHandle, void *context); +// The function signature for a hot plug connection callback. +// The provided hDevice will never be NULL. +// hHandle may be NULL if the internal call to LJUSB_OpenDevice() failed. + +typedef void (*LJUSB_HotPlugDisconnectedCallback)(DEVICE hDevice, void *context); +// The function signature for a hot plug disconnection callback. +// The provided hDevice will never be NULL. + +bool LJUSB_RegisterHotPlug(unsigned long productID, + LJUSB_HotPlugConnectedCallback connectedCallback, + LJUSB_HotPlugDisconnectedCallback disconnectedCallback, + void *context); +// Registers for notification of Labjack devices being connected or disconnected. +// Returns true if successfully setup, or false if some error occurs. +// If true is returned, must eventually be balanced with a call to LJUSB_DeregisterHotPlug(). +// Do not invoke this more than once without first calling LJUSB_DeregisterHotPlug(). +// If you want notifications for all Labjack models, pass the special value 0 as productID. +// The callbacks will be called when a device is connected or disconnected. +// Both callback functions must be provided, neither may be NULL. +// Note: device enumeration is performed immediately, so be prepared for the callback to occur even before this function returns. +// Also, the callback may be invoked from a private libsub thread (or not). +// For connected devices, LJUSB_OpenDevice() is called for you. +// For disconnected devices, you need to call LJUSB_CloseDevice() yourself. +// The context pointer can be anything, including NULL; it is not used, it is merely passed back to your callbacks. + +void LJUSB_DeregisterHotPlug(void); +// Deregisters for notification of Labjack devices being connected or disconnected. +// Must eventually be called for every successful call to LJUSB_RegisterHotPlug(). + +int LJUSB_HandleEventsTimeoutCompleted(struct timeval *tv, int *completed); +// In order for the hot plug callbacks to ever be called, you must call this function, for example, in your application's event loop. + +DEVICE LJUSB_DeviceFromHandle(HANDLE hDevice); +// Returns the DEVICE that owns the given HANDLE. Returns NULL if NULL is given. +// This can be useful within your hot plug connection callbacks. + +void LJUSB_RefCountIncrement(DEVICE device); +// Increments the reference count of the given device. Do not pass NULL. +// If your hot plug bookkeeping holds onto DEVICES, you should increment the reference count to make sure the object does not get deallocated while you are still holding on to it. + +void LJUSB_RefCountDecrement(DEVICE device); +// Decrements the reference count of the given device. Do not pass NULL. + bool LJUSB_ResetConnection(HANDLE hDevice); // Performs a USB port reset to reinitialize a device. // Returns true on success, or false on error and errno is set. @@ -286,7 +333,7 @@ void LJUSB_CloseDevice(HANDLE hDevice); // Closes the handle of a LabJack USB device. bool LJUSB_IsHandleValid(HANDLE hDevice); -// Returns true if the handle is valid; this is, it is still connected to a +// Returns true if the handle is valid; that is, it is still connected to a // device on the system. unsigned short LJUSB_GetDeviceDescriptorReleaseNumber(HANDLE hDevice);