Skip to content

Commit

Permalink
Create hidapi threading abstraction so libusb can be built on platfor…
Browse files Browse the repository at this point in the history
…ms without pthreads
  • Loading branch information
slouken committed Jun 1, 2023
1 parent d3013f0 commit 3510842
Show file tree
Hide file tree
Showing 2 changed files with 192 additions and 93 deletions.
122 changes: 29 additions & 93 deletions libusb/hid.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
#include <sys/ioctl.h>
#include <sys/utsname.h>
#include <fcntl.h>
#include <pthread.h>
#include <wchar.h>

/* GNU / LibUSB */
Expand All @@ -51,66 +50,10 @@

#include "hidapi_libusb.h"

#if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__

/* Barrier implementation because Android/Bionic don't have pthread_barrier.
This implementation came from Brent Priddy and was posted on
StackOverflow. It is used with his permission. */
typedef int pthread_barrierattr_t;
typedef struct pthread_barrier {
pthread_mutex_t mutex;
pthread_cond_t cond;
int count;
int trip_count;
} pthread_barrier_t;

static int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned int count)
{
if(count == 0) {
errno = EINVAL;
return -1;
}

if(pthread_mutex_init(&barrier->mutex, 0) < 0) {
return -1;
}
if(pthread_cond_init(&barrier->cond, 0) < 0) {
pthread_mutex_destroy(&barrier->mutex);
return -1;
}
barrier->trip_count = count;
barrier->count = 0;

return 0;
}

static int pthread_barrier_destroy(pthread_barrier_t *barrier)
{
pthread_cond_destroy(&barrier->cond);
pthread_mutex_destroy(&barrier->mutex);
return 0;
}

static int pthread_barrier_wait(pthread_barrier_t *barrier)
{
pthread_mutex_lock(&barrier->mutex);
++(barrier->count);
if(barrier->count >= barrier->trip_count)
{
barrier->count = 0;
pthread_cond_broadcast(&barrier->cond);
pthread_mutex_unlock(&barrier->mutex);
return 1;
}
else
{
pthread_cond_wait(&barrier->cond, &(barrier->mutex));
pthread_mutex_unlock(&barrier->mutex);
return 0;
}
}

#ifndef HIDAPI_THREAD_MODEL_INCLUDE
#define HIDAPI_THREAD_MODEL_INCLUDE "hidapi_thread_pthread.h"
#endif
#include HIDAPI_THREAD_MODEL_INCLUDE

#ifdef __cplusplus
extern "C" {
Expand Down Expand Up @@ -168,10 +111,7 @@ struct hid_device_ {
int blocking; /* boolean */

/* Read thread objects */
pthread_t thread;
pthread_mutex_t mutex; /* Protects input_reports */
pthread_cond_t condition;
pthread_barrier_t barrier; /* Ensures correct startup sequence */
hidapi_thread_state thread_state;
int shutdown_thread;
int transfer_loop_finished;
struct libusb_transfer *transfer;
Expand Down Expand Up @@ -201,19 +141,15 @@ static hid_device *new_hid_device(void)
hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
dev->blocking = 1;

pthread_mutex_init(&dev->mutex, NULL);
pthread_cond_init(&dev->condition, NULL);
pthread_barrier_init(&dev->barrier, NULL, 2);
hidapi_thread_state_init(&dev->thread_state);

return dev;
}

static void free_hid_device(hid_device *dev)
{
/* Clean up the thread objects */
pthread_barrier_destroy(&dev->barrier);
pthread_cond_destroy(&dev->condition);
pthread_mutex_destroy(&dev->mutex);
hidapi_thread_state_destroy(&dev->thread_state);

hid_free_enumeration(dev->device_info);

Expand Down Expand Up @@ -912,13 +848,13 @@ static void read_callback(struct libusb_transfer *transfer)
rpt->len = transfer->actual_length;
rpt->next = NULL;

pthread_mutex_lock(&dev->mutex);
hidapi_thread_mutex_lock(&dev->thread_state);

/* Attach the new report object to the end of the list. */
if (dev->input_reports == NULL) {
/* The list is empty. Put it at the root. */
dev->input_reports = rpt;
pthread_cond_signal(&dev->condition);
hidapi_thread_cond_signal(&dev->thread_state);
}
else {
/* Find the end of the list and attach. */
Expand All @@ -937,7 +873,7 @@ static void read_callback(struct libusb_transfer *transfer)
return_data(dev, NULL, 0);
}
}
pthread_mutex_unlock(&dev->mutex);
hidapi_thread_mutex_unlock(&dev->thread_state);
}
else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) {
dev->shutdown_thread = 1;
Expand Down Expand Up @@ -996,7 +932,7 @@ static void *read_thread(void *param)
}

/* Notify the main thread that the read thread is up and running. */
pthread_barrier_wait(&dev->barrier);
hidapi_thread_barrier_wait(&dev->thread_state);

/* Handle all the events. */
while (!dev->shutdown_thread) {
Expand Down Expand Up @@ -1028,15 +964,15 @@ static void *read_thread(void *param)
make sure that a thread which is about to go to sleep waiting on
the condition actually will go to sleep before the condition is
signaled. */
pthread_mutex_lock(&dev->mutex);
pthread_cond_broadcast(&dev->condition);
pthread_mutex_unlock(&dev->mutex);
hidapi_thread_mutex_lock(&dev->thread_state);
hidapi_thread_cond_broadcast(&dev->thread_state);
hidapi_thread_mutex_unlock(&dev->thread_state);

/* The dev->transfer->buffer and dev->transfer objects are cleaned up
in hid_close(). They are not cleaned up here because this thread
could end either due to a disconnect or due to a user
call to hid_close(). In both cases the objects can be safely
cleaned up after the call to pthread_join() (in hid_close()), but
cleaned up after the call to hidapi_thread_join() (in hid_close()), but
since hid_close() calls libusb_cancel_transfer(), on these objects,
they can not be cleaned up here. */

Expand Down Expand Up @@ -1128,15 +1064,15 @@ static int hidapi_initialize_device(hid_device *dev, int config_number, const st
}
}

pthread_create(&dev->thread, NULL, read_thread, dev);
hidapi_thread_create(&dev->thread_state, read_thread, dev);

/* Wait here for the read thread to be initialized. */
pthread_barrier_wait(&dev->barrier);
hidapi_thread_barrier_wait(&dev->thread_state);
return 1;
}


hid_device * HID_API_EXPORT hid_open_path(const char *path)
HID_API_EXPORT hid_device *hid_open_path(const char *path)
{
hid_device *dev = NULL;

Expand Down Expand Up @@ -1347,7 +1283,7 @@ static int return_data(hid_device *dev, unsigned char *data, size_t length)
static void cleanup_mutex(void *param)
{
hid_device *dev = param;
pthread_mutex_unlock(&dev->mutex);
hidapi_thread_mutex_unlock(&dev->thread_state);
}


Expand All @@ -1363,8 +1299,8 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
/* error: variable ‘bytes_read’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered] */
int bytes_read; /* = -1; */

pthread_mutex_lock(&dev->mutex);
pthread_cleanup_push(&cleanup_mutex, dev);
hidapi_thread_mutex_lock(&dev->thread_state);
hidapi_thread_cleanup_push(cleanup_mutex, dev);

bytes_read = -1;

Expand All @@ -1385,7 +1321,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
if (milliseconds == -1) {
/* Blocking */
while (!dev->input_reports && !dev->shutdown_thread) {
pthread_cond_wait(&dev->condition, &dev->mutex);
hidapi_thread_cond_wait(&dev->thread_state);
}
if (dev->input_reports) {
bytes_read = return_data(dev, data, length);
Expand All @@ -1395,7 +1331,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
/* Non-blocking, but called with timeout. */
int res;
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
hidapi_thread_gettime(&ts);
ts.tv_sec += milliseconds / 1000;
ts.tv_nsec += (milliseconds % 1000) * 1000000;
if (ts.tv_nsec >= 1000000000L) {
Expand All @@ -1404,7 +1340,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
}

while (!dev->input_reports && !dev->shutdown_thread) {
res = pthread_cond_timedwait(&dev->condition, &dev->mutex, &ts);
res = hidapi_thread_cond_timedwait(&dev->thread_state, &ts);
if (res == 0) {
if (dev->input_reports) {
bytes_read = return_data(dev, data, length);
Expand All @@ -1415,7 +1351,7 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
or the read thread was shutdown. Run the
loop again (ie: don't break). */
}
else if (res == ETIMEDOUT) {
else if (res == HIDAPI_THREAD_TIMED_OUT) {
/* Timed out. */
bytes_read = 0;
break;
Expand All @@ -1433,8 +1369,8 @@ int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t
}

ret:
pthread_mutex_unlock(&dev->mutex);
pthread_cleanup_pop(0);
hidapi_thread_mutex_unlock(&dev->thread_state);
hidapi_thread_cleanup_pop(0);

return bytes_read;
}
Expand Down Expand Up @@ -1552,7 +1488,7 @@ void HID_API_EXPORT hid_close(hid_device *dev)
libusb_cancel_transfer(dev->transfer);

/* Wait for read_thread() to end. */
pthread_join(dev->thread, NULL);
hidapi_thread_join(&dev->thread_state);

/* Clean up the Transfer objects allocated in read_thread(). */
free(dev->transfer->buffer);
Expand All @@ -1575,11 +1511,11 @@ void HID_API_EXPORT hid_close(hid_device *dev)
libusb_close(dev->device_handle);

/* Clear out the queue of received reports. */
pthread_mutex_lock(&dev->mutex);
hidapi_thread_mutex_lock(&dev->thread_state);
while (dev->input_reports) {
return_data(dev, NULL, 0);
}
pthread_mutex_unlock(&dev->mutex);
hidapi_thread_mutex_unlock(&dev->thread_state);

free_hid_device(dev);
}
Expand Down
Loading

0 comments on commit 3510842

Please sign in to comment.