simple: rework the event loop
authorBartosz Golaszewski <bartekgola@gmail.com>
Mon, 25 Sep 2017 17:13:48 +0000 (19:13 +0200)
committerBartosz Golaszewski <bartekgola@gmail.com>
Wed, 27 Sep 2017 08:01:12 +0000 (10:01 +0200)
Implement a new routine for monitoring multiple lines with the simple
event loop and extend the simple loop interface with custom polling
capabilities.

Signed-off-by: Bartosz Golaszewski <bartekgola@gmail.com>
include/gpiod.h
src/lib/simple.c
tests/tests-simple-api.c

index 6946a33e1bebf7fcdbdfb33460b38b9544a5a859..5d52eefe23cc1fd7b20078bd96fc8a3a0ecf2f9f 100644 (file)
@@ -155,27 +155,86 @@ enum {
  *
  * The callback function takes the following arguments: event type (int),
  * GPIO line offset (unsigned int), event timestamp (const struct timespec *)
- * and a pointer to user data.
+ * and a pointer to user data (void *).
  */
-typedef int (*gpiod_event_cb)(int, unsigned int,
-                             const struct timespec *, void *);
+typedef int (*gpiod_event_handle_cb)(int, unsigned int,
+                                    const struct timespec *, void *);
+
+/**
+ * @brief Return status values that the simple event poll callback can return.
+ */
+enum {
+       GPIOD_EVENT_POLL_ERR = -1,
+       /**< Polling error occurred (the polling function should set errno). */
+       GPIOD_EVENT_POLL_TIMEOUT = 0,
+       /**< Poll timed out. */
+       GPIOD_EVENT_POLL_EVENT = 1,
+       /**< Line event occurred. */
+       GPIOD_EVENT_POLL_STOP = 2,
+       /**< The event loop should stop processing events. */
+};
+
+/**
+ * @brief Simple event poll callback signature.
+ *
+ * The poll callback function takes the following arguments: number of lines
+ * (unsigned int), an array of file descriptors on which input events should
+ * be monitored (const int *), pointer to an integer which the function should
+ * set to the offset in the fd array corresponding with the descriptor on which
+ * an event occurred (int *), poll timeout (const struct timespec *) and a
+ * pointer to user data (void *).
+ *
+ * The callback should poll for input events on the set of descriptors and
+ * return an appropriate value that can be interpreted by the event loop
+ * routine.
+ */
+typedef int (*gpiod_event_poll_cb)(unsigned int, const int *, unsigned int *,
+                                  const struct timespec *, void *);
 
 /**
  * @brief Wait for events on a single GPIO line.
  * @param consumer Name of the consumer.
  * @param device Name, path or number of the gpiochip.
- * @param offset GPIO line offset on the chip.
+ * @param offset GPIO line offset to monitor.
  * @param active_low The active state of this line - true if low.
  * @param timeout Maximum wait time for each iteration.
- * @param callback Callback function to call on event occurence.
- * @param cbdata User data passed to the callback.
- * @return 0 no errors were encountered, -1 if an error occured.
+ * @param poll_cb Callback function to call when waiting for events.
+ * @param event_cb Callback function to call on event occurrence.
+ * @param data User data passed to the callback.
+ * @return 0 no errors were encountered, -1 if an error occurred.
  *
+ * The poll callback can be NULL in which case the routine will fall back to
+ * a basic, ppoll() based callback.
  */
 int gpiod_simple_event_loop(const char *consumer, const char *device,
                            unsigned int offset, bool active_low,
                            const struct timespec *timeout,
-                           gpiod_event_cb callback, void *cbdata) GPIOD_API;
+                           gpiod_event_poll_cb poll_cb,
+                           gpiod_event_handle_cb event_cb,
+                           void *data) GPIOD_API;
+
+/**
+ * @brief Wait for events on multiple GPIO lines.
+ * @param consumer Name of the consumer.
+ * @param device Name, path or number of the gpiochip.
+ * @param offsets Array of GPIO line offsets to monitor.
+ * @param num_lines Number of lines to monitor.
+ * @param active_low The active state of this line - true if low.
+ * @param timeout Maximum wait time for each iteration.
+ * @param poll_cb Callback function to call when waiting for events.
+ * @param event_cb Callback function to call on event occurrence.
+ * @param data User data passed to the callback.
+ * @return 0 no errors were encountered, -1 if an error occurred.
+ *
+ * The callback functions work just like in the single line variant.
+ */
+int gpiod_simple_event_loop_multiple(const char *consumer, const char *device,
+                                    const unsigned int *offsets,
+                                    unsigned int num_lines, bool active_low,
+                                    const struct timespec *timeout,
+                                    gpiod_event_poll_cb poll_cb,
+                                    gpiod_event_handle_cb event_cb,
+                                    void *data) GPIOD_API;
 
 /**
  * @}
index c2905c51af6ebbf49783f1fef42d07b8b4c21e3f..f7041d5f09b9a84bd4bf2e512f09965af853b731 100644 (file)
@@ -14,6 +14,9 @@
 
 #include <string.h>
 #include <errno.h>
+#include <poll.h>
+
+#define UNUSED __attribute__((unused))
 
 int gpiod_simple_get_value(const char *consumer, const char *device,
                           unsigned int offset, bool active_low)
@@ -134,57 +137,130 @@ int gpiod_simple_set_value_multiple(const char *consumer, const char *device,
        return 0;
 }
 
+static int basic_event_poll(unsigned int num_lines, const int *fds,
+                           unsigned int *event_offset,
+                           const struct timespec *timeout,
+                           void *data UNUSED)
+{
+       struct pollfd poll_fds[GPIOD_REQUEST_MAX_LINES];
+       unsigned int i;
+       int status;
+
+       memset(poll_fds, 0, sizeof(poll_fds));
+
+       for (i = 0; i < num_lines; i++) {
+               poll_fds[i].fd = fds[i];
+               poll_fds[i].events = POLLIN | POLLPRI;
+       }
+
+       status = ppoll(poll_fds, num_lines, timeout, NULL);
+       if (status < 0) {
+               if (errno == EINTR)
+                       return GPIOD_EVENT_POLL_TIMEOUT;
+               else
+                       return GPIOD_EVENT_POLL_ERR;
+       } else if (status == 0) {
+               return GPIOD_EVENT_POLL_TIMEOUT;
+       }
+
+       for (i = 0; !poll_fds[i].revents; i++);
+       *event_offset = i;
+
+       return GPIOD_EVENT_POLL_EVENT;
+}
+
 int gpiod_simple_event_loop(const char *consumer, const char *device,
                            unsigned int offset, bool active_low,
                            const struct timespec *timeout,
-                           gpiod_event_cb callback, void *cbdata)
+                           gpiod_event_poll_cb poll_cb,
+                           gpiod_event_handle_cb event_cb,
+                           void *data)
+{
+       return gpiod_simple_event_loop_multiple(consumer, device, &offset, 1,
+                                               active_low, timeout, poll_cb,
+                                               event_cb, data);
+}
+
+int gpiod_simple_event_loop_multiple(const char *consumer, const char *device,
+                                    const unsigned int *offsets,
+                                    unsigned int num_lines, bool active_low,
+                                    const struct timespec *timeout,
+                                    gpiod_event_poll_cb poll_cb,
+                                    gpiod_event_handle_cb event_cb,
+                                    void *data)
 {
+       unsigned int i, event_offset, line_offset;
+       int fds[GPIOD_REQUEST_MAX_LINES];
        struct gpiod_line_event event;
-       int status, evtype, flags;
+       struct gpiod_line_bulk bulk;
        struct gpiod_chip *chip;
        struct gpiod_line *line;
+       int ret, flags, evtype;
+
+       if (num_lines > GPIOD_REQUEST_MAX_LINES) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (!poll_cb)
+               poll_cb = basic_event_poll;
 
        chip = gpiod_chip_open_lookup(device);
        if (!chip)
                return -1;
 
-       line = gpiod_chip_get_line(chip, offset);
-       if (!line) {
-               gpiod_chip_close(chip);
-               return -1;
+       gpiod_line_bulk_init(&bulk);
+
+       for (i = 0; i < num_lines; i++) {
+               line = gpiod_chip_get_line(chip, offsets[i]);
+               if (!line) {
+                       gpiod_chip_close(chip);
+                       return -1;
+               }
+
+               gpiod_line_bulk_add(&bulk, line);
        }
 
        flags = active_low ? GPIOD_REQUEST_ACTIVE_LOW : 0;
 
-       status = gpiod_line_request_both_edges_events_flags(line, consumer,
-                                                           flags);
-       if (status < 0) {
+       ret = gpiod_line_request_bulk_both_edges_events_flags(&bulk,
+                                                             consumer, flags);
+       if (ret) {
                gpiod_chip_close(chip);
-               return -1;
+               return ret;
        }
 
+       memset(fds, 0, sizeof(fds));
+       for (i = 0; i < num_lines; i++)
+               fds[i] = gpiod_line_event_get_fd(
+                               gpiod_line_bulk_get_line(&bulk, i));
+
        for (;;) {
-               status = gpiod_line_event_wait(line, timeout);
-               if (status < 0) {
-                       if (errno == EINTR)
-                               evtype = GPIOD_EVENT_CB_TIMEOUT;
-                       else
-                               goto out;
-               } else if (status == 0) {
+               ret = poll_cb(num_lines, fds, &event_offset, timeout, data);
+               if (ret < 0) {
+                       goto out;
+               } else if (ret == GPIOD_EVENT_POLL_TIMEOUT) {
                        evtype = GPIOD_EVENT_CB_TIMEOUT;
+                       line_offset = 0;
+               } else if (ret == GPIOD_EVENT_POLL_STOP) {
+                       ret = 0;
+                       goto out;
                } else {
-                       status = gpiod_line_event_read(line, &event);
-                       if (status < 0)
+                       line = gpiod_line_bulk_get_line(&bulk, event_offset);
+                       ret = gpiod_line_event_read(line, &event);
+                       if (ret < 0)
                                goto out;
 
                        evtype = event.event_type == GPIOD_EVENT_RISING_EDGE
                                                ? GPIOD_EVENT_CB_RISING_EDGE
                                                : GPIOD_EVENT_CB_FALLING_EDGE;
+
+                       line_offset = offsets[event_offset];
                }
 
-               status = callback(evtype, offset, &event.ts, cbdata);
-               if (status == GPIOD_EVENT_CB_STOP) {
-                       status = 0;
+               ret = event_cb(evtype, line_offset, &event.ts, data);
+               if (ret == GPIOD_EVENT_CB_STOP) {
+                       ret = 0;
                        goto out;
                }
        }
@@ -192,5 +268,5 @@ int gpiod_simple_event_loop(const char *consumer, const char *device,
 out:
        gpiod_chip_close(chip);
 
-       return status;
+       return ret;
 }
index ab894154443c51748879b6357dd6a08b53dbe340..e9f38bf09cacdc526c2039a542f6131087629dc3 100644 (file)
@@ -152,7 +152,8 @@ static void simple_event_loop(void)
        test_set_event(0, 3, TEST_EVENT_ALTERNATING, 100);
 
        status = gpiod_simple_event_loop(TEST_CONSUMER, test_chip_name(0), 3,
-                                        false, &ts, simple_event_cb, &evdata);
+                                        false, &ts, NULL, simple_event_cb,
+                                        &evdata);
 
        TEST_ASSERT_RET_OK(status);
        TEST_ASSERT(evdata.got_rising_edge);