Use a reference counted USB session

Replace the global USB library context with a reference counted session
to manage the lifetime of the USB library context. For the libusb based
implementation, this is actually a much better match for the underlying
libusb api, and allows to eliminate the global state. For the hidapi
based implementation, the global state is unavoidable because the hidapi
doesn't support multiple sessions. Therefore we use a singleton session.
This commit is contained in:
Jef Driesen 2017-07-20 11:35:27 +02:00
parent 5bec560673
commit 9477791bfe

View File

@ -68,8 +68,16 @@ typedef pthread_mutex_t dc_mutex_t;
#define ISINSTANCE(device) dc_iostream_isinstance((device), &dc_usbhid_vtable)
typedef struct dc_usbhid_session_t {
size_t refcount;
#if defined(USE_LIBUSB)
libusb_context *handle;
#endif
} dc_usbhid_session_t;
struct dc_usbhid_device_t {
unsigned short vid, pid;
dc_usbhid_session_t *session;
};
#ifdef USBHID
@ -84,6 +92,7 @@ static dc_status_t dc_usbhid_close (dc_iostream_t *iostream);
typedef struct dc_usbhid_iterator_t {
dc_iterator_t base;
dc_filter_t filter;
dc_usbhid_session_t *session;
#if defined(USE_LIBUSB)
struct libusb_device **devices;
size_t count;
@ -97,6 +106,7 @@ typedef struct dc_usbhid_t {
/* Base class. */
dc_iostream_t base;
/* Internal state. */
dc_usbhid_session_t *session;
#if defined(USE_LIBUSB)
libusb_device_handle *handle;
int interface;
@ -133,10 +143,9 @@ static const dc_iostream_vtable_t dc_usbhid_vtable = {
dc_usbhid_close, /* close */
};
#ifdef USE_HIDAPI
static dc_mutex_t g_usbhid_mutex = DC_MUTEX_INIT;
static size_t g_usbhid_refcount = 0;
#ifdef USE_LIBUSB
static libusb_context *g_usbhid_ctx = NULL;
static dc_usbhid_session_t *g_usbhid_session = NULL;
#endif
#if defined(USE_LIBUSB)
@ -162,6 +171,7 @@ syserror(int errcode)
}
#endif
#ifdef USE_HIDAPI
static void
dc_mutex_lock (dc_mutex_t *mutex)
{
@ -183,55 +193,113 @@ dc_mutex_unlock (dc_mutex_t *mutex)
pthread_mutex_unlock (mutex);
#endif
}
#endif
static dc_status_t
dc_usbhid_init (dc_context_t *context)
dc_usbhid_session_new (dc_usbhid_session_t **out, dc_context_t *context)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_usbhid_session_t *session = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
#ifdef USE_HIDAPI
dc_mutex_lock (&g_usbhid_mutex);
if (g_usbhid_refcount == 0) {
#if defined(USE_LIBUSB)
int rc = libusb_init (&g_usbhid_ctx);
if (rc != LIBUSB_SUCCESS) {
ERROR (context, "Failed to initialize usb support (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error;
}
#elif defined(USE_HIDAPI)
int rc = hid_init();
if (rc < 0) {
ERROR (context, "Failed to initialize usb support.");
status = DC_STATUS_IO;
goto error;
}
if (g_usbhid_session) {
g_usbhid_session->refcount++;
*out = g_usbhid_session;
dc_mutex_unlock (&g_usbhid_mutex);
return DC_STATUS_SUCCESS;
}
#endif
session = (dc_usbhid_session_t *) malloc (sizeof(dc_usbhid_session_t));
if (session == NULL) {
ERROR (context, "Failed to allocate memory.");
status = DC_STATUS_NOMEMORY;
goto error_unlock;
}
g_usbhid_refcount++;
session->refcount = 1;
#if defined(USE_LIBUSB)
int rc = libusb_init (&session->handle);
if (rc != LIBUSB_SUCCESS) {
ERROR (context, "Failed to initialize usb support (%s).",
libusb_error_name (rc));
status = syserror (rc);
goto error_free;
}
#elif defined(USE_HIDAPI)
int rc = hid_init();
if (rc < 0) {
ERROR (context, "Failed to initialize usb support.");
status = DC_STATUS_IO;
goto error_free;
}
g_usbhid_session = session;
error:
dc_mutex_unlock (&g_usbhid_mutex);
#endif
*out = session;
return status;
error_free:
free (session);
error_unlock:
#ifdef USE_HIDAPI
dc_mutex_unlock (&g_usbhid_mutex);
#endif
return status;
}
static dc_status_t
dc_usbhid_exit (void)
static dc_usbhid_session_t *
dc_usbhid_session_ref (dc_usbhid_session_t *session)
{
dc_mutex_lock (&g_usbhid_mutex);
if (session == NULL)
return NULL;
if (--g_usbhid_refcount == 0) {
#ifdef USE_HIDAPI
dc_mutex_lock (&g_usbhid_mutex);
#endif
session->refcount++;
#ifdef USE_HIDAPI
dc_mutex_unlock (&g_usbhid_mutex);
#endif
return session;
}
static dc_status_t
dc_usbhid_session_unref (dc_usbhid_session_t *session)
{
if (session == NULL)
return DC_STATUS_SUCCESS;
#ifdef USE_HIDAPI
dc_mutex_lock (&g_usbhid_mutex);
#endif
if (--session->refcount == 0) {
#if defined(USE_LIBUSB)
libusb_exit (g_usbhid_ctx);
g_usbhid_ctx = NULL;
libusb_exit (session->handle);
#elif defined(USE_HIDAPI)
hid_exit ();
g_usbhid_session = NULL;
#endif
free (session);
}
#ifdef USE_HIDAPI
dc_mutex_unlock (&g_usbhid_mutex);
#endif
return DC_STATUS_SUCCESS;
}
@ -258,6 +326,12 @@ dc_usbhid_device_get_pid (dc_usbhid_device_t *device)
void
dc_usbhid_device_free(dc_usbhid_device_t *device)
{
if (device == NULL)
return;
#ifdef USBHID
dc_usbhid_session_unref (device->session);
#endif
free (device);
}
@ -278,7 +352,7 @@ dc_usbhid_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto
}
// Initialize the usb library.
status = dc_usbhid_init (context);
status = dc_usbhid_session_new (&iterator->session, context);
if (status != DC_STATUS_SUCCESS) {
goto error_free;
}
@ -286,12 +360,12 @@ dc_usbhid_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto
#if defined(USE_LIBUSB)
// Enumerate the USB devices.
struct libusb_device **devices = NULL;
ssize_t ndevices = libusb_get_device_list (g_usbhid_ctx, &devices);
ssize_t ndevices = libusb_get_device_list (iterator->session->handle, &devices);
if (ndevices < 0) {
ERROR (context, "Failed to enumerate the usb devices (%s).",
libusb_error_name (ndevices));
status = syserror (ndevices);
goto error_usb_exit;
goto error_session_unref;
}
iterator->devices = devices;
@ -301,7 +375,7 @@ dc_usbhid_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto
struct hid_device_info *devices = hid_enumerate(0x0, 0x0);
if (devices == NULL) {
status = DC_STATUS_IO;
goto error_usb_exit;
goto error_session_unref;
}
iterator->devices = devices;
@ -313,8 +387,8 @@ dc_usbhid_iterator_new (dc_iterator_t **out, dc_context_t *context, dc_descripto
return DC_STATUS_SUCCESS;
error_usb_exit:
dc_usbhid_exit ();
error_session_unref:
dc_usbhid_session_unref (iterator->session);
error_free:
dc_iterator_deallocate ((dc_iterator_t *) iterator);
return status;
@ -407,6 +481,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out)
return DC_STATUS_NOMEMORY;
}
device->session = dc_usbhid_session_ref (iterator->session);
device->vid = dev.idVendor;
device->pid = dev.idProduct;
@ -432,6 +507,7 @@ dc_usbhid_iterator_next (dc_iterator_t *abstract, void *out)
return DC_STATUS_NOMEMORY;
}
device->session = dc_usbhid_session_ref (iterator->session);
device->vid = current->vendor_id;
device->pid = current->product_id;
@ -454,7 +530,7 @@ dc_usbhid_iterator_free (dc_iterator_t *abstract)
#elif defined(USE_HIDAPI)
hid_free_enumeration (iterator->devices);
#endif
dc_usbhid_exit ();
dc_usbhid_session_unref (iterator->session);
return DC_STATUS_SUCCESS;
}
@ -480,7 +556,7 @@ dc_usbhid_open (dc_iostream_t **out, dc_context_t *context, unsigned int vid, un
}
// Initialize the usb library.
status = dc_usbhid_init (context);
status = dc_usbhid_session_new (&usbhid->session, context);
if (status != DC_STATUS_SUCCESS) {
goto error_free;
}
@ -491,12 +567,12 @@ dc_usbhid_open (dc_iostream_t **out, dc_context_t *context, unsigned int vid, un
int rc = 0;
// Enumerate the USB devices.
ssize_t ndevices = libusb_get_device_list (g_usbhid_ctx, &devices);
ssize_t ndevices = libusb_get_device_list (usbhid->session->handle, &devices);
if (ndevices < 0) {
ERROR (context, "Failed to enumerate the usb devices (%s).",
libusb_error_name (ndevices));
status = syserror (ndevices);
goto error_usb_exit;
goto error_session_unref;
}
// Find the first device matching the VID/PID.
@ -615,7 +691,7 @@ dc_usbhid_open (dc_iostream_t **out, dc_context_t *context, unsigned int vid, un
if (usbhid->handle == NULL) {
ERROR (context, "Failed to open the usb device.");
status = DC_STATUS_IO;
goto error_usb_exit;
goto error_session_unref;
}
usbhid->timeout = -1;
@ -633,8 +709,8 @@ error_usb_free_config:
error_usb_free_list:
libusb_free_device_list (devices, 1);
#endif
error_usb_exit:
dc_usbhid_exit ();
error_session_unref:
dc_usbhid_session_unref (usbhid->session);
error_free:
dc_iostream_deallocate ((dc_iostream_t *) usbhid);
return status;
@ -656,7 +732,7 @@ dc_usbhid_close (dc_iostream_t *abstract)
#elif defined(USE_HIDAPI)
hid_close(usbhid->handle);
#endif
dc_usbhid_exit();
dc_usbhid_session_unref (usbhid->session);
return status;
}