diff --git a/lib/libusbx52/Makefile.am b/lib/libusbx52/Makefile.am index 68e5fa0..1e59b18 100644 --- a/lib/libusbx52/Makefile.am +++ b/lib/libusbx52/Makefile.am @@ -9,18 +9,18 @@ ACLOCAL_AMFLAGS = -I m4 # libusb stub library for use by test programs check_LTLIBRARIES = libusbx52.la -libusbx52_la_SOURCES = usb_x52_stub.c -libusbx52_la_CFLAGS = @LIBUSB_CFLAGS@ +libusbx52_la_SOURCES = usb_x52_stub.c usb_x52_hotplug.c usb_x52_vector.c +libusbx52_la_CFLAGS = @LIBUSB_CFLAGS@ -pthread libusbx52_la_LDFLAGS = -rpath /nowhere -module # Utility programs for use by tests check_PROGRAMS = x52test_create_device_list x52test_log_actions x52test_create_device_list_SOURCES = util/create_device_list.c -x52test_create_device_list_CFLAGS = @LIBUSB_CFLAGS@ +x52test_create_device_list_CFLAGS = @LIBUSB_CFLAGS@ -pthread x52test_log_actions_SOURCES = util/log_actions.c -x52test_log_actions_CFLAGS = @X52_INCLUDE@ @LIBUSB_CFLAGS@ +x52test_log_actions_CFLAGS = @X52_INCLUDE@ @LIBUSB_CFLAGS@ -pthread x52test_log_actions_LDADD = libusbx52.la EXTRA_DIST = README.md libusbx52.h diff --git a/lib/libusbx52/libusbx52.h b/lib/libusbx52/libusbx52.h index e7d7ca1..78ce355 100644 --- a/lib/libusbx52/libusbx52.h +++ b/lib/libusbx52/libusbx52.h @@ -7,7 +7,7 @@ */ #include -// #include +#include #include struct libusb_device { @@ -23,6 +23,17 @@ struct libusb_context { int debug_level; int num_devices; struct libusb_device *devices; + + // Hotplug support + int hotplug_vid; + int hotplug_pid; + libusb_hotplug_event events; + libusb_hotplug_callback_fn callback; + void * cb_user_data; + + // Hotplug threading + volatile int stop_thread; + pthread_t hotplug_pthread; }; struct libusb_device_handle { @@ -65,3 +76,20 @@ struct libusb_device_handle { */ #define DEFAULT_OUTPUT_DATA_FILE "/tmp/libusbx52_output_data" +/** + * @brief Device update FIFO environment variable + * + * This is used by the test driver to update the simulated USB device list + */ +#define INPUT_DEVICE_FIFO_ENV "LIBUSBX52_DEVICE_FIFO" + +/** + * @brief Default file location of the device update FIFO + * + * This FIFO is read by a thread in libusbx52 and used to simulate the hotplug + * functionality of libusb. + */ +#define DEFAULT_INPUT_DEVICE_FIFO_FILE "/tmp/libusbx52_device_fifo" + +libusb_device* vector_push(libusb_context *ctx, int vid, int pid); +libusb_device* vector_pop(libusb_context *ctx, int vid, int pid); diff --git a/lib/libusbx52/usb_x52_hotplug.c b/lib/libusbx52/usb_x52_hotplug.c new file mode 100644 index 0000000..f1b6a0d --- /dev/null +++ b/lib/libusbx52/usb_x52_hotplug.c @@ -0,0 +1,124 @@ +/* + * LibUSB stub driver for hotplug APIs + * + * Copyright (C) 2020 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#include +#include "libusbx52.h" + +#define CB_HANDLE_MAGIC 0xca11bacc + +static void *service_hotplug_events(void *args) +{ + libusb_context *ctx = args; + int parsed; + int vid; + int pid; + char action; + char *dev_fifo_file; + FILE *fifo; + libusb_device *dev; + + // Get the filename of the FIFO + dev_fifo_file = getenv(INPUT_DEVICE_FIFO_ENV); + if (dev_fifo_file == NULL || dev_fifo_file[0] == '\0') { + dev_fifo_file = DEFAULT_INPUT_DEVICE_FIFO_FILE; + } + + // Remove the FIFO if it exists + unlink(dev_fifo_file); + + // Create the FIFO + mkfifo(dev_fifo_file, 0777); + + fifo = fopen(dev_fifo_file, "r"); + if (fifo == NULL) { + return NULL; + } + + while (!ctx->stop_thread) { + parsed = fscanf(fifo, "%c %x %x", &action, &vid, &pid); + if (parsed == 3) { + switch (action) { + case '+': + dev = vector_push(ctx, vid, pid); + if ((ctx->events & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) && + (ctx->hotplug_vid == LIBUSB_HOTPLUG_MATCH_ANY || ctx->hotplug_vid == vid) && + (ctx->hotplug_pid == LIBUSB_HOTPLUG_MATCH_ANY || ctx->hotplug_pid == pid)) { + (ctx->callback)(ctx, dev, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED, + ctx->cb_user_data); + } + break; + + case '-': + dev = vector_pop(ctx, vid, pid); + if ((ctx->events & LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) && + (ctx->hotplug_vid == LIBUSB_HOTPLUG_MATCH_ANY || ctx->hotplug_vid == vid) && + (ctx->hotplug_pid == LIBUSB_HOTPLUG_MATCH_ANY || ctx->hotplug_pid == pid)) { + (ctx->callback)(ctx, dev, + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + ctx->cb_user_data); + } + break; + + case 'x': + /* Terminate the loop */ + ctx->stop_thread = 1; + break; + + } + } + } + + // Close the FIFO + fclose(fifo); + return NULL; +} + +int libusb_hotplug_register_callback(libusb_context *ctx, + libusb_hotplug_event events, + libusb_hotplug_flag flags, + int vendor_id, + int product_id, + int dev_class, + libusb_hotplug_callback_fn cb_fn, + void *user_data, + libusb_hotplug_callback_handle *cb_handle) +{ + // Save the events, flags, etc in the context. + ctx->hotplug_vid = vendor_id; + ctx->hotplug_pid = product_id; + ctx->events = events; + + ctx->callback = cb_fn; + ctx->cb_user_data = user_data; + + // Spawn the thread + pthread_create(&(ctx->hotplug_pthread), NULL, service_hotplug_events, ctx); + + // Since the stub library only accepts a single callback function, + // we will return a static value that will be used to check when + // deregistering. Only if the value matches will we terminate the thread. + *cb_handle = CB_HANDLE_MAGIC; + return LIBUSB_SUCCESS; +} + +void libusb_hotplug_deregister_callback(libusb_context *ctx, + libusb_hotplug_callback_handle cb_handle) +{ + if (cb_handle == CB_HANDLE_MAGIC) { + ctx->stop_thread = 1; + pthread_join(ctx->hotplug_pthread, NULL); + } +} diff --git a/lib/libusbx52/usb_x52_stub.c b/lib/libusbx52/usb_x52_stub.c index 9209539..5eb0dcd 100644 --- a/lib/libusbx52/usb_x52_stub.c +++ b/lib/libusbx52/usb_x52_stub.c @@ -51,7 +51,6 @@ int libusb_init(libusb_context **ctx) } /* Determine the number of devices in the file */ - dev_count = 0; do { parsed = fscanf(dev_list, "%x %x", &vid, &pid); /* @@ -61,40 +60,11 @@ int libusb_init(libusb_context **ctx) if (parsed < 2) { break; } - dev_count++; - } while (!feof(dev_list)); - /* Make sure we have at least 1 device */ - if (dev_count == 0) { - rc = LIBUSB_ERROR_NOT_FOUND; - goto init_err_recovery; - } - - /* We now have the number of devices, allocate memory for them */ - tmp_ctx->devices = calloc(dev_count, sizeof(*(tmp_ctx->devices))); - if (tmp_ctx->devices == NULL) { - rc = LIBUSB_ERROR_NO_MEM; - goto init_err_recovery; - } - tmp_ctx->num_devices = dev_count; - - /* Rewind and read the file again, but now put them into the device list */ - rewind(dev_list); - - for (i = 0; i < dev_count && !feof(dev_list); i++) { - /* Set the base fields */ - tmp_ctx->devices[i].context = tmp_ctx; - tmp_ctx->devices[i].index = i; - - parsed = fscanf(dev_list, "%x %x", &vid, &pid); - if (parsed < 2) { - /* Parse error, skip this device */ - continue; + if (vector_push(tmp_ctx, vid, pid) == NULL) { + goto init_err_recovery; } - /* Set the VID & PID */ - tmp_ctx->devices[i].desc.idVendor = vid; - tmp_ctx->devices[i].desc.idProduct = pid; - } + } while (!feof(dev_list)); /* Done, close the file and return */ fclose(dev_list); @@ -103,6 +73,11 @@ int libusb_init(libusb_context **ctx) return LIBUSB_SUCCESS; init_err_recovery: + /* Free the device list, if it is available */ + if (tmp_ctx->devices != NULL) { + free(tmp_ctx->devices); + } + /* Close the device list file if it is open */ if (dev_list) { fclose(dev_list); diff --git a/lib/libusbx52/usb_x52_vector.c b/lib/libusbx52/usb_x52_vector.c new file mode 100644 index 0000000..6082917 --- /dev/null +++ b/lib/libusbx52/usb_x52_vector.c @@ -0,0 +1,83 @@ +/* + * LibUSB stub - device list + * + * Copyright (C) 2020 Nirenjan Krishnan (nirenjan@nirenjan.org) + * + * SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0 + */ + +#include +#include +#include +#include +#include "libusbx52.h" + +static pthread_mutex_t vector_mutex = PTHREAD_MUTEX_INITIALIZER; + +libusb_device * vector_push(libusb_context *ctx, int vid, int pid) +{ + int i; + int num_devices; + libusb_device *devlist; + + pthread_mutex_lock(&vector_mutex); + + // Make sure that we have an empty slot, if not, we need to reallocate the + // device list + for (i = 0; i < ctx->num_devices; i++) { + if (ctx->devices[i].context == NULL) { + break; + } + } + + if (i == ctx->num_devices) { + // No empty slots, we will need to reallocate the device list + num_devices = ctx->num_devices + 1; + devlist = calloc(num_devices, sizeof(*devlist)); + if (devlist == NULL) { + pthread_mutex_unlock(&vector_mutex); + return NULL; + } + memcpy(devlist, ctx->devices, ctx->num_devices * sizeof(*devlist)); + ctx->num_devices = num_devices; + + free(ctx->devices); + ctx->devices = devlist; + } + + ctx->devices[i].context = ctx; + ctx->devices[i].index = i; + ctx->devices[i].desc.idVendor = vid; + ctx->devices[i].desc.idProduct = pid; + + pthread_mutex_unlock(&vector_mutex); + return &(ctx->devices[i]); +} + +libusb_device * vector_pop(libusb_context *ctx, int vid, int pid) +{ + int i; + libusb_device *dev; + + // Search through the device list for a matching VID/PID pair + // If found, then delete it from the list + + pthread_mutex_lock(&vector_mutex); + + for (i = 0; i < ctx->num_devices; i++) { + dev = &(ctx->devices[i]); + if (dev->desc.idVendor == vid && dev->desc.idProduct == pid) { + break; + } + } + + if (i < ctx->num_devices) { + memset(dev, 0, sizeof(*dev)); + } else { + dev = NULL; + } + + pthread_mutex_unlock(&vector_mutex); + + return dev; +}