]> sigrok.org Git - libsigrok.git/commitdiff
USB: Handle live changes to the set of FDs to poll
authorDaniel Elstner <redacted>
Mon, 7 Sep 2015 10:41:29 +0000 (12:41 +0200)
committerDaniel Elstner <redacted>
Mon, 7 Sep 2015 22:08:16 +0000 (00:08 +0200)
Introduce new internal session API for changing the set of polled
file descriptors for an already installed event source. Use the
new API to apply changes to the USB poll FDs when requested to do
so by libusb. Doing so is necessary to make the generic USB code
work on Windows.

src/libsigrok-internal.h
src/session.c
src/usb.c

index 42ce04a67213f18d6c7077ebbe15ef79b6d3ddcc..3bae560df5d6e15382ff31b2830840a533d4477d 100644 (file)
@@ -735,11 +735,14 @@ struct sr_session {
 };
 
 SR_PRIV int sr_session_source_add_internal(struct sr_session *session,
-               const GPollFD *pollfds, int num_fds, int timeout,
-               sr_receive_data_callback cb, void *cb_data,
+               int timeout, sr_receive_data_callback cb, void *cb_data,
                gintptr poll_object);
+SR_PRIV int sr_session_source_poll_add(struct sr_session *session,
+               gintptr poll_object, gintptr fd, int events);
 SR_PRIV int sr_session_source_remove_internal(struct sr_session *session,
                gintptr poll_object);
+SR_PRIV int sr_session_source_poll_remove(struct sr_session *session,
+               gintptr poll_object, gintptr fd);
 SR_PRIV int sr_session_send(const struct sr_dev_inst *sdi,
                const struct sr_datafeed_packet *packet);
 SR_PRIV int sr_session_stop_sync(struct sr_session *session);
index 849dc0d3bd0dd9b46ca03bf0c182e2a0f31a098f..0f4a0d70e82bf733d2cca1595b819c4c10ee0e9a 100644 (file)
@@ -526,8 +526,16 @@ static int sr_session_iteration(struct sr_session *session)
                 */
                sr_spew("callback for event source %" G_GINTPTR_FORMAT " with"
                        " event mask 0x%.2X", poll_object, (unsigned)revents);
-               if (!source->cb(fd, revents, source->cb_data))
-                       sr_session_source_remove_internal(session, poll_object);
+               if (!source->cb(fd, revents, source->cb_data)) {
+                       /* Hackish, to be cleaned up when porting to
+                        * the GLib main loop.
+                        */
+                       if (poll_object == (gintptr)session->ctx->libusb_ctx)
+                               usb_source_remove(session, session->ctx);
+                       else
+                               sr_session_source_remove_internal(session,
+                                               poll_object);
+               }
                /*
                 * We want to take as little time as possible to stop
                 * the session if we have been told to do so. Therefore,
@@ -902,8 +910,6 @@ SR_PRIV int sr_session_send(const struct sr_dev_inst *sdi,
  * Add an event source for a file descriptor.
  *
  * @param session The session to use. Must not be NULL.
- * @param[in] pollfds The FDs to poll, or NULL if @a num_fds is 0.
- * @param[in] num_fds Number of FDs in the array.
  * @param[in] timeout Max time in ms to wait before the callback is called,
  *                    or -1 to wait indefinitely.
  * @param cb Callback function to add. Must not be NULL.
@@ -915,23 +921,17 @@ SR_PRIV int sr_session_send(const struct sr_dev_inst *sdi,
  * @retval SR_ERR An event source for @a poll_object is already installed.
  */
 SR_PRIV int sr_session_source_add_internal(struct sr_session *session,
-               const GPollFD *pollfds, int num_fds, int timeout,
-               sr_receive_data_callback cb, void *cb_data,
+               int timeout, sr_receive_data_callback cb, void *cb_data,
                gintptr poll_object)
 {
        struct source src;
        unsigned int i;
-       int k;
 
        /* Note: cb_data can be NULL, that's not a bug. */
        if (!cb) {
                sr_err("%s: cb was NULL", __func__);
                return SR_ERR_ARG;
        }
-       if (!pollfds && num_fds != 0) {
-               sr_err("%s: pollfds was NULL", __func__);
-               return SR_ERR_ARG;
-       }
        /* Make sure that poll_object is unique.
         */
        for (i = 0; i < session->sources->len; ++i) {
@@ -942,12 +942,12 @@ SR_PRIV int sr_session_source_add_internal(struct sr_session *session,
                        return SR_ERR;
                }
        }
-       sr_dbg("Installing event source %" G_GINTPTR_FORMAT " with %d FDs"
-               " and %d ms timeout.", poll_object, num_fds, timeout);
+       sr_dbg("Installing event source %" G_GINTPTR_FORMAT
+               " with %d ms timeout.", poll_object, timeout);
        src.cb = cb;
        src.cb_data = cb_data;
        src.poll_object = poll_object;
-       src.num_fds = num_fds;
+       src.num_fds = 0;
        src.triggered = FALSE;
 
        if (timeout >= 0) {
@@ -959,12 +959,56 @@ SR_PRIV int sr_session_source_add_internal(struct sr_session *session,
        }
        g_array_append_val(session->sources, src);
 
-       for (k = 0; k < num_fds; ++k) {
-               sr_dbg("Registering poll FD %" G_GINTPTR_FORMAT
-                       " with event mask 0x%.2X.",
-                       (gintptr)pollfds[k].fd, (unsigned)pollfds[k].events);
+       return SR_OK;
+}
+
+SR_PRIV int sr_session_source_poll_add(struct sr_session *session,
+               gintptr poll_object, gintptr fd, int events)
+{
+       struct source *source;
+       GPollFD pollfd;
+       unsigned int i;
+       int fd_index, k;
+
+       source = NULL;
+       fd_index = 0;
+
+       /* Look up existing event source.
+        */
+       for (i = 0; i < session->sources->len; ++i) {
+               source = &g_array_index(session->sources, struct source, i);
+               if (source->poll_object == poll_object)
+                       break;
+               fd_index += source->num_fds;
+       }
+       if (!source) {
+               sr_err("Cannot add poll FD %" G_GINTPTR_FORMAT
+                       " to non-existing event source %" G_GINTPTR_FORMAT
+                       ".",  fd, poll_object);
+               return SR_ERR;
        }
-       g_array_append_vals(session->pollfds, pollfds, num_fds);
+       /* Make sure the FD is unique.
+        */
+       for (k = 0; k < source->num_fds; ++k)
+               if (g_array_index(session->pollfds, GPollFD, fd_index + k)
+                               .fd == fd) {
+                       sr_err("Cannot add poll FD %" G_GINTPTR_FORMAT
+                               " twice to event source %" G_GINTPTR_FORMAT
+                               ".", fd, poll_object);
+                       return SR_ERR;
+               }
+
+       pollfd.fd = fd;
+       pollfd.events = events;
+       pollfd.revents = 0;
+
+       g_array_insert_val(session->pollfds,
+               fd_index + source->num_fds, pollfd);
+       ++source->num_fds;
+
+       sr_dbg("Added poll FD %" G_GINTPTR_FORMAT " with event mask 0x%.2X"
+               " to event source %" G_GINTPTR_FORMAT ".",
+               fd, (unsigned)events, poll_object);
 
        return SR_OK;
 }
@@ -973,7 +1017,7 @@ SR_PRIV int sr_session_source_add_internal(struct sr_session *session,
  * Add an event source for a file descriptor.
  *
  * @param session The session to use. Must not be NULL.
- * @param fd The file descriptor.
+ * @param fd The file descriptor, or a negative value to create a timer source.
  * @param events Events to check for.
  * @param timeout Max time in ms to wait before the callback is called,
  *                or -1 to wait indefinitely.
@@ -988,18 +1032,17 @@ SR_PRIV int sr_session_source_add_internal(struct sr_session *session,
 SR_API int sr_session_source_add(struct sr_session *session, int fd,
                int events, int timeout, sr_receive_data_callback cb, void *cb_data)
 {
-       GPollFD p;
+       int ret;
 
        if (fd < 0 && timeout < 0) {
                sr_err("Timer source without timeout would block indefinitely");
                return SR_ERR_ARG;
        }
-       p.fd = fd;
-       p.events = events;
-       p.revents = 0;
+       ret = sr_session_source_add_internal(session, timeout, cb, cb_data, fd);
+       if (ret != SR_OK || fd < 0)
+               return ret;
 
-       return sr_session_source_add_internal(session,
-               &p, (fd < 0) ? 0 : 1, timeout, cb, cb_data, fd);
+       return sr_session_source_poll_add(session, fd, fd, events);
 }
 
 /**
@@ -1021,12 +1064,19 @@ SR_API int sr_session_source_add_pollfd(struct sr_session *session,
                GPollFD *pollfd, int timeout, sr_receive_data_callback cb,
                void *cb_data)
 {
+       int ret;
+
        if (!pollfd) {
                sr_err("%s: pollfd was NULL", __func__);
                return SR_ERR_ARG;
        }
-       return sr_session_source_add_internal(session, pollfd, 1,
+       ret = sr_session_source_add_internal(session,
                        timeout, cb, cb_data, (gintptr)pollfd);
+       if (ret != SR_OK)
+               return ret;
+
+       return sr_session_source_poll_add(session,
+                       (gintptr)pollfd, pollfd->fd, pollfd->events);
 }
 
 /**
@@ -1049,17 +1099,22 @@ SR_API int sr_session_source_add_channel(struct sr_session *session,
                GIOChannel *channel, int events, int timeout,
                sr_receive_data_callback cb, void *cb_data)
 {
-       GPollFD p;
+       int ret;
 
+       ret = sr_session_source_add_internal(session,
+                       timeout, cb, cb_data, (gintptr)channel);
+       if (ret != SR_OK)
+               return ret;
 #ifdef G_OS_WIN32
+       GPollFD p;
        g_io_channel_win32_make_pollfd(channel, events, &p);
+
+       return sr_session_source_poll_add(session,
+                       (gintptr)channel, p.fd, p.events);
 #else
-       p.fd = g_io_channel_unix_get_fd(channel);
-       p.events = events;
-       p.revents = 0;
+       return sr_session_source_poll_add(session, (gintptr)channel,
+                       g_io_channel_unix_get_fd(channel), events);
 #endif
-       return sr_session_source_add_internal(session, &p, 1,
-                       timeout, cb, cb_data, (gintptr)channel);
 }
 
 /**
@@ -1108,6 +1163,52 @@ SR_PRIV int sr_session_source_remove_internal(struct sr_session *session,
        return SR_ERR_BUG;
 }
 
+SR_PRIV int sr_session_source_poll_remove(struct sr_session *session,
+               gintptr poll_object, gintptr fd)
+{
+       struct source *source;
+       unsigned int i;
+       int fd_index, k;
+
+       source = NULL;
+       fd_index = 0;
+
+       /* Look up existing event source.
+        */
+       for (i = 0; i < session->sources->len; ++i) {
+               source = &g_array_index(session->sources, struct source, i);
+               if (source->poll_object == poll_object)
+                       break;
+               fd_index += source->num_fds;
+       }
+       if (!source) {
+               sr_err("Cannot remove poll FD %" G_GINTPTR_FORMAT
+                       " from non-existing event source %" G_GINTPTR_FORMAT
+                       ".", fd, poll_object);
+               return SR_ERR;
+       }
+       /* Look up the FD in the poll set.
+        */
+       for (k = 0; k < source->num_fds; ++k)
+               if (g_array_index(session->pollfds, GPollFD, fd_index + k)
+                               .fd == fd) {
+
+                       g_array_remove_index(session->pollfds, fd_index + k);
+                       --source->num_fds;
+
+                       sr_dbg("Removed poll FD %" G_GINTPTR_FORMAT
+                               " from event source %" G_GINTPTR_FORMAT ".",
+                               fd, poll_object);
+                       return SR_OK;
+               }
+
+       sr_err("Cannot remove non-existing poll FD %" G_GINTPTR_FORMAT
+               " from event source %" G_GINTPTR_FORMAT ".",
+               fd, poll_object);
+
+       return SR_ERR;
+}
+
 /**
  * Remove the source belonging to the specified file descriptor.
  *
index cc63b86096a65683f4d97ebb52663ae654757a22..122371f6268f2008d31a48b24e61b0c94279d05d 100644 (file)
--- a/src/usb.c
+++ b/src/usb.c
@@ -184,55 +184,88 @@ SR_PRIV void sr_usb_close(struct sr_usb_dev_inst *usb)
        sr_dbg("Closed USB device %d.%d.", usb->bus, usb->address);
 }
 
+#if (LIBUSB_API_VERSION < 0x01000104)
+typedef int libusb_os_handle;
+#endif
+
+static LIBUSB_CALL void usb_pollfd_added(libusb_os_handle fd,
+               short events, void *user_data)
+{
+       struct sr_session *session;
+       gintptr tag;
+
+       session = user_data;
+       tag = (gintptr)session->ctx->libusb_ctx;
+#ifdef G_OS_WIN32
+       events = G_IO_IN;
+#endif
+       sr_session_source_poll_add(session, tag, (gintptr)fd, events);
+}
+
+static LIBUSB_CALL void usb_pollfd_removed(libusb_os_handle fd, void *user_data)
+{
+       struct sr_session *session;
+       gintptr tag;
+
+       session = user_data;
+       tag = (gintptr)session->ctx->libusb_ctx;
+
+       sr_session_source_poll_remove(session, tag, (gintptr)fd);
+}
+
 SR_PRIV int usb_source_add(struct sr_session *session, struct sr_context *ctx,
                int timeout, sr_receive_data_callback cb, void *cb_data)
 {
-       const struct libusb_pollfd **lupfd;
-       GPollFD *pollfds;
+       const struct libusb_pollfd **pollfds;
+       gintptr tag;
        int i;
-       int num_fds = 0;
        int ret;
+       int events;
 
        if (ctx->usb_source_present) {
                sr_err("A USB event source is already present.");
                return SR_ERR;
        }
-       lupfd = libusb_get_pollfds(ctx->libusb_ctx);
-       if (!lupfd || !lupfd[0]) {
-               free(lupfd);
+       pollfds = libusb_get_pollfds(ctx->libusb_ctx);
+       if (!pollfds) {
                sr_err("Failed to get libusb file descriptors.");
                return SR_ERR;
        }
-       while (lupfd[num_fds])
-               ++num_fds;
-       pollfds = g_new(GPollFD, num_fds);
-
-       for (i = 0; i < num_fds; ++i) {
-#if defined(G_OS_WIN32) && (GLIB_SIZEOF_VOID_P == 4)
-               /* Avoid a warning on 32-bit Windows. */
-               pollfds[i].fd = (gintptr)lupfd[i]->fd;
-#else
-               pollfds[i].fd = lupfd[i]->fd;
-#endif
+       tag = (gintptr)ctx->libusb_ctx;
+       ret = sr_session_source_add_internal(session,
+                       timeout, cb, cb_data, tag);
+
+       ctx->usb_source_present = (ret == SR_OK);
+
+       for (i = 0; ret == SR_OK && pollfds[i]; ++i) {
 #ifdef G_OS_WIN32
-               pollfds[i].events = G_IO_IN;
+               events = G_IO_IN;
 #else
-               pollfds[i].events = lupfd[i]->events;
+               events = pollfds[i]->events;
 #endif
-               pollfds[i].revents = 0;
+               ret = sr_session_source_poll_add(session, tag,
+                               (gintptr)pollfds[i]->fd, events);
        }
-       free(lupfd);
-       ret = sr_session_source_add_internal(session, pollfds, num_fds,
-                       timeout, cb, cb_data, (gintptr)ctx->libusb_ctx);
-       g_free(pollfds);
+#if (LIBUSB_API_VERSION >= 0x01000104)
+       libusb_free_pollfds(pollfds);
+#else
+       free(pollfds);
+#endif
+       if (ret != SR_OK)
+               return ret;
 
-       ctx->usb_source_present = (ret == SR_OK);
+       libusb_set_pollfd_notifiers(ctx->libusb_ctx,
+               &usb_pollfd_added, &usb_pollfd_removed, session);
 
-       return ret;
+       return SR_OK;
 }
 
 SR_PRIV int usb_source_remove(struct sr_session *session, struct sr_context *ctx)
 {
+       ctx->usb_source_present = FALSE;
+
+       libusb_set_pollfd_notifiers(ctx->libusb_ctx, NULL, NULL, NULL);
+
        return sr_session_source_remove_internal(session,
                        (gintptr)ctx->libusb_ctx);
 }