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@xxxxxxxxx> --- 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(-) diff --git a/include/gpiod.h b/include/gpiod.h index d86c6ac..cbc83f9 100644 --- a/include/gpiod.h +++ b/include/gpiod.h @@ -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. diff --git a/lib/internal.c b/lib/internal.c index c80d01f..aaf96ff 100644 --- a/lib/internal.c +++ b/lib/internal.c @@ -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) { diff --git a/lib/internal.h b/lib/internal.h index 61d7aec..6b2105b 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -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); diff --git a/lib/line-request.c b/lib/line-request.c index e867d91..dd2a5a2 100644 --- a/lib/line-request.c +++ b/lib/line-request.c @@ -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, diff --git a/tests/tests-edge-event.c b/tests/tests-edge-event.c index b744ca5..3281831 100644 --- a/tests/tests-edge-event.c +++ b/tests/tests-edge-event.c @@ -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) { /* -- 2.34.1