Skip to content

Commit

Permalink
Added hotplug support
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seanm committed Dec 21, 2021
1 parent 2afb07a commit ced0f20
Show file tree
Hide file tree
Showing 2 changed files with 198 additions and 2 deletions.
148 changes: 148 additions & 0 deletions liblabjackusb/labjackusb.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <sys/utsname.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>

#include <libusb-1.0/libusb.h>

Expand Down Expand Up @@ -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 };

Expand Down Expand Up @@ -717,6 +722,149 @@ 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\n");
}
}
gHotPlugConnectedCallback(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 productID = (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,
productID,
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)
{
libusb_ref_device(device);
}

void LJUSB_RefCountDecrement(DEVICE device)
{
libusb_unref_device(device);
}

bool LJUSB_ResetConnection(HANDLE hDevice)
{
int r;
Expand Down
52 changes: 50 additions & 2 deletions liblabjackusb/labjackusb.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@
#define LJUSB_LIBRARY_VERSION 2.0600f

#include <stdbool.h>
#include <sys/time.h>

typedef void * HANDLE;
typedef void * HANDLE; // Really libusb_device_handle*.
typedef void * DEVICE; // Really libusb_device*.
typedef unsigned int UINT;
typedef unsigned char BYTE;

Expand Down Expand Up @@ -210,6 +212,52 @@ 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)(HANDLE hDevice, void *context);
// The function signature for a hot plug connection callback.
// hDevice may be NULL if the internal call to LJUSB_OpenDevice() failed.
// Note the asymetry between connection (giving a HANDLE) and disconnection (giving a DEVICE).

typedef void (*LJUSB_HotPlugDisconnectedCallback)(DEVICE hDevice, void *context);
// The function signature for a hot plug disconnection callback.
// hDevice will never be NULL.
// Note the asymetry between connection (giving a HANDLE) and disconnection (giving a DEVICE).

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 callback, where a HANDLE is provided. Your bookkeeping might want to keep track of the corresponding DEVICE for when the disconnection callback arrives (which provides a DEVICE, not a HANDLE).

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.
Expand Down Expand Up @@ -284,7 +332,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);
Expand Down

0 comments on commit ced0f20

Please sign in to comment.