diff options
author | Richard Hughes <richard@hughsie.com> | 2022-10-05 12:30:56 +0100 |
---|---|---|
committer | Richard Hughes <richard@hughsie.com> | 2022-10-14 11:21:22 +0100 |
commit | 922bb06bcc8d3bd3184439e6c6373e4d7af93483 (patch) | |
tree | d66cd0139453573f00654a0d8d20fcf58938e7b0 /gusb/gusb-source.c | |
parent | cfb40102b0985d52cbd5d9348dedd1a4cc06b10c (diff) | |
download | gusb-922bb06bcc8d3bd3184439e6c6373e4d7af93483.tar.gz |
Use GUsbSource on Linux to avoid threading issueswip/hughsie/GUsbSource
Having two threads interact is causing difficult to debug deadlocks. Just use
libusb_set_pollfd_notifiers() and use the locking in libusb_handle_events().
Diffstat (limited to 'gusb/gusb-source.c')
-rw-r--r-- | gusb/gusb-source.c | 165 |
1 files changed, 162 insertions, 3 deletions
diff --git a/gusb/gusb-source.c b/gusb/gusb-source.c index becb8c8..a6ed19b 100644 --- a/gusb/gusb-source.c +++ b/gusb/gusb-source.c @@ -10,13 +10,25 @@ * SECTION:gusb-source * @short_description: GSource integration for libusb * - * This object used to integrate libusb into the GLib main loop before we used - * a thread. It's now pretty much unused. + * This object can be used to integrate libusb into the GLib main loop. */ #include "config.h" +#include <libusb-1.0/libusb.h> +#include <stdlib.h> + +#include "gusb-context-private.h" +#include "gusb-context.h" +#include "gusb-source-private.h" #include "gusb-source.h" +#include "gusb-util.h" + +/* the <poll.h> header is not available on all platforms */ +#ifndef POLLIN +#define POLLIN 0x0001 +#define POLLOUT 0x0004 +#endif /** * g_usb_source_error_quark: @@ -34,6 +46,153 @@ g_usb_source_error_quark(void) return quark; } +struct _GUsbSource { + GSource source; + GSList *pollfds; + libusb_context *ctx; +}; + +static void +g_usb_source_pollfd_add(GUsbSource *self, int fd, short events) +{ + GPollFD *pollfd = g_new0(GPollFD, 1); + + pollfd->fd = fd; + pollfd->events = 0; + pollfd->revents = 0; + if (events & POLLIN) + pollfd->events |= G_IO_IN; + if (events & POLLOUT) + pollfd->events |= G_IO_OUT; + + self->pollfds = g_slist_prepend(self->pollfds, pollfd); + g_source_add_poll((GSource *)self, pollfd); +} + +static void +g_usb_source_pollfd_added_cb(int fd, short events, void *user_data) +{ + GUsbSource *self = user_data; + g_usb_source_pollfd_add(self, fd, events); +} + +static void +g_usb_source_pollfd_removed_cb(int fd, void *user_data) +{ + GUsbSource *self = user_data; + + /* find the pollfd in the list */ + for (GSList *elem = self->pollfds; elem != NULL; elem = elem->next) { + GPollFD *pollfd = elem->data; + if (pollfd->fd == fd) { + g_source_remove_poll((GSource *)self, pollfd); + g_free(pollfd); + self->pollfds = g_slist_delete_link(self->pollfds, elem); + return; + } + } + g_warning("couldn't find fd %d in list", fd); +} + +static gboolean +g_usb_source_prepare(GSource *source, gint *timeout) +{ + GUsbSource *self = (GUsbSource *)source; + + /* before the poll */ + libusb_lock_events(self->ctx); + + /* release the lock at least once per minute */ + *timeout = 60000; + return FALSE; +} + +static gboolean +g_usb_source_check(GSource *source) +{ + GUsbSource *self = (GUsbSource *)source; + + for (GSList *elem = self->pollfds; elem != NULL; elem = elem->next) { + GPollFD *pollfd = elem->data; + if (pollfd->revents) + return TRUE; + } + libusb_unlock_events(self->ctx); + return FALSE; +} + +static gboolean +g_usb_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) +{ + GUsbSource *self = (GUsbSource *)source; + struct timeval tv = {0, 0}; + gint rc; + + rc = libusb_handle_events_locked(self->ctx, &tv); + libusb_unlock_events(self->ctx); + if (rc < 0) + g_warning("failed to handle event: %s [%i]", g_usb_strerror(rc), rc); + + if (callback != NULL) + callback(user_data); + + return G_SOURCE_CONTINUE; +} + +static void +g_usb_source_finalize(GSource *source) +{ + GUsbSource *self = (GUsbSource *)source; + g_slist_free(self->pollfds); +} + +static GSourceFuncs usb_source_funcs = {g_usb_source_prepare, + g_usb_source_check, + g_usb_source_dispatch, + g_usb_source_finalize}; + +GUsbSource * +_g_usb_source_new(GMainContext *main_ctx, GUsbContext *gusb_ctx) +{ + GUsbSource *self; + const struct libusb_pollfd **pollfds; + + self = (GUsbSource *)g_source_new(&usb_source_funcs, sizeof(GUsbSource)); + self->pollfds = NULL; + self->ctx = _g_usb_context_get_context(gusb_ctx); + + /* watch the fd's already created */ + pollfds = libusb_get_pollfds(self->ctx); + for (guint i = 0; pollfds != NULL && pollfds[i] != NULL; i++) + g_usb_source_pollfd_add(self, pollfds[i]->fd, pollfds[i]->events); + free(pollfds); + + /* watch for PollFD changes */ + g_source_attach((GSource *)self, main_ctx); + libusb_set_pollfd_notifiers(self->ctx, + g_usb_source_pollfd_added_cb, + g_usb_source_pollfd_removed_cb, + self); + return self; +} + +static void +g_usb_source_pollfd_remove_cb(gpointer data, gpointer user_data) +{ + GPollFD *pollfd = (GPollFD *)data; + GSource *source = (GSource *)user_data; + g_source_remove_poll(source, pollfd); +} + +void +_g_usb_source_destroy(GUsbSource *self) +{ + libusb_set_pollfd_notifiers(self->ctx, NULL, NULL, NULL); + g_slist_foreach(self->pollfds, g_usb_source_pollfd_remove_cb, self); + g_slist_free_full(g_steal_pointer(&self->pollfds), g_free); + g_source_destroy((GSource *)self); +} + /** * g_usb_source_set_callback: * @self: a #GUsbSource @@ -41,7 +200,7 @@ g_usb_source_error_quark(void) * @data: data to pass to @func * @notify: a #GDestroyNotify * - * This function does nothing. + * Set a callback to be called when the source is dispatched. * * Since: 0.1.0 **/ |