@@ -1176,6 +1176,23 @@ int gpiod_line_request_get_fd(struct gpiod_line_request *request);
int gpiod_line_request_wait_edge_events(struct gpiod_line_request *request,
int64_t timeout_ns);
+/**
+ * @brief Wait for edge events on any of the requested lines or a
+ * POLLHUP/POLLERR event on the given file descriptor.
+ * @param request GPIO line request.
+ * @param fd file descriptor from any I/O, channel, fifo, or pipe.
+ * @param timeout_ns Wait time limit in nanoseconds. If set to 0, the function
+ * returns immediately. If set to a negative number, the
+ * function blocks indefinitely until an event becomes
+ * available.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ * pending, 2 if the file descriptor raised an event.
+ *
+ * Lines must have edge detection set for edge events to be emitted.
+ * By default edge detection is disabled.
+ */
+int gpiod_line_request_wait_edge_events_or(struct gpiod_line_request *request,
+ int fd, int64_t timeout_ns);
/**
* @brief Read a number of edge events from a line request.
* @param request GPIO line request.
@@ -81,22 +81,18 @@ out:
return ret;
}
-int gpiod_poll_fd(int fd, int64_t timeout_ns)
+static int
+gpiod_poll_fds(struct pollfd *pfds, uint8_t len, int64_t timeout_ns)
{
struct timespec ts;
- struct pollfd pfd;
int ret;
- memset(&pfd, 0, sizeof(pfd));
- pfd.fd = fd;
- pfd.events = POLLIN | POLLPRI;
-
if (timeout_ns >= 0) {
ts.tv_sec = timeout_ns / 1000000000ULL;
ts.tv_nsec = timeout_ns % 1000000000ULL;
}
- ret = ppoll(&pfd, 1, timeout_ns < 0 ? NULL : &ts, NULL);
+ ret = ppoll(pfds, len, timeout_ns < 0 ? NULL : &ts, NULL);
if (ret < 0)
return -1;
else if (ret == 0)
@@ -105,6 +101,34 @@ int gpiod_poll_fd(int fd, int64_t timeout_ns)
return 1;
}
+int gpiod_poll_fd(int fd, int64_t timeout_ns)
+{
+ struct pollfd pfd;
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = fd;
+ pfd.events = POLLIN | POLLPRI;
+
+ return gpiod_poll_fds(&pfd, 1, timeout_ns);
+}
+
+int gpiod_poll_fd_or(int fd1, int fd2, int64_t timeout_ns)
+{
+ struct pollfd pfds[2];
+ int ret;
+
+ memset(&pfds, 0, sizeof(pfds));
+ pfds[0].fd = fd1;
+ pfds[0].events = POLLIN | POLLPRI;
+ pfds[1].fd = fd2;
+ pfds[1].events = POLLIN | POLLERR;
+
+ ret = gpiod_poll_fds(pfds, 2, timeout_ns);
+ if (ret >= 1 && pfds[1].revents != 0)
+ return 2;
+ return ret;
+}
+
int gpiod_set_output_value(enum gpiod_line_value in, enum gpiod_line_value *out)
{
switch (in) {
@@ -36,6 +36,7 @@ gpiod_info_event_from_uapi(struct gpio_v2_line_info_changed *uapi_evt);
struct gpiod_info_event *gpiod_info_event_read_fd(int fd);
int gpiod_poll_fd(int fd, int64_t timeout);
+int gpiod_poll_fd_or(int fd1, int fd2, int64_t timeout);
int gpiod_set_output_value(enum gpiod_line_value in,
enum gpiod_line_value *out);
@@ -295,6 +295,15 @@ gpiod_line_request_wait_edge_events(struct gpiod_line_request *request,
return gpiod_poll_fd(request->fd, timeout_ns);
}
+GPIOD_API int
+gpiod_line_request_wait_edge_events_or(struct gpiod_line_request *request,
+ int fd, int64_t timeout_ns)
+{
+ assert(request);
+
+ return gpiod_poll_fd_or(request->fd, fd, timeout_ns);
+}
+
GPIOD_API int
gpiod_line_request_read_edge_events(struct gpiod_line_request *request,
struct gpiod_edge_event_buffer *buffer,
@@ -81,6 +81,17 @@ GPIOD_TEST_CASE(cannot_request_lines_in_output_mode_with_edge_detection)
gpiod_test_expect_errno(EINVAL);
}
+static gpointer trigger_cancel_channel(gpointer data)
+{
+ int cancel_sender = ((int *)data)[1];
+
+ g_usleep(1000);
+
+ g_assert_cmpint(write(cancel_sender, "\0", 1), ==, 1);
+
+ return NULL;
+}
+
static gpointer falling_and_rising_edge_events(gpointer data)
{
GPIOSimChip *sim = data;
@@ -361,6 +372,46 @@ GPIOD_TEST_CASE(read_rising_edge_event_polled)
g_thread_join(thread);
}
+GPIOD_TEST_CASE(cancel_edge_event_wait)
+{
+ static const guint offset = 2;
+ gint channel[2];
+
+ g_autoptr(GPIOSimChip) sim = g_gpiosim_chip_new("num-lines", 8, NULL);
+ g_autoptr(struct_gpiod_chip) chip = NULL;
+ g_autoptr(struct_gpiod_line_settings) settings = NULL;
+ g_autoptr(struct_gpiod_line_config) line_cfg = NULL;
+ g_autoptr(struct_gpiod_line_request) request = NULL;
+ g_autoptr(GThread) thread = NULL;
+ g_autoptr(struct_gpiod_edge_event_buffer) buffer = NULL;
+ gint ret;
+
+ chip = gpiod_test_open_chip_or_fail(g_gpiosim_chip_get_dev_path(sim));
+ settings = gpiod_test_create_line_settings_or_fail();
+ line_cfg = gpiod_test_create_line_config_or_fail();
+ buffer = gpiod_test_create_edge_event_buffer_or_fail(64);
+
+ gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT);
+ gpiod_line_settings_set_edge_detection(settings,
+ GPIOD_LINE_EDGE_RISING);
+
+ gpiod_test_line_config_add_line_settings_or_fail(line_cfg, &offset, 1,
+ settings);
+
+ request = gpiod_test_chip_request_lines_or_fail(chip, NULL, line_cfg);
+
+ g_assert_cmpint(pipe(channel), ==, 0);
+
+ thread = g_thread_new("trigger-cancel-channel",
+ trigger_cancel_channel, channel);
+
+ ret = gpiod_line_request_wait_edge_events_or(request, channel[0], 1000000000);
+ g_assert_cmpint(ret, ==, 2); /* Canceled */
+ close(channel[0]);
+ close(channel[1]);
+ g_thread_join(thread);
+}
+
GPIOD_TEST_CASE(read_both_events_blocking)
{
/*
Add a function in core `gpiod_line_request_wait_edge_events_or` with an additional argument `fd` to trigger ppoll manually from another thread. It allows users to gracefully cancel and join a worker thread waiting for an edge event. Signed-off-by: Adrien Zinger <zinger.ad@gmail.com> --- include/gpiod.h | 17 ++++++++++++++ lib/internal.c | 38 ++++++++++++++++++++++++------ lib/internal.h | 1 + lib/line-request.c | 9 +++++++ tests/tests-edge-event.c | 51 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 7 deletions(-)