From 57b33aec01a86c28b30797ecbae394a2f07ddd42 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 25 May 2023 12:43:09 -0700 Subject: [PATCH] hidapi/libusb: allow building on Windows, using the SDL C runtime Signed-off-by: Sam Lantinga --- src/hidapi/SDL_hidapi_libusb.h | 202 +++++++++++++++++++++++++++++++++ src/hidapi/libusb/hid.c | 200 ++++++++++++++++++++++---------- 2 files changed, 344 insertions(+), 58 deletions(-) diff --git a/src/hidapi/SDL_hidapi_libusb.h b/src/hidapi/SDL_hidapi_libusb.h index ead421ba0..e0a124833 100644 --- a/src/hidapi/SDL_hidapi_libusb.h +++ b/src/hidapi/SDL_hidapi_libusb.h @@ -19,6 +19,28 @@ 3. This notice may not be removed or altered from any source distribution. */ +/* Define standard library functions in terms of SDL */ +#define HIDAPI_USING_SDL_RUNTIME +#define free SDL_free +#define iconv_t SDL_iconv_t +#define ICONV_CONST +#define iconv(a,b,c,d,e) SDL_iconv(a, (const char **)b, c, d, e) +#define iconv_open SDL_iconv_open +#define iconv_close SDL_iconv_close +#define malloc SDL_malloc +#define realloc SDL_realloc +#define setlocale(X, Y) NULL +#define snprintf SDL_snprintf +#define strcmp SDL_strcmp +#define strdup SDL_strdup +#define strncpy SDL_strlcpy +#ifdef tolower +#undef tolower +#endif +#define tolower SDL_tolower +#define wcsdup SDL_wcsdup + + #ifndef __FreeBSD__ /* this is awkwardly inlined, so we need to re-implement it here * so we can override the libusb_control_transfer call */ @@ -32,5 +54,185 @@ static int SDL_libusb_get_string_descriptor(libusb_device_handle *dev, #define libusb_get_string_descriptor SDL_libusb_get_string_descriptor #endif /* __FreeBSD__ */ +#define HIDAPI_THREAD_STATE_DEFINED + +/* 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 struct _SDL_ThreadBarrier +{ + SDL_Mutex *mutex; + SDL_Condition *cond; + Uint32 count; + Uint32 trip_count; +} SDL_ThreadBarrier; + +static int SDL_CreateThreadBarrier(SDL_ThreadBarrier *barrier, Uint32 count) +{ + SDL_assert(barrier != NULL); + SDL_assert(count != 0); + + barrier->mutex = SDL_CreateMutex(); + if (barrier->mutex == NULL) { + return -1; /* Error set by CreateMutex */ + } + barrier->cond = SDL_CreateCondition(); + if (barrier->cond == NULL) { + return -1; /* Error set by CreateCond */ + } + + barrier->trip_count = count; + barrier->count = 0; + + return 0; +} + +static void SDL_DestroyThreadBarrier(SDL_ThreadBarrier *barrier) +{ + SDL_DestroyCondition(barrier->cond); + SDL_DestroyMutex(barrier->mutex); +} + +static int SDL_WaitThreadBarrier(SDL_ThreadBarrier *barrier) +{ + SDL_LockMutex(barrier->mutex); + barrier->count += 1; + if (barrier->count >= barrier->trip_count) { + barrier->count = 0; + SDL_BroadcastCondition(barrier->cond); + SDL_UnlockMutex(barrier->mutex); + return 1; + } + SDL_WaitCondition(barrier->cond, barrier->mutex); + SDL_UnlockMutex(barrier->mutex); + return 0; +} + +#include "../thread/SDL_systhread.h" + +#define THREAD_STATE_WAIT_TIMED_OUT SDL_MUTEX_TIMEDOUT + +typedef struct +{ + SDL_Thread *thread; + SDL_Mutex *mutex; /* Protects input_reports */ + SDL_Condition *condition; + SDL_ThreadBarrier barrier; /* Ensures correct startup sequence */ + +} hid_device_thread_state; + +static void thread_state_init(hid_device_thread_state *state) +{ + state->mutex = SDL_CreateMutex(); + state->condition = SDL_CreateCondition(); + SDL_CreateThreadBarrier(&state->barrier, 2); +} + +static void thread_state_free(hid_device_thread_state *state) +{ + SDL_DestroyThreadBarrier(&state->barrier); + SDL_DestroyCondition(state->condition); + SDL_DestroyMutex(state->mutex); +} + +static void thread_state_push_cleanup(void (*routine)(void *), void *arg) +{ + /* There isn't an equivalent in SDL, and it's only useful for threads calling hid_read_timeout() */ +} + +static void thread_state_pop_cleanup(int execute) +{ +} + +static void thread_state_lock(hid_device_thread_state *state) +{ + SDL_LockMutex(state->mutex); +} + +static void thread_state_unlock(hid_device_thread_state *state) +{ + SDL_UnlockMutex(state->mutex); +} + +static void thread_state_wait_condition(hid_device_thread_state *state) +{ + SDL_WaitCondition(state->condition, state->mutex); +} + +static int thread_state_wait_condition_timeout(hid_device_thread_state *state, struct timespec *ts) +{ + Uint64 end_time; + Sint64 timeout_ns; + Sint32 timeout_ms; + + end_time = ts->tv_sec; + end_time *= 1000000000L; + end_time += ts->tv_nsec; + timeout_ns = (Sint64)(end_time - SDL_GetTicksNS()); + if (timeout_ns <= 0) { + timeout_ms = 0; + } else { + timeout_ms = (Sint32)SDL_NS_TO_MS(timeout_ns); + } + return SDL_WaitConditionTimeout(state->condition, state->mutex, timeout_ms); +} + +static void thread_state_signal_condition(hid_device_thread_state *state) +{ + SDL_SignalCondition(state->condition); +} + +static void thread_state_broadcast_condition(hid_device_thread_state *state) +{ + SDL_BroadcastCondition(state->condition); +} + +static void thread_state_wait_barrier(hid_device_thread_state *state) +{ + SDL_WaitThreadBarrier(&state->barrier); +} + +typedef struct +{ + void *(*func)(void*); + void *func_arg; + +} RunInputThreadParam; + +static int RunInputThread(void *param) +{ + RunInputThreadParam *data = (RunInputThreadParam *)param; + void *(*func)(void*) = data->func; + void *func_arg = data->func_arg; + SDL_free(data); + func(func_arg); + return 0; +} + +static void thread_state_create_thread(hid_device_thread_state *state, void *(*func)(void*), void *func_arg) +{ + RunInputThreadParam *param = (RunInputThreadParam *)malloc(sizeof(*param)); + /* Note that the hidapi code didn't check for thread creation failure. + * We'll crash if malloc() fails + */ + param->func = func; + param->func_arg = func_arg; + state->thread = SDL_CreateThreadInternal(RunInputThread, "libusb", 0, param); +} + +static void thread_state_join_thread(hid_device_thread_state *state) +{ + SDL_WaitThread(state->thread, NULL); +} + +static void thread_state_get_current_time(struct timespec *ts) +{ + Uint64 ns = SDL_GetTicksNS(); + + ts->tv_sec = ns / 1000000000L; + ts->tv_nsec = ns % 1000000000L; +} + #undef HIDAPI_H__ #include "libusb/hid.c" diff --git a/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c index 05b393f65..47a3e3e1b 100644 --- a/src/hidapi/libusb/hid.c +++ b/src/hidapi/libusb/hid.c @@ -20,6 +20,7 @@ https://github.com/libusb/hidapi . ********************************************************/ +#ifndef HIDAPI_USING_SDL_RUNTIME #define _GNU_SOURCE /* needed for wcsdup() before glibc 2.10 */ /* C */ @@ -37,7 +38,6 @@ #include #include #include -#include #include /* GNU / LibUSB */ @@ -48,9 +48,43 @@ #define ICONV_CONST #endif #endif +#endif /* HIDAPI_USING_SDL_RUNTIME */ #include "hidapi_libusb.h" +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DEBUG_PRINTF +#define LOG(...) fprintf(stderr, __VA_ARGS__) +#else +#define LOG(...) do {} while (0) +#endif + +#ifndef __FreeBSD__ +#define DETACH_KERNEL_DRIVER +#endif + +/* Uncomment to enable the retrieval of Usage and Usage Page in +hid_enumerate(). Warning, on platforms different from FreeBSD +this is very invasive as it requires the detach +and re-attach of the kernel driver. See comments inside hid_enumerate(). +libusb HIDAPI programs are encouraged to use the interface number +instead to differentiate between interfaces on a composite HID device. */ +/*#define INVASIVE_GET_USAGE*/ + +/* Linked List of input reports received from the device. */ +struct input_report { + uint8_t *data; + size_t len; + struct input_report *next; +}; + +#ifndef HIDAPI_THREAD_STATE_DEFINED + +#include + #if defined(__ANDROID__) && __ANDROID_API__ < __ANDROID_API_N__ /* Barrier implementation because Android/Bionic don't have pthread_barrier. @@ -112,35 +146,92 @@ static int pthread_barrier_wait(pthread_barrier_t *barrier) #endif -#ifdef __cplusplus -extern "C" { -#endif +#define THREAD_STATE_WAIT_TIMED_OUT ETIMEDOUT -#ifdef DEBUG_PRINTF -#define LOG(...) fprintf(stderr, __VA_ARGS__) -#else -#define LOG(...) do {} while (0) -#endif +typdef struct +{ + pthread_t thread; + pthread_mutex_t mutex; /* Protects input_reports */ + pthread_cond_t condition; + pthread_barrier_t barrier; /* Ensures correct startup sequence */ -#ifndef __FreeBSD__ -#define DETACH_KERNEL_DRIVER -#endif +} hid_device_thread_state; -/* Uncomment to enable the retrieval of Usage and Usage Page in -hid_enumerate(). Warning, on platforms different from FreeBSD -this is very invasive as it requires the detach -and re-attach of the kernel driver. See comments inside hid_enumerate(). -libusb HIDAPI programs are encouraged to use the interface number -instead to differentiate between interfaces on a composite HID device. */ -/*#define INVASIVE_GET_USAGE*/ +static void thread_state_init(hid_device_thread_state *state) +{ + pthread_mutex_init(&state->mutex, NULL); + pthread_cond_init(&state->condition, NULL); + pthread_barrier_init(&state->barrier, NULL, 2); +} -/* Linked List of input reports received from the device. */ -struct input_report { - uint8_t *data; - size_t len; - struct input_report *next; -}; +static void thread_state_free(hid_device_thread_state *state) +{ + pthread_barrier_destroy(&state->barrier); + pthread_cond_destroy(&state->condition); + pthread_mutex_destroy(&state->mutex); +} +static void thread_state_push_cleanup(void (*routine)(void *), void *arg) +{ + pthread_cleanup_push(&cleanup_mutex, dev); +} + +static void thread_state_pop_cleanup(int execute) +{ + pthread_cleanup_pop(execute); +} + +static void thread_state_lock(hid_device_thread_state *state) +{ + pthread_mutex_lock(&state->mutex); +} + +static void thread_state_unlock(hid_device_thread_state *state) +{ + pthread_mutex_unlock(&state->mutex); +} + +static void thread_state_wait_condition(hid_device_thread_state *state) +{ + pthread_cond_wait(&state->condition, &state->mutex); +} + +static int thread_state_wait_condition_timeout(hid_device_thread_state *state, struct timespec *ts) +{ + return pthread_cond_timedwait(&state->condition, &state->mutex, ts); +} + +static void thread_state_signal_condition(hid_device_thread_state *state) +{ + pthread_cond_signal(&state->condition); +} + +static void thread_state_broadcast_condition(hid_device_thread_state *state) +{ + pthread_cond_broadcast(&state->condition); +} + +static void thread_state_wait_barrier(hid_device_thread_state *state) +{ + pthread_barrier_wait(&state->barrier); +} + +static void thread_state_create_thread(hid_device_thread_state *state, void *(*func)(void*), void *func_arg) +{ + pthread_create(&dev->thread, NULL, func, param); +} + +static void thread_state_join_thread(hid_device_thread_state *state) +{ + pthread_join(state->thread, NULL); +} + +static void thread_state_get_current_time(struct timespec *ts) +{ + clock_gettime(CLOCK_REALTIME, ts); +} + +#endif /* !HIDAPI_THREAD_STATE_DEFINED */ struct hid_device_ { /* Handle to the actual device. */ @@ -171,10 +262,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 */ + hid_device_thread_state thread_state; int shutdown_thread; int transfer_loop_finished; struct libusb_transfer *transfer; @@ -208,9 +296,7 @@ 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); + thread_state_init(&dev->thread_state); return dev; } @@ -218,9 +304,7 @@ static hid_device *new_hid_device(void) 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); + thread_state_free(&dev->thread_state); hid_free_enumeration(dev->device_info); @@ -1143,13 +1227,13 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer) rpt->len = transfer->actual_length; rpt->next = NULL; - pthread_mutex_lock(&dev->mutex); + thread_state_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); + thread_state_signal_condition(&dev->thread_state); } else { /* Find the end of the list and attach. */ @@ -1168,7 +1252,7 @@ static void LIBUSB_CALL read_callback(struct libusb_transfer *transfer) return_data(dev, NULL, 0); } } - pthread_mutex_unlock(&dev->mutex); + thread_state_unlock(&dev->thread_state); } else if (transfer->status == LIBUSB_TRANSFER_CANCELLED) { dev->shutdown_thread = 1; @@ -1227,7 +1311,7 @@ static void *read_thread(void *param) } /* Notify the main thread that the read thread is up and running. */ - pthread_barrier_wait(&dev->barrier); + thread_state_wait_barrier(&dev->thread_state); /* Handle all the events. */ while (!dev->shutdown_thread) { @@ -1259,15 +1343,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); + thread_state_lock(&dev->thread_state); + thread_state_broadcast_condition(&dev->thread_state); + thread_state_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 thread_state_join() (in hid_close()), but since hid_close() calls libusb_cancel_transfer(), on these objects, they can not be cleaned up here. */ @@ -1445,15 +1529,15 @@ static int hidapi_initialize_device(hid_device *dev, int config_number, const st calculate_device_quirks(dev, desc.idVendor, desc.idProduct); - pthread_create(&dev->thread, NULL, read_thread, dev); + thread_state_create_thread(&dev->thread_state, read_thread, dev); /* Wait here for the read thread to be initialized. */ - pthread_barrier_wait(&dev->barrier); + thread_state_wait_barrier(&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; @@ -1673,7 +1757,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); + thread_state_unlock(&dev->thread_state); } @@ -1689,8 +1773,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); + thread_state_lock(&dev->thread_state); + thread_state_push_cleanup(cleanup_mutex, dev); bytes_read = -1; @@ -1711,7 +1795,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); + thread_state_wait_condition(&dev->thread_state); } if (dev->input_reports) { bytes_read = return_data(dev, data, length); @@ -1721,7 +1805,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); + thread_state_get_current_time(&ts); ts.tv_sec += milliseconds / 1000; ts.tv_nsec += (milliseconds % 1000) * 1000000; if (ts.tv_nsec >= 1000000000L) { @@ -1730,7 +1814,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 = thread_state_wait_condition_timeout(&dev->thread_state, &ts); if (res == 0) { if (dev->input_reports) { bytes_read = return_data(dev, data, length); @@ -1741,7 +1825,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 == THREAD_STATE_WAIT_TIMED_OUT) { /* Timed out. */ bytes_read = 0; break; @@ -1759,8 +1843,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); + thread_state_unlock(&dev->thread_state); + thread_state_pop_cleanup(0); return bytes_read; } @@ -1878,7 +1962,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); + thread_state_join_thread(&dev->thread_state); /* Clean up the Transfer objects allocated in read_thread(). */ free(dev->transfer->buffer); @@ -1901,11 +1985,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); + thread_state_lock(&dev->thread_state); while (dev->input_reports) { return_data(dev, NULL, 0); } - pthread_mutex_unlock(&dev->mutex); + thread_state_unlock(&dev->thread_state); free_hid_device(dev); }