Skip to contentSkip to conversation

teach libgpiod to read multiple line events at once

PATCH In review Bartosz Golaszewski 3 participants 15 messages Dec 18, 2019 – Dec 24, 2019

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover teach libgpiod to read multiple line events at once

From: Bartosz Golaszewski <[email protected]>

In v2 of my series adding the line status watch I introduced a regression that made it impossible to read more than one event at the same time. This is because I use libgpiod for testing and it doesn't allow to read more than one event from user-space.

This series adds this missing functionality so that we can test v3 and avoid this problem.

Bartosz Golaszewski (7):
core: use gpiod_line_event_get_fd() in gpiod_line_event_read()
core: provide functions for reading multiple line events at once
tests: event: extend test coverage for reading multiple line events at
once
bindings: cxx: provide a method for reading multiple line events
bindings: cxx: tests: add a test-case for reading multiple line events
bindings: python: add a method for reading multiple line events
bindings: python: tests: add a test-case for reading multiple line
events

 bindings/cxx/gpiod.hpp                 |  7 +++
 bindings/cxx/line.cpp                  | 45 +++++++++++---
 bindings/cxx/tests/tests-event.cpp     | 31 ++++++++++
 bindings/python/gpiodmodule.c          | 57 ++++++++++++++++++
 bindings/python/tests/gpiod_py_test.py | 22 +++++++
 include/gpiod.h                        | 25 ++++++++
 lib/core.c                             | 68 ++++++++++++++++-----
 tests/tests-event.c                    | 83 ++++++++++++++++++++++++++
 8 files changed, 314 insertions(+), 24 deletions(-)
Signature · 1 line
2.23.0
#1 lore raw

PATCH 1/7 core: use gpiod_line_event_get_fd() in gpiod_line_event_read()

From: Bartosz Golaszewski <[email protected]>

We can drop the redundant line state check if we directly call gpiod_line_event_get_fd() from gpiod_line_event_read(). As opposed to line_get_fd() it checks if the line was requested for events.

---
 lib/core.c | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)
lib/core.c +2 -5
index b9fd6f4..89f5465 100644
@@ -1012,12 +1012,9 @@ int gpiod_line_event_read(struct gpiod_line *line,
 {
 	int fd;
 
-	if (line->state != LINE_REQUESTED_EVENTS) {
-		errno = EPERM;
+	fd = gpiod_line_event_get_fd(line);
+	if (fd < 0)
 		return -1;
-	}
-
-	fd = line_get_fd(line);
 
 	return gpiod_line_event_read_fd(fd, event);
 }
Signature · 1 line
2.23.0
#2 lore raw

PATCH 2/7 core: provide functions for reading multiple line events at once

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

The kernel allows us to read multiple (up to 16) line events with a single read() call, but up to this point libgpiod only supported reading one event at a time. Add two new functions that allow users to read more events at the same time.

---
 include/gpiod.h | 25 ++++++++++++++++++++
 lib/core.c      | 61 ++++++++++++++++++++++++++++++++++++++++---------
 2 files changed, 75 insertions(+), 11 deletions(-)
include/gpiod.h +25 -0
index 0ecb804..b275f4a 100644
@@ -1479,6 +1479,19 @@ int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
 int gpiod_line_event_read(struct gpiod_line *line,
 			  struct gpiod_line_event *event) GPIOD_API;
 
+/**
+ * @brief Read up to a certain number of events from the GPIO line.
+ * @param line GPIO line object.
+ * @param events Buffer to which the event data will be copied. Must hold at
+ *               least the amount of events specified in num_events.
+ * @param num_events Specifies how many events can be stored in the buffer.
+ * @return On success returns the number of events stored in the buffer, on
+ *         failure -1 is returned.
+ */
+int gpiod_line_event_read_multiple(struct gpiod_line *line,
+				   struct gpiod_line_event *events,
+				   unsigned int num_events) GPIOD_API;
+
 /**
  * @brief Get the event file descriptor.
  * @param line GPIO line object.
@@ -1503,6 +1516,18 @@ int gpiod_line_event_get_fd(struct gpiod_line *line) GPIOD_API;
  */
 int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event) GPIOD_API;
 
+/**
+ * @brief Read up to a certain number of events directly from a file descriptor.
+ * @param fd File descriptor.
+ * @param events Buffer to which the event data will be copied. Must hold at
+ *               least the amount of events specified in num_events.
+ * @param num_events Specifies how many events can be stored in the buffer.
+ * @return On success returns the number of events stored in the buffer, on
+ *         failure -1 is returned.
+ */
+int gpiod_line_event_read_fd_multiple(int fd, struct gpiod_line_event *events,
+				      unsigned int num_events) GPIOD_API;
+
 /**
  * @}
  *
lib/core.c +50 -11

88 lines not on this page · raw message

Signature · 1 line
2.23.0
#3 lore raw

PATCH 3/7 tests: event: extend test coverage for reading multiple line events at once

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add test cases for new helpers allowing users to read multiple events at once.

---
 tests/tests-event.c | 83 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
tests/tests-event.c +83 -0
index d425d1a..1f4a2eb 100644
@@ -552,3 +552,86 @@ GPIOD_TEST_CASE(invalid_fd, 0, { 8 })
 	g_assert_cmpint(ret, ==, -1);
 	g_assert_cmpint(errno, ==, EINVAL);
 }
+
+GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
+{
+	g_autoptr(gpiod_chip_struct) chip = NULL;
+	struct gpiod_line_event events[3];
+	struct timespec ts = { 1, 0 };
+	struct gpiod_line *line;
+	gint ret;
+
+	chip = gpiod_chip_open(gpiod_test_chip_path(0));
+	g_assert_nonnull(chip);
+	gpiod_test_return_if_failed();
+
+	line = gpiod_chip_get_line(chip, 4);
+	g_assert_nonnull(line);
+	gpiod_test_return_if_failed();
+
+	ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
+	g_assert_cmpint(ret, ==, 0);
+
+	gpiod_test_chip_set_pull(0, 4, 1);
+	usleep(10000);
+	gpiod_test_chip_set_pull(0, 4, 0);
+	usleep(10000);
+	gpiod_test_chip_set_pull(0, 4, 1);
+	usleep(10000);
+
+	ret = gpiod_line_event_wait(line, &ts);
+	g_assert_cmpint(ret, ==, 1);
+
+	ret = gpiod_line_event_read_multiple(line, events, 3);
+	g_assert_cmpint(ret, ==, 3);
+
+	g_assert_cmpint(events[0].event_type, ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+	g_assert_cmpint(events[1].event_type, ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
+	g_assert_cmpint(events[2].event_type, ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+}
+
+GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
+{
+	g_autoptr(gpiod_chip_struct) chip = NULL;
+	struct gpiod_line_event events[3];
+	struct timespec ts = { 1, 0 };
+	struct gpiod_line *line;
+	gint ret, fd;
+
+	chip = gpiod_chip_open(gpiod_test_chip_path(0));
+	g_assert_nonnull(chip);
+	gpiod_test_return_if_failed();
+
+	line = gpiod_chip_get_line(chip, 4);
+	g_assert_nonnull(line);
+	gpiod_test_return_if_failed();
+
+	ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
+	g_assert_cmpint(ret, ==, 0);
+
+	gpiod_test_chip_set_pull(0, 4, 1);
+	usleep(10000);
+	gpiod_test_chip_set_pull(0, 4, 0);
+	usleep(10000);
+	gpiod_test_chip_set_pull(0, 4, 1);
+	usleep(10000);
+
+	ret = gpiod_line_event_wait(line, &ts);
+	g_assert_cmpint(ret, ==, 1);
+
+	fd = gpiod_line_event_get_fd(line);
+	g_assert_cmpint(fd, >=, 0);
+
+	ret = gpiod_line_event_read_fd_multiple(fd, events, 3);
+	g_assert_cmpint(ret, ==, 3);
+
+	g_assert_cmpint(events[0].event_type, ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+	g_assert_cmpint(events[1].event_type, ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
+	g_assert_cmpint(events[2].event_type, ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+}
Signature · 1 line
2.23.0
#4 lore raw

Re: [libgpiod][PATCH 3/7] tests: event: extend test coverage for reading multiple line events at once

Quoted 35 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add test cases for new helpers allowing users to read multiple events at once.

---
 tests/tests-event.c | 83 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 83 insertions(+)
tests/tests-event.c +17 -0
index d425d1a..1f4a2eb 100644
@@ -552,3 +552,86 @@ GPIOD_TEST_CASE(invalid_fd, 0, { 8 })
 	g_assert_cmpint(ret, ==, -1);
 	g_assert_cmpint(errno, ==, EINVAL);
 }
+
+GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
+{
+	g_autoptr(gpiod_chip_struct) chip = NULL;
+	struct gpiod_line_event events[3];
+	struct timespec ts = { 1, 0 };
+	struct gpiod_line *line;
+	gint ret;
+
+	chip = gpiod_chip_open(gpiod_test_chip_path(0));
+	g_assert_nonnull(chip);
+	gpiod_test_return_if_failed();
+
+	line = gpiod_chip_get_line(chip, 4);
+	g_assert_nonnull(line);
+	gpiod_test_return_if_failed();
+

tests/tests-event.c GPIOD_TEST_CASE(invalid_fd, 0, { 8 }) · line 581

Quoted 10 lines
tests/tests-event.c +10 -0
+	ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
+	g_assert_cmpint(ret, ==, 0);
+
+	gpiod_test_chip_set_pull(0, 4, 1);
+	usleep(10000);
+	gpiod_test_chip_set_pull(0, 4, 0);
+	usleep(10000);
+	gpiod_test_chip_set_pull(0, 4, 1);
+	usleep(10000);
+

I assume the sleep is to wait for the event to be generated from the call gpiod_test_chip_set_pull, which is not guaranteed to occur before the call returns, otherwise you can toggle the line too fast and may miss events.
Arbitrary sleeps in code, including tests, should be avoided as they are brittle and obsure what you are actually waiting for. An alternative in this case is to add a second event fd and wait for the event to arrive there before continuing.

Kent.

Quoted 59 lines
tests/tests-event.c +56 -0
+	ret = gpiod_line_event_wait(line, &ts);
+	g_assert_cmpint(ret, ==, 1);
+
+	ret = gpiod_line_event_read_multiple(line, events, 3);
+	g_assert_cmpint(ret, ==, 3);
+
+	g_assert_cmpint(events[0].event_type, ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+	g_assert_cmpint(events[1].event_type, ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
+	g_assert_cmpint(events[2].event_type, ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+}
+
+GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
+{
+	g_autoptr(gpiod_chip_struct) chip = NULL;
+	struct gpiod_line_event events[3];
+	struct timespec ts = { 1, 0 };
+	struct gpiod_line *line;
+	gint ret, fd;
+
+	chip = gpiod_chip_open(gpiod_test_chip_path(0));
+	g_assert_nonnull(chip);
+	gpiod_test_return_if_failed();
+
+	line = gpiod_chip_get_line(chip, 4);
+	g_assert_nonnull(line);
+	gpiod_test_return_if_failed();
+
+	ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
+	g_assert_cmpint(ret, ==, 0);
+
+	gpiod_test_chip_set_pull(0, 4, 1);
+	usleep(10000);
+	gpiod_test_chip_set_pull(0, 4, 0);
+	usleep(10000);
+	gpiod_test_chip_set_pull(0, 4, 1);
+	usleep(10000);
+
+	ret = gpiod_line_event_wait(line, &ts);
+	g_assert_cmpint(ret, ==, 1);
+
+	fd = gpiod_line_event_get_fd(line);
+	g_assert_cmpint(fd, >=, 0);
+
+	ret = gpiod_line_event_read_fd_multiple(fd, events, 3);
+	g_assert_cmpint(ret, ==, 3);
+
+	g_assert_cmpint(events[0].event_type, ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+	g_assert_cmpint(events[1].event_type, ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
+	g_assert_cmpint(events[2].event_type, ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+}
Signature · 1 line
2.23.0
#5 lore raw

Re: [libgpiod][PATCH 3/7] tests: event: extend test coverage for reading multiple line events at once

czw., 19 gru 2019 o 14:35 Kent Gibson <[email protected]> napisał(a):

Quoted 17 lines

tests/tests-event.c GPIOD_TEST_CASE(invalid_fd, 0, { 8 }) · line 581

Quoted 12 lines
tests/tests-event.c +11 -0
+
+     ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
+     g_assert_cmpint(ret, ==, 0);
+
+     gpiod_test_chip_set_pull(0, 4, 1);
+     usleep(10000);
+     gpiod_test_chip_set_pull(0, 4, 0);
+     usleep(10000);
+     gpiod_test_chip_set_pull(0, 4, 1);
+     usleep(10000);
+

I assume the sleep is to wait for the event to be generated from the call gpiod_test_chip_set_pull, which is not guaranteed to occur before the call returns, otherwise you can toggle the line too fast and may miss events.

Yes, this is why I put it there. Otherwise, some simulated interrupts were being dropped when they fired while the previous ones were still served.

Quoted 2 lines

Arbitrary sleeps in code, including tests, should be avoided as they are brittle and obsure what you are actually waiting for.

Indeed.

Quoted 3 lines

An alternative in this case is to add a second event fd and wait for the event to arrive there before continuing.

I'm not sure I understand. We can't have two event fd's for the same line. Or are you thinking about setting up a second line, generating events on it and consuming them so that we can rely on the timing to make sure the events were registered for the first one too?

Bart

#6 lore raw

Re: [libgpiod][PATCH 3/7] tests: event: extend test coverage for reading multiple line events at once

Quoted 28 lines · Bartosz Golaszewski

czw., 19 gru 2019 o 14:35 Kent Gibson <[email protected]> napisał(a):

Quoted 17 lines

tests/tests-event.c GPIOD_TEST_CASE(invalid_fd, 0, { 8 }) · line 581

Quoted 12 lines
tests/tests-event.c +11 -0
+
+     ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
+     g_assert_cmpint(ret, ==, 0);
+
+     gpiod_test_chip_set_pull(0, 4, 1);
+     usleep(10000);
+     gpiod_test_chip_set_pull(0, 4, 0);
+     usleep(10000);
+     gpiod_test_chip_set_pull(0, 4, 1);
+     usleep(10000);
+

I assume the sleep is to wait for the event to be generated from the call gpiod_test_chip_set_pull, which is not guaranteed to occur before the call returns, otherwise you can toggle the line too fast and may miss events.

Yes, this is why I put it there. Otherwise, some simulated interrupts were being dropped when they fired while the previous ones were still served.

Quoted 2 lines

Arbitrary sleeps in code, including tests, should be avoided as they are brittle and obsure what you are actually waiting for.

Indeed.

Quoted 9 lines
Quoted 3 lines

An alternative in this case is to add a second event fd and wait for the event to arrive there before continuing.

I'm not sure I understand. We can't have two event fd's for the same line. Or are you thinking about setting up a second line, generating events on it and consuming them so that we can rely on the timing to make sure the events were registered for the first one too?

I was thinking of two event fds on the one line, and you are correct, there can only be one, so that wont work.
Wrt using two lines, I'm not sure ordering can be guaranteed as you end up with two separate debugfs writes...

Kent.

#7 lore raw

Re: [libgpiod][PATCH 3/7] tests: event: extend test coverage for reading multiple line events at once

czw., 19 gru 2019 o 15:05 Kent Gibson <[email protected]> napisał(a):

Quoted 40 lines · Bartosz Golaszewski
Quoted 28 lines

czw., 19 gru 2019 o 14:35 Kent Gibson <[email protected]> napisał(a):

Quoted 17 lines

tests/tests-event.c GPIOD_TEST_CASE(invalid_fd, 0, { 8 }) · line 581

Quoted 12 lines
tests/tests-event.c +11 -0
+
+     ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
+     g_assert_cmpint(ret, ==, 0);
+
+     gpiod_test_chip_set_pull(0, 4, 1);
+     usleep(10000);
+     gpiod_test_chip_set_pull(0, 4, 0);
+     usleep(10000);
+     gpiod_test_chip_set_pull(0, 4, 1);
+     usleep(10000);
+

I assume the sleep is to wait for the event to be generated from the call gpiod_test_chip_set_pull, which is not guaranteed to occur before the call returns, otherwise you can toggle the line too fast and may miss events.

Yes, this is why I put it there. Otherwise, some simulated interrupts were being dropped when they fired while the previous ones were still served.

Quoted 2 lines

Arbitrary sleeps in code, including tests, should be avoided as they are brittle and obsure what you are actually waiting for.

Indeed.

Quoted 9 lines
Quoted 3 lines

An alternative in this case is to add a second event fd and wait for the event to arrive there before continuing.

I'm not sure I understand. We can't have two event fd's for the same line. Or are you thinking about setting up a second line, generating events on it and consuming them so that we can rely on the timing to make sure the events were registered for the first one too?

Quoted 5 lines

I was thinking of two event fds on the one line, and you are correct, there can only be one, so that wont work.
Wrt using two lines, I'm not sure ordering can be guaranteed as you end up with two separate debugfs writes...

Yeah I gave it a spin and it turns out it's not reliable - some events still get dropped albeit less than without any syncing. The usleep() calls are still better than this. Any other ideas? I agree this is not optimal, but couldn't come up with anything else.

Bart

#8 lore raw

Re: [libgpiod][PATCH 3/7] tests: event: extend test coverage for reading multiple line events at once

Quoted 47 lines · Bartosz Golaszewski

czw., 19 gru 2019 o 15:05 Kent Gibson <[email protected]> napisał(a):

Quoted 40 lines · Bartosz Golaszewski
Quoted 28 lines

czw., 19 gru 2019 o 14:35 Kent Gibson <[email protected]> napisał(a):

Quoted 17 lines

tests/tests-event.c GPIOD_TEST_CASE(invalid_fd, 0, { 8 }) · line 581

Quoted 12 lines
tests/tests-event.c +11 -0
+
+     ret = gpiod_line_request_both_edges_events(line, GPIOD_TEST_CONSUMER);
+     g_assert_cmpint(ret, ==, 0);
+
+     gpiod_test_chip_set_pull(0, 4, 1);
+     usleep(10000);
+     gpiod_test_chip_set_pull(0, 4, 0);
+     usleep(10000);
+     gpiod_test_chip_set_pull(0, 4, 1);
+     usleep(10000);
+

I assume the sleep is to wait for the event to be generated from the call gpiod_test_chip_set_pull, which is not guaranteed to occur before the call returns, otherwise you can toggle the line too fast and may miss events.

Yes, this is why I put it there. Otherwise, some simulated interrupts were being dropped when they fired while the previous ones were still served.

Quoted 2 lines

Arbitrary sleeps in code, including tests, should be avoided as they are brittle and obsure what you are actually waiting for.

Indeed.

Quoted 9 lines
Quoted 3 lines

An alternative in this case is to add a second event fd and wait for the event to arrive there before continuing.

I'm not sure I understand. We can't have two event fd's for the same line. Or are you thinking about setting up a second line, generating events on it and consuming them so that we can rely on the timing to make sure the events were registered for the first one too?

Quoted 5 lines

I was thinking of two event fds on the one line, and you are correct, there can only be one, so that wont work.
Wrt using two lines, I'm not sure ordering can be guaranteed as you end up with two separate debugfs writes...

Quoted 5 lines

Yeah I gave it a spin and it turns out it's not reliable - some events still get dropped albeit less than without any syncing. The usleep() calls are still better than this. Any other ideas? I agree this is not optimal, but couldn't come up with anything else.

The two options I can think of are getting the debugfs write to block until the simulated interrupt has been serviced, or adding a multi-line set to the mockup so the two lines can be set simultaneously. I'm not sure the first is possible.
And the second wont help if it results in two interrupts unless the order the interrupts are serviced is guaranteed.
Either way it seems like a whole lot of work just to remove the sleeps, so the sleeps seem like a reasonable workaround :(.

Kent.

#9 lore raw

Re: [libgpiod][PATCH 3/7] tests: event: extend test coverage for reading multiple line events at once

czw., 19 gru 2019 o 15:36 Kent Gibson <[email protected]> napisał(a):

Quoted 20 lines
Quoted 14 lines
Quoted 8 lines
Quoted 7 lines

I'm not sure I understand. We can't have two event fd's for the same line. Or are you thinking about setting up a second line, generating events on it and consuming them so that we can rely on the timing to make sure the events were registered for the first one too?

Quoted 5 lines

I was thinking of two event fds on the one line, and you are correct, there can only be one, so that wont work.
Wrt using two lines, I'm not sure ordering can be guaranteed as you end up with two separate debugfs writes...

Quoted 5 lines

Yeah I gave it a spin and it turns out it's not reliable - some events still get dropped albeit less than without any syncing. The usleep() calls are still better than this. Any other ideas? I agree this is not optimal, but couldn't come up with anything else.

Quoted 9 lines

The two options I can think of are getting the debugfs write to block until the simulated interrupt has been serviced, or adding a multi-line set to the mockup so the two lines can be set simultaneously. I'm not sure the first is possible.
And the second wont help if it results in two interrupts unless the order the interrupts are serviced is guaranteed.
Either way it seems like a whole lot of work just to remove the sleeps, so the sleeps seem like a reasonable workaround :(.

Now when I looked at gpio-mockup again I'm under the impression that the interrupt should be fired from the set() callback, not from the debugfs write callback. But this is a minor detail and it won't help here.

Anyway, I'm really not sure how to do it any differently. We can also be less strict in user-space - we can schedule, let's say, 16 events without sleeps and not really verify the number of read events - just check if it's bigger than 1 and that what we read are actual events (valid event_type).

Bart

#10 lore raw

Re: [libgpiod][PATCH 3/7] tests: event: extend test coverage for reading multiple line events at once

czw., 19 gru 2019 o 17:19 Bartosz Golaszewski <[email protected]> napisał(a):

Quoted 37 lines

czw., 19 gru 2019 o 15:36 Kent Gibson <[email protected]> napisał(a):

Quoted 20 lines
Quoted 14 lines
Quoted 8 lines
Quoted 7 lines

I'm not sure I understand. We can't have two event fd's for the same line. Or are you thinking about setting up a second line, generating events on it and consuming them so that we can rely on the timing to make sure the events were registered for the first one too?

Quoted 5 lines

I was thinking of two event fds on the one line, and you are correct, there can only be one, so that wont work.
Wrt using two lines, I'm not sure ordering can be guaranteed as you end up with two separate debugfs writes...

Quoted 5 lines

Yeah I gave it a spin and it turns out it's not reliable - some events still get dropped albeit less than without any syncing. The usleep() calls are still better than this. Any other ideas? I agree this is not optimal, but couldn't come up with anything else.

Quoted 9 lines

The two options I can think of are getting the debugfs write to block until the simulated interrupt has been serviced, or adding a multi-line set to the mockup so the two lines can be set simultaneously. I'm not sure the first is possible.
And the second wont help if it results in two interrupts unless the order the interrupts are serviced is guaranteed.
Either way it seems like a whole lot of work just to remove the sleeps, so the sleeps seem like a reasonable workaround :(.

Now when I looked at gpio-mockup again I'm under the impression that the interrupt should be fired from the set() callback, not from the debugfs write callback. But this is a minor detail and it won't help here.

Quoted 7 lines

Anyway, I'm really not sure how to do it any differently. We can also be less strict in user-space - we can schedule, let's say, 16 events without sleeps and not really verify the number of read events - just check if it's bigger than 1 and that what we read are actual events (valid event_type).

Bart

If there are no other issues, I'll apply this series as is (with some comments explaining why we do this). We can improve the tests later once we have something better in place in the kernel. For now - they are pretty stable, I've been running them a lot and never saw an issue with missed events.

Bart

#11 lore raw

PATCH 4/7 bindings: cxx: provide a method for reading multiple line events

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add a new method to gpiod::line class allowing to read up to 16 line events at once.

---
 bindings/cxx/gpiod.hpp |  7 +++++++
 bindings/cxx/line.cpp  | 45 ++++++++++++++++++++++++++++++++++--------
 2 files changed, 44 insertions(+), 8 deletions(-)
bindings/cxx/gpiod.hpp +7 -0
index 078201b..bd99d28 100644
@@ -421,6 +421,12 @@ public:
 	 */
 	GPIOD_API line_event event_read(void) const;
 
+	/**
+	 * @brief Read up to 16 line events.
+	 * @return Vector of line event objects.
+	 */
+	GPIOD_API ::std::vector<line_event> event_read_multiple(void) const;
+
 	/**
 	 * @brief Get the event file descriptor associated with this line.
 	 * @return File descriptor number.
@@ -513,6 +519,7 @@ private:
 	line(::gpiod_line* line, const chip& owner);
 
 	void throw_if_null(void) const;
+	line_event make_line_event(const ::gpiod_line_event& event) const noexcept;
 
 	::gpiod_line* _m_line;
 	chip _m_chip;
bindings/cxx/line.cpp +37 -8

60 lines not on this page · raw message

Signature · 1 line
2.23.0
#12 lore raw

PATCH 5/7 bindings: cxx: tests: add a test-case for reading multiple line events

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Extend the test coverage of C++ bindings with tests of reading of multiple line events at once.

---
 bindings/cxx/tests/tests-event.cpp | 31 ++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)
bindings/cxx/tests/tests-event.cpp +31 -0
index 8713333..63b6cc5 100644
@@ -8,6 +8,7 @@
 #include <catch2/catch.hpp>
 #include <gpiod.hpp>
 #include <poll.h>
+#include <thread>
 
 #include "gpio-mockup.hpp"
 
@@ -217,3 +218,33 @@ TEST_CASE("It's possible to read values from lines requested for events", "[even
 		REQUIRE(line.get_value() == 0);
 	}
 }
+
+TEST_CASE("It's possible to read more than one line event", "[event][line]")
+{
+	mockup::probe_guard mockup_chips({ 8 });
+	::gpiod::chip chip(mockup::instance().chip_name(0));
+	auto line = chip.get_line(4);
+	::gpiod::line_request config;
+
+	config.consumer = consumer.c_str();
+	config.request_type = ::gpiod::line_request::EVENT_BOTH_EDGES;
+
+	line.request(config);
+
+	mockup::instance().chip_set_pull(0, 4, 1);
+	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+	mockup::instance().chip_set_pull(0, 4, 0);
+	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+	mockup::instance().chip_set_pull(0, 4, 1);
+	::std::this_thread::sleep_for(::std::chrono::milliseconds(10));
+
+	auto events = line.event_read_multiple();
+
+	REQUIRE(events.size() == 3);
+	REQUIRE(events.at(0).event_type == ::gpiod::line_event::RISING_EDGE);
+	REQUIRE(events.at(1).event_type == ::gpiod::line_event::FALLING_EDGE);
+	REQUIRE(events.at(2).event_type == ::gpiod::line_event::RISING_EDGE);
+	REQUIRE(events.at(0).source == line);
+	REQUIRE(events.at(1).source == line);
+	REQUIRE(events.at(2).source == line);
+}
Signature · 1 line
2.23.0
#13 lore raw

PATCH 6/7 bindings: python: add a method for reading multiple line events

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Add a new method to the Line class allowing to read up to 16 line events at once.

---
 bindings/python/gpiodmodule.c | 57 +++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)
bindings/python/gpiodmodule.c +57 -0
index 27a8118..b0b52ee 100644
@@ -850,6 +850,57 @@ static gpiod_LineEventObject *gpiod_Line_event_read(gpiod_LineObject *self,
 	return ret;
 }
 
+PyDoc_STRVAR(gpiod_Line_event_read_multiple_doc,
+"event_read_multiple() -> list of gpiod.LineEvent object\n"
+"\n"
+"Read up to 16 line events from this GPIO line object.");
+
+static PyObject *gpiod_Line_event_read_multiple(gpiod_LineObject *self,
+						PyObject *Py_UNUSED(ignored))
+{
+	struct gpiod_line_event evbuf[16];
+	gpiod_LineEventObject *event;
+	int rv, num_events, i;
+	PyObject *events;
+
+	if (gpiod_ChipIsClosed(self->owner))
+		return NULL;
+
+	memset(evbuf, 0, sizeof(evbuf));
+	Py_BEGIN_ALLOW_THREADS;
+	num_events = gpiod_line_event_read_multiple(self->line, evbuf,
+					sizeof(evbuf) / sizeof(*evbuf));
+	Py_END_ALLOW_THREADS;
+	if (num_events < 0)
+		return PyErr_SetFromErrno(PyExc_OSError);
+
+	events = PyList_New(num_events);
+	if (!events)
+		return NULL;
+
+	for (i = 0; i < num_events; i++) {
+		event = PyObject_New(gpiod_LineEventObject,
+				     &gpiod_LineEventType);
+		if (!event) {
+			Py_DECREF(events);
+			return NULL;
+		}
+
+		memcpy(&event->event, &evbuf[i], sizeof(event->event));
+		Py_INCREF(self);
+		event->source = self;
+
+		rv = PyList_SetItem(events, i, (PyObject *)event);
+		if (rv < 0) {
+			Py_DECREF(events);
+			Py_DECREF(event);
+			return NULL;
+		}
+	}
+
+	return events;
+}
+
 PyDoc_STRVAR(gpiod_Line_event_get_fd_doc,
 "event_get_fd() -> integer\n"
 "\n"
@@ -1026,6 +1077,12 @@ static PyMethodDef gpiod_Line_methods[] = {
 		.ml_flags = METH_NOARGS,
 		.ml_doc = gpiod_Line_event_read_doc,
 	},
+	{
+		.ml_name = "event_read_multiple",
+		.ml_meth = (PyCFunction)gpiod_Line_event_read_multiple,
+		.ml_flags = METH_NOARGS,
+		.ml_doc = gpiod_Line_event_read_multiple_doc,
+	},
 	{
 		.ml_name = "event_get_fd",
 		.ml_meth = (PyCFunction)gpiod_Line_event_get_fd,
Signature · 1 line
2.23.0
#14 lore raw

PATCH 7/7 bindings: python: tests: add a test-case for reading multiple line events

In reply to Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Extend the test coverage of Python bindings with tests of reading of multiple line events at once.

---
 bindings/python/tests/gpiod_py_test.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)
bindings/python/tests/gpiod_py_test.py +22 -0
index 53d99d8..572aad8 100755
@@ -12,6 +12,7 @@ import gpiod
 import gpiomockup
 import os
 import select
+import time
 import threading
 import unittest
 
@@ -833,6 +834,27 @@ class EventSingleLine(MockupTestCase):
                 self.assertEqual(event.type, gpiod.LineEvent.RISING_EDGE)
                 self.assertEqual(event.source.offset(), 4)
 
+    def test_single_line_read_multiple_events(self):
+        with gpiod.Chip(mockup.chip_name(0)) as chip:
+            line = chip.get_line(4)
+            line.request(consumer=default_consumer,
+                         type=gpiod.LINE_REQ_EV_BOTH_EDGES)
+            mockup.chip_set_pull(0, 4, 1)
+            time.sleep(0.01)
+            mockup.chip_set_pull(0, 4, 0)
+            time.sleep(0.01)
+            mockup.chip_set_pull(0, 4, 1)
+            time.sleep(0.01)
+            self.assertTrue(line.event_wait(sec=1))
+            events = line.event_read_multiple()
+            self.assertEqual(len(events), 3)
+            self.assertEqual(events[0].type, gpiod.LineEvent.RISING_EDGE)
+            self.assertEqual(events[1].type, gpiod.LineEvent.FALLING_EDGE)
+            self.assertEqual(events[2].type, gpiod.LineEvent.RISING_EDGE)
+            self.assertEqual(events[0].source.offset(), 4)
+            self.assertEqual(events[1].source.offset(), 4)
+            self.assertEqual(events[2].source.offset(), 4)
+
 class EventBulk(MockupTestCase):
 
     chip_sizes = ( 8, )
Signature · 1 line
2.23.0