All of lore.kernel.org
 help / color / mirror / Atom feed
* [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
@ 2021-04-10 14:51 Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 1/6] treewide: rename chip property accessors Bartosz Golaszewski
                   ` (6 more replies)
  0 siblings, 7 replies; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-10 14:51 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij, Andy Shevchenko
  Cc: linux-gpio, Bartosz Golaszewski

This series introduces an entirely reworked API for the core C part of
libgpiod. It's not fully functional as the bindings are not modified,
and starting from patch 5, even the tests stop working. This is on
purpose as I'd like to reach an agreement on the interface before
spending time on reworking the tests.

My plan for the development of v2.0 is to keep the changes in a separate
branch until all bits & pieces are complete and then rearrange them into
a bisectable series that will be merged into the master branch.

A couple points regarding the design of the new API:
- all objects have become opaque and can only be accessed using dedicated
  functions
- line objects as well as bulk containers have been removed
- line requests are now configured using three types of structures: attributes,
  line config and request config structures, which follows the kernel API
- line request handles have now a lifetime separate from the parent chips to
  leverage the separation of chip and request file descriptors
- line events are now opaque but they can be stored in a dedicated container
  so that memory allocations are not necessary everytime an event is read from
  the kernel
- the library code has been split into several compilation units for easier
  maintenance

The new API is completely documented in include/gpiod.h doxygen comments.

Please let me know what you think. I am aware that these patches are huge and
difficult to review but I'm really only expecting a general API review - all
other implementation details can be improved later.

The branch from which this series was generated can be found on my github:
  https://github.com/brgl/libgpiod-private/tree/topic/core-lib-API-v2

Bartosz Golaszewski (6):
  treewide: rename chip property accessors
  core: add refcounting helpers
  core: implement line_info objects
  core: rework line events
  core: rework line requests
  core: implement line watch events

 bindings/cxx/chip.cpp         |    6 +-
 bindings/python/gpiodmodule.c |   14 +-
 include/gpiod.h               | 1224 +++++++++++++++++---------------
 lib/Makefile.am               |   15 +-
 lib/attr.c                    |  232 ++++++
 lib/chip.c                    |  234 +++++++
 lib/config.c                  |  158 +++++
 lib/core.c                    | 1240 ---------------------------------
 lib/event.c                   |  218 ++++++
 lib/handle.c                  |  126 ++++
 lib/helpers.c                 |  302 --------
 lib/info.c                    |  171 +++++
 lib/internal.c                |  147 ++++
 lib/internal.h                |   43 ++
 lib/mask.c                    |   43 ++
 lib/misc.c                    |   80 +++
 lib/request.c                 |  118 ++++
 lib/watch.c                   |   96 +++
 tests/gpiod-test.h            |    5 +
 tests/tests-chip.c            |   33 +-
 tests/tests-event.c           |  470 +++++++++----
 tests/tests-line.c            |  425 ++++++++---
 tools/gpio-tools-test.bats    |   12 +-
 tools/gpiodetect.c            |   17 +-
 tools/gpiofind.c              |    3 +-
 tools/gpioget.c               |   57 +-
 tools/gpioinfo.c              |   58 +-
 tools/gpiomon.c               |  130 ++--
 tools/gpioset.c               |   85 ++-
 tools/tools-common.c          |    8 +-
 tools/tools-common.h          |    2 +-
 31 files changed, 3255 insertions(+), 2517 deletions(-)
 create mode 100644 lib/attr.c
 create mode 100644 lib/chip.c
 create mode 100644 lib/config.c
 delete mode 100644 lib/core.c
 create mode 100644 lib/event.c
 create mode 100644 lib/handle.c
 delete mode 100644 lib/helpers.c
 create mode 100644 lib/info.c
 create mode 100644 lib/internal.c
 create mode 100644 lib/mask.c
 create mode 100644 lib/request.c
 create mode 100644 lib/watch.c

-- 
2.30.1


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [libgpiod][RFC 1/6] treewide: rename chip property accessors
  2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
@ 2021-04-10 14:51 ` Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 2/6] core: add refcounting helpers Bartosz Golaszewski
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-10 14:51 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij, Andy Shevchenko
  Cc: linux-gpio, Bartosz Golaszewski

In v2 API all getters will be called using the following pattern:

    gpiod_<object>_get_<what>

Apply this to already existing getters for gpiod_chip.

Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
---
 bindings/cxx/chip.cpp         |  6 +++---
 bindings/python/gpiodmodule.c | 14 +++++++-------
 include/gpiod.h               |  6 +++---
 lib/core.c                    |  6 +++---
 lib/helpers.c                 |  6 +++---
 tests/tests-chip.c            | 25 ++++++++++++++-----------
 tools/gpiodetect.c            |  6 +++---
 tools/gpiofind.c              |  2 +-
 tools/gpioinfo.c              |  4 ++--
 9 files changed, 39 insertions(+), 36 deletions(-)

diff --git a/bindings/cxx/chip.cpp b/bindings/cxx/chip.cpp
index d5a9837..ee6ab6f 100644
--- a/bindings/cxx/chip.cpp
+++ b/bindings/cxx/chip.cpp
@@ -62,21 +62,21 @@ GPIOD_CXX_API ::std::string chip::name(void) const
 {
 	this->throw_if_noref();
 
-	return ::std::string(::gpiod_chip_name(this->_m_chip.get()));
+	return ::std::string(::gpiod_chip_get_name(this->_m_chip.get()));
 }
 
 GPIOD_CXX_API ::std::string chip::label(void) const
 {
 	this->throw_if_noref();
 
-	return ::std::string(::gpiod_chip_label(this->_m_chip.get()));
+	return ::std::string(::gpiod_chip_get_label(this->_m_chip.get()));
 }
 
 GPIOD_CXX_API unsigned int chip::num_lines(void) const
 {
 	this->throw_if_noref();
 
-	return ::gpiod_chip_num_lines(this->_m_chip.get());
+	return ::gpiod_chip_get_num_lines(this->_m_chip.get());
 }
 
 GPIOD_CXX_API line chip::get_line(unsigned int offset) const
diff --git a/bindings/python/gpiodmodule.c b/bindings/python/gpiodmodule.c
index 8bfb4c4..ed039e4 100644
--- a/bindings/python/gpiodmodule.c
+++ b/bindings/python/gpiodmodule.c
@@ -1918,9 +1918,9 @@ static PyObject *gpiod_Chip_repr(gpiod_ChipObject *self)
 		return NULL;
 
 	return PyUnicode_FromFormat("'%s /%s/ %u lines'",
-				    gpiod_chip_name(self->chip),
-				    gpiod_chip_label(self->chip),
-				    gpiod_chip_num_lines(self->chip));
+				    gpiod_chip_get_name(self->chip),
+				    gpiod_chip_get_label(self->chip),
+				    gpiod_chip_get_num_lines(self->chip));
 }
 
 PyDoc_STRVAR(gpiod_Chip_close_doc,
@@ -1971,7 +1971,7 @@ static PyObject *gpiod_Chip_name(gpiod_ChipObject *self,
 	if (gpiod_ChipIsClosed(self))
 		return NULL;
 
-	return PyUnicode_FromFormat("%s", gpiod_chip_name(self->chip));
+	return PyUnicode_FromFormat("%s", gpiod_chip_get_name(self->chip));
 }
 
 PyDoc_STRVAR(gpiod_Chip_label_doc,
@@ -1985,7 +1985,7 @@ static PyObject *gpiod_Chip_label(gpiod_ChipObject *self,
 	if (gpiod_ChipIsClosed(self))
 		return NULL;
 
-	return PyUnicode_FromFormat("%s", gpiod_chip_label(self->chip));
+	return PyUnicode_FromFormat("%s", gpiod_chip_get_label(self->chip));
 }
 
 PyDoc_STRVAR(gpiod_Chip_num_lines_doc,
@@ -1999,7 +1999,7 @@ static PyObject *gpiod_Chip_num_lines(gpiod_ChipObject *self,
 	if (gpiod_ChipIsClosed(self))
 		return NULL;
 
-	return Py_BuildValue("I", gpiod_chip_num_lines(self->chip));
+	return Py_BuildValue("I", gpiod_chip_get_num_lines(self->chip));
 }
 
 static gpiod_LineObject *
@@ -2381,7 +2381,7 @@ static gpiod_LineObject *gpiod_LineIter_next(gpiod_LineIterObject *self)
 {
 	struct gpiod_line *line;
 
-	if (self->offset == gpiod_chip_num_lines(self->owner->chip))
+	if (self->offset == gpiod_chip_get_num_lines(self->owner->chip))
 		return NULL; /* Last element. */
 
 	line = gpiod_chip_get_line(self->owner->chip, self->offset++);
diff --git a/include/gpiod.h b/include/gpiod.h
index 5aea01f..a4ce01f 100644
--- a/include/gpiod.h
+++ b/include/gpiod.h
@@ -94,21 +94,21 @@ void gpiod_chip_unref(struct gpiod_chip *chip);
  * @param chip The GPIO chip object.
  * @return Pointer to a human-readable string containing the chip name.
  */
-const char *gpiod_chip_name(struct gpiod_chip *chip);
+const char *gpiod_chip_get_name(struct gpiod_chip *chip);
 
 /**
  * @brief Get the GPIO chip label as represented in the kernel.
  * @param chip The GPIO chip object.
  * @return Pointer to a human-readable string containing the chip label.
  */
-const char *gpiod_chip_label(struct gpiod_chip *chip);
+const char *gpiod_chip_get_label(struct gpiod_chip *chip);
 
 /**
  * @brief Get the number of GPIO lines exposed by this chip.
  * @param chip The GPIO chip object.
  * @return Number of GPIO lines.
  */
-unsigned int gpiod_chip_num_lines(struct gpiod_chip *chip);
+unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip);
 
 /**
  * @brief Get the handle to the GPIO line at given offset.
diff --git a/lib/core.c b/lib/core.c
index 32c4238..2e7ee4b 100644
--- a/lib/core.c
+++ b/lib/core.c
@@ -335,17 +335,17 @@ GPIOD_API void gpiod_chip_unref(struct gpiod_chip *chip)
 	free(chip);
 }
 
-GPIOD_API const char *gpiod_chip_name(struct gpiod_chip *chip)
+GPIOD_API const char *gpiod_chip_get_name(struct gpiod_chip *chip)
 {
 	return chip->name;
 }
 
-GPIOD_API const char *gpiod_chip_label(struct gpiod_chip *chip)
+GPIOD_API const char *gpiod_chip_get_label(struct gpiod_chip *chip)
 {
 	return chip->label;
 }
 
-GPIOD_API unsigned int gpiod_chip_num_lines(struct gpiod_chip *chip)
+GPIOD_API unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip)
 {
 	return chip->num_lines;
 }
diff --git a/lib/helpers.c b/lib/helpers.c
index 9c4b28e..fb53518 100644
--- a/lib/helpers.c
+++ b/lib/helpers.c
@@ -45,11 +45,11 @@ gpiod_chip_get_all_lines(struct gpiod_chip *chip)
 	struct gpiod_line *line;
 	unsigned int offset;
 
-	bulk = gpiod_line_bulk_new(gpiod_chip_num_lines(chip));
+	bulk = gpiod_line_bulk_new(gpiod_chip_get_num_lines(chip));
 	if (!bulk)
 		return NULL;
 
-	for (offset = 0; offset < gpiod_chip_num_lines(chip); offset++) {
+	for (offset = 0; offset < gpiod_chip_get_num_lines(chip); offset++) {
 		line = gpiod_chip_get_line(chip, offset);
 		if (!line) {
 			gpiod_line_bulk_free(bulk);
@@ -68,7 +68,7 @@ GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
 	struct gpiod_line *line;
 	const char *tmp;
 
-	num_lines = gpiod_chip_num_lines(chip);
+	num_lines = gpiod_chip_get_num_lines(chip);
 
 	for (offset = 0; offset < num_lines; offset++) {
 		line = gpiod_chip_get_line(chip, offset);
diff --git a/tests/tests-chip.c b/tests/tests-chip.c
index a87dc9a..46fb8d2 100644
--- a/tests/tests-chip.c
+++ b/tests/tests-chip.c
@@ -63,9 +63,12 @@ GPIOD_TEST_CASE(get_name, 0, { 8, 8, 8})
 	g_assert_nonnull(chip2);
 	gpiod_test_return_if_failed();
 
-	g_assert_cmpstr(gpiod_chip_name(chip0), ==, gpiod_test_chip_name(0));
-	g_assert_cmpstr(gpiod_chip_name(chip1), ==, gpiod_test_chip_name(1));
-	g_assert_cmpstr(gpiod_chip_name(chip2), ==, gpiod_test_chip_name(2));
+	g_assert_cmpstr(gpiod_chip_get_name(chip0), ==,
+			gpiod_test_chip_name(0));
+	g_assert_cmpstr(gpiod_chip_get_name(chip1), ==,
+			gpiod_test_chip_name(1));
+	g_assert_cmpstr(gpiod_chip_get_name(chip2), ==,
+			gpiod_test_chip_name(2));
 }
 
 GPIOD_TEST_CASE(get_label, 0, { 8, 8, 8})
@@ -83,9 +86,9 @@ GPIOD_TEST_CASE(get_label, 0, { 8, 8, 8})
 	g_assert_nonnull(chip2);
 	gpiod_test_return_if_failed();
 
-	g_assert_cmpstr(gpiod_chip_label(chip0), ==, "gpio-mockup-A");
-	g_assert_cmpstr(gpiod_chip_label(chip1), ==, "gpio-mockup-B");
-	g_assert_cmpstr(gpiod_chip_label(chip2), ==, "gpio-mockup-C");
+	g_assert_cmpstr(gpiod_chip_get_label(chip0), ==, "gpio-mockup-A");
+	g_assert_cmpstr(gpiod_chip_get_label(chip1), ==, "gpio-mockup-B");
+	g_assert_cmpstr(gpiod_chip_get_label(chip2), ==, "gpio-mockup-C");
 }
 
 GPIOD_TEST_CASE(num_lines, 0, { 1, 4, 8, 16, 32 })
@@ -109,11 +112,11 @@ GPIOD_TEST_CASE(num_lines, 0, { 1, 4, 8, 16, 32 })
 	g_assert_nonnull(chip4);
 	gpiod_test_return_if_failed();
 
-	g_assert_cmpuint(gpiod_chip_num_lines(chip0), ==, 1);
-	g_assert_cmpuint(gpiod_chip_num_lines(chip1), ==, 4);
-	g_assert_cmpuint(gpiod_chip_num_lines(chip2), ==, 8);
-	g_assert_cmpuint(gpiod_chip_num_lines(chip3), ==, 16);
-	g_assert_cmpuint(gpiod_chip_num_lines(chip4), ==, 32);
+	g_assert_cmpuint(gpiod_chip_get_num_lines(chip0), ==, 1);
+	g_assert_cmpuint(gpiod_chip_get_num_lines(chip1), ==, 4);
+	g_assert_cmpuint(gpiod_chip_get_num_lines(chip2), ==, 8);
+	g_assert_cmpuint(gpiod_chip_get_num_lines(chip3), ==, 16);
+	g_assert_cmpuint(gpiod_chip_get_num_lines(chip4), ==, 32);
 }
 
 GPIOD_TEST_CASE(get_line, 0, { 16 })
diff --git a/tools/gpiodetect.c b/tools/gpiodetect.c
index 9f4c28c..10706e2 100644
--- a/tools/gpiodetect.c
+++ b/tools/gpiodetect.c
@@ -76,9 +76,9 @@ int main(int argc, char **argv)
 		}
 
 		printf("%s [%s] (%u lines)\n",
-		       gpiod_chip_name(chip),
-		       gpiod_chip_label(chip),
-		       gpiod_chip_num_lines(chip));
+		       gpiod_chip_get_name(chip),
+		       gpiod_chip_get_label(chip),
+		       gpiod_chip_get_num_lines(chip));
 
 		gpiod_chip_unref(chip);
 		free(entries[i]);
diff --git a/tools/gpiofind.c b/tools/gpiofind.c
index 83af76b..32b7852 100644
--- a/tools/gpiofind.c
+++ b/tools/gpiofind.c
@@ -76,7 +76,7 @@ int main(int argc, char **argv)
 		offset = gpiod_chip_find_line(chip, argv[0]);
 		if (offset >= 0) {
 			printf("%s %u\n",
-			       gpiod_chip_name(chip), offset);
+			       gpiod_chip_get_name(chip), offset);
 			gpiod_chip_unref(chip);
 			return EXIT_SUCCESS;
 		}
diff --git a/tools/gpioinfo.c b/tools/gpioinfo.c
index eba8c72..3d89111 100644
--- a/tools/gpioinfo.c
+++ b/tools/gpioinfo.c
@@ -130,9 +130,9 @@ static void list_lines(struct gpiod_chip *chip)
 	int direction;
 
 	printf("%s - %u lines:\n",
-	       gpiod_chip_name(chip), gpiod_chip_num_lines(chip));
+	       gpiod_chip_get_name(chip), gpiod_chip_get_num_lines(chip));
 
-	for (offset = 0; offset < gpiod_chip_num_lines(chip); offset++) {
+	for (offset = 0; offset < gpiod_chip_get_num_lines(chip); offset++) {
 		line = gpiod_chip_get_line(chip, offset);
 		if (!line)
 			die_perror("unable to retrieve the line object from chip");
-- 
2.30.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [libgpiod][RFC 2/6] core: add refcounting helpers
  2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 1/6] treewide: rename chip property accessors Bartosz Golaszewski
@ 2021-04-10 14:51 ` Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 3/6] core: implement line_info objects Bartosz Golaszewski
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-10 14:51 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij, Andy Shevchenko
  Cc: linux-gpio, Bartosz Golaszewski

In v2.0 all objects will be opaque and refcounted. Add a set of helper
functions and data structures for refcounting.

Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
---
 lib/Makefile.am |  2 +-
 lib/core.c      | 52 ++++++++++++++++++++++++++-----------------------
 lib/internal.c  | 22 +++++++++++++++++++++
 lib/internal.h  | 19 ++++++++++++++++++
 4 files changed, 70 insertions(+), 25 deletions(-)
 create mode 100644 lib/internal.c

diff --git a/lib/Makefile.am b/lib/Makefile.am
index 8441584..5c7f353 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -2,7 +2,7 @@
 # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
 
 lib_LTLIBRARIES = libgpiod.la
-libgpiod_la_SOURCES = core.c helpers.c internal.h misc.c uapi/gpio.h
+libgpiod_la_SOURCES = core.c helpers.c internal.c internal.h misc.c uapi/gpio.h
 libgpiod_la_CFLAGS = -Wall -Wextra -g -std=gnu89
 libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
 libgpiod_la_CFLAGS += -include $(top_builddir)/config.h
diff --git a/lib/core.c b/lib/core.c
index 2e7ee4b..0f3937b 100644
--- a/lib/core.c
+++ b/lib/core.c
@@ -65,7 +65,7 @@ struct gpiod_line {
 };
 
 struct gpiod_chip {
-	int refcount;
+	struct gpiod_refcount refcount;
 
 	struct gpiod_line **lines;
 	unsigned int num_lines;
@@ -246,6 +246,30 @@ out:
 	return ret;
 }
 
+static void chip_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_chip *chip;
+	struct gpiod_line *line;
+	unsigned int i;
+
+	chip = gpiod_container_of(refcount, struct gpiod_chip, refcount);
+
+	if (chip->lines) {
+		for (i = 0; i < chip->num_lines; i++) {
+			line = chip->lines[i];
+			if (line) {
+				gpiod_line_release(line);
+				free(line);
+			}
+		}
+
+		free(chip->lines);
+	}
+
+	close(chip->fd);
+	free(chip);
+}
+
 GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
 {
 	struct gpiochip_info info;
@@ -276,7 +300,7 @@ GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
 
 	chip->fd = fd;
 	chip->num_lines = info.lines;
-	chip->refcount = 1;
+	gpiod_refcount_init(&chip->refcount, chip_release);
 
 	/*
 	 * GPIO device must have a name - don't bother checking this field. In
@@ -306,33 +330,13 @@ err_close_fd:
 
 GPIOD_API struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip)
 {
-	chip->refcount++;
+	gpiod_refcount_ref(&chip->refcount);
 	return chip;
 }
 
 GPIOD_API void gpiod_chip_unref(struct gpiod_chip *chip)
 {
-	struct gpiod_line *line;
-	unsigned int i;
-
-	chip->refcount--;
-	if (chip->refcount > 0)
-		return;
-
-	if (chip->lines) {
-		for (i = 0; i < chip->num_lines; i++) {
-			line = chip->lines[i];
-			if (line) {
-				gpiod_line_release(line);
-				free(line);
-			}
-		}
-
-		free(chip->lines);
-	}
-
-	close(chip->fd);
-	free(chip);
+	gpiod_refcount_unref(&chip->refcount);
 }
 
 GPIOD_API const char *gpiod_chip_get_name(struct gpiod_chip *chip)
diff --git a/lib/internal.c b/lib/internal.c
new file mode 100644
index 0000000..52b9461
--- /dev/null
+++ b/lib/internal.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include "internal.h"
+
+void gpiod_refcount_init(struct gpiod_refcount *refcount,
+			 gpiod_refcount_release release)
+{
+	refcount->refcnt = 1;
+	refcount->release = release;
+}
+
+void gpiod_refcount_ref(struct gpiod_refcount *refcount)
+{
+	refcount->refcnt++;
+}
+
+void gpiod_refcount_unref(struct gpiod_refcount *refcount)
+{
+	if (--refcount->refcnt == 0)
+		refcount->release(refcount);
+}
diff --git a/lib/internal.h b/lib/internal.h
index 8b3f69a..a652879 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -4,8 +4,27 @@
 #ifndef __LIBGPIOD_GPIOD_INTERNAL_H__
 #define __LIBGPIOD_GPIOD_INTERNAL_H__
 
+#include <stddef.h>
+
 /* For internal library use only. */
 
 #define GPIOD_API __attribute__((visibility("default")))
 
+#define gpiod_container_of(ptr, type, member) ({			\
+	void *__mptr = (void *)(ptr);					\
+	((type *)(__mptr - offsetof(type, member))); })
+
+struct gpiod_refcount;
+typedef void (*gpiod_refcount_release)(struct gpiod_refcount *);
+
+struct gpiod_refcount {
+	unsigned int refcnt;
+	gpiod_refcount_release release;
+};
+
+void gpiod_refcount_init(struct gpiod_refcount *refcount,
+			 gpiod_refcount_release release);
+void gpiod_refcount_ref(struct gpiod_refcount *refcount);
+void gpiod_refcount_unref(struct gpiod_refcount *refcount);
+
 #endif /* __LIBGPIOD_GPIOD_INTERNAL_H__ */
-- 
2.30.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [libgpiod][RFC 3/6] core: implement line_info objects
  2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 1/6] treewide: rename chip property accessors Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 2/6] core: add refcounting helpers Bartosz Golaszewski
@ 2021-04-10 14:51 ` Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 4/6] core: rework line events Bartosz Golaszewski
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-10 14:51 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij, Andy Shevchenko
  Cc: linux-gpio, Bartosz Golaszewski

Implement an opaque object storing a snapshot of a GPIO line's status
info and add functions for accessing its fields.

Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
---
 include/gpiod.h    | 119 ++++++++++---
 lib/Makefile.am    |   2 +-
 lib/core.c         |  63 ++-----
 lib/helpers.c      |  16 +-
 lib/info.c         | 170 ++++++++++++++++++
 lib/internal.h     |   6 +
 tests/gpiod-test.h |   2 +
 tests/tests-chip.c |   8 +-
 tests/tests-line.c | 425 ++++++++++++++++++++++++++++++++++++---------
 tools/gpioinfo.c   |  42 ++---
 10 files changed, 672 insertions(+), 181 deletions(-)
 create mode 100644 lib/info.c

diff --git a/include/gpiod.h b/include/gpiod.h
index a4ce01f..7da9ff4 100644
--- a/include/gpiod.h
+++ b/include/gpiod.h
@@ -35,6 +35,7 @@ extern "C" {
 
 struct gpiod_chip;
 struct gpiod_line;
+struct gpiod_line_info;
 struct gpiod_line_bulk;
 
 /**
@@ -110,6 +111,17 @@ const char *gpiod_chip_get_label(struct gpiod_chip *chip);
  */
 unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip);
 
+/**
+ * @brief Get the current snapshot of information about the line at given
+ *        offset.
+ * @param chip The GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return New GPIO line info object that must be freed using
+ *         ::gpiod_line_info_free or NULL if an error occurred.
+ */
+struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
+						 unsigned int offset);
+
 /**
  * @brief Get the handle to the GPIO line at given offset.
  * @param chip The GPIO chip object.
@@ -286,6 +298,20 @@ enum {
 	/**< The internal pull-down bias is enabled. */
 };
 
+/**
+ * @brief Possible edge detection settings.
+ */
+enum {
+	GPIOD_LINE_EDGE_NONE = 1,
+	/**< Line edge detection is disabled. */
+	GPIOD_LINE_EDGE_RISING,
+	/**< Line detects rising edge events. */
+	GPIOD_LINE_EDGE_FALLING,
+	/**< Line detect falling edge events. */
+	GPIOD_LINE_EDGE_BOTH,
+	/**< Line detects both rising and falling edge events. */
+};
+
 /**
  * @brief Read the GPIO line offset.
  * @param line GPIO line object.
@@ -293,64 +319,111 @@ enum {
  */
 unsigned int gpiod_line_offset(struct gpiod_line *line);
 
+/**
+ * @brief Increase the reference count for this line info object.
+ * @param info GPIO line info object.
+ * @return Passed reference to the line info object.
+ */
+struct gpiod_line_info *
+gpiod_line_info_ref(struct gpiod_line_info *info);
+
+/**
+ * @brief Decrease the reference count on this line info object. If it reaches
+ *        0, then free all associated resources.
+ * @param info GPIO line info object.
+ */
+void gpiod_line_info_unref(struct gpiod_line_info *info);
+
+/**
+ * @brief Get the hardware offset of the line.
+ * @param info GPIO line info object.
+ * @return Offset of the line within the parent chip.
+ */
+unsigned int gpiod_line_info_get_offset(struct gpiod_line_info *info);
+
 /**
  * @brief Read the GPIO line name.
- * @param line GPIO line object.
+ * @param info GPIO line info object.
  * @return Name of the GPIO line as it is represented in the kernel. This
  *         routine returns a pointer to a null-terminated string or NULL if
  *         the line is unnamed.
  */
-const char *gpiod_line_name(struct gpiod_line *line);
+const char *gpiod_line_get_name(struct gpiod_line_info *info);
+
+/**
+ * @brief Check if the line is currently in use.
+ * @param info GPIO line object.
+ * @return True if the line is in use, false otherwise.
+ *
+ * The user space can't know exactly why a line is busy. It may have been
+ * requested by another process or hogged by the kernel. It only matters that
+ * the line is used and we can't request it.
+ */
+bool gpiod_line_is_used(struct gpiod_line_info *info);
 
 /**
  * @brief Read the GPIO line consumer name.
- * @param line GPIO line object.
+ * @param info GPIO line info object.
  * @return Name of the GPIO consumer name as it is represented in the
  *         kernel. This routine returns a pointer to a null-terminated string
  *         or NULL if the line is not used.
  */
-const char *gpiod_line_consumer(struct gpiod_line *line);
+const char *gpiod_line_get_consumer(struct gpiod_line_info *info);
 
 /**
  * @brief Read the GPIO line direction setting.
- * @param line GPIO line object.
+ * @param info GPIO line info object.
  * @return Returns GPIOD_LINE_DIRECTION_INPUT or GPIOD_LINE_DIRECTION_OUTPUT.
  */
-int gpiod_line_direction(struct gpiod_line *line);
+int gpiod_line_get_direction(struct gpiod_line_info *info);
 
 /**
  * @brief Check if the signal of this line is inverted.
- * @param line GPIO line object.
+ * @param info GPIO line object.
  * @return True if this line is "active-low", false otherwise.
  */
-bool gpiod_line_is_active_low(struct gpiod_line *line);
+bool gpiod_line_is_active_low(struct gpiod_line_info *info);
 
 /**
  * @brief Read the GPIO line bias setting.
- * @param line GPIO line object.
+ * @param info GPIO line object.
  * @return Returns GPIOD_LINE_BIAS_PULL_UP, GPIOD_LINE_BIAS_PULL_DOWN,
  *         GPIOD_LINE_BIAS_DISABLE or GPIOD_LINE_BIAS_UNKNOWN.
  */
-int gpiod_line_bias(struct gpiod_line *line);
-
-/**
- * @brief Check if the line is currently in use.
- * @param line GPIO line object.
- * @return True if the line is in use, false otherwise.
- *
- * The user space can't know exactly why a line is busy. It may have been
- * requested by another process or hogged by the kernel. It only matters that
- * the line is used and we can't request it.
- */
-bool gpiod_line_is_used(struct gpiod_line *line);
+int gpiod_line_get_bias(struct gpiod_line_info *info);
 
 /**
  * @brief Read the GPIO line drive setting.
- * @param line GPIO line object.
+ * @param info GPIO line info object.
  * @return Returns GPIOD_LINE_DRIVE_PUSH_PULL, GPIOD_LINE_DRIVE_OPEN_DRAIN or
  *         GPIOD_LINE_DRIVE_OPEN_SOURCE.
  */
-int gpiod_line_drive(struct gpiod_line *line);
+int gpiod_line_get_drive(struct gpiod_line_info *info);
+
+/**
+ * @brief Read the current edge detection setting of this line.
+ * @param info GPIO line info object.
+ * @return Returns GPIOD_LINE_EDGE_NONE, GPIOD_LINE_EDGE_RISING,
+ *         GPIOD_LINE_EDGE_FALLING or GPIOD_LINE_EDGE_BOTH.
+ */
+int gpiod_line_get_edge_detection(struct gpiod_line_info *info);
+
+/**
+ * @brief Check if this line is debounced (either by hardware or by the kernel
+ *        software debouncer).
+ * @param info GPIO line info object.
+ * @return True if the line is debounced, false otherwise.
+ */
+bool gpiod_line_is_debounced(struct gpiod_line_info *info);
+
+/**
+ * @brief Read the current debounce period in microseconds.
+ * @param info GPIO line info object.
+ * @return Current debounce period in microseconds, 0 if the line is not
+ *         debounced.
+ */
+unsigned long
+gpiod_line_get_debounce_period(struct gpiod_line_info *info);
 
 /**
  * @brief Get the handle to the GPIO chip controlling this line.
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 5c7f353..c5d6070 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -2,7 +2,7 @@
 # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
 
 lib_LTLIBRARIES = libgpiod.la
-libgpiod_la_SOURCES = core.c helpers.c internal.c internal.h misc.c uapi/gpio.h
+libgpiod_la_SOURCES = core.c helpers.c internal.c internal.h info.c misc.c uapi/gpio.h
 libgpiod_la_CFLAGS = -Wall -Wextra -g -std=gnu89
 libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
 libgpiod_la_CFLAGS += -include $(top_builddir)/config.h
diff --git a/lib/core.c b/lib/core.c
index 0f3937b..526dcaa 100644
--- a/lib/core.c
+++ b/lib/core.c
@@ -180,6 +180,22 @@ GPIOD_API void gpiod_line_bulk_foreach_line(struct gpiod_line_bulk *bulk,
 	     (index) < (bulk)->num_lines;				\
 	     (index)++, (line) = (bulk)->lines[(index)])
 
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_get_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+	struct gpio_v2_line_info infobuf;
+	int ret;
+
+	memset(&infobuf, 0, sizeof(infobuf));
+	infobuf.offset = offset;
+
+	ret = ioctl(chip->fd, GPIO_V2_GET_LINEINFO_IOCTL, &infobuf);
+	if (ret < 0)
+		return NULL;
+
+	return gpiod_line_info_from_kernel(&infobuf);
+}
+
 GPIOD_API bool gpiod_is_gpiochip_device(const char *path)
 {
 	char *realname, *sysfsp, devpath[64];
@@ -449,53 +465,6 @@ GPIOD_API unsigned int gpiod_line_offset(struct gpiod_line *line)
 	return line->offset;
 }
 
-GPIOD_API const char *gpiod_line_name(struct gpiod_line *line)
-{
-	return line->name[0] == '\0' ? NULL : line->name;
-}
-
-GPIOD_API const char *gpiod_line_consumer(struct gpiod_line *line)
-{
-	return line->consumer[0] == '\0' ? NULL : line->consumer;
-}
-
-GPIOD_API int gpiod_line_direction(struct gpiod_line *line)
-{
-	return line->direction;
-}
-
-GPIOD_API bool gpiod_line_is_active_low(struct gpiod_line *line)
-{
-	return line->active_low;
-}
-
-GPIOD_API int gpiod_line_bias(struct gpiod_line *line)
-{
-	if (line->info_flags & GPIOLINE_FLAG_BIAS_DISABLE)
-		return GPIOD_LINE_BIAS_DISABLED;
-	if (line->info_flags & GPIOLINE_FLAG_BIAS_PULL_UP)
-		return GPIOD_LINE_BIAS_PULL_UP;
-	if (line->info_flags & GPIOLINE_FLAG_BIAS_PULL_DOWN)
-		return GPIOD_LINE_BIAS_PULL_DOWN;
-
-	return GPIOD_LINE_BIAS_UNKNOWN;
-}
-
-GPIOD_API bool gpiod_line_is_used(struct gpiod_line *line)
-{
-	return line->info_flags & GPIOLINE_FLAG_KERNEL;
-}
-
-GPIOD_API int gpiod_line_drive(struct gpiod_line *line)
-{
-	if (line->info_flags & GPIOLINE_FLAG_OPEN_DRAIN)
-		return GPIOD_LINE_DRIVE_OPEN_DRAIN;
-	if (line->info_flags & GPIOLINE_FLAG_OPEN_SOURCE)
-		return GPIOD_LINE_DRIVE_OPEN_SOURCE;
-
-	return GPIOD_LINE_DRIVE_PUSH_PULL;
-}
-
 static int line_info_v2_to_info_flags(struct gpio_v2_line_info *info)
 {
 	int iflags = 0;
diff --git a/lib/helpers.c b/lib/helpers.c
index fb53518..6e15dcf 100644
--- a/lib/helpers.c
+++ b/lib/helpers.c
@@ -65,19 +65,23 @@ gpiod_chip_get_all_lines(struct gpiod_chip *chip)
 GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
 {
 	unsigned int offset, num_lines;
-	struct gpiod_line *line;
+	struct gpiod_line_info *info;
 	const char *tmp;
 
 	num_lines = gpiod_chip_get_num_lines(chip);
 
 	for (offset = 0; offset < num_lines; offset++) {
-		line = gpiod_chip_get_line(chip, offset);
-		if (!line)
+		info = gpiod_chip_get_line_info(chip, offset);
+		if (!info)
 			return -1;
 
-		tmp = gpiod_line_name(line);
-		if (tmp && strcmp(tmp, name) == 0)
-			return gpiod_line_offset(line);
+		tmp = gpiod_line_get_name(info);
+		if (tmp && strcmp(tmp, name) == 0) {
+			gpiod_line_info_unref(info);
+			return offset;
+		}
+
+		gpiod_line_info_unref(info);
 	}
 
 	errno = ENOENT;
diff --git a/lib/info.c b/lib/info.c
new file mode 100644
index 0000000..5f7c463
--- /dev/null
+++ b/lib/info.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <gpiod.h>
+#include <string.h>
+
+#include "internal.h"
+#include "uapi/gpio.h"
+
+struct gpiod_line_info {
+	struct gpiod_refcount refcount;
+	unsigned int offset;
+	char name[GPIO_MAX_NAME_SIZE];
+	bool used;
+	char consumer[GPIO_MAX_NAME_SIZE];
+	int direction;
+	bool active_low;
+	int bias;
+	int drive;
+	int edge;
+	bool debounced;
+	unsigned long debounce_period;
+};
+
+GPIOD_API struct gpiod_line_info *
+gpiod_line_info_ref(struct gpiod_line_info *info)
+{
+	gpiod_refcount_ref(&info->refcount);
+	return info;
+}
+
+static void line_info_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_line_info *info;
+
+	info = gpiod_container_of(refcount, struct gpiod_line_info, refcount);
+
+	free(info);
+}
+
+GPIOD_API void gpiod_line_info_unref(struct gpiod_line_info *info)
+{
+	gpiod_refcount_unref(&info->refcount);
+}
+
+GPIOD_API unsigned int gpiod_line_info_get_offset(struct gpiod_line_info *info)
+{
+	return info->offset;
+}
+
+GPIOD_API const char *gpiod_line_get_name(struct gpiod_line_info *info)
+{
+	return info->name[0] == '\0' ? NULL : info->name;
+}
+
+GPIOD_API bool gpiod_line_is_used(struct gpiod_line_info *info)
+{
+	return info->used;
+}
+
+GPIOD_API const char *gpiod_line_get_consumer(struct gpiod_line_info *info)
+{
+	return info->consumer[0] == '\0' ? NULL : info->consumer;
+}
+
+GPIOD_API int gpiod_line_get_direction(struct gpiod_line_info *info)
+{
+	return info->direction;
+}
+
+GPIOD_API bool gpiod_line_is_active_low(struct gpiod_line_info *info)
+{
+	return info->active_low;
+}
+
+GPIOD_API int gpiod_line_get_bias(struct gpiod_line_info *info)
+{
+	return info->bias;
+}
+
+GPIOD_API int gpiod_line_get_drive(struct gpiod_line_info *info)
+{
+	return info->drive;
+}
+
+GPIOD_API int gpiod_line_get_edge_detection(struct gpiod_line_info *info)
+{
+	return info->edge;
+}
+
+GPIOD_API bool gpiod_line_is_debounced(struct gpiod_line_info *info)
+{
+	return info->debounced;
+}
+
+GPIOD_API unsigned long
+gpiod_line_get_debounce_period(struct gpiod_line_info *info)
+{
+	return info->debounce_period;
+}
+
+struct gpiod_line_info *
+gpiod_line_info_from_kernel(struct gpio_v2_line_info *infobuf)
+{
+	struct gpio_v2_line_attribute *attr;
+	struct gpiod_line_info *info;
+	unsigned int i;
+
+	info = malloc(sizeof(*info));
+	if (!info)
+		return NULL;
+
+	memset(info, 0, sizeof(*info));
+
+	gpiod_refcount_init(&info->refcount, line_info_release);
+	info->offset = infobuf->offset;
+	strncpy(info->name, infobuf->name, GPIO_MAX_NAME_SIZE);
+
+	info->used = !!(infobuf->flags & GPIO_V2_LINE_FLAG_USED);
+	strncpy(info->consumer, infobuf->consumer, GPIO_MAX_NAME_SIZE);
+
+	if (infobuf->flags & GPIO_V2_LINE_FLAG_OUTPUT)
+		info->direction = GPIOD_LINE_DIRECTION_OUTPUT;
+	else
+		info->direction = GPIOD_LINE_DIRECTION_INPUT;
+
+	if (infobuf->flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW)
+		info->active_low = true;
+
+	if (infobuf->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)
+		info->bias = GPIOD_LINE_BIAS_PULL_UP;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN)
+		info->bias = GPIOD_LINE_BIAS_PULL_DOWN;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED)
+		info->bias = GPIOD_LINE_BIAS_DISABLED;
+	else
+		info->bias = GPIOD_LINE_BIAS_UNKNOWN;
+
+	if (infobuf->flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN)
+		info->drive = GPIOD_LINE_DRIVE_OPEN_DRAIN;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE)
+		info->drive = GPIOD_LINE_DRIVE_OPEN_SOURCE;
+	else
+		info->drive = GPIOD_LINE_DRIVE_PUSH_PULL;
+
+	if ((infobuf->flags & GPIO_V2_LINE_FLAG_EDGE_RISING) &&
+	    (infobuf->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING))
+		info->edge = GPIOD_LINE_EDGE_BOTH;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_EDGE_RISING)
+		info->edge = GPIOD_LINE_EDGE_RISING;
+	else if (infobuf->flags & GPIO_V2_LINE_FLAG_EDGE_FALLING)
+		info->edge = GPIOD_LINE_EDGE_FALLING;
+	else
+		info->edge = GPIOD_LINE_EDGE_NONE;
+
+	/*
+	 * We assume that the kernel returns correct configuration and that no
+	 * attributes repeat.
+	 */
+	for (i = 0; i < infobuf->num_attrs; i++) {
+		attr = &infobuf->attrs[i];
+
+		if (attr->id == GPIO_V2_LINE_ATTR_ID_DEBOUNCE) {
+			info->debounced = true;
+			info->debounce_period = attr->debounce_period_us;
+		}
+	}
+
+	return info;
+}
diff --git a/lib/internal.h b/lib/internal.h
index a652879..2d1627d 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -4,8 +4,11 @@
 #ifndef __LIBGPIOD_GPIOD_INTERNAL_H__
 #define __LIBGPIOD_GPIOD_INTERNAL_H__
 
+#include <gpiod.h>
 #include <stddef.h>
 
+#include "uapi/gpio.h"
+
 /* For internal library use only. */
 
 #define GPIOD_API __attribute__((visibility("default")))
@@ -27,4 +30,7 @@ void gpiod_refcount_init(struct gpiod_refcount *refcount,
 void gpiod_refcount_ref(struct gpiod_refcount *refcount);
 void gpiod_refcount_unref(struct gpiod_refcount *refcount);
 
+struct gpiod_line_info *
+gpiod_line_info_from_kernel(struct gpio_v2_line_info *infobuf);
+
 #endif /* __LIBGPIOD_GPIOD_INTERNAL_H__ */
diff --git a/tests/gpiod-test.h b/tests/gpiod-test.h
index a093f83..6b93a96 100644
--- a/tests/gpiod-test.h
+++ b/tests/gpiod-test.h
@@ -21,9 +21,11 @@
  */
 typedef struct gpiod_chip gpiod_chip_struct;
 typedef struct gpiod_line_bulk gpiod_line_bulk_struct;
+typedef struct gpiod_line_info gpiod_line_info_struct;
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_chip_struct, gpiod_chip_unref);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_bulk_struct, gpiod_line_bulk_free);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_info_struct, gpiod_line_info_unref);
 
 /* These are private definitions and should not be used directly. */
 typedef void (*_gpiod_test_func)(void);
diff --git a/tests/tests-chip.c b/tests/tests-chip.c
index 46fb8d2..cad440a 100644
--- a/tests/tests-chip.c
+++ b/tests/tests-chip.c
@@ -195,8 +195,8 @@ GPIOD_TEST_CASE(get_all_lines, 0, { 4 })
 
 GPIOD_TEST_CASE(find_line_good, GPIOD_TEST_FLAG_NAMED_LINES, { 8, 8, 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct gpiod_line *line;
 	int offset;
 
 	chip = gpiod_chip_open(gpiod_test_chip_path(1));
@@ -207,11 +207,11 @@ GPIOD_TEST_CASE(find_line_good, GPIOD_TEST_FLAG_NAMED_LINES, { 8, 8, 8 })
 	g_assert_cmpint(offset, ==, 4);
 	gpiod_test_return_if_failed();
 
-	line = gpiod_chip_get_line(chip, 4);
-	g_assert_nonnull(line);
+	info = gpiod_chip_get_line_info(chip, 4);
+	g_assert_nonnull(info);
 	gpiod_test_return_if_failed();
 
-	g_assert_cmpstr(gpiod_line_name(line), ==, "gpio-mockup-B-4");
+	g_assert_cmpstr(gpiod_line_get_name(info), ==, "gpio-mockup-B-4");
 }
 
 GPIOD_TEST_CASE(find_line_unique_not_found,
diff --git a/tests/tests-line.c b/tests/tests-line.c
index 3985990..5fa1fd5 100644
--- a/tests/tests-line.c
+++ b/tests/tests-line.c
@@ -58,8 +58,43 @@ GPIOD_TEST_CASE(request_already_requested, 0, { 8 })
 	g_assert_cmpint(errno, ==, EBUSY);
 }
 
+GPIOD_TEST_CASE(line_name, GPIOD_TEST_FLAG_NAMED_LINES, { 8 })
+{
+	g_autoptr(gpiod_line_info_struct) info = NULL;
+	g_autoptr(gpiod_chip_struct) chip = NULL;
+
+	chip = gpiod_chip_open(gpiod_test_chip_path(0));
+	g_assert_nonnull(chip);
+	gpiod_test_return_if_failed();
+
+	info = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info);
+	gpiod_test_return_if_failed();
+
+	g_assert_nonnull(gpiod_line_get_name(info));
+	g_assert_cmpstr(gpiod_line_get_name(info), ==, "gpio-mockup-A-2");
+}
+
+GPIOD_TEST_CASE(line_name_unnamed, 0, { 8 })
+{
+	g_autoptr(gpiod_line_info_struct) info = NULL;
+	g_autoptr(gpiod_chip_struct) chip = NULL;
+
+	chip = gpiod_chip_open(gpiod_test_chip_path(0));
+	g_assert_nonnull(chip);
+	gpiod_test_return_if_failed();
+
+	info = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info);
+	gpiod_test_return_if_failed();
+
+	g_assert_null(gpiod_line_get_name(info));
+}
+
 GPIOD_TEST_CASE(consumer, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info_before = NULL;
+	g_autoptr(gpiod_line_info_struct) info_after = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line;
 	gint ret;
@@ -72,15 +107,25 @@ GPIOD_TEST_CASE(consumer, 0, { 8 })
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
 
-	g_assert_null(gpiod_line_consumer(line));
+	info_before = gpiod_chip_get_line_info(chip, 0);
+	g_assert_nonnull(info_before);
+	gpiod_test_return_if_failed();
+	g_assert_null(gpiod_line_get_consumer(info_before));
 
 	ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpstr(gpiod_line_consumer(line), ==, GPIOD_TEST_CONSUMER);
+
+	info_after = gpiod_chip_get_line_info(chip, 0);
+	g_assert_nonnull(info_after);
+	gpiod_test_return_if_failed();
+	g_assert_cmpstr(gpiod_line_get_consumer(info_after),
+			==, GPIOD_TEST_CONSUMER);
 }
 
 GPIOD_TEST_CASE(consumer_long_string, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info_before = NULL;
+	g_autoptr(gpiod_line_info_struct) info_after = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line;
 	gint ret;
@@ -93,15 +138,22 @@ GPIOD_TEST_CASE(consumer_long_string, 0, { 8 })
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
 
-	g_assert_null(gpiod_line_consumer(line));
+	info_before = gpiod_chip_get_line_info(chip, 0);
+	g_assert_nonnull(info_before);
+	gpiod_test_return_if_failed();
+	g_assert_null(gpiod_line_get_consumer(info_before));
 
 	ret = gpiod_line_request_input(line,
 			"consumer string over 32 characters long");
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_cmpstr(gpiod_line_consumer(line), ==,
+
+	info_after = gpiod_chip_get_line_info(chip, 0);
+	g_assert_nonnull(info_after);
+	gpiod_test_return_if_failed();
+	g_assert_cmpstr(gpiod_line_get_consumer(info_after), ==,
 			"consumer string over 32 charact");
-	g_assert_cmpuint(strlen(gpiod_line_consumer(line)), ==, 31);
+	g_assert_cmpuint(strlen(gpiod_line_get_consumer(info_after)), ==, 31);
 }
 
 GPIOD_TEST_CASE(request_bulk_output, 0, { 8, 8 })
@@ -336,9 +388,6 @@ GPIOD_TEST_CASE(set_config_bulk_null_values, 0, { 8 })
 	ret = gpiod_line_request_bulk_output(bulk, GPIOD_TEST_CONSUMER, 0);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_false(gpiod_line_is_active_low(line0));
-	g_assert_false(gpiod_line_is_active_low(line1));
-	g_assert_false(gpiod_line_is_active_low(line2));
 
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
@@ -348,9 +397,7 @@ GPIOD_TEST_CASE(set_config_bulk_null_values, 0, { 8 })
 			GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
 			GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW, NULL);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_true(gpiod_line_is_active_low(line0));
-	g_assert_true(gpiod_line_is_active_low(line1));
-	g_assert_true(gpiod_line_is_active_low(line2));
+
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 1);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 1);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
@@ -358,9 +405,7 @@ GPIOD_TEST_CASE(set_config_bulk_null_values, 0, { 8 })
 	ret = gpiod_line_set_config_bulk(bulk,
 			GPIOD_LINE_REQUEST_DIRECTION_OUTPUT, 0, NULL);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_false(gpiod_line_is_active_low(line0));
-	g_assert_false(gpiod_line_is_active_low(line1));
-	g_assert_false(gpiod_line_is_active_low(line2));
+
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
@@ -368,6 +413,9 @@ GPIOD_TEST_CASE(set_config_bulk_null_values, 0, { 8 })
 
 GPIOD_TEST_CASE(set_flags_active_state, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line;
 	gint ret;
@@ -383,22 +431,41 @@ GPIOD_TEST_CASE(set_flags_active_state, 0, { 8 })
 	ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 1);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_false(gpiod_line_is_active_low(line));
+
+	info0 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+	g_assert_false(gpiod_line_is_active_low(info0));
+
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
 
 	ret = gpiod_line_set_flags(line, GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_true(gpiod_line_is_active_low(line));
+
+	info1 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+	g_assert_true(gpiod_line_is_active_low(info1));
+
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
 
 	ret = gpiod_line_set_flags(line, 0);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_false(gpiod_line_is_active_low(line));
+
+	info2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info2);
+	gpiod_test_return_if_failed();
+	g_assert_false(gpiod_line_is_active_low(info2));
+
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
 }
 
 GPIOD_TEST_CASE(set_flags_bias, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2 = NULL;
+	g_autoptr(gpiod_line_info_struct) info3 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line;
 	gint ret;
@@ -414,28 +481,50 @@ GPIOD_TEST_CASE(set_flags_bias, 0, { 8 })
 	ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
+
+	info0 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_bias(info0), ==,
+			GPIOD_LINE_BIAS_UNKNOWN);
 
 	ret = gpiod_line_set_flags(line,
 		GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_DISABLED);
+
+	info1 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_bias(info1), ==,
+			GPIOD_LINE_BIAS_DISABLED);
 
 	ret = gpiod_line_set_flags(line,
 		GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_PULL_UP);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
 
+	info2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info2);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_bias(info2), ==,
+			GPIOD_LINE_BIAS_PULL_UP);
+
 	ret = gpiod_line_set_flags(line,
 		GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_PULL_DOWN);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
+
+	info3 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info3);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_bias(info3), ==, GPIOD_LINE_BIAS_PULL_DOWN);
 }
 
 GPIOD_TEST_CASE(set_flags_drive, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line;
 	gint ret;
@@ -451,23 +540,39 @@ GPIOD_TEST_CASE(set_flags_drive, 0, { 8 })
 	ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 0);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_cmpint(gpiod_line_drive(line), ==, GPIOD_LINE_DRIVE_PUSH_PULL);
+
+	info0 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_drive(info0), ==,
+			GPIOD_LINE_DRIVE_PUSH_PULL);
 
 	ret = gpiod_line_set_flags(line,
 		GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_drive(line), ==,
+
+	info1 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_drive(info1), ==,
 			GPIOD_LINE_DRIVE_OPEN_DRAIN);
 
 	ret = gpiod_line_set_flags(line,
 		GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_drive(line), ==,
+
+	info2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info2);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_drive(info2), ==,
 			GPIOD_LINE_DRIVE_OPEN_SOURCE);
 }
 
 GPIOD_TEST_CASE(set_direction, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line;
 	gint ret;
@@ -483,24 +588,49 @@ GPIOD_TEST_CASE(set_direction, 0, { 8 })
 	ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 0);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_cmpint(gpiod_line_direction(line), ==,
-			GPIOD_LINE_DIRECTION_OUTPUT);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
 
+	info0 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_direction(info0), ==,
+			GPIOD_LINE_DIRECTION_OUTPUT);
+
 	ret = gpiod_line_set_direction_input(line);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+
+	info1 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_direction(info1), ==,
 			GPIOD_LINE_DIRECTION_INPUT);
 
 	ret = gpiod_line_set_direction_output(line, 1);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
+
+	info2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info2);
+	gpiod_test_return_if_failed();
+	g_assert_cmpint(gpiod_line_get_direction(info2), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
 }
 
 GPIOD_TEST_CASE(set_direction_bulk, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0_0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info0_1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info0_2 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1_0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1_1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1_2 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2_0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2_1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2_2 = NULL;
+	g_autoptr(gpiod_line_info_struct) info3_0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info3_1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info3_2 = NULL;
 	g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line0, *line1, *line2;
@@ -536,23 +666,42 @@ GPIOD_TEST_CASE(set_direction_bulk, 0, { 8 })
 			GPIOD_TEST_CONSUMER, values);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_cmpint(gpiod_line_direction(line0), ==,
+
+	info0_0 = gpiod_chip_get_line_info(chip, 0);
+	info0_1 = gpiod_chip_get_line_info(chip, 1);
+	info0_2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0_0);
+	g_assert_nonnull(info0_1);
+	g_assert_nonnull(info0_2);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info0_0), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_direction(line1), ==,
+	g_assert_cmpint(gpiod_line_get_direction(info0_1), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_direction(line2), ==,
+	g_assert_cmpint(gpiod_line_get_direction(info0_2), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
+
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 1);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 1);
 
 	ret = gpiod_line_set_direction_input_bulk(bulk);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_direction(line0), ==,
+
+	info1_0 = gpiod_chip_get_line_info(chip, 0);
+	info1_1 = gpiod_chip_get_line_info(chip, 1);
+	info1_2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1_0);
+	g_assert_nonnull(info1_1);
+	g_assert_nonnull(info1_2);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info1_0), ==,
 			GPIOD_LINE_DIRECTION_INPUT);
-	g_assert_cmpint(gpiod_line_direction(line1), ==,
+	g_assert_cmpint(gpiod_line_get_direction(info1_1), ==,
 			GPIOD_LINE_DIRECTION_INPUT);
-	g_assert_cmpint(gpiod_line_direction(line2), ==,
+	g_assert_cmpint(gpiod_line_get_direction(info1_2), ==,
 			GPIOD_LINE_DIRECTION_INPUT);
 
 	values[0] = 2;
@@ -561,24 +710,44 @@ GPIOD_TEST_CASE(set_direction_bulk, 0, { 8 })
 
 	ret = gpiod_line_set_direction_output_bulk(bulk, values);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_direction(line0), ==,
+
+	info2_0 = gpiod_chip_get_line_info(chip, 0);
+	info2_1 = gpiod_chip_get_line_info(chip, 1);
+	info2_2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info2_0);
+	g_assert_nonnull(info2_1);
+	g_assert_nonnull(info2_2);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info2_0), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_direction(line1), ==,
+	g_assert_cmpint(gpiod_line_get_direction(info2_1), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_direction(line2), ==,
+	g_assert_cmpint(gpiod_line_get_direction(info2_2), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
+
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 1);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 1);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
 
 	ret = gpiod_line_set_direction_output_bulk(bulk, NULL);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_direction(line0), ==,
+
+	info3_0 = gpiod_chip_get_line_info(chip, 0);
+	info3_1 = gpiod_chip_get_line_info(chip, 1);
+	info3_2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info3_0);
+	g_assert_nonnull(info3_1);
+	g_assert_nonnull(info3_2);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info3_0), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_direction(line1), ==,
+	g_assert_cmpint(gpiod_line_get_direction(info3_1), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
-	g_assert_cmpint(gpiod_line_direction(line2), ==,
+	g_assert_cmpint(gpiod_line_get_direction(info3_2), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
+
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 0), ==, 0);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 1), ==, 0);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 2), ==, 0);
@@ -658,6 +827,8 @@ GPIOD_TEST_CASE(output_value_caching, 0, { 8 })
 
 GPIOD_TEST_CASE(direction, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line;
 	gint ret;
@@ -673,7 +844,12 @@ GPIOD_TEST_CASE(direction, 0, { 8 })
 	ret = gpiod_line_request_output(line, GPIOD_TEST_CONSUMER, 1);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+
+	info0 = gpiod_chip_get_line_info(chip, 5);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info0), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 5), ==, 1);
 
@@ -681,12 +857,21 @@ GPIOD_TEST_CASE(direction, 0, { 8 })
 
 	ret = gpiod_line_request_input(line, GPIOD_TEST_CONSUMER);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+
+	info1 = gpiod_chip_get_line_info(chip, 5);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info1), ==,
 			GPIOD_LINE_DIRECTION_INPUT);
 }
 
 GPIOD_TEST_CASE(active_state, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2 = NULL;
+	g_autoptr(gpiod_line_info_struct) info3 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line *line;
 	gint ret;
@@ -703,7 +888,11 @@ GPIOD_TEST_CASE(active_state, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_false(gpiod_line_is_active_low(line));
+	info0 = gpiod_chip_get_line_info(chip, 5);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+
+	g_assert_false(gpiod_line_is_active_low(info0));
 
 	gpiod_line_release(line);
 
@@ -712,7 +901,11 @@ GPIOD_TEST_CASE(active_state, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	info1 = gpiod_chip_get_line_info(chip, 5);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info1), ==,
 			GPIOD_LINE_DIRECTION_INPUT);
 
 	gpiod_line_release(line);
@@ -722,7 +915,11 @@ GPIOD_TEST_CASE(active_state, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	info2 = gpiod_chip_get_line_info(chip, 5);
+	g_assert_nonnull(info2);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info2), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 5), ==, 1);
 
@@ -733,7 +930,11 @@ GPIOD_TEST_CASE(active_state, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	info3 = gpiod_chip_get_line_info(chip, 5);
+	g_assert_nonnull(info3);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_get_direction(info3), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
 	g_assert_cmpint(gpiod_test_chip_get_value(0, 5), ==, 0);
 
@@ -741,6 +942,9 @@ GPIOD_TEST_CASE(active_state, 0, { 8 })
 
 GPIOD_TEST_CASE(misc_flags, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line_request_config config;
 	struct gpiod_line *line;
@@ -754,9 +958,15 @@ GPIOD_TEST_CASE(misc_flags, 0, { 8 })
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
 
-	g_assert_false(gpiod_line_is_used(line));
-	g_assert_cmpint(gpiod_line_drive(line), ==, GPIOD_LINE_DRIVE_PUSH_PULL);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
+	info0 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+
+	g_assert_false(gpiod_line_is_used(info0));
+	g_assert_cmpint(gpiod_line_get_drive(info0), ==,
+			GPIOD_LINE_DRIVE_PUSH_PULL);
+	g_assert_cmpint(gpiod_line_get_bias(info0), ==,
+			GPIOD_LINE_BIAS_UNKNOWN);
 
 	config.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
 	config.consumer = GPIOD_TEST_CONSUMER;
@@ -766,11 +976,16 @@ GPIOD_TEST_CASE(misc_flags, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_true(gpiod_line_is_used(line));
-	g_assert_cmpint(gpiod_line_drive(line), ==,
+	info1 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+
+	g_assert_true(gpiod_line_is_used(info1));
+	g_assert_cmpint(gpiod_line_get_drive(info1), ==,
 			GPIOD_LINE_DRIVE_OPEN_DRAIN);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	g_assert_cmpint(gpiod_line_get_bias(info1), ==,
+			GPIOD_LINE_BIAS_UNKNOWN);
+	g_assert_cmpint(gpiod_line_get_direction(info1), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
 
 	gpiod_line_release(line);
@@ -781,11 +996,16 @@ GPIOD_TEST_CASE(misc_flags, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_true(gpiod_line_is_used(line));
-	g_assert_cmpint(gpiod_line_drive(line), ==,
+	info2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info2);
+	gpiod_test_return_if_failed();
+
+	g_assert_true(gpiod_line_is_used(info2));
+	g_assert_cmpint(gpiod_line_get_drive(info2), ==,
 			GPIOD_LINE_DRIVE_OPEN_SOURCE);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	g_assert_cmpint(gpiod_line_get_bias(info2), ==,
+			GPIOD_LINE_BIAS_UNKNOWN);
+	g_assert_cmpint(gpiod_line_get_direction(info2), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
 
 	gpiod_line_release(line);
@@ -793,6 +1013,10 @@ GPIOD_TEST_CASE(misc_flags, 0, { 8 })
 
 GPIOD_TEST_CASE(misc_flags_work_together, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
+	g_autoptr(gpiod_line_info_struct) info2 = NULL;
+	g_autoptr(gpiod_line_info_struct) info3 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line_request_config config;
 	struct gpiod_line *line;
@@ -820,12 +1044,16 @@ GPIOD_TEST_CASE(misc_flags_work_together, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_true(gpiod_line_is_used(line));
-	g_assert_cmpint(gpiod_line_drive(line), ==,
+	info0 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+
+	g_assert_true(gpiod_line_is_used(info0));
+	g_assert_cmpint(gpiod_line_get_drive(info0), ==,
 			GPIOD_LINE_DRIVE_OPEN_DRAIN);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
-	g_assert_true(gpiod_line_is_active_low(line));
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	g_assert_cmpint(gpiod_line_get_bias(info0), ==, GPIOD_LINE_BIAS_UNKNOWN);
+	g_assert_true(gpiod_line_is_active_low(info0));
+	g_assert_cmpint(gpiod_line_get_direction(info0), ==,
 			GPIOD_LINE_DIRECTION_OUTPUT);
 
 	gpiod_line_release(line);
@@ -837,11 +1065,16 @@ GPIOD_TEST_CASE(misc_flags_work_together, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_true(gpiod_line_is_used(line));
-	g_assert_cmpint(gpiod_line_drive(line), ==,
+	info1 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+
+	g_assert_true(gpiod_line_is_used(info1));
+	g_assert_cmpint(gpiod_line_get_drive(info1), ==,
 			GPIOD_LINE_DRIVE_OPEN_SOURCE);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_UNKNOWN);
-	g_assert_true(gpiod_line_is_active_low(line));
+	g_assert_cmpint(gpiod_line_get_bias(info1), ==,
+			GPIOD_LINE_BIAS_UNKNOWN);
+	g_assert_true(gpiod_line_is_active_low(info1));
 
 	gpiod_line_release(line);
 
@@ -858,11 +1091,17 @@ GPIOD_TEST_CASE(misc_flags_work_together, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_true(gpiod_line_is_used(line));
-	g_assert_cmpint(gpiod_line_drive(line), ==, GPIOD_LINE_DRIVE_PUSH_PULL);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_PULL_DOWN);
-	g_assert_true(gpiod_line_is_active_low(line));
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	info2 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info2);
+	gpiod_test_return_if_failed();
+
+	g_assert_true(gpiod_line_is_used(info2));
+	g_assert_cmpint(gpiod_line_get_drive(info2), ==,
+			GPIOD_LINE_DRIVE_PUSH_PULL);
+	g_assert_cmpint(gpiod_line_get_bias(info2), ==,
+			GPIOD_LINE_BIAS_PULL_DOWN);
+	g_assert_true(gpiod_line_is_active_low(info2));
+	g_assert_cmpint(gpiod_line_get_direction(info2), ==,
 			GPIOD_LINE_DIRECTION_INPUT);
 
 	ret = gpiod_line_get_value(line);
@@ -877,11 +1116,17 @@ GPIOD_TEST_CASE(misc_flags_work_together, 0, { 8 })
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
 
-	g_assert_true(gpiod_line_is_used(line));
-	g_assert_cmpint(gpiod_line_drive(line), ==, GPIOD_LINE_DRIVE_PUSH_PULL);
-	g_assert_cmpint(gpiod_line_bias(line), ==, GPIOD_LINE_BIAS_PULL_UP);
-	g_assert_true(gpiod_line_is_active_low(line));
-	g_assert_cmpint(gpiod_line_direction(line), ==,
+	info3 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info3);
+	gpiod_test_return_if_failed();
+
+	g_assert_true(gpiod_line_is_used(info3));
+	g_assert_cmpint(gpiod_line_get_drive(info3), ==,
+			GPIOD_LINE_DRIVE_PUSH_PULL);
+	g_assert_cmpint(gpiod_line_get_bias(info3), ==,
+			GPIOD_LINE_BIAS_PULL_UP);
+	g_assert_true(gpiod_line_is_active_low(info3));
+	g_assert_cmpint(gpiod_line_get_direction(info3), ==,
 			GPIOD_LINE_DIRECTION_INPUT);
 
 	ret = gpiod_line_get_value(line);
@@ -1018,6 +1263,8 @@ GPIOD_TEST_CASE(release_one_use_another, 0, { 8 })
 
 GPIOD_TEST_CASE(null_consumer, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line_request_config config;
 	struct gpiod_line *line;
@@ -1038,7 +1285,11 @@ GPIOD_TEST_CASE(null_consumer, 0, { 8 })
 	ret = gpiod_line_request(line, &config, 0);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_cmpstr(gpiod_line_consumer(line), ==, "?");
+
+	info0 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+	g_assert_cmpstr(gpiod_line_get_consumer(info0), ==, "?");
 
 	gpiod_line_release(line);
 
@@ -1050,11 +1301,17 @@ GPIOD_TEST_CASE(null_consumer, 0, { 8 })
 
 	ret = gpiod_line_request(line, &config, 0);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpstr(gpiod_line_consumer(line), ==, "?");
+
+	info1 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+	g_assert_cmpstr(gpiod_line_get_consumer(info1), ==, "?");
 }
 
 GPIOD_TEST_CASE(empty_consumer, 0, { 8 })
 {
+	g_autoptr(gpiod_line_info_struct) info0 = NULL;
+	g_autoptr(gpiod_line_info_struct) info1 = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
 	struct gpiod_line_request_config config;
 	struct gpiod_line *line;
@@ -1075,7 +1332,11 @@ GPIOD_TEST_CASE(empty_consumer, 0, { 8 })
 	ret = gpiod_line_request(line, &config, 0);
 	g_assert_cmpint(ret, ==, 0);
 	gpiod_test_return_if_failed();
-	g_assert_cmpstr(gpiod_line_consumer(line), ==, "?");
+
+	info0 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info0);
+	gpiod_test_return_if_failed();
+	g_assert_cmpstr(gpiod_line_get_consumer(info0), ==, "?");
 
 	gpiod_line_release(line);
 
@@ -1087,5 +1348,9 @@ GPIOD_TEST_CASE(empty_consumer, 0, { 8 })
 
 	ret = gpiod_line_request(line, &config, 0);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpstr(gpiod_line_consumer(line), ==, "?");
+
+	info1 = gpiod_chip_get_line_info(chip, 2);
+	g_assert_nonnull(info1);
+	gpiod_test_return_if_failed();
+	g_assert_cmpstr(gpiod_line_get_consumer(info1), ==, "?");
 }
diff --git a/tools/gpioinfo.c b/tools/gpioinfo.c
index 3d89111..7901036 100644
--- a/tools/gpioinfo.c
+++ b/tools/gpioinfo.c
@@ -11,36 +11,36 @@
 
 #include "tools-common.h"
 
-typedef bool (*is_set_func)(struct gpiod_line *);
+typedef bool (*is_set_func)(struct gpiod_line_info *);
 
 struct flag {
 	const char *name;
 	is_set_func is_set;
 };
 
-static bool line_bias_is_pullup(struct gpiod_line *line)
+static bool line_bias_is_pullup(struct gpiod_line_info *info)
 {
-	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_PULL_UP;
+	return gpiod_line_get_bias(info) == GPIOD_LINE_BIAS_PULL_UP;
 }
 
-static bool line_bias_is_pulldown(struct gpiod_line *line)
+static bool line_bias_is_pulldown(struct gpiod_line_info *info)
 {
-	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_PULL_DOWN;
+	return gpiod_line_get_bias(info) == GPIOD_LINE_BIAS_PULL_DOWN;
 }
 
-static bool line_bias_is_disabled(struct gpiod_line *line)
+static bool line_bias_is_disabled(struct gpiod_line_info *info)
 {
-	return gpiod_line_bias(line) == GPIOD_LINE_BIAS_DISABLED;
+	return gpiod_line_get_bias(info) == GPIOD_LINE_BIAS_DISABLED;
 }
 
-static bool line_drive_is_open_drain(struct gpiod_line *line)
+static bool line_drive_is_open_drain(struct gpiod_line_info *info)
 {
-	return gpiod_line_drive(line) == GPIOD_LINE_DRIVE_OPEN_DRAIN;
+	return gpiod_line_get_drive(info) == GPIOD_LINE_DRIVE_OPEN_DRAIN;
 }
 
-static bool line_drive_is_open_source(struct gpiod_line *line)
+static bool line_drive_is_open_source(struct gpiod_line_info *info)
 {
-	return gpiod_line_drive(line) == GPIOD_LINE_DRIVE_OPEN_SOURCE;
+	return gpiod_line_get_drive(info) == GPIOD_LINE_DRIVE_OPEN_SOURCE;
 }
 
 static const struct flag flags[] = {
@@ -124,8 +124,8 @@ static PRINTF(3, 4) void prinfo(bool *of,
 static void list_lines(struct gpiod_chip *chip)
 {
 	bool flag_printed, of, active_low;
+	struct gpiod_line_info *info;
 	const char *name, *consumer;
-	struct gpiod_line *line;
 	unsigned int i, offset;
 	int direction;
 
@@ -133,14 +133,14 @@ static void list_lines(struct gpiod_chip *chip)
 	       gpiod_chip_get_name(chip), gpiod_chip_get_num_lines(chip));
 
 	for (offset = 0; offset < gpiod_chip_get_num_lines(chip); offset++) {
-		line = gpiod_chip_get_line(chip, offset);
-		if (!line)
+		info = gpiod_chip_get_line_info(chip, offset);
+		if (!info)
 			die_perror("unable to retrieve the line object from chip");
 
-		name = gpiod_line_name(line);
-		consumer = gpiod_line_consumer(line);
-		direction = gpiod_line_direction(line);
-		active_low = gpiod_line_is_active_low(line);
+		name = gpiod_line_get_name(info);
+		consumer = gpiod_line_get_consumer(info);
+		direction = gpiod_line_get_direction(info);
+		active_low = gpiod_line_is_active_low(info);
 
 		of = false;
 
@@ -152,7 +152,7 @@ static void list_lines(struct gpiod_chip *chip)
 		     : prinfo(&of, 12, "unnamed");
 		printf(" ");
 
-		if (!gpiod_line_is_used(line))
+		if (!gpiod_line_is_used(info))
 			prinfo(&of, 12, "unused");
 		else
 			consumer ? prinfo(&of, 12, "\"%s\"", consumer)
@@ -167,7 +167,7 @@ static void list_lines(struct gpiod_chip *chip)
 
 		flag_printed = false;
 		for (i = 0; i < ARRAY_SIZE(flags); i++) {
-			if (flags[i].is_set(line)) {
+			if (flags[i].is_set(info)) {
 				if (flag_printed)
 					printf(" ");
 				else
@@ -180,6 +180,8 @@ static void list_lines(struct gpiod_chip *chip)
 			printf("]");
 
 		printf("\n");
+
+		gpiod_line_info_unref(info);
 	}
 }
 
-- 
2.30.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [libgpiod][RFC 4/6] core: rework line events
  2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
                   ` (2 preceding siblings ...)
  2021-04-10 14:51 ` [libgpiod][RFC 3/6] core: implement line_info objects Bartosz Golaszewski
@ 2021-04-10 14:51 ` Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 5/6] core: rework line requests Bartosz Golaszewski
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-10 14:51 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij, Andy Shevchenko
  Cc: linux-gpio, Bartosz Golaszewski

Introduce opaque event objects: gpiod_line_event and
gpiod_line_event_buffer. The latter serves as a container into which
multiple events can be read without having to dynamically allocate memory
for each one as reading line events should be as fast as possible.

Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
---
 include/gpiod.h     |  65 +++---
 lib/Makefile.am     |   9 +-
 lib/core.c          |  98 +--------
 lib/event.c         | 250 +++++++++++++++++++++++
 lib/misc.c          |   5 +
 tests/gpiod-test.h  |   3 +
 tests/tests-event.c | 470 ++++++++++++++++++++++++++++++++------------
 tools/gpiomon.c     |  45 +++--
 8 files changed, 690 insertions(+), 255 deletions(-)
 create mode 100644 lib/event.c

diff --git a/include/gpiod.h b/include/gpiod.h
index 7da9ff4..e99233b 100644
--- a/include/gpiod.h
+++ b/include/gpiod.h
@@ -6,7 +6,7 @@
 
 #include <stdbool.h>
 #include <stdlib.h>
-#include <time.h>
+#include <stdint.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -37,6 +37,8 @@ struct gpiod_chip;
 struct gpiod_line;
 struct gpiod_line_info;
 struct gpiod_line_bulk;
+struct gpiod_line_event;
+struct gpiod_line_event_buffer;
 
 /**
  * @defgroup common Common helper macros
@@ -928,17 +930,35 @@ enum {
 	/**< Falling edge event. */
 };
 
-/**
- * @brief Structure holding event info.
- */
-struct gpiod_line_event {
-	struct timespec ts;
-	/**< Best estimate of time of event occurrence. */
-	int event_type;
-	/**< Type of the event that occurred. */
-	int offset;
-	/**< Offset of line on which the event occurred. */
-};
+struct gpiod_line_event *gpiod_line_event_ref(struct gpiod_line_event *event);
+
+void gpiod_line_event_unref(struct gpiod_line_event *event);
+
+int gpiod_line_event_get_event_type(struct gpiod_line_event *event);
+
+uint64_t gpiod_line_event_get_timestamp(struct gpiod_line_event *event);
+
+unsigned int gpiod_line_event_get_line_offset(struct gpiod_line_event *event);
+
+unsigned int gpiod_line_event_get_global_seqno(struct gpiod_line_event *event);
+
+unsigned int gpiod_line_event_get_line_seqno(struct gpiod_line_event *event);
+
+struct gpiod_line_event_buffer *
+gpiod_line_event_buffer_new(unsigned int capacity);
+
+struct gpiod_line_event_buffer *
+gpiod_line_event_buffer_ref(struct gpiod_line_event_buffer *buf);
+
+void gpiod_line_event_buffer_unref(struct gpiod_line_event_buffer *buf);
+
+struct gpiod_line_event *
+gpiod_line_event_buffer_get_event(struct gpiod_line_event_buffer *buf,
+				  unsigned long index);
+
+struct gpiod_line_event *
+gpiod_line_event_buffer_copy_event(struct gpiod_line_event_buffer *buf,
+				   unsigned long index);
 
 /**
  * @brief Wait for an event on a single line.
@@ -947,8 +967,7 @@ struct gpiod_line_event {
  * @return 0 if wait timed out, -1 if an error occurred, 1 if an event
  *         occurred.
  */
-int gpiod_line_event_wait(struct gpiod_line *line,
-			  const struct timespec *timeout);
+int gpiod_line_event_wait(struct gpiod_line *line, uint64_t timeout);
 
 /**
  * @brief Wait for events on a set of lines.
@@ -959,8 +978,7 @@ int gpiod_line_event_wait(struct gpiod_line *line,
  * @return 0 if wait timed out, -1 if an error occurred, 1 if at least one
  *         event occurred.
  */
-int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
-			       const struct timespec *timeout,
+int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk, uint64_t timeout,
 			       struct gpiod_line_bulk *event_bulk);
 
 /**
@@ -971,7 +989,7 @@ int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
  * @note This function will block if no event was queued for this line.
  */
 int gpiod_line_event_read(struct gpiod_line *line,
-			  struct gpiod_line_event *event);
+			  struct gpiod_line_event_buffer *buf);
 
 /**
  * @brief Read up to a certain number of events from the GPIO line.
@@ -983,7 +1001,7 @@ int gpiod_line_event_read(struct gpiod_line *line,
  *         failure -1 is returned.
  */
 int gpiod_line_event_read_multiple(struct gpiod_line *line,
-				   struct gpiod_line_event *events,
+				   struct gpiod_line_event_buffer *buf,
 				   unsigned int num_events);
 
 /**
@@ -1008,19 +1026,20 @@ int gpiod_line_event_get_fd(struct gpiod_line *line);
  * directly read the event data from it using this routine. This function
  * translates the kernel representation of the event to the libgpiod format.
  */
-int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event);
+int gpiod_line_event_read_fd(int fd, struct gpiod_line_event_buffer *buf);
 
 /**
  * @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.
+ * @param max_events Specifies the maximum number of events to read.
  * @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);
+int gpiod_line_event_read_fd_multiple(int fd,
+				      struct gpiod_line_event_buffer *buf,
+				      unsigned int max_events);
 
 /**
  * @}
@@ -1033,6 +1052,8 @@ int gpiod_line_event_read_fd_multiple(int fd, struct gpiod_line_event *events,
  * Various libgpiod-related functions.
  */
 
+uint64_t gpiod_sec_to_nsec(uint64_t sec);
+
 /**
  * @brief Get the API version of the library as a human-readable string.
  * @return Human-readable string containing the library version.
diff --git a/lib/Makefile.am b/lib/Makefile.am
index c5d6070..0f19eec 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -2,7 +2,14 @@
 # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
 
 lib_LTLIBRARIES = libgpiod.la
-libgpiod_la_SOURCES = core.c helpers.c internal.c internal.h info.c misc.c uapi/gpio.h
+libgpiod_la_SOURCES =	core.c \
+			event.c \
+			helpers.c \
+			internal.h \
+			internal.c \
+			info.c \
+			misc.c \
+			uapi/gpio.h
 libgpiod_la_CFLAGS = -Wall -Wextra -g -std=gnu89
 libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
 libgpiod_la_CFLAGS += -include $(top_builddir)/config.h
diff --git a/lib/core.c b/lib/core.c
index 526dcaa..ed65653 100644
--- a/lib/core.c
+++ b/lib/core.c
@@ -15,6 +15,7 @@
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <sys/types.h>
+#include <time.h>
 #include <unistd.h>
 
 #include "internal.h"
@@ -1060,8 +1061,7 @@ GPIOD_API int gpiod_line_set_direction_output_bulk(struct gpiod_line_bulk *bulk,
 					  line->req_flags, values);
 }
 
-GPIOD_API int gpiod_line_event_wait(struct gpiod_line *line,
-				    const struct timespec *timeout)
+GPIOD_API int gpiod_line_event_wait(struct gpiod_line *line, uint64_t timeout)
 {
 	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
 
@@ -1069,12 +1069,13 @@ GPIOD_API int gpiod_line_event_wait(struct gpiod_line *line,
 }
 
 GPIOD_API int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
-					 const struct timespec *timeout,
+					 uint64_t timeout,
 					 struct gpiod_line_bulk *event_bulk)
 {
 	struct pollfd fds[LINE_REQUEST_MAX_LINES];
 	unsigned int off, num_lines;
 	struct gpiod_line *line;
+	struct timespec ts;
 	int rv;
 
 	if (!line_bulk_all_requested(bulk))
@@ -1088,7 +1089,10 @@ GPIOD_API int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
 		fds[off].events = POLLIN | POLLPRI;
 	}
 
-	rv = ppoll(fds, num_lines, timeout, NULL);
+	ts.tv_sec = timeout / 1000000000ULL;
+	ts.tv_nsec = timeout % 1000000000ULL;
+
+	rv = ppoll(fds, num_lines, &ts, NULL);
 	if (rv < 0)
 		return -1;
 	else if (rv == 0)
@@ -1116,31 +1120,6 @@ GPIOD_API int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
 	return 1;
 }
 
-GPIOD_API int gpiod_line_event_read(struct gpiod_line *line,
-				    struct gpiod_line_event *event)
-{
-	int ret;
-
-	ret = gpiod_line_event_read_multiple(line, event, 1);
-	if (ret < 0)
-		return -1;
-
-	return 0;
-}
-
-GPIOD_API int gpiod_line_event_read_multiple(struct gpiod_line *line,
-					     struct gpiod_line_event *events,
-					     unsigned int num_events)
-{
-	int fd;
-
-	fd = gpiod_line_event_get_fd(line);
-	if (fd < 0)
-		return -1;
-
-	return gpiod_line_event_read_fd_multiple(fd, events, num_events);
-}
-
 GPIOD_API int gpiod_line_event_get_fd(struct gpiod_line *line)
 {
 	if (line->state != LINE_REQUESTED_EVENTS) {
@@ -1150,64 +1129,3 @@ GPIOD_API int gpiod_line_event_get_fd(struct gpiod_line *line)
 
 	return line_get_fd(line);
 }
-
-GPIOD_API int gpiod_line_event_read_fd(int fd, struct gpiod_line_event *event)
-{
-	int ret;
-
-	ret = gpiod_line_event_read_fd_multiple(fd, event, 1);
-	if (ret < 0)
-		return -1;
-
-	return 0;
-}
-
-GPIOD_API int gpiod_line_event_read_fd_multiple(int fd,
-						struct gpiod_line_event *events,
-						unsigned int num_events)
-{
-	/*
-	 * 16 is the maximum number of events the kernel can store in the FIFO
-	 * so we can allocate the buffer on the stack.
-	 *
-	 * NOTE: This is no longer strictly true for uAPI v2.  While 16 is
-	 * the default for single line, a request with multiple lines will
-	 * have a larger buffer.  So need to rethink the allocation here,
-	 * or at least the comment above...
-	 */
-	struct gpio_v2_line_event evdata[16], *curr;
-	struct gpiod_line_event *event;
-	unsigned int events_read, i;
-	ssize_t rd;
-
-	memset(evdata, 0, sizeof(evdata));
-
-	if (num_events > 16)
-		num_events = 16;
-
-	rd = read(fd, evdata, num_events * sizeof(*evdata));
-	if (rd < 0) {
-		return -1;
-	} else if ((unsigned int)rd < sizeof(*evdata)) {
-		errno = EIO;
-		return -1;
-	}
-
-	events_read = rd / sizeof(*evdata);
-	if (events_read < num_events)
-		num_events = events_read;
-
-	for (i = 0; i < num_events; i++) {
-		curr = &evdata[i];
-		event = &events[i];
-
-		event->offset = curr->offset;
-		event->event_type = curr->id == GPIO_V2_LINE_EVENT_RISING_EDGE
-					? GPIOD_LINE_EVENT_RISING_EDGE
-					: GPIOD_LINE_EVENT_FALLING_EDGE;
-		event->ts.tv_sec = curr->timestamp_ns / 1000000000ULL;
-		event->ts.tv_nsec = curr->timestamp_ns % 1000000000ULL;
-	}
-
-	return i;
-}
diff --git a/lib/event.c b/lib/event.c
new file mode 100644
index 0000000..f4dfce8
--- /dev/null
+++ b/lib/event.c
@@ -0,0 +1,250 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+#include <errno.h>
+#include <gpiod.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "internal.h"
+#include "uapi/gpio.h"
+
+/* As defined in the kernel. */
+#define EVENT_BUFFER_MAX_CAPACITY	(GPIO_V2_LINES_MAX * 16)
+
+struct gpiod_line_event {
+	struct gpiod_refcount refcount;
+	int event_type;
+	uint64_t timestamp;
+	unsigned int line_offset;
+	unsigned int global_seqno;
+	unsigned int line_seqno;
+};
+
+struct gpiod_line_event_buffer {
+	struct gpiod_refcount refcount;
+	unsigned int capacity;
+	unsigned int num_events;
+	struct gpiod_line_event *events;
+	struct gpio_v2_line_event *event_data;
+};
+
+static void line_event_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_line_event *event;
+
+	event = gpiod_container_of(refcount, struct gpiod_line_event, refcount);
+
+	free(event);
+}
+
+GPIOD_API struct gpiod_line_event *
+gpiod_line_event_ref(struct gpiod_line_event *event)
+{
+	gpiod_refcount_ref(&event->refcount);
+	return event;
+}
+
+GPIOD_API void gpiod_line_event_unref(struct gpiod_line_event *event)
+{
+	gpiod_refcount_unref(&event->refcount);
+}
+
+GPIOD_API int gpiod_line_event_get_event_type(struct gpiod_line_event *event)
+{
+	return event->event_type;
+}
+
+GPIOD_API uint64_t
+gpiod_line_event_get_timestamp(struct gpiod_line_event *event)
+{
+	return event->timestamp;
+}
+
+GPIOD_API unsigned int
+gpiod_line_event_get_line_offset(struct gpiod_line_event *event)
+{
+	return event->line_offset;
+}
+
+GPIOD_API unsigned int
+gpiod_line_event_get_global_seqno(struct gpiod_line_event *event)
+{
+	return event->global_seqno;
+}
+
+GPIOD_API unsigned int
+gpiod_line_event_get_line_seqno(struct gpiod_line_event *event)
+{
+	return event->line_seqno;
+}
+
+static void line_event_buffer_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_line_event_buffer *buf;
+
+	buf = gpiod_container_of(refcount,
+				 struct gpiod_line_event_buffer, refcount);
+
+	free(buf);
+}
+
+GPIOD_API struct gpiod_line_event_buffer *
+gpiod_line_event_buffer_new(unsigned int capacity)
+{
+	struct gpiod_line_event_buffer *buf;
+
+	if (capacity == 0 || capacity > EVENT_BUFFER_MAX_CAPACITY) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	buf = malloc(sizeof(*buf));
+	if (!buf)
+		return NULL;
+
+	memset(buf, 0, sizeof(*buf));
+	gpiod_refcount_init(&buf->refcount, line_event_buffer_release);
+	buf->capacity = capacity;
+
+	buf->events = calloc(capacity, sizeof(*buf->events));
+	if (!buf->events) {
+		free(buf);
+		return NULL;
+	}
+
+	buf->event_data = calloc(capacity, sizeof(*buf->event_data));
+	if (!buf->event_data) {
+		free(buf->events);
+		free(buf);
+		return NULL;
+	}
+
+	return buf;
+}
+
+GPIOD_API struct gpiod_line_event_buffer *
+gpiod_line_event_buffer_ref(struct gpiod_line_event_buffer *buf)
+{
+	gpiod_refcount_ref(&buf->refcount);
+	return buf;
+}
+
+GPIOD_API void
+gpiod_line_event_buffer_unref(struct gpiod_line_event_buffer *buf)
+{
+	gpiod_refcount_unref(&buf->refcount);
+}
+
+GPIOD_API struct gpiod_line_event *
+gpiod_line_event_buffer_get_event(struct gpiod_line_event_buffer *buf,
+				  unsigned long index)
+{
+	if (index >= buf->num_events) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	return &buf->events[index];
+}
+
+GPIOD_API struct gpiod_line_event *
+gpiod_line_event_buffer_copy_event(struct gpiod_line_event_buffer *buf,
+                                   unsigned long index)
+{
+	struct gpiod_line_event *event;
+
+	if (index >= buf->num_events) {
+		errno = EINVAL;
+		return NULL;
+	}
+
+	event = malloc(sizeof(*event));
+	if (!event)
+		return NULL;
+
+	memcpy(event, &buf->events[index], sizeof(*event));
+	gpiod_refcount_init(&event->refcount, line_event_release);
+
+	return event;
+}
+
+GPIOD_API int gpiod_line_event_read(struct gpiod_line *line,
+				    struct gpiod_line_event_buffer *buf)
+{
+	int ret;
+
+	ret = gpiod_line_event_read_multiple(line, buf, 1);
+	if (ret < 0)
+		return -1;
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_event_read_multiple(struct gpiod_line *line,
+			       struct gpiod_line_event_buffer *buf,
+			       unsigned int num_events)
+{
+	int fd;
+
+	fd = gpiod_line_event_get_fd(line);
+	if (fd < 0)
+		return -1;
+
+	return gpiod_line_event_read_fd_multiple(fd, buf, num_events);
+}
+
+GPIOD_API int gpiod_line_event_read_fd(int fd,
+				       struct gpiod_line_event_buffer *buf)
+{
+	int ret;
+
+	ret = gpiod_line_event_read_fd_multiple(fd, buf, 1);
+	if (ret < 0)
+		return -1;
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_event_read_fd_multiple(int fd,
+				  struct gpiod_line_event_buffer *buf,
+				  unsigned int max_events)
+{
+	struct gpio_v2_line_event *curr;
+	struct gpiod_line_event *event;
+	unsigned int i;
+	ssize_t rd;
+
+	memset(buf->event_data, 0, sizeof(*buf->event_data) * buf->capacity);
+	memset(buf->events, 0, sizeof(*buf->events) * buf->capacity);
+
+	if (max_events > buf->capacity)
+		max_events = buf->capacity;
+
+	rd = read(fd, buf->event_data, max_events * sizeof(*buf->event_data));
+	if (rd < 0) {
+		return -1;
+	} else if ((unsigned int)rd < sizeof(*buf->event_data)) {
+		errno = EIO;
+		return -1;
+	}
+
+	buf->num_events = rd / sizeof(*buf->event_data);
+
+	for (i = 0; i < buf->num_events; i++) {
+		curr = &buf->event_data[i];
+		event = &buf->events[i];
+
+		event->line_offset = curr->offset;
+		event->event_type = curr->id == GPIO_V2_LINE_EVENT_RISING_EDGE
+					? GPIOD_LINE_EVENT_RISING_EDGE
+					: GPIOD_LINE_EVENT_FALLING_EDGE;
+		event->timestamp = curr->timestamp_ns;
+		event->global_seqno = curr->seqno;
+		event->line_seqno = curr->line_seqno;
+	}
+
+	return i;
+}
diff --git a/lib/misc.c b/lib/misc.c
index 984405b..67654f9 100644
--- a/lib/misc.c
+++ b/lib/misc.c
@@ -7,6 +7,11 @@
 
 #include "internal.h"
 
+GPIOD_API uint64_t gpiod_sec_to_nsec(uint64_t sec)
+{
+	return sec * 1000000000ULL;
+}
+
 GPIOD_API const char *gpiod_version_string(void)
 {
 	return GPIOD_VERSION_STR;
diff --git a/tests/gpiod-test.h b/tests/gpiod-test.h
index 6b93a96..236cb16 100644
--- a/tests/gpiod-test.h
+++ b/tests/gpiod-test.h
@@ -22,10 +22,13 @@
 typedef struct gpiod_chip gpiod_chip_struct;
 typedef struct gpiod_line_bulk gpiod_line_bulk_struct;
 typedef struct gpiod_line_info gpiod_line_info_struct;
+typedef struct gpiod_line_event_buffer gpiod_line_event_buffer_struct;
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_chip_struct, gpiod_chip_unref);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_bulk_struct, gpiod_line_bulk_free);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_info_struct, gpiod_line_info_unref);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_event_buffer_struct,
+			      gpiod_line_event_buffer_unref);
 
 /* These are private definitions and should not be used directly. */
 typedef void (*_gpiod_test_func)(void);
diff --git a/tests/tests-event.c b/tests/tests-event.c
index 53d3e8c..cb4aac4 100644
--- a/tests/tests-event.c
+++ b/tests/tests-event.c
@@ -10,10 +10,11 @@
 
 GPIOD_TEST_CASE(rising_edge_good, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -21,6 +22,10 @@ GPIOD_TEST_CASE(rising_edge_good, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -31,21 +36,27 @@ GPIOD_TEST_CASE(rising_edge_good, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
 }
 
 GPIOD_TEST_CASE(falling_edge_good, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -53,6 +64,10 @@ GPIOD_TEST_CASE(falling_edge_good, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -64,21 +79,29 @@ GPIOD_TEST_CASE(falling_edge_good, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 }
 
 GPIOD_TEST_CASE(rising_edge_ignore_falling, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf0 = NULL;
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf1 = NULL;
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf2 = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev[3];
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *events[3];
 	struct gpiod_line *line;
 	gint ret;
 
@@ -86,6 +109,14 @@ GPIOD_TEST_CASE(rising_edge_ignore_falling, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf0 = gpiod_line_event_buffer_new(1);
+	event_buf1 = gpiod_line_event_buffer_new(1);
+	event_buf2 = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf0);
+	g_assert_nonnull(event_buf1);
+	g_assert_nonnull(event_buf2);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -96,32 +127,44 @@ GPIOD_TEST_CASE(rising_edge_ignore_falling, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
-	ret = gpiod_line_event_read(line, &ev[0]);
+	ret = gpiod_line_event_read(line, event_buf0);
 	g_assert_cmpint(ret, ==, 0);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
-	ret = gpiod_line_event_read(line, &ev[1]);
+	ret = gpiod_line_event_read(line, event_buf1);
 	g_assert_cmpint(ret, ==, 0);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
-	ret = gpiod_line_event_read(line, &ev[2]);
+	ret = gpiod_line_event_read(line, event_buf2);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev[0].event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-	g_assert_cmpint(ev[1].event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-	g_assert_cmpint(ev[2].event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
+	events[0] = gpiod_line_event_buffer_get_event(event_buf0, 0);
+	events[1] = gpiod_line_event_buffer_get_event(event_buf1, 0);
+	events[2] = gpiod_line_event_buffer_get_event(event_buf2, 0);
+	g_assert_nonnull(events[0]);
+	g_assert_nonnull(events[1]);
+	g_assert_nonnull(events[2]);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[0]), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[1]), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[2]), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
 }
 
 GPIOD_TEST_CASE(both_edges, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -129,6 +172,10 @@ GPIOD_TEST_CASE(both_edges, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -139,29 +186,40 @@ GPIOD_TEST_CASE(both_edges, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 }
 
 GPIOD_TEST_CASE(both_edges_active_low, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -169,6 +227,10 @@ GPIOD_TEST_CASE(both_edges_active_low, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -180,29 +242,40 @@ GPIOD_TEST_CASE(both_edges_active_low, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
 }
 
 GPIOD_TEST_CASE(both_edges_bias_disable, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -210,6 +283,10 @@ GPIOD_TEST_CASE(both_edges_bias_disable, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -221,29 +298,40 @@ GPIOD_TEST_CASE(both_edges_bias_disable, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 }
 
 GPIOD_TEST_CASE(both_edges_bias_pull_down, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -251,6 +339,10 @@ GPIOD_TEST_CASE(both_edges_bias_pull_down, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -262,29 +354,40 @@ GPIOD_TEST_CASE(both_edges_bias_pull_down, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 }
 
 GPIOD_TEST_CASE(both_edges_bias_pull_up, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -292,6 +395,10 @@ GPIOD_TEST_CASE(both_edges_bias_pull_up, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -303,29 +410,40 @@ GPIOD_TEST_CASE(both_edges_bias_pull_up, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
 }
 
 GPIOD_TEST_CASE(falling_edge_active_low, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -333,6 +451,10 @@ GPIOD_TEST_CASE(falling_edge_active_low, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -344,21 +466,27 @@ GPIOD_TEST_CASE(falling_edge_active_low, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 }
 
 GPIOD_TEST_CASE(get_value, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -366,6 +494,10 @@ GPIOD_TEST_CASE(get_value, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -381,21 +513,27 @@ GPIOD_TEST_CASE(get_value, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 }
 
 GPIOD_TEST_CASE(get_value_active_low, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 
@@ -403,6 +541,10 @@ GPIOD_TEST_CASE(get_value_active_low, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -419,13 +561,18 @@ GPIOD_TEST_CASE(get_value_active_low, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 }
 
 GPIOD_TEST_CASE(get_values, 0, { 8 })
@@ -548,12 +695,13 @@ GPIOD_TEST_CASE(get_values_active_low, 0, { 8 })
 
 GPIOD_TEST_CASE(wait_multiple, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(GpiodTestEventThread) ev_thread = NULL;
 	g_autoptr(gpiod_line_bulk_struct) ev_bulk = NULL;
 	g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret, i;
 
@@ -561,6 +709,10 @@ GPIOD_TEST_CASE(wait_multiple, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	bulk = gpiod_line_bulk_new(8);
 	ev_bulk = gpiod_line_bulk_new(8);
 	g_assert_nonnull(bulk);
@@ -582,17 +734,23 @@ GPIOD_TEST_CASE(wait_multiple, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 4, 100);
 
-	ret = gpiod_line_event_wait_bulk(bulk, &ts, ev_bulk);
+	ret = gpiod_line_event_wait_bulk(bulk, timeout, ev_bulk);
 	g_assert_cmpint(ret, ==, 1);
 
 	g_assert_cmpuint(gpiod_line_bulk_num_lines(ev_bulk), ==, 1);
 	line = gpiod_line_bulk_get_line(ev_bulk, 0);
 	g_assert_cmpuint(gpiod_line_offset(line), ==, 4);
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
-	g_assert_cmpint(ev.offset, ==, 4);
+
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
+	g_assert_cmpint(gpiod_line_event_get_line_offset(event), ==, 4);
 }
 
 GPIOD_TEST_CASE(get_fd_when_values_requested, 0, { 8 })
@@ -659,7 +817,7 @@ GPIOD_TEST_CASE(invalid_fd, 0, { 8 })
 	g_autoptr(gpiod_line_bulk_struct) ev_bulk = NULL;
 	g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
+	uint64_t timeout = gpiod_sec_to_nsec(1);
 	struct gpiod_line *line;
 	gint ret, fd;
 
@@ -678,7 +836,7 @@ GPIOD_TEST_CASE(invalid_fd, 0, { 8 })
 	fd = gpiod_line_event_get_fd(line);
 	close(fd);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, -1);
 	g_assert_cmpint(errno, ==, EINVAL);
 
@@ -693,16 +851,17 @@ GPIOD_TEST_CASE(invalid_fd, 0, { 8 })
 	 * as well.
 	 */
 	gpiod_line_bulk_add_line(bulk, line);
-	ret = gpiod_line_event_wait_bulk(bulk, &ts, ev_bulk);
+	ret = gpiod_line_event_wait_bulk(bulk, timeout, ev_bulk);
 	g_assert_cmpint(ret, ==, -1);
 	g_assert_cmpint(errno, ==, EINVAL);
 }
 
 GPIOD_TEST_CASE(read_events_individually, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct timespec ts = { 1, 0 };
-	struct gpiod_line_event ev;
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *event;
 	struct gpiod_line *line;
 	gint ret;
 	guint i;
@@ -711,6 +870,10 @@ GPIOD_TEST_CASE(read_events_individually, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(1);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 7);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -727,45 +890,61 @@ GPIOD_TEST_CASE(read_events_individually, 0, { 8 })
 	}
 
 	/* read them individually... */
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_RISING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
 
-	ret = gpiod_line_event_read(line, &ev);
+	ret = gpiod_line_event_read(line, event_buf);
 	g_assert_cmpint(ret, ==, 0);
 
-	g_assert_cmpint(ev.event_type, ==, GPIOD_LINE_EVENT_FALLING_EDGE);
+	event = gpiod_line_event_buffer_get_event(event_buf, 0);
+	g_assert_nonnull(event);
+	gpiod_test_return_if_failed();
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
+			GPIOD_LINE_EVENT_FALLING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 0);
 }
 
 GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct gpiod_line_event events[5];
-	struct timespec ts = { 1, 0 };
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *events[4];
 	struct gpiod_line *line;
 	gint ret;
 	guint i;
@@ -774,6 +953,10 @@ GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(5);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 4);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -795,23 +978,30 @@ GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
 		usleep(10000);
 	}
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
 
 	/* read a chunk */
-	ret = gpiod_line_event_read_multiple(line, events, 3);
+	ret = gpiod_line_event_read_multiple(line, event_buf, 3);
 	g_assert_cmpint(ret, ==, 3);
 
-	g_assert_cmpint(events[0].event_type, ==,
+	events[0] = gpiod_line_event_buffer_get_event(event_buf, 0);
+	events[1] = gpiod_line_event_buffer_get_event(event_buf, 1);
+	events[2] = gpiod_line_event_buffer_get_event(event_buf, 2);
+	g_assert_nonnull(events[0]);
+	g_assert_nonnull(events[1]);
+	g_assert_nonnull(events[2]);
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[0]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
-	g_assert_cmpint(events[1].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[1]), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
-	g_assert_cmpint(events[2].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[2]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -820,27 +1010,37 @@ GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
 	 * read the remainder
 	 * - note the attempt to read more than are available
 	 */
-	ret = gpiod_line_event_read_multiple(line, events, 5);
+	ret = gpiod_line_event_read_multiple(line, event_buf, 5);
 	g_assert_cmpint(ret, ==, 4);
 
-	g_assert_cmpint(events[0].event_type, ==,
+	events[0] = gpiod_line_event_buffer_get_event(event_buf, 0);
+	events[1] = gpiod_line_event_buffer_get_event(event_buf, 1);
+	events[2] = gpiod_line_event_buffer_get_event(event_buf, 2);
+	events[3] = gpiod_line_event_buffer_get_event(event_buf, 3);
+	g_assert_nonnull(events[0]);
+	g_assert_nonnull(events[1]);
+	g_assert_nonnull(events[2]);
+	g_assert_nonnull(events[3]);
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[0]), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
-	g_assert_cmpint(events[1].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[1]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
-	g_assert_cmpint(events[2].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[2]), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
-	g_assert_cmpint(events[3].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[3]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 0);
 }
 
 GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
 {
+	g_autoptr(gpiod_line_event_buffer_struct) event_buf = NULL;
 	g_autoptr(gpiod_chip_struct) chip = NULL;
-	struct gpiod_line_event events[5];
-	struct timespec ts = { 1, 0 };
+	uint64_t timeout = gpiod_sec_to_nsec(1);
+	struct gpiod_line_event *events[4];
 	struct gpiod_line *line;
 	gint ret, fd;
 	guint i;
@@ -849,6 +1049,10 @@ GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
 	g_assert_nonnull(chip);
 	gpiod_test_return_if_failed();
 
+	event_buf = gpiod_line_event_buffer_new(5);
+	g_assert_nonnull(event_buf);
+	gpiod_test_return_if_failed();
+
 	line = gpiod_chip_get_line(chip, 4);
 	g_assert_nonnull(line);
 	gpiod_test_return_if_failed();
@@ -863,7 +1067,7 @@ GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
 		usleep(10000);
 	}
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -872,17 +1076,24 @@ GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
 	g_assert_cmpint(fd, >=, 0);
 
 	/* read a chunk */
-	ret = gpiod_line_event_read_fd_multiple(fd, events, 3);
+	ret = gpiod_line_event_read_fd_multiple(fd, event_buf, 3);
 	g_assert_cmpint(ret, ==, 3);
 
-	g_assert_cmpint(events[0].event_type, ==,
+	events[0] = gpiod_line_event_buffer_get_event(event_buf, 0);
+	events[1] = gpiod_line_event_buffer_get_event(event_buf, 1);
+	events[2] = gpiod_line_event_buffer_get_event(event_buf, 2);
+	g_assert_nonnull(events[0]);
+	g_assert_nonnull(events[1]);
+	g_assert_nonnull(events[2]);
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[0]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
-	g_assert_cmpint(events[1].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[1]), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
-	g_assert_cmpint(events[2].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[2]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -891,18 +1102,27 @@ GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
 	 * read the remainder
 	 * - note the attempt to read more than are available
 	 */
-	ret = gpiod_line_event_read_fd_multiple(fd, events, 5);
+	ret = gpiod_line_event_read_fd_multiple(fd, event_buf, 5);
 	g_assert_cmpint(ret, ==, 4);
 
-	g_assert_cmpint(events[0].event_type, ==,
+	events[0] = gpiod_line_event_buffer_get_event(event_buf, 0);
+	events[1] = gpiod_line_event_buffer_get_event(event_buf, 1);
+	events[2] = gpiod_line_event_buffer_get_event(event_buf, 2);
+	events[3] = gpiod_line_event_buffer_get_event(event_buf, 3);
+	g_assert_nonnull(events[0]);
+	g_assert_nonnull(events[1]);
+	g_assert_nonnull(events[2]);
+	g_assert_nonnull(events[3]);
+
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[0]), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
-	g_assert_cmpint(events[1].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[1]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
-	g_assert_cmpint(events[2].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[2]), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
-	g_assert_cmpint(events[3].event_type, ==,
+	g_assert_cmpint(gpiod_line_event_get_event_type(events[3]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, &ts);
+	ret = gpiod_line_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 0);
 }
diff --git a/tools/gpiomon.c b/tools/gpiomon.c
index dda9f6f..3e6b715 100644
--- a/tools/gpiomon.c
+++ b/tools/gpiomon.c
@@ -13,6 +13,8 @@
 
 #include "tools-common.h"
 
+#define EVENT_BUF_SIZE 32
+
 static const struct option longopts[] = {
 	{ "help",		no_argument,		NULL,	'h' },
 	{ "version",		no_argument,		NULL,	'v' },
@@ -64,10 +66,8 @@ struct mon_ctx {
 	char *fmt;
 };
 
-static void event_print_custom(unsigned int offset,
-			       const struct timespec *ts,
-			       int event_type,
-			       struct mon_ctx *ctx)
+static void event_print_custom(unsigned int offset, uint64_t timeout,
+			       int event_type, struct mon_ctx *ctx)
 {
 	char *prev, *curr, fmt;
 
@@ -94,10 +94,10 @@ static void event_print_custom(unsigned int offset,
 				fputc('0', stdout);
 			break;
 		case 's':
-			printf("%ld", ts->tv_sec);
+			printf("%ld", timeout / 1000000000);
 			break;
 		case 'n':
-			printf("%ld", ts->tv_nsec);
+			printf("%ld", timeout % 1000000000);
 			break;
 		case '%':
 			fputc('%', stdout);
@@ -119,8 +119,7 @@ end:
 }
 
 static void event_print_human_readable(unsigned int offset,
-				       const struct timespec *ts,
-				       int event_type)
+				       uint64_t timeout, int event_type)
 {
 	char *evname;
 
@@ -130,11 +129,11 @@ static void event_print_human_readable(unsigned int offset,
 		evname = "FALLING EDGE";
 
 	printf("event: %s offset: %u timestamp: [%8ld.%09ld]\n",
-	       evname, offset, ts->tv_sec, ts->tv_nsec);
+	       evname, offset, timeout / 1000000000, timeout % 1000000000);
 }
 
 static void handle_event(unsigned int line_offset, unsigned int event_type,
-			 struct timespec *timestamp, struct mon_ctx *ctx)
+			 uint64_t timestamp, struct mon_ctx *ctx)
 {
 	if (!ctx->silent) {
 		if (ctx->fmt)
@@ -156,8 +155,8 @@ int main(int argc, char **argv)
 	unsigned int offsets[64], num_lines = 0, offset,
 		     events_wanted = 0, events_done = 0, x;
 	bool watch_rising = false, watch_falling = false;
+	uint64_t timeout = gpiod_sec_to_nsec(10);
 	int flags = 0;
-	struct timespec timeout = { 10, 0 };
 	int optc, opti, rv, i, y, event_type;
 	struct mon_ctx ctx;
 	struct gpiod_chip *chip;
@@ -165,7 +164,8 @@ int main(int argc, char **argv)
 	char *end;
 	struct gpiod_line_request_config config;
 	struct gpiod_line *line;
-	struct gpiod_line_event events[16];
+	struct gpiod_line_event_buffer *event_buffer;
+	struct gpiod_line_event *event;
 
 	/*
 	 * FIXME: use signalfd once the API has been converted to using a single file
@@ -271,9 +271,13 @@ int main(int argc, char **argv)
 	if (!evlines)
 		die("out of memory");
 
+	event_buffer = gpiod_line_event_buffer_new(EVENT_BUF_SIZE);
+	if (!event_buffer)
+		die_perror("unable to allocate the line event buffer");
+
 	for (;;) {
 		gpiod_line_bulk_reset(evlines);
-		rv = gpiod_line_event_wait_bulk(lines, &timeout, evlines);
+		rv = gpiod_line_event_wait_bulk(lines, timeout, evlines);
 		if (rv < 0)
 			die_perror("error waiting for events");
 		if (rv == 0)
@@ -284,15 +288,22 @@ int main(int argc, char **argv)
 		for (x = 0; x < num_lines; x++) {
 			line = gpiod_line_bulk_get_line(evlines, x);
 
-			rv = gpiod_line_event_read_multiple(line, events,
-							    ARRAY_SIZE(events));
+			rv = gpiod_line_event_read_multiple(line, event_buffer,
+							    EVENT_BUF_SIZE);
 			if (rv < 0)
 				die_perror("error reading line events");
 
 			for (y = 0; y < rv; y++) {
+				event = gpiod_line_event_buffer_get_event(
+							event_buffer, y);
+				if (!event)
+					die_perror("unable to retrieve the event");
+
 				handle_event(gpiod_line_offset(line),
-					     events[y].event_type,
-					     &events[y].ts, &ctx);
+					gpiod_line_event_get_event_type(event),
+					gpiod_line_event_get_timestamp(event),
+					&ctx);
+
 				events_done++;
 
 				if (events_wanted &&
-- 
2.30.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [libgpiod][RFC 5/6] core: rework line requests
  2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
                   ` (3 preceding siblings ...)
  2021-04-10 14:51 ` [libgpiod][RFC 4/6] core: rework line events Bartosz Golaszewski
@ 2021-04-10 14:51 ` Bartosz Golaszewski
  2021-04-10 14:51 ` [libgpiod][RFC 6/6] core: implement line watch events Bartosz Golaszewski
  2021-04-14 14:15 ` [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Kent Gibson
  6 siblings, 0 replies; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-10 14:51 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij, Andy Shevchenko
  Cc: linux-gpio, Bartosz Golaszewski

This introduces the bulk of changes for the v2 API in libgpiod. All
objects become opaque. The API supports all features exposed by the
v2 of the kernel uAPI.

Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
---
 include/gpiod.h            | 1023 ++++++++++++++------------------
 lib/Makefile.am            |    9 +-
 lib/attr.c                 |  232 ++++++++
 lib/chip.c                 |  183 ++++++
 lib/config.c               |  158 +++++
 lib/core.c                 | 1131 ------------------------------------
 lib/event.c                |   44 +-
 lib/handle.c               |  144 +++++
 lib/helpers.c              |  306 ----------
 lib/info.c                 |    1 +
 lib/internal.c             |  101 ++++
 lib/internal.h             |   14 +
 lib/mask.c                 |   43 ++
 lib/misc.c                 |   75 +++
 lib/request.c              |  118 ++++
 tests/tests-event.c        |   58 +-
 tools/gpio-tools-test.bats |   12 +-
 tools/gpiodetect.c         |   11 +-
 tools/gpiofind.c           |    1 +
 tools/gpioget.c            |   57 +-
 tools/gpioinfo.c           |   12 +-
 tools/gpiomon.c            |  113 ++--
 tools/gpioset.c            |   85 ++-
 tools/tools-common.c       |    8 +-
 tools/tools-common.h       |    2 +-
 25 files changed, 1722 insertions(+), 2219 deletions(-)
 create mode 100644 lib/attr.c
 create mode 100644 lib/chip.c
 create mode 100644 lib/config.c
 delete mode 100644 lib/core.c
 create mode 100644 lib/handle.c
 delete mode 100644 lib/helpers.c
 create mode 100644 lib/mask.c
 create mode 100644 lib/request.c

diff --git a/include/gpiod.h b/include/gpiod.h
index e99233b..8831d4c 100644
--- a/include/gpiod.h
+++ b/include/gpiod.h
@@ -1,11 +1,11 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 /* SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com> */
+/* SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl> */
 
 #ifndef __LIBGPIOD_GPIOD_H__
 #define __LIBGPIOD_GPIOD_H__
 
 #include <stdbool.h>
-#include <stdlib.h>
 #include <stdint.h>
 
 #ifdef __cplusplus
@@ -29,48 +29,32 @@ extern "C" {
  * errno to one of the error values defined in errno.h upon failure. The way
  * of notifying the caller that an error occurred varies between functions,
  * but in general a function that returns an int, returns -1 on error, while
- * a function returning a pointer bails out on error condition by returning
+ * a function returning a pointer indicates an error condition by returning
  * a NULL pointer.
+ *
+ * <p>In general libgpiod functions are not NULL-aware and it's expected that
+ * users pass valid pointers to objects as arguments.
+ *
+ * <p>Almost all opaque objects are reference counted. The associated resources
+ * are released when the reference count reaches 0.
  */
 
 struct gpiod_chip;
-struct gpiod_line;
 struct gpiod_line_info;
-struct gpiod_line_bulk;
+struct gpiod_line_attr;
+struct gpiod_line_config;
+struct gpiod_request_config;
+struct gpiod_request_handle;
 struct gpiod_line_event;
 struct gpiod_line_event_buffer;
 
 /**
- * @defgroup common Common helper macros
- * @{
- *
- * Commonly used utility macros.
- */
-
-/**
- * @brief Shift 1 by given offset.
- * @param nr Bit position.
- * @return 1 shifted by nr.
- */
-#define GPIOD_BIT(nr)		(1UL << (nr))
-
-/**
- * @}
- *
- * @defgroup chips GPIO chip operations
+ * @defgroup chips GPIO chips
  * @{
  *
- * Functions and data structures dealing with GPIO chips.
+ * Functions and data structures for manipulating GPIO chips.
  */
 
-/**
- * @brief Check if the file pointed to by path is a GPIO chip character device.
- * @param path Path to check.
- * @return True if the file exists and is a GPIO chip character device or a
- *         symbolic link to it.
- */
-bool gpiod_is_gpiochip_device(const char *path);
-
 /**
  * @brief Open a gpiochip by path.
  * @param path Path to the gpiochip device file.
@@ -80,7 +64,7 @@ struct gpiod_chip *gpiod_chip_open(const char *path);
 
 /**
  * @brief Increase the refcount on this GPIO object.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @return Passed reference to the GPIO chip.
  */
 struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip);
@@ -88,27 +72,27 @@ struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip);
 /**
  * @brief Decrease the refcount on this GPIO object. If the refcount reaches 0,
  *        close the chip device and free all associated resources.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  */
 void gpiod_chip_unref(struct gpiod_chip *chip);
 
 /**
  * @brief Get the GPIO chip name as represented in the kernel.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @return Pointer to a human-readable string containing the chip name.
  */
 const char *gpiod_chip_get_name(struct gpiod_chip *chip);
 
 /**
  * @brief Get the GPIO chip label as represented in the kernel.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @return Pointer to a human-readable string containing the chip label.
  */
 const char *gpiod_chip_get_label(struct gpiod_chip *chip);
 
 /**
  * @brief Get the number of GPIO lines exposed by this chip.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @return Number of GPIO lines.
  */
 unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip);
@@ -116,143 +100,34 @@ unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip);
 /**
  * @brief Get the current snapshot of information about the line at given
  *        offset.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @param offset The offset of the GPIO line.
- * @return New GPIO line info object that must be freed using
- *         ::gpiod_line_info_free or NULL if an error occurred.
+ * @return New GPIO line info object or NULL if an error occurred.
  */
 struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
 						 unsigned int offset);
 
-/**
- * @brief Get the handle to the GPIO line at given offset.
- * @param chip The GPIO chip object.
- * @param offset The offset of the GPIO line.
- * @return Pointer to the GPIO line handle or NULL if an error occured.
- */
-struct gpiod_line *
-gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset);
-
-/**
- * @brief Retrieve a set of lines and store them in a line bulk object.
- * @param chip The GPIO chip object.
- * @param offsets Array of offsets of lines to retrieve.
- * @param num_offsets Number of lines to retrieve.
- * @return New line bulk object or NULL on error.
- */
-struct gpiod_line_bulk *
-gpiod_chip_get_lines(struct gpiod_chip *chip, unsigned int *offsets,
-		     unsigned int num_offsets);
-
-/**
- * @brief Retrieve all lines exposed by a chip and store them in a bulk object.
- * @param chip The GPIO chip object.
- * @return New line bulk object or NULL on error.
- */
-struct gpiod_line_bulk *
-gpiod_chip_get_all_lines(struct gpiod_chip *chip);
-
 /**
  * @brief Map a GPIO line's name to its offset within the chip.
- * @param chip The GPIO chip object.
+ * @param chip GPIO chip object.
  * @param name Name of the GPIO line to map.
- * @return Offset of the line within the chip or -1 if a line with given name
- *         is not exposed by the chip.
+ * @return Offset of the line within the chip or -1 on error.
+ * @note If a line with given name is not exposed by the chip, the function
+ *       sets errno to ENOENT.
  */
 int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name);
 
 /**
- * @}
- *
- * @defgroup lines GPIO line operations
- * @{
- *
- * Functions and data structures dealing with GPIO lines.
- *
- * @defgroup line_bulk Operating on multiple lines
- * @{
- *
- * Convenience data structures and helper functions for storing and operating
- * on multiple lines at once.
- */
-
-/**
- * @brief Allocate and initialize a new line bulk object.
- * @param max_lines Maximum number of lines this object can hold.
- * @return New line bulk object or NULL on error.
- */
-struct gpiod_line_bulk *gpiod_line_bulk_new(unsigned int max_lines);
-
-/**
- * @brief Reset a bulk object. Remove all lines and set size to 0.
- * @param bulk Bulk object to reset.
- */
-void gpiod_line_bulk_reset(struct gpiod_line_bulk *bulk);
-
-/**
- * @brief Release all resources allocated for this bulk object.
- * @param bulk Bulk object to free.
- */
-void gpiod_line_bulk_free(struct gpiod_line_bulk *bulk);
-
-/**
- * @brief Add a single line to a GPIO bulk object.
- * @param bulk Line bulk object.
- * @param line Line to add.
- * @return 0 on success, -1 on error.
- * @note The line is added at the next free bulk index.
- *
- * The function can fail if this bulk already holds its maximum amount of
- * lines or if the added line is associated with a different chip than all
- * the other lines already held by this object.
- */
-int gpiod_line_bulk_add_line(struct gpiod_line_bulk *bulk,
-			     struct gpiod_line *line);
-
-/**
- * @brief Retrieve the line handle from a line bulk object at given index.
- * @param bulk Line bulk object.
- * @param index Index of the line to retrieve.
- * @return Line handle at given index or NULL if index is greater or equal to
- *         the number of lines this bulk can hold.
- */
-struct gpiod_line *
-gpiod_line_bulk_get_line(struct gpiod_line_bulk *bulk, unsigned int index);
-
-/**
- * @brief Retrieve the number of GPIO lines held by this line bulk object.
- * @param bulk Line bulk object.
- * @return Number of lines held by this line bulk.
- */
-unsigned int gpiod_line_bulk_num_lines(struct gpiod_line_bulk *bulk);
-
-/**
- * @brief Values returned by the callback passed to
- *        ::gpiod_line_bulk_foreach_line.
+ * @brief Request a set of lines for exclusive usage.
+ * @param chip GPIO chip object.
+ * @param req_cfg Request config object.
+ * @param line_cfg Line config object.
+ * @return New request handle object or NULL if an error occurred.
  */
-enum {
-	/**< Continue the loop. */
-	GPIOD_LINE_BULK_CB_NEXT = 0,
-	/**< Stop the loop. */
-	GPIOD_LINE_BULK_CB_STOP,
-};
-
-/**
- * @brief Signature of the callback passed to ::gpiod_line_bulk_foreach_line.
- *
- * Takes the current line and additional user data as arguments.
- */
-typedef int (*gpiod_line_bulk_foreach_cb)(struct gpiod_line *, void *);
-
-/**
- * @brief Iterate over all lines held by this bulk object.
- * @param bulk Bulk object to iterate over.
- * @param func Callback to be called for each line.
- * @param data User data pointer that is passed to the callback.
- */
-void gpiod_line_bulk_foreach_line(struct gpiod_line_bulk *bulk,
-				  gpiod_line_bulk_foreach_cb func,
-				  void *data);
+struct gpiod_request_handle *
+gpiod_chip_request_lines(struct gpiod_chip *chip,
+			 struct gpiod_request_config *req_cfg,
+			 struct gpiod_line_config *line_cfg);
 
 /**
  * @}
@@ -314,13 +189,6 @@ enum {
 	/**< Line detects both rising and falling edge events. */
 };
 
-/**
- * @brief Read the GPIO line offset.
- * @param line GPIO line object.
- * @return Line offset.
- */
-unsigned int gpiod_line_offset(struct gpiod_line *line);
-
 /**
  * @brief Increase the reference count for this line info object.
  * @param info GPIO line info object.
@@ -428,485 +296,460 @@ unsigned long
 gpiod_line_get_debounce_period(struct gpiod_line_info *info);
 
 /**
- * @brief Get the handle to the GPIO chip controlling this line.
- * @param line The GPIO line object.
- * @return Pointer to the GPIO chip handle controlling this line.
+ * @}
+ *
+ * @defgroup line_mask Line mask bitmaps
+ * @{
+ */
+
+/**
+ * @brief Bitmask used to store values of lines in a single request (up to 64
+ *        lines) as well as to specify on which lines in a request to act when
+ *        reading/setting values or applying configuration options.
+ */
+typedef uint64_t gpiod_line_mask;
+
+/**
+ * @brief Set all bits in the mask.
+ * @param mask The GPIO line mask.
+ */
+void gpiod_line_mask_fill(gpiod_line_mask *mask);
+
+/**
+ * @brief Clear all bits in the mask.
+ * @param mask The GPIO line mask.
+ */
+void gpiod_line_mask_zero(gpiod_line_mask *mask);
+
+/**
+ * @brief Test a single bit.
+ * @param mask The GPIO line mask.
+ * @param offset Offset of the bit to test.
+ * @return True if the bit is set, false otherwise.
+ */
+bool gpiod_line_mask_test_bit(gpiod_line_mask mask, unsigned int offset);
+
+/**
+ * @brief Set a single bit.
+ * @param mask The GPIO line mask.
+ * @param offset Offset of the bit to set.
+ */
+void gpiod_line_mask_set_bit(gpiod_line_mask *mask, unsigned int offset);
+
+/**
+ * @brief Clear a single bit.
+ * @param mask The GPIO line mask.
+ * @param offset Offset of the bit to clear.
+ */
+void gpiod_line_mask_clear_bit(gpiod_line_mask *mask, unsigned int offset);
+
+/**
+ * @brief Set or clear a single bit depending on a boolean value.
+ * @param mask The GPIO line mask.
+ * @param offset Offset of the bit to assign.
+ * @param value Boolean value to indicate the value of the bit.
  */
-struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line);
+void gpiod_line_mask_assign_bit(gpiod_line_mask *mask,
+				unsigned int offset, bool value);
 
 /**
  * @}
  *
- * @defgroup line_request Line requests
+ * @defgroup line_config Line and request configuration objects
  * @{
  *
- * Interface for requesting GPIO lines from userspace for both values and
- * events.
+ * Functions for manipulating line attributes and configuration objects.
  */
 
 /**
  * @brief Available types of requests.
  */
 enum {
-	GPIOD_LINE_REQUEST_DIRECTION_AS_IS = 1,
+	GPIOD_LINE_CONFIG_DIRECTION_AS_IS = 1,
 	/**< Request the line(s), but don't change current direction. */
-	GPIOD_LINE_REQUEST_DIRECTION_INPUT,
+	GPIOD_LINE_CONFIG_DIRECTION_INPUT,
 	/**< Request the line(s) for reading the GPIO line state. */
-	GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
+	GPIOD_LINE_CONFIG_DIRECTION_OUTPUT,
 	/**< Request the line(s) for setting the GPIO line state. */
-	GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE,
+	GPIOD_LINE_CONFIG_EVENT_FALLING_EDGE,
 	/**< Only watch falling edge events. */
-	GPIOD_LINE_REQUEST_EVENT_RISING_EDGE,
+	GPIOD_LINE_CONFIG_EVENT_RISING_EDGE,
 	/**< Only watch rising edge events. */
-	GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES,
+	GPIOD_LINE_CONFIG_EVENT_BOTH_EDGES,
 	/**< Monitor both types of events. */
 };
 
 /**
- * @brief Miscellaneous GPIO request flags.
+ * @brief Available drive settings for line requests.
  */
 enum {
-	GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN	= GPIOD_BIT(0),
-	/**< The line is an open-drain port. */
-	GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE	= GPIOD_BIT(1),
-	/**< The line is an open-source port. */
-	GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW	= GPIOD_BIT(2),
-	/**< The active state of the line is low (high is the default). */
-	GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED	= GPIOD_BIT(3),
-	/**< The line has neither either pull-up nor pull-down resistor. */
-	GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN	= GPIOD_BIT(4),
-	/**< The line has pull-down resistor enabled. */
-	GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP	= GPIOD_BIT(5),
-	/**< The line has pull-up resistor enabled. */
+	GPIOD_LINE_CONFIG_DRIVE_PUSH_PULL = 1,
+	/**< Drive setting should be set to push-pull (the default). */
+	GPIOD_LINE_CONFIG_DRIVE_OPEN_DRAIN,
+	/**< Line output should be set to open-drain. */
+	GPIOD_LINE_CONFIG_DRIVE_OPEN_SOURCE,
+	/**< Line output should be set to open-source. */
 };
 
 /**
- * @brief Structure holding configuration of a line request.
+ * @brief Available internal bias settings for line requests.
  */
-struct gpiod_line_request_config {
-	const char *consumer;
-	/**< Name of the consumer. */
-	int request_type;
-	/**< Request type. */
-	int flags;
-	/**< Other configuration flags. */
+enum {
+	GPIOD_LINE_CONFIG_BIAS_DISABLED = 1,
+	/**< The internal bias should be disabled (the default). */
+	GPIOD_LINE_CONFIG_BIAS_PULL_UP,
+	/**< The internal pull-up bias is enabled. */
+	GPIOD_LINE_CONFIG_BIAS_PULL_DOWN,
+	/**< The internal pull-down bias is enabled. */
 };
 
 /**
- * @brief Reserve a single line.
- * @param line GPIO line object.
- * @param config Request options.
- * @param default_val Initial line value - only relevant if we're setting
- *                    the direction to output.
- * @return 0 if the line was properly reserved. In case of an error this
- *         routine returns -1 and sets the last error number.
- *
- * If this routine succeeds, the caller takes ownership of the GPIO line until
- * it's released.
+ * @brief Available line attribute types.
  */
-int gpiod_line_request(struct gpiod_line *line,
-		       const struct gpiod_line_request_config *config,
-		       int default_val);
+enum {
+	GPIOD_LINE_ATTR_TYPE_OPTS,
+	/**< The attribute specifies a set of simple line options. */
+	GPIOD_LINE_ATTR_TYPE_OUTPUT_VALUES,
+	/**< The attribute contains output values for a set of GPIO lines. */
+	GPIOD_LINE_ATTR_TYPE_DEBOUNCE,
+	/**< The attribute defines debounce config. */
+};
 
 /**
- * @brief Reserve a single line, set the direction to input.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @return 0 if the line was properly reserved, -1 on failure.
+ * @brief Create a new line attribute.
+ * @param type Type of this attribute.
+ * @return New line attribute or NULL on error.
  */
-int gpiod_line_request_input(struct gpiod_line *line, const char *consumer);
+struct gpiod_line_attr *gpiod_line_attr_new(int type);
 
 /**
- * @brief Reserve a single line, set the direction to output.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param default_val Initial line value.
- * @return 0 if the line was properly reserved, -1 on failure.
+ * @brief Increase the reference count of this object.
+ * @param attr Line attribute object.
+ * @return Passed reference to the line attribute.
  */
-int gpiod_line_request_output(struct gpiod_line *line,
-			      const char *consumer, int default_val);
+struct gpiod_line_attr *gpiod_line_attr_ref(struct gpiod_line_attr *attr);
 
 /**
- * @brief Request rising edge event notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Decrease the reference count to the line attribute. If the refcount
+ *        reaches 0, free all resources associated with this object.
+ * @param attr Line attribute object.
  */
-int gpiod_line_request_rising_edge_events(struct gpiod_line *line,
-					  const char *consumer);
+void gpiod_line_attr_unref(struct gpiod_line_attr *attr);
 
 /**
- * @brief Request falling edge event notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the line mask of this attribute.
+ * @param attr Line attribute object.
+ * @param mask New line mask.
+ *
+ * An attribute can be associated with up to 64 lines (the maximum for a single
+ * line request). Each bit in the mask bitmap represents a single line from the
+ * request at the same offset as the bit. If the bit is set, the configuration
+ * specified in this attribute will apply to the associated line.
  */
-int gpiod_line_request_falling_edge_events(struct gpiod_line *line,
-					   const char *consumer);
+void gpiod_line_attr_set_line_mask(struct gpiod_line_attr *attr,
+				   gpiod_line_mask mask);
 
 /**
- * @brief Request all event type notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the request type.
+ * @param attr Line attribute object.
+ * @param request_type New request type.
+ * @return 0 on success, -1 on error.
+ * @note Can only be used with attributes of type ::GPIOD_LINE_ATTR_TYPE_OPTS.
  */
-int gpiod_line_request_both_edges_events(struct gpiod_line *line,
-					 const char *consumer);
+int gpiod_line_attr_set_request_type(struct gpiod_line_attr *attr,
+				     int request_type);
 
 /**
- * @brief Reserve a single line, set the direction to input.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the line was properly reserved, -1 on failure.
+ * @brief Set the drive setting.
+ * @param attr Line attribute object.
+ * @param drive New drive setting.
+ * @return 0 on success, -1 on error.
+ * @note Can only be used with attributes of type ::GPIOD_LINE_ATTR_TYPE_OPTS.
  */
-int gpiod_line_request_input_flags(struct gpiod_line *line,
-				   const char *consumer, int flags);
+int gpiod_line_attr_set_drive(struct gpiod_line_attr *attr, int drive);
 
 /**
- * @brief Reserve a single line, set the direction to output.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @param default_val Initial line value.
- * @return 0 if the line was properly reserved, -1 on failure.
+ * @brief Set the bias setting.
+ * @param attr Line attribute object.
+ * @param bias New bias setting.
+ * @return 0 on success, -1 on error.
+ * @note Can only be used with attributes of type ::GPIOD_LINE_ATTR_TYPE_OPTS.
  */
-int gpiod_line_request_output_flags(struct gpiod_line *line,
-				    const char *consumer, int flags,
-				    int default_val);
+int gpiod_line_attr_set_bias(struct gpiod_line_attr *attr, int bias);
 
 /**
- * @brief Request rising edge event notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the active-low setting.
+ * @param attr Line attribute object.
+ * @param active_low New active-low setting.
+ * @return 0 on success, -1 on error.
+ * @note Can only be used with attributes of type ::GPIOD_LINE_ATTR_TYPE_OPTS.
  */
-int gpiod_line_request_rising_edge_events_flags(struct gpiod_line *line,
-						const char *consumer,
-						int flags);
+int gpiod_line_attr_set_active_low(struct gpiod_line_attr *attr,
+				   bool active_low);
 
 /**
- * @brief Request falling edge event notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the realtime clock setting.
+ * @param attr Line attribute object.
+ * @param clock_realtime New realtime clock setting.
+ * @return 0 on success, -1 on error.
+ * @note Can only be used with attributes of type ::GPIOD_LINE_ATTR_TYPE_OPTS.
+ *
+ * This option can be used to tell the kernel to get the line event timestamps
+ * from the realtime clock, not the monotonic clock which is the default.
  */
-int gpiod_line_request_falling_edge_events_flags(struct gpiod_line *line,
-						 const char *consumer,
-						 int flags);
+int gpiod_line_attr_set_clock_realtime(struct gpiod_line_attr *attr,
+				       bool clock_realtime);
 
 /**
- * @brief Request all event type notifications on a single line.
- * @param line GPIO line object.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the debounce config.
+ * @param attr Line attribute object.
+ * @param debounce Activate or de-activate debouncing.
+ * @param debounce_period New debounce period in microseconds.
+ * @return 0 on success, -1 on error.
+ * @note Can only be used with attributes of type
+ *       ::GPIOD_LINE_ATTR_TYPE_DEBOUNCE.
+ *
  */
-int gpiod_line_request_both_edges_events_flags(struct gpiod_line *line,
-					       const char *consumer,
-					       int flags);
+int gpiod_line_attr_set_debounce(struct gpiod_line_attr *attr, bool debounce,
+				 unsigned long debounce_period);
 
 /**
- * @brief Reserve a set of GPIO lines.
- * @param bulk Set of GPIO lines to reserve.
- * @param config Request options.
- * @param default_vals Initial line values - only relevant if we're setting
- *                     the direction to output.
- * @return 0 if all lines were properly requested. In case of an error
- *         this routine returns -1 and sets the last error number.
+ * @brief Set the output values carried by this attribute.
+ * @param attr Line attribute object.
+ * @param values GPIO line mask specifing the output values.
+ * @return 0 on success
+ * @note Can only be used with attributes of type
+ *       ::GPIOD_LINE_ATTR_TYPE_OUTPUT_VALUES.
  *
- * If this routine succeeds, the caller takes ownership of the GPIO lines
- * until they're released. All the requested lines must be provided by the
- * same gpiochip.
+ * Each bit in the values mask is associated with a line in a request handle.
+ * If the bit is set, the line should be driven high, if the bit is cleared,
+ * the line should be driven low.
  */
-int gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
-			    const struct gpiod_line_request_config *config,
-			    const int *default_vals);
+int gpiod_line_attr_set_output_values(struct gpiod_line_attr *attr,
+				      gpiod_line_mask values);
 
 /**
- * @brief Reserve a set of GPIO lines, set the direction to input.
- * @param bulk Set of GPIO lines to reserve.
- * @param consumer Name of the consumer.
- * @return 0 if the lines were properly reserved, -1 on failure.
+ * @brief Create a new line config object.
+ * @return New line config object or NULL on error.
  */
-int gpiod_line_request_bulk_input(struct gpiod_line_bulk *bulk,
-				  const char *consumer);
+struct gpiod_line_config *gpiod_line_config_new(void);
 
 /**
- * @brief Reserve a set of GPIO lines, set the direction to output.
- * @param bulk Set of GPIO lines to reserve.
- * @param consumer Name of the consumer.
- * @param default_vals Initial line values.
- * @return 0 if the lines were properly reserved, -1 on failure.
+ * @brief Increase the reference count of this line config object.
+ * @param config Line config object.
+ * @return Passed reference to the line config object.
  */
-int gpiod_line_request_bulk_output(struct gpiod_line_bulk *bulk,
-				   const char *consumer,
-				   const int *default_vals);
+struct gpiod_line_config *
+gpiod_line_config_ref(struct gpiod_line_config *config);
 
 /**
- * @brief Request rising edge event notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Decrease the reference count of this line config object. If the
+ *        reference count reaches 0, free all associated resources.
+ * @param config Line config object.
  */
-int gpiod_line_request_bulk_rising_edge_events(struct gpiod_line_bulk *bulk,
-					       const char *consumer);
+void gpiod_line_config_unref(struct gpiod_line_config *config);
 
 /**
- * @brief Request falling edge event notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Add a new line attribute to this line config.
+ * @param config Line config object.
+ * @param attr Line attribute to add.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_request_bulk_falling_edge_events(struct gpiod_line_bulk *bulk,
-						const char *consumer);
+int gpiod_line_config_add_attribute(struct gpiod_line_config *config,
+				    struct gpiod_line_attr *attr);
 
 /**
- * @brief Request all event type notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Remove all attributes from this config.
+ * @param config Line config.
  */
-int gpiod_line_request_bulk_both_edges_events(struct gpiod_line_bulk *bulk,
-					      const char *consumer);
+void gpiod_line_config_clear_attrs(struct gpiod_line_config *config);
 
 /**
- * @brief Reserve a set of GPIO lines, set the direction to input.
- * @param bulk Set of GPIO lines to reserve.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the lines were properly reserved, -1 on failure.
+ * @brief Set the request type.
+ * @param config Line config.
+ * @param request_type New request type.
+ * @return 0 on success, -1 on error.
  */
-int gpiod_line_request_bulk_input_flags(struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags);
+int gpiod_line_config_set_request_type(struct gpiod_line_config *config,
+				       int request_type);
 
 /**
- * @brief Reserve a set of GPIO lines, set the direction to output.
- * @param bulk Set of GPIO lines to reserve.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @param default_vals Initial line values.
- * @return 0 if the lines were properly reserved, -1 on failure.
+ * @brief Set the drive setting.
+ * @param config Line config.
+ * @param drive New drive setting.
+ * @return 0 on success, -1 on error.
  */
-int gpiod_line_request_bulk_output_flags(struct gpiod_line_bulk *bulk,
-					 const char *consumer, int flags,
-					 const int *default_vals);
+int gpiod_line_config_set_drive(struct gpiod_line_config *config, int drive);
 
 /**
- * @brief Request rising edge event notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the bias setting.
+ * @param config Line config.
+ * @param bias New bias setting.
+ * @return 0 on success, -1 on error.
  */
-int gpiod_line_request_bulk_rising_edge_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags);
+int gpiod_line_config_set_bias(struct gpiod_line_config *config, int bias);
 
 /**
- * @brief Request falling edge event notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the active-low setting.
+ * @param config Line config.
+ * @param active_low New active-low setting.
  */
-int gpiod_line_request_bulk_falling_edge_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags);
+void gpiod_line_config_set_active_low(struct gpiod_line_config *config,
+				      bool active_low);
 
 /**
- * @brief Request all event type notifications on a set of lines.
- * @param bulk Set of GPIO lines to request.
- * @param consumer Name of the consumer.
- * @param flags Additional request flags.
- * @return 0 if the operation succeeds, -1 on failure.
+ * @brief Set the realtime clock setting.
+ * @param config Line config.
+ * @param clock_realtime New realtime clock setting.
+ *
+ * This option can be used to tell the kernel to get the line event timestamps
+ * from the realtime clock, not the monotonic clock which is the default.
  */
-int gpiod_line_request_bulk_both_edges_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags);
+void
+gpiod_line_config_set_event_clock_realtime(struct gpiod_line_config *config,
+					   bool clock_realtime);
 
 /**
- * @brief Release a previously reserved line.
- * @param line GPIO line object.
+ * @brief Create a new request config object.
+ * @return New request config object or NULL on error.
  */
-void gpiod_line_release(struct gpiod_line *line);
+struct gpiod_request_config *gpiod_request_config_new(void);
 
 /**
- * @brief Release a set of previously reserved lines.
- * @param bulk Set of GPIO lines to release.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Increase the reference count of this request config object.
+ * @param config Request config object.
+ * @return Passed reference to this request config.
  */
-void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk);
+struct gpiod_request_config *
+gpiod_request_config_ref(struct gpiod_request_config *config);
 
 /**
- * @}
- *
- * @defgroup line_value Reading & setting line values
- * @{
- *
- * Functions allowing to read and set GPIO line values for single lines and
- * in bulk.
+ * @brief Decrease the reference count of this request config object. If the
+ *        reference count reaches 0, free all associated resources.
+ * @param config Request config object.
  */
+void gpiod_request_config_unref(struct gpiod_request_config *config);
 
 /**
- * @brief Read current value of a single GPIO line.
- * @param line GPIO line object.
- * @return 0 or 1 if the operation succeeds. On error this routine returns -1
- *         and sets the last error number.
+ * @brief Set the consumer string.
+ * @param config Request config object.
+ * @param consumer Consumer name.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_get_value(struct gpiod_line *line);
+int gpiod_request_config_set_consumer(struct gpiod_request_config *config,
+				      const char *consumer);
 
 /**
- * @brief Read current values of a set of GPIO lines.
- * @param bulk Set of GPIO lines to reserve.
- * @param values An array big enough to hold line_bulk->num_lines values.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If succeeds, this routine fills the values array with a set of values in
- * the same order, the lines are added to line_bulk. If the lines were not
- * previously requested together, the behavior is undefined.
+ * @brief Set line offsets for this request.
+ * @param config Request config object.
+ * @param num_lines Number of offsets.
+ * @param offsets Array of line offsets.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk, int *values);
+int gpiod_request_config_set_offsets(struct gpiod_request_config *config,
+				     unsigned int num_lines,
+				     unsigned int *offsets);
 
 /**
- * @brief Set the value of a single GPIO line.
- * @param line GPIO line object.
- * @param value New value.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @brief Set the size of the kernel event buffer.
+ * @param config Request config object.
+ * @param event_buffer_size New event buffer size.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_set_value(struct gpiod_line *line, int value);
+int
+gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
+					   unsigned int event_buffer_size);
 
 /**
- * @brief Set the values of a set of GPIO lines.
- * @param bulk Set of GPIO lines to reserve.
- * @param values An array holding line_bulk->num_lines new values for lines.
- *               A NULL pointer is interpreted as a logical low for all lines.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @}
  *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @defgroup request_handle Line request handle operations
+ * @{
  */
-int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk, const int *values);
 
 /**
- * @}
- *
- * @defgroup line_config Setting line configuration
- * @{
- *
- * Functions allowing modification of config options of GPIO lines requested
- * from user-space.
- */
-
-/**
- * @brief Update the configuration of a single GPIO line.
- * @param line GPIO line object.
- * @param direction Updated direction which may be one of
- *                  GPIOD_LINE_REQUEST_DIRECTION_AS_IS,
- *                  GPIOD_LINE_REQUEST_DIRECTION_INPUT, or
- *                  GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @param flags Replacement flags.
- * @param value The new output value for the line when direction is
- *              GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- */
-int gpiod_line_set_config(struct gpiod_line *line, int direction,
-			  int flags, int value);
-
-/**
- * @brief Update the configuration of a set of GPIO lines.
- * @param bulk Set of GPIO lines.
- * @param direction Updated direction which may be one of
- *                  GPIOD_LINE_REQUEST_DIRECTION_AS_IS,
- *                  GPIOD_LINE_REQUEST_DIRECTION_INPUT, or
- *                  GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- * @param flags Replacement flags.
- * @param values An array holding line_bulk->num_lines new logical values
- *               for lines when direction is
- *               GPIOD_LINE_REQUEST_DIRECTION_OUTPUT.
- *               A NULL pointer is interpreted as a logical low for all lines.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Increase the reference count of this request handle.
+ * @param handle GPIO request handle.
+ * @return Passed reference to this request handle.
  */
-int gpiod_line_set_config_bulk(struct gpiod_line_bulk *bulk,
-			       int direction, int flags, const int *values);
+struct gpiod_request_handle *
+gpiod_request_handle_ref(struct gpiod_request_handle *handle);
 
+/**
+ * @brief Decrease the reference count of this request handle. If the reference
+ *        count reaches 0, close the associated file descriptor and release
+ *        all allocated resources.
+ * @param handle GPIO request handle.
+ */
+void gpiod_request_handle_unref(struct gpiod_request_handle *handle);
 
 /**
- * @brief Update the configuration flags of a single GPIO line.
- * @param line GPIO line object.
- * @param flags Replacement flags.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @brief Read values of lines associated with this request handle.
+ * @param handle GPIO request handle.
+ * @param values Address of the bitmap to store values in.
+ * @param mask Mask of the lines to read values from.
+ * @return 0 on success, -1 on failure.
+ * @note The bits in the values and mask bitmaps represent the lines associated
+ *       with this request handle at the same offsets. Only values of lines
+ *       for which the relevant bits in mask are set will be read.
  */
-int gpiod_line_set_flags(struct gpiod_line *line, int flags);
+int gpiod_request_handle_get_values(struct gpiod_request_handle *handle,
+				    gpiod_line_mask *values,
+				    gpiod_line_mask mask);
 
 /**
- * @brief Update the configuration flags of a set of GPIO lines.
- * @param bulk Set of GPIO lines.
- * @param flags Replacement flags.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Set values of lines associated with this request handle.
+ * @param handle GPIO request handle.
+ * @param values Mask of line values to set.
+ * @param mask Mask of the lines to set values for.
+ * @return 0 on success, -1 on failure.
+ * @note The bits in the values and mask bitmaps represent the lines associated
+ *       with this request handle at the same offsets. Only values of lines
+ *       for which the relevant bits in mask are set will be modified.
  */
-int gpiod_line_set_flags_bulk(struct gpiod_line_bulk *bulk, int flags);
+int gpiod_request_handle_set_values(struct gpiod_request_handle *handle,
+				    gpiod_line_mask values,
+				    gpiod_line_mask mask);
 
 /**
- * @brief Set the direction of a single GPIO line to input.
- * @param line GPIO line object.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @brief Update the configuration of lines associated with this request handle.
+ * @param handle GPIO request handle.
+ * @param config New line config to apply.
+ * @return 0 on success, -1 on failure.
  */
-int gpiod_line_set_direction_input(struct gpiod_line *line);
+int gpiod_request_handle_set_config(struct gpiod_request_handle *handle,
+				    struct gpiod_line_config *config);
 
 /**
- * @brief Set the direction of a set of GPIO lines to input.
- * @param bulk Set of GPIO lines.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Get the file descriptor associated with this request handle.
+ * @param handle GPIO request handle.
+ * @return Number of the file descriptor associated with this request. This
+ *         function never fails.
  */
-int
-gpiod_line_set_direction_input_bulk(struct gpiod_line_bulk *bulk);
+int gpiod_request_handle_get_fd(struct gpiod_request_handle *handle);
 
 /**
- * @brief Set the direction of a single GPIO line to output.
- * @param line GPIO line object.
- * @param value The logical value output on the line.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
+ * @brief Wait for line events on any of the lines associated with this handle.
+ * @param handle GPIO request handle.
+ * @param timeout Wait time limit in nanoseconds.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ *         pending.
  */
-int gpiod_line_set_direction_output(struct gpiod_line *line, int value);
+int gpiod_request_handle_event_wait(struct gpiod_request_handle *handle,
+				    uint64_t timeout);
 
 /**
- * @brief Set the direction of a set of GPIO lines to output.
- * @param bulk Set of GPIO lines.
- * @param values An array holding line_bulk->num_lines new logical values
- *               for lines.  A NULL pointer is interpreted as a logical low
- *               for all lines.
- * @return 0 is the operation succeeds. In case of an error this routine
- *         returns -1 and sets the last error number.
- *
- * If the lines were not previously requested together, the behavior is
- * undefined.
+ * @brief Read a number of line events from a request handle.
+ * @param handle GPIO request handle.
+ * @param buf Line event buffer.
+ * @param max_events Maximum number of events to read.
+ * @return On success returns the number of events read from the file
+ *         descriptor, on failure return -1.
+ * @note This function will block if no event was queued for this line.
  */
-int gpiod_line_set_direction_output_bulk(struct gpiod_line_bulk *bulk,
-					 const int *values);
+int gpiod_request_handle_event_read(struct gpiod_request_handle *handle,
+				    struct gpiod_line_event_buffer *buf,
+				    unsigned int max_events);
 
 /**
  * @}
@@ -930,120 +773,133 @@ enum {
 	/**< Falling edge event. */
 };
 
+/**
+ * @brief Increase the reference count of this line event.
+ * @param event GPIO line event.
+ * @return Passed reference to the event object.
+ * @note Must not be used with events held by the event buffer (retrieved
+ *       using ::gpiod_line_event_buffer_get_event. Only event objects
+ *       created by ::gpiod_line_event_buffer_copy_event are refcounted.
+ */
 struct gpiod_line_event *gpiod_line_event_ref(struct gpiod_line_event *event);
 
+/**
+ * @brief Decrease the reference count of this line event. If the reference
+ *        count reaches zero, free all associated resources.
+ * @param event GPIO line event.
+ */
 void gpiod_line_event_unref(struct gpiod_line_event *event);
 
+/**
+ * @brief Get the event type.
+ * @param event GPIO line event.
+ * @return The event type (::GPIOD_LINE_EVENT_RISING_EDGE or
+ *         ::GPIOD_LINE_EVENT_FALLING_EDGE).
+ */
 int gpiod_line_event_get_event_type(struct gpiod_line_event *event);
 
+/**
+ * @brief Get the timestamp of the event.
+ * @param event GPIO line event.
+ * @return Timestamp in nanoseconds.
+ */
 uint64_t gpiod_line_event_get_timestamp(struct gpiod_line_event *event);
 
+/**
+ * @brief Get the hardware offset of the line on which the event was triggered.
+ * @param event GPIO line event.
+ * @return Line offset.
+ */
 unsigned int gpiod_line_event_get_line_offset(struct gpiod_line_event *event);
 
+/**
+ * @brief Get the global sequence number of this event.
+ * @param event GPIO line event.
+ * @return Sequence number of the event relative to all lines in the associated
+ *         line request.
+ */
 unsigned int gpiod_line_event_get_global_seqno(struct gpiod_line_event *event);
 
+/**
+ * @brief Get the event sequence number specific to this line.
+ * @param event GPIO line event.
+ * @return Sequence number of the event relative to this line within the
+ *         lifetime of the associated line request.
+ */
 unsigned int gpiod_line_event_get_line_seqno(struct gpiod_line_event *event);
 
+/**
+ * @brief Create a new line event buffer.
+ * @param capacity Number of events this buffer can store (min = 1, max = 1024).
+ * @return New line event buffer or NULL on error.
+ */
 struct gpiod_line_event_buffer *
 gpiod_line_event_buffer_new(unsigned int capacity);
 
+/**
+ * @brief Increase the reference count of this event buffer.
+ * @param buf Line event buffer.
+ * @return Passed reference to the event buffer.
+ */
 struct gpiod_line_event_buffer *
 gpiod_line_event_buffer_ref(struct gpiod_line_event_buffer *buf);
 
+/**
+ * @brief Decrease the reference count of this event buffer. If the reference
+ *        count reaches 0, free all associated resources.
+ * @param buf Line event buffer.
+ */
 void gpiod_line_event_buffer_unref(struct gpiod_line_event_buffer *buf);
 
+/**
+ * @brief Get a weak reference to an event in the buffer.
+ * @param buf Line event buffer.
+ * @param index Index of the event in the buffer.
+ * @return Pointer to an event stored in the buffer. The lifetime of this
+ *         event is tied to the buffer object. Users must not call event
+ *         reference counting functions on pointers retrieved using this
+ *         function.
+ */
 struct gpiod_line_event *
 gpiod_line_event_buffer_get_event(struct gpiod_line_event_buffer *buf,
 				  unsigned long index);
 
+/**
+ * @brief Get a copy of a line event.
+ * @param buf Line event buffer.
+ * @param index Index of the event in the buffer.
+ * @return Returns a copy of the line event stored in this buffer. The event's
+ *         lifetime is managed by the caller. The event's reference count must
+ *         be decreases using ::gpiod_line_event_unref.
+ */
 struct gpiod_line_event *
 gpiod_line_event_buffer_copy_event(struct gpiod_line_event_buffer *buf,
 				   unsigned long index);
 
 /**
- * @brief Wait for an event on a single line.
- * @param line GPIO line object.
- * @param timeout Wait time limit.
- * @return 0 if wait timed out, -1 if an error occurred, 1 if an event
- *         occurred.
- */
-int gpiod_line_event_wait(struct gpiod_line *line, uint64_t timeout);
-
-/**
- * @brief Wait for events on a set of lines.
- * @param bulk Set of GPIO lines to monitor.
- * @param timeout Wait time limit.
- * @param event_bulk Bulk object in which to store the line handles on which
- *                   events occurred. Can be NULL.
- * @return 0 if wait timed out, -1 if an error occurred, 1 if at least one
- *         event occurred.
- */
-int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk, uint64_t timeout,
-			       struct gpiod_line_bulk *event_bulk);
-
-/**
- * @brief Read next pending event from the GPIO line.
- * @param line GPIO line object.
- * @param event Buffer to which the event data will be copied.
- * @return 0 if the event was read correctly, -1 on error.
- * @note This function will block if no event was queued for this line.
- */
-int gpiod_line_event_read(struct gpiod_line *line,
-			  struct gpiod_line_event_buffer *buf);
-
-/**
- * @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_buffer *buf,
-				   unsigned int num_events);
-
-/**
- * @brief Get the event file descriptor.
- * @param line GPIO line object.
- * @return Number of the event file descriptor or -1 if the user tries to
- *         retrieve the descriptor from a line that wasn't configured for
- *         event monitoring.
- *
- * Users may want to poll the event file descriptor on their own. This routine
- * allows to access it.
+ * @brief Get the number of events this buffers stores.
+ * @param buf Line event buffer.
+ * @return Number of events stored in this buffer.
  */
-int gpiod_line_event_get_fd(struct gpiod_line *line);
+unsigned int
+gpiod_line_event_buffer_num_events(struct gpiod_line_event_buffer *buf);
 
 /**
- * @brief Read the last GPIO event directly from a file descriptor.
+ * @brief Read GPIO line events directly from a file descriptor.
  * @param fd File descriptor.
- * @param event Buffer in which the event data will be stored.
- * @return 0 if the event was read correctly, -1 on error.
+ * @param buf Line event buffer.
+ * @param max_events Maximum number of events to read.
+ * @return On success returns the number of events read from the file
+ *         descriptor, on failure return -1.
  *
  * Users who directly poll the file descriptor for incoming events can also
  * directly read the event data from it using this routine. This function
  * translates the kernel representation of the event to the libgpiod format.
  */
-int gpiod_line_event_read_fd(int fd, struct gpiod_line_event_buffer *buf);
-
-/**
- * @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 max_events Specifies the maximum number of events to read.
- * @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_buffer *buf,
-				      unsigned int max_events);
+int gpiod_line_event_buffer_read_fd(int fd, struct gpiod_line_event_buffer *buf,
+				    unsigned int max_events);
 
 /**
- * @}
- *
  * @}
  *
  * @defgroup misc Stuff that didn't fit anywhere else
@@ -1052,6 +908,19 @@ int gpiod_line_event_read_fd_multiple(int fd,
  * Various libgpiod-related functions.
  */
 
+/**
+ * @brief Check if the file pointed to by path is a GPIO chip character device.
+ * @param path Path to check.
+ * @return True if the file exists and is a GPIO chip character device or a
+ *         symbolic link to it.
+ */
+bool gpiod_is_gpiochip_device(const char *path);
+
+/**
+ * @brief Convert seconds to nanoseconds.
+ * @param sec Number of seconds to convert.
+ * @return The same duration in nanoseconds.
+ */
 uint64_t gpiod_sec_to_nsec(uint64_t sec);
 
 /**
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 0f19eec..8713d52 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -2,14 +2,19 @@
 # SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
 
 lib_LTLIBRARIES = libgpiod.la
-libgpiod_la_SOURCES =	core.c \
+libgpiod_la_SOURCES =	attr.c \
+			chip.c \
+			config.c \
 			event.c \
-			helpers.c \
+			handle.c \
 			internal.h \
 			internal.c \
 			info.c \
+			mask.c \
 			misc.c \
+			request.c \
 			uapi/gpio.h
+
 libgpiod_la_CFLAGS = -Wall -Wextra -g -std=gnu89
 libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
 libgpiod_la_CFLAGS += -include $(top_builddir)/config.h
diff --git a/lib/attr.c b/lib/attr.c
new file mode 100644
index 0000000..893f4d9
--- /dev/null
+++ b/lib/attr.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line attribute data structure and functions. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+#include "uapi/gpio.h"
+
+struct line_attr_flags {
+	int request_type;
+	int drive;
+	int bias;
+	bool active_low;
+	bool clock_realtime;
+};
+
+struct line_attr_debounce {
+	bool debounced;
+	unsigned long debounce_period;
+};
+
+struct gpiod_line_attr {
+	struct gpiod_refcount refcount;
+	int type;
+	gpiod_line_mask mask;
+	union {
+		struct line_attr_flags flags;
+		uint64_t values;
+		struct line_attr_debounce debounce;
+	};
+};
+
+static void line_attr_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_line_attr *attr;
+
+	attr = gpiod_container_of(refcount, struct gpiod_line_attr, refcount);
+
+	free(attr);
+}
+
+GPIOD_API struct gpiod_line_attr *gpiod_line_attr_new(int type)
+{
+	struct gpiod_line_attr *attr;
+
+	switch (type) {
+	case GPIOD_LINE_ATTR_TYPE_OPTS:
+	case GPIOD_LINE_ATTR_TYPE_OUTPUT_VALUES:
+	case GPIOD_LINE_ATTR_TYPE_DEBOUNCE:
+		break;
+	default:
+		errno = EINVAL;
+		return NULL;
+	}
+
+	attr = malloc(sizeof(*attr));
+	if (!attr)
+		return NULL;
+
+	memset(attr, 0, sizeof(*attr));
+	gpiod_refcount_init(&attr->refcount, line_attr_release);
+	attr->type = type;
+
+	return attr;
+}
+
+GPIOD_API struct gpiod_line_attr *
+gpiod_line_attr_ref(struct gpiod_line_attr *attr)
+{
+	gpiod_refcount_ref(&attr->refcount);
+	return attr;
+}
+
+GPIOD_API void gpiod_line_attr_unref(struct gpiod_line_attr *attr)
+{
+	gpiod_refcount_unref(&attr->refcount);
+}
+
+GPIOD_API void gpiod_line_attr_set_line_mask(struct gpiod_line_attr *attr,
+					     gpiod_line_mask mask)
+{
+	attr->mask = mask;
+}
+
+static int line_attr_expect_type(struct gpiod_line_attr *attr, int type)
+{
+	if (attr->type != type) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	return 0;
+}
+
+GPIOD_API int gpiod_line_attr_set_request_type(struct gpiod_line_attr *attr,
+					       int request_type)
+{
+	int ret;
+
+	ret = line_attr_expect_type(attr, GPIOD_LINE_ATTR_TYPE_OPTS);
+	if (ret)
+		return -1;
+
+	ret = gpiod_validate_request_type(request_type);
+	if (ret)
+		return -1;
+
+	attr->flags.request_type = request_type;
+	return 0;
+}
+
+GPIOD_API int gpiod_line_attr_set_drive(struct gpiod_line_attr *attr, int drive)
+{
+	int ret;
+
+	ret = line_attr_expect_type(attr, GPIOD_LINE_ATTR_TYPE_OPTS);
+	if (ret)
+		return -1;
+
+	ret = gpiod_validate_drive(drive);
+	if (ret)
+		return -1;
+
+	attr->flags.drive = drive;
+	return 0;
+}
+
+GPIOD_API int gpiod_line_attr_set_bias(struct gpiod_line_attr *attr, int bias)
+{
+	int ret;
+
+	ret = line_attr_expect_type(attr, GPIOD_LINE_ATTR_TYPE_OPTS);
+	if (ret)
+		return -1;
+
+	ret = gpiod_validate_bias(bias);
+	if (ret)
+		return -1;
+
+	attr->flags.bias = bias;
+	return 0;
+}
+
+GPIOD_API int gpiod_line_attr_set_active_low(struct gpiod_line_attr *attr,
+					     bool active_low)
+{
+	int ret;
+
+	ret = line_attr_expect_type(attr, GPIOD_LINE_ATTR_TYPE_OPTS);
+	if (ret)
+		return -1;
+
+	attr->flags.active_low = active_low;
+	return 0;
+}
+
+GPIOD_API int gpiod_line_attr_set_clock_realtime(struct gpiod_line_attr *attr,
+						 bool clock_realtime)
+{
+	int ret;
+
+	ret = line_attr_expect_type(attr, GPIOD_LINE_ATTR_TYPE_OPTS);
+	if (ret)
+		return -1;
+
+	attr->flags.clock_realtime = clock_realtime;
+	return 0;
+}
+
+GPIOD_API int gpiod_line_attr_set_debounce(struct gpiod_line_attr *attr,
+					   bool debounce,
+					   unsigned long debounce_period)
+{
+	int ret;
+
+	ret = line_attr_expect_type(attr, GPIOD_LINE_ATTR_TYPE_DEBOUNCE);
+	if (ret)
+		return -1;
+
+	attr->debounce.debounced = debounce;
+	if (debounce)
+		attr->debounce.debounce_period = debounce_period;
+
+	return 0;
+}
+
+GPIOD_API int gpiod_line_attr_set_output_values(struct gpiod_line_attr *attr,
+						gpiod_line_mask values)
+{
+	int ret;
+
+	ret = line_attr_expect_type(attr, GPIOD_LINE_ATTR_TYPE_OUTPUT_VALUES);
+	if (ret)
+		return -1;
+
+	attr->values = values;
+	return 0;
+}
+
+void gpiod_line_attr_to_kernel(struct gpiod_line_attr *attr,
+			       struct gpio_v2_line_config_attribute *attrbuf)
+{
+	struct gpio_v2_line_attribute *la = &attrbuf->attr;
+	struct line_attr_flags *flags;
+
+	attrbuf->mask = attr->mask;
+
+	switch (attr->type) {
+	case GPIOD_LINE_ATTR_TYPE_OPTS:
+		la->id = GPIO_V2_LINE_ATTR_ID_FLAGS;
+		flags = &attr->flags;
+		la->flags = gpiod_make_kernel_flags(flags->request_type,
+						    flags->drive,
+						    flags->bias,
+						    flags->active_low,
+						    flags->clock_realtime);
+		break;
+	case GPIOD_LINE_ATTR_TYPE_OUTPUT_VALUES:
+		la->id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
+		la->values = attr->values;
+		break;
+	case GPIOD_LINE_ATTR_TYPE_DEBOUNCE:
+		la->id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE;
+		la->debounce_period_us = attr->debounce.debounce_period;
+		break;
+	}
+}
diff --git a/lib/chip.c b/lib/chip.c
new file mode 100644
index 0000000..9bacfe7
--- /dev/null
+++ b/lib/chip.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line attribute data structure and functions. */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "internal.h"
+#include "uapi/gpio.h"
+
+struct gpiod_chip {
+	struct gpiod_refcount refcount;
+	int fd;
+	unsigned int num_lines;
+	char name[32];
+	char label[32];
+};
+
+static void chip_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_chip *chip;
+
+	chip = gpiod_container_of(refcount, struct gpiod_chip, refcount);
+
+	close(chip->fd);
+	free(chip);
+}
+
+GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
+{
+	struct gpiochip_info info;
+	struct gpiod_chip *chip;
+	int rv, fd;
+
+	fd = open(path, O_RDWR | O_CLOEXEC);
+	if (fd < 0)
+		return NULL;
+
+	/*
+	 * We were able to open the file but is it really a gpiochip character
+	 * device?
+	 */
+	if (!gpiod_is_gpiochip_device(path))
+		goto err_close_fd;
+
+	chip = malloc(sizeof(*chip));
+	if (!chip)
+		goto err_close_fd;
+
+	memset(chip, 0, sizeof(*chip));
+	memset(&info, 0, sizeof(info));
+
+	rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
+	if (rv < 0)
+		goto err_free_chip;
+
+	chip->fd = fd;
+	chip->num_lines = info.lines;
+	gpiod_refcount_init(&chip->refcount, chip_release);
+
+	/*
+	 * GPIO device must have a name - don't bother checking this field. In
+	 * the worst case (would have to be a weird kernel bug) it'll be empty.
+	 */
+	strncpy(chip->name, info.name, sizeof(chip->name));
+
+	/*
+	 * The kernel sets the label of a GPIO device to "unknown" if it
+	 * hasn't been defined in DT, board file etc. On the off-chance that
+	 * we got an empty string, do the same.
+	 */
+	if (info.label[0] == '\0')
+		strncpy(chip->label, "unknown", sizeof(chip->label));
+	else
+		strncpy(chip->label, info.label, sizeof(chip->label));
+
+	return chip;
+
+err_free_chip:
+	free(chip);
+err_close_fd:
+	close(fd);
+
+	return NULL;
+}
+
+GPIOD_API struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip)
+{
+	gpiod_refcount_ref(&chip->refcount);
+	return chip;
+}
+
+GPIOD_API void gpiod_chip_unref(struct gpiod_chip *chip)
+{
+	gpiod_refcount_unref(&chip->refcount);
+}
+
+GPIOD_API const char *gpiod_chip_get_name(struct gpiod_chip *chip)
+{
+	return chip->name;
+}
+
+GPIOD_API const char *gpiod_chip_get_label(struct gpiod_chip *chip)
+{
+	return chip->label;
+}
+
+GPIOD_API unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip)
+{
+	return chip->num_lines;
+}
+
+static int chip_read_line_info(int fd, unsigned int offset,
+			       struct gpio_v2_line_info *infobuf)
+{
+	int ret;
+
+	memset(infobuf, 0, sizeof(*infobuf));
+	infobuf->offset = offset;
+
+	ret = ioctl(fd, GPIO_V2_GET_LINEINFO_IOCTL, infobuf);
+	if (ret)
+		return -1;
+
+	return 0;
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_get_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+	struct gpio_v2_line_info infobuf;
+	int ret;
+
+	ret = chip_read_line_info(chip->fd, offset, &infobuf);
+	if (ret)
+		return NULL;
+
+	return gpiod_line_info_from_kernel(&infobuf);
+}
+
+GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
+{
+	struct gpio_v2_line_info infobuf;
+	unsigned int offset;
+	int ret;
+
+	for (offset = 0; offset < chip->num_lines; offset++) {
+		ret = chip_read_line_info(chip->fd, offset, &infobuf);
+		if (ret)
+			return -1;
+
+		if (strcmp(name, infobuf.name) == 0)
+			return offset;
+	}
+
+	errno = ENOENT;
+	return -1;
+}
+
+GPIOD_API struct gpiod_request_handle *
+gpiod_chip_request_lines(struct gpiod_chip *chip,
+			 struct gpiod_request_config *req_cfg,
+			 struct gpiod_line_config *line_cfg)
+{
+	struct gpio_v2_line_request reqbuf;
+	int ret;
+
+	memset(&reqbuf, 0, sizeof(reqbuf));
+	gpiod_request_config_to_kernel(req_cfg, &reqbuf);
+	gpiod_line_config_to_kernel(line_cfg, &reqbuf.config);
+
+	ret = ioctl(chip->fd, GPIO_V2_GET_LINE_IOCTL, &reqbuf);
+	if (ret < 0)
+		return NULL;
+
+	return gpiod_request_handle_from_fd(reqbuf.fd);
+}
diff --git a/lib/config.c b/lib/config.c
new file mode 100644
index 0000000..924302e
--- /dev/null
+++ b/lib/config.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line configuration data structure and functions. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+#include "uapi/gpio.h"
+
+#define LINE_CONFIG_ATTR_MAX 10
+
+struct gpiod_line_config {
+	struct gpiod_refcount refcount;
+	int request_type;
+	int drive;
+	int bias;
+	bool active_low;
+	bool clock_realtime;
+	struct gpiod_line_attr *attrs[LINE_CONFIG_ATTR_MAX];
+	unsigned int num_attrs;
+};
+
+static void line_config_unref_attrs(struct gpiod_line_config *config)
+{
+	unsigned int i;
+
+	for (i = 0; i < config->num_attrs; i++)
+		gpiod_line_attr_unref(config->attrs[i]);
+}
+
+static void line_config_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_line_config *config;
+
+	config = gpiod_container_of(refcount,
+				    struct gpiod_line_config, refcount);
+
+	line_config_unref_attrs(config);
+	free(config);
+}
+
+GPIOD_API struct gpiod_line_config *gpiod_line_config_new(void)
+{
+	struct gpiod_line_config *config;
+
+	config = malloc(sizeof(*config));
+	if (!config)
+		return NULL;
+
+	memset(config, 0, sizeof(*config));
+	gpiod_refcount_init(&config->refcount, line_config_release);
+
+	return config;
+}
+
+GPIOD_API struct gpiod_line_config *
+gpiod_line_config_ref(struct gpiod_line_config *config)
+{
+	gpiod_refcount_ref(&config->refcount);
+	return config;
+}
+
+GPIOD_API void gpiod_line_config_unref(struct gpiod_line_config *config)
+{
+	gpiod_refcount_unref(&config->refcount);
+}
+
+GPIOD_API int gpiod_line_config_add_attribute(struct gpiod_line_config *config,
+					      struct gpiod_line_attr *attr)
+{
+	if (config->num_attrs == LINE_CONFIG_ATTR_MAX) {
+		errno = EBUSY;
+		return -1;
+	}
+
+	config->attrs[config->num_attrs++] = gpiod_line_attr_ref(attr);
+	return 0;
+}
+
+GPIOD_API void gpiod_line_config_clear_attrs(struct gpiod_line_config *config)
+{
+	line_config_unref_attrs(config);
+	memset(config->attrs, 0, sizeof(config->attrs));
+	config->num_attrs = 0;
+}
+
+GPIOD_API int
+gpiod_line_config_set_request_type(struct gpiod_line_config *config,
+				   int request_type)
+{
+	int ret;
+
+	ret = gpiod_validate_request_type(request_type);
+	if (ret)
+		return -1;
+
+	config->request_type = request_type;
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_config_set_drive(struct gpiod_line_config *config, int drive)
+{
+	int ret;
+
+	ret = gpiod_validate_drive(drive);
+	if (ret)
+		return -1;
+
+	config->drive = drive;
+	return 0;
+}
+
+GPIOD_API int
+gpiod_line_config_set_bias(struct gpiod_line_config *config, int bias)
+{
+	int ret;
+
+	ret = gpiod_validate_bias(bias);
+	if (ret)
+		return -1;
+
+	config->bias = bias;
+	return 0;
+}
+
+GPIOD_API void
+gpiod_line_config_set_active_low(struct gpiod_line_config *config,
+				 bool active_low)
+{
+	config->active_low = active_low;
+}
+
+GPIOD_API void
+gpiod_line_config_set_event_clock_realtime(struct gpiod_line_config *config,
+					   bool clock_realtime)
+{
+	config->clock_realtime = clock_realtime;
+}
+
+void gpiod_line_config_to_kernel(struct gpiod_line_config *config,
+				 struct gpio_v2_line_config *cfgbuf)
+{
+	unsigned int i;
+
+	cfgbuf->flags = gpiod_make_kernel_flags(config->request_type,
+						config->drive, config->bias,
+						config->active_low,
+						config->clock_realtime);
+
+	cfgbuf->num_attrs = config->num_attrs;
+	for (i = 0; i < config->num_attrs; i++)
+		gpiod_line_attr_to_kernel(config->attrs[i], &cfgbuf->attrs[i]);
+}
diff --git a/lib/core.c b/lib/core.c
deleted file mode 100644
index ed65653..0000000
--- a/lib/core.c
+++ /dev/null
@@ -1,1131 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-/* Low-level, core library code. */
-
-#include <errno.h>
-#include <fcntl.h>
-#include <gpiod.h>
-#include <limits.h>
-#include <poll.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/stat.h>
-#include <sys/sysmacros.h>
-#include <sys/types.h>
-#include <time.h>
-#include <unistd.h>
-
-#include "internal.h"
-#include "uapi/gpio.h"
-
-#define LINE_REQUEST_MAX_LINES	64
-
-enum {
-	LINE_FREE = 0,
-	LINE_REQUESTED_VALUES,
-	LINE_REQUESTED_EVENTS,
-};
-
-struct line_fd_handle {
-	int fd;
-	int refcount;
-};
-
-struct gpiod_line {
-	unsigned int offset;
-
-	/* The direction of the GPIO line. */
-	int direction;
-
-	/* Is this line active-low?. */
-	bool active_low;
-
-	/* The logical value last written to the line. */
-	int output_value;
-
-	/* The GPIOLINE_FLAGs returned by GPIO_GET_LINEINFO_IOCTL. */
-	__u32 info_flags;
-
-	/* The GPIOD_LINE_REQUEST_FLAGs provided to request the line. */
-	__u32 req_flags;
-
-	/*
-	 * Indicator of LINE_FREE, LINE_REQUESTED_VALUES or
-	 * LINE_REQUESTED_EVENTS.
-	 */
-	int state;
-
-	struct gpiod_chip *chip;
-	struct line_fd_handle *fd_handle;
-
-	char name[32];
-	char consumer[32];
-};
-
-struct gpiod_chip {
-	struct gpiod_refcount refcount;
-
-	struct gpiod_line **lines;
-	unsigned int num_lines;
-
-	int fd;
-
-	char name[32];
-	char label[32];
-};
-
-/*
- * The structure is defined in a way that allows internal users to allocate
- * bulk objects that hold a single line on the stack - that way we can reuse
- * a lot of code between functions that take single lines and those that take
- * bulks as arguments while not unnecessarily allocating memory dynamically.
- */
-struct gpiod_line_bulk {
-	unsigned int num_lines;
-	unsigned int max_lines;
-	struct gpiod_line *lines[1];
-};
-
-#define BULK_SINGLE_LINE_INIT(line) \
-		{ 1, 1, { (line) } }
-
-GPIOD_API struct gpiod_line_bulk *gpiod_line_bulk_new(unsigned int max_lines)
-{
-	struct gpiod_line_bulk *bulk;
-	size_t size;
-
-	if (max_lines == 0) {
-		errno = EINVAL;
-		return NULL;
-	}
-
-	size = sizeof(struct gpiod_line_bulk) +
-	       (max_lines - 1) * sizeof(struct gpiod_line *);
-
-	bulk = malloc(size);
-	if (!bulk)
-		return NULL;
-
-	bulk->max_lines = max_lines;
-	gpiod_line_bulk_reset(bulk);
-
-	return bulk;
-}
-
-GPIOD_API void gpiod_line_bulk_reset(struct gpiod_line_bulk *bulk)
-{
-	bulk->num_lines = 0;
-	memset(bulk->lines, 0, bulk->max_lines * sizeof(struct line *));
-}
-
-GPIOD_API void gpiod_line_bulk_free(struct gpiod_line_bulk *bulk)
-{
-	free(bulk);
-}
-
-GPIOD_API int gpiod_line_bulk_add_line(struct gpiod_line_bulk *bulk,
-				       struct gpiod_line *line)
-{
-	if (bulk->num_lines == bulk->max_lines) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	if (bulk->num_lines != 0) {
-		if (bulk->lines[0]->chip != gpiod_line_get_chip(line)) {
-			errno = EINVAL;
-			return -1;
-		}
-	}
-
-	bulk->lines[bulk->num_lines++] = line;
-
-	return 0;
-}
-
-GPIOD_API struct gpiod_line *
-gpiod_line_bulk_get_line(struct gpiod_line_bulk *bulk, unsigned int index)
-{
-	if (index >= bulk->num_lines) {
-		errno = EINVAL;
-		return NULL;
-	}
-
-	return bulk->lines[index];
-}
-
-GPIOD_API unsigned int gpiod_line_bulk_num_lines(struct gpiod_line_bulk *bulk)
-{
-	return bulk->num_lines;
-}
-
-GPIOD_API void gpiod_line_bulk_foreach_line(struct gpiod_line_bulk *bulk,
-					    gpiod_line_bulk_foreach_cb func,
-					    void *data)
-{
-	unsigned int index;
-	int ret;
-
-	for (index = 0; index < bulk->num_lines; index++) {
-		ret = func(bulk->lines[index], data);
-		if (ret == GPIOD_LINE_BULK_CB_STOP)
-			return;
-	}
-}
-
-#define line_bulk_foreach_line(bulk, line, index)			\
-	for ((index) = 0, (line) = (bulk)->lines[0];			\
-	     (index) < (bulk)->num_lines;				\
-	     (index)++, (line) = (bulk)->lines[(index)])
-
-GPIOD_API struct gpiod_line_info *
-gpiod_chip_get_line_info(struct gpiod_chip *chip, unsigned int offset)
-{
-	struct gpio_v2_line_info infobuf;
-	int ret;
-
-	memset(&infobuf, 0, sizeof(infobuf));
-	infobuf.offset = offset;
-
-	ret = ioctl(chip->fd, GPIO_V2_GET_LINEINFO_IOCTL, &infobuf);
-	if (ret < 0)
-		return NULL;
-
-	return gpiod_line_info_from_kernel(&infobuf);
-}
-
-GPIOD_API bool gpiod_is_gpiochip_device(const char *path)
-{
-	char *realname, *sysfsp, devpath[64];
-	struct stat statbuf;
-	bool ret = false;
-	int rv;
-
-	rv = lstat(path, &statbuf);
-	if (rv)
-		goto out;
-
-	/*
-	 * Is it a symbolic link? We have to resolve it before checking
-	 * the rest.
-	 */
-	realname = S_ISLNK(statbuf.st_mode) ? realpath(path, NULL)
-					    : strdup(path);
-	if (realname == NULL)
-		goto out;
-
-	rv = stat(realname, &statbuf);
-	if (rv)
-		goto out_free_realname;
-
-	/* Is it a character device? */
-	if (!S_ISCHR(statbuf.st_mode)) {
-		/*
-		 * Passing a file descriptor not associated with a character
-		 * device to ioctl() makes it set errno to ENOTTY. Let's do
-		 * the same in order to stay compatible with the versions of
-		 * libgpiod from before the introduction of this routine.
-		 */
-		errno = ENOTTY;
-		goto out_free_realname;
-	}
-
-	/* Is the device associated with the GPIO subsystem? */
-	snprintf(devpath, sizeof(devpath), "/sys/dev/char/%u:%u/subsystem",
-		 major(statbuf.st_rdev), minor(statbuf.st_rdev));
-
-	sysfsp = realpath(devpath, NULL);
-	if (!sysfsp)
-		goto out_free_realname;
-
-	if (strcmp(sysfsp, "/sys/bus/gpio") != 0) {
-		/*
-		 * This is a character device but not the one we're after.
-		 * Before the introduction of this function, we'd fail with
-		 * ENOTTY on the first GPIO ioctl() call for this file
-		 * descriptor. Let's stay compatible here and keep returning
-		 * the same error code.
-		 */
-		errno = ENOTTY;
-		goto out_free_sysfsp;
-	}
-
-	ret = true;
-
-out_free_sysfsp:
-	free(sysfsp);
-out_free_realname:
-	free(realname);
-out:
-	return ret;
-}
-
-static void chip_release(struct gpiod_refcount *refcount)
-{
-	struct gpiod_chip *chip;
-	struct gpiod_line *line;
-	unsigned int i;
-
-	chip = gpiod_container_of(refcount, struct gpiod_chip, refcount);
-
-	if (chip->lines) {
-		for (i = 0; i < chip->num_lines; i++) {
-			line = chip->lines[i];
-			if (line) {
-				gpiod_line_release(line);
-				free(line);
-			}
-		}
-
-		free(chip->lines);
-	}
-
-	close(chip->fd);
-	free(chip);
-}
-
-GPIOD_API struct gpiod_chip *gpiod_chip_open(const char *path)
-{
-	struct gpiochip_info info;
-	struct gpiod_chip *chip;
-	int rv, fd;
-
-	fd = open(path, O_RDWR | O_CLOEXEC);
-	if (fd < 0)
-		return NULL;
-
-	/*
-	 * We were able to open the file but is it really a gpiochip character
-	 * device?
-	 */
-	if (!gpiod_is_gpiochip_device(path))
-		goto err_close_fd;
-
-	chip = malloc(sizeof(*chip));
-	if (!chip)
-		goto err_close_fd;
-
-	memset(chip, 0, sizeof(*chip));
-	memset(&info, 0, sizeof(info));
-
-	rv = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &info);
-	if (rv < 0)
-		goto err_free_chip;
-
-	chip->fd = fd;
-	chip->num_lines = info.lines;
-	gpiod_refcount_init(&chip->refcount, chip_release);
-
-	/*
-	 * GPIO device must have a name - don't bother checking this field. In
-	 * the worst case (would have to be a weird kernel bug) it'll be empty.
-	 */
-	strncpy(chip->name, info.name, sizeof(chip->name));
-
-	/*
-	 * The kernel sets the label of a GPIO device to "unknown" if it
-	 * hasn't been defined in DT, board file etc. On the off-chance that
-	 * we got an empty string, do the same.
-	 */
-	if (info.label[0] == '\0')
-		strncpy(chip->label, "unknown", sizeof(chip->label));
-	else
-		strncpy(chip->label, info.label, sizeof(chip->label));
-
-	return chip;
-
-err_free_chip:
-	free(chip);
-err_close_fd:
-	close(fd);
-
-	return NULL;
-}
-
-GPIOD_API struct gpiod_chip *gpiod_chip_ref(struct gpiod_chip *chip)
-{
-	gpiod_refcount_ref(&chip->refcount);
-	return chip;
-}
-
-GPIOD_API void gpiod_chip_unref(struct gpiod_chip *chip)
-{
-	gpiod_refcount_unref(&chip->refcount);
-}
-
-GPIOD_API const char *gpiod_chip_get_name(struct gpiod_chip *chip)
-{
-	return chip->name;
-}
-
-GPIOD_API const char *gpiod_chip_get_label(struct gpiod_chip *chip)
-{
-	return chip->label;
-}
-
-GPIOD_API unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip)
-{
-	return chip->num_lines;
-}
-
-static int line_update(struct gpiod_line *line);
-
-GPIOD_API struct gpiod_line *
-gpiod_chip_get_line(struct gpiod_chip *chip, unsigned int offset)
-{
-	struct gpiod_line *line;
-	int rv;
-
-	if (offset >= chip->num_lines) {
-		errno = EINVAL;
-		return NULL;
-	}
-
-	if (!chip->lines) {
-		chip->lines = calloc(chip->num_lines,
-				     sizeof(struct gpiod_line *));
-		if (!chip->lines)
-			return NULL;
-	}
-
-	if (!chip->lines[offset]) {
-		line = malloc(sizeof(*line));
-		if (!line)
-			return NULL;
-
-		memset(line, 0, sizeof(*line));
-
-		line->offset = offset;
-		line->chip = chip;
-
-		chip->lines[offset] = line;
-	} else {
-		line = chip->lines[offset];
-	}
-
-	rv = line_update(line);
-	if (rv < 0)
-		return NULL;
-
-	return line;
-}
-
-static struct line_fd_handle *line_make_fd_handle(int fd)
-{
-	struct line_fd_handle *handle;
-
-	handle = malloc(sizeof(*handle));
-	if (!handle)
-		return NULL;
-
-	handle->fd = fd;
-	handle->refcount = 0;
-
-	return handle;
-}
-
-static void line_fd_incref(struct gpiod_line *line)
-{
-	line->fd_handle->refcount++;
-}
-
-static void line_fd_decref(struct gpiod_line *line)
-{
-	struct line_fd_handle *handle = line->fd_handle;
-
-	handle->refcount--;
-
-	if (handle->refcount == 0) {
-		close(handle->fd);
-		free(handle);
-		line->fd_handle = NULL;
-	}
-}
-
-static void line_set_fd(struct gpiod_line *line, struct line_fd_handle *handle)
-{
-	line->fd_handle = handle;
-	line_fd_incref(line);
-}
-
-static int line_get_fd(struct gpiod_line *line)
-{
-	return line->fd_handle->fd;
-}
-
-GPIOD_API struct gpiod_chip *gpiod_line_get_chip(struct gpiod_line *line)
-{
-	return line->chip;
-}
-
-GPIOD_API unsigned int gpiod_line_offset(struct gpiod_line *line)
-{
-	return line->offset;
-}
-
-static int line_info_v2_to_info_flags(struct gpio_v2_line_info *info)
-{
-	int iflags = 0;
-
-	if (info->flags & GPIO_V2_LINE_FLAG_USED)
-		iflags |= GPIOLINE_FLAG_KERNEL;
-
-	if (info->flags & GPIO_V2_LINE_FLAG_OPEN_DRAIN)
-		iflags |= GPIOLINE_FLAG_OPEN_DRAIN;
-	if (info->flags & GPIO_V2_LINE_FLAG_OPEN_SOURCE)
-		iflags |= GPIOLINE_FLAG_OPEN_SOURCE;
-
-	if (info->flags & GPIO_V2_LINE_FLAG_BIAS_DISABLED)
-		iflags |= GPIOLINE_FLAG_BIAS_DISABLE;
-	if (info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_UP)
-		iflags |= GPIOLINE_FLAG_BIAS_PULL_UP;
-	if (info->flags & GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN)
-		iflags |= GPIOLINE_FLAG_BIAS_PULL_DOWN;
-
-	return iflags;
-}
-
-static int line_update(struct gpiod_line *line)
-{
-	struct gpio_v2_line_info info;
-	int rv;
-
-	memset(&info, 0, sizeof(info));
-	info.offset = line->offset;
-
-	rv = ioctl(line->chip->fd, GPIO_V2_GET_LINEINFO_IOCTL, &info);
-	if (rv < 0)
-		return -1;
-
-	line->direction = info.flags & GPIO_V2_LINE_FLAG_OUTPUT
-						? GPIOD_LINE_DIRECTION_OUTPUT
-						: GPIOD_LINE_DIRECTION_INPUT;
-
-	line->active_low = !!(info.flags & GPIO_V2_LINE_FLAG_ACTIVE_LOW);
-
-	line->info_flags = line_info_v2_to_info_flags(&info);
-
-	strncpy(line->name, info.name, sizeof(line->name));
-	strncpy(line->consumer, info.consumer, sizeof(line->consumer));
-
-	return 0;
-}
-
-static bool line_is_requested(struct gpiod_line *line)
-{
-	return (line->state == LINE_REQUESTED_VALUES ||
-		line->state == LINE_REQUESTED_EVENTS);
-}
-
-static bool line_bulk_all_requested(struct gpiod_line_bulk *bulk)
-{
-	struct gpiod_line *line;
-	unsigned int idx;
-
-	line_bulk_foreach_line(bulk, line, idx) {
-		if (!line_is_requested(line)) {
-			errno = EPERM;
-			return false;
-		}
-	}
-
-	return true;
-}
-
-static bool line_bulk_all_requested_values(struct gpiod_line_bulk *bulk)
-{
-	struct gpiod_line *line;
-	unsigned int idx;
-
-	line_bulk_foreach_line(bulk, line, idx) {
-		if (line->state != LINE_REQUESTED_VALUES) {
-			errno = EPERM;
-			return false;
-		}
-	}
-
-	return true;
-}
-
-static bool line_request_direction_is_valid(int direction)
-{
-	if ((direction == GPIOD_LINE_REQUEST_DIRECTION_AS_IS) ||
-	    (direction == GPIOD_LINE_REQUEST_DIRECTION_INPUT) ||
-	    (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT))
-		return true;
-
-	errno = EINVAL;
-	return false;
-}
-
-static void line_request_type_to_gpio_v2_line_config(int reqtype,
-		struct gpio_v2_line_config *config)
-{
-	if (reqtype == GPIOD_LINE_REQUEST_DIRECTION_AS_IS)
-		return;
-
-	if (reqtype == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) {
-		config->flags |= GPIO_V2_LINE_FLAG_OUTPUT;
-		return;
-	}
-	config->flags |= GPIO_V2_LINE_FLAG_INPUT;
-
-	if (reqtype == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE)
-		config->flags |= GPIO_V2_LINE_FLAG_EDGE_RISING;
-	else if (reqtype == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE)
-		config->flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING;
-	else if (reqtype == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES)
-		config->flags |= (GPIO_V2_LINE_FLAG_EDGE_RISING |
-				  GPIO_V2_LINE_FLAG_EDGE_FALLING);
-}
-
-static void line_request_flag_to_gpio_v2_line_config(int flags,
-		struct gpio_v2_line_config *config)
-{
-	if (flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
-		config->flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
-
-	if (flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN)
-		config->flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
-	else if (flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)
-		config->flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
-
-	if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED)
-		config->flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
-	else if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
-		config->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
-	else if (flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
-		config->flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
-}
-
-static void line_request_config_to_gpio_v2_line_config(
-		const struct gpiod_line_request_config *reqcfg,
-		struct gpio_v2_line_config *lc)
-{
-	line_request_type_to_gpio_v2_line_config(reqcfg->request_type, lc);
-	line_request_flag_to_gpio_v2_line_config(reqcfg->flags, lc);
-}
-
-static bool line_request_config_validate(
-		const struct gpiod_line_request_config *config)
-{
-	int bias_flags = 0;
-
-	if ((config->request_type != GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) &&
-	    (config->flags & (GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN |
-			      GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)))
-		return false;
-
-
-	if ((config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN) &&
-	    (config->flags & GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE)) {
-		return false;
-	}
-
-	if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED)
-		bias_flags++;
-	if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
-		bias_flags++;
-	if (config->flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
-		bias_flags++;
-	if (bias_flags > 1)
-		return false;
-
-	return true;
-}
-
-static void lines_bitmap_set_bit(__u64 *bits, int nr)
-{
-	*bits |= _BITULL(nr);
-}
-
-static void lines_bitmap_clear_bit(__u64 *bits, int nr)
-{
-	*bits &= ~_BITULL(nr);
-}
-
-static int lines_bitmap_test_bit(__u64 bits, int nr)
-{
-	return !!(bits & _BITULL(nr));
-}
-
-static void lines_bitmap_assign_bit(__u64 *bits, int nr, bool value)
-{
-	if (value)
-		lines_bitmap_set_bit(bits, nr);
-	else
-		lines_bitmap_clear_bit(bits, nr);
-}
-
-static int line_request_values(struct gpiod_line_bulk *bulk,
-			       const struct gpiod_line_request_config *config,
-			       const int *vals)
-{
-	struct gpiod_line *line;
-	struct line_fd_handle *line_fd;
-	struct gpio_v2_line_request req;
-	unsigned int i;
-	int rv, fd;
-
-	if (!line_request_config_validate(config)) {
-		errno = EINVAL;
-		return -1;
-	}
-
-	memset(&req, 0, sizeof(req));
-
-	req.num_lines = gpiod_line_bulk_num_lines(bulk);
-	line_request_config_to_gpio_v2_line_config(config, &req.config);
-
-	line_bulk_foreach_line(bulk, line, i)
-		req.offsets[i] = gpiod_line_offset(line);
-
-	if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT &&
-	    vals) {
-		req.config.num_attrs = 1;
-		req.config.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
-		line_bulk_foreach_line(bulk, line, i) {
-			lines_bitmap_assign_bit(
-				&req.config.attrs[0].mask, i, 1);
-			lines_bitmap_assign_bit(
-				&req.config.attrs[0].attr.values,
-				i, vals[i]);
-		}
-	}
-
-	if (config->consumer)
-		strncpy(req.consumer, config->consumer,
-			sizeof(req.consumer) - 1);
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	fd = line->chip->fd;
-
-	rv = ioctl(fd, GPIO_V2_GET_LINE_IOCTL, &req);
-	if (rv < 0)
-		return -1;
-
-	line_fd = line_make_fd_handle(req.fd);
-	if (!line_fd)
-		return -1;
-
-	line_bulk_foreach_line(bulk, line, i) {
-		line->state = LINE_REQUESTED_VALUES;
-		line->req_flags = config->flags;
-		if (config->request_type == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
-			line->output_value = lines_bitmap_test_bit(
-				req.config.attrs[0].attr.values, i);
-		line_set_fd(line, line_fd);
-
-		rv = line_update(line);
-		if (rv) {
-			gpiod_line_release_bulk(bulk);
-			return rv;
-		}
-	}
-
-	return 0;
-}
-
-static int line_request_event_single(struct gpiod_line *line,
-			const struct gpiod_line_request_config *config)
-{
-	struct line_fd_handle *line_fd;
-	struct gpio_v2_line_request req;
-	int rv;
-
-	memset(&req, 0, sizeof(req));
-
-	if (config->consumer)
-		strncpy(req.consumer, config->consumer,
-			sizeof(req.consumer) - 1);
-
-	req.offsets[0] = gpiod_line_offset(line);
-	req.num_lines = 1;
-	line_request_config_to_gpio_v2_line_config(config, &req.config);
-
-	rv = ioctl(line->chip->fd, GPIO_V2_GET_LINE_IOCTL, &req);
-	if (rv < 0)
-		return -1;
-
-	line_fd = line_make_fd_handle(req.fd);
-	if (!line_fd)
-		return -1;
-
-	line->state = LINE_REQUESTED_EVENTS;
-	line->req_flags = config->flags;
-	line_set_fd(line, line_fd);
-
-	rv = line_update(line);
-	if (rv) {
-		gpiod_line_release(line);
-		return rv;
-	}
-
-	return 0;
-}
-
-static int line_request_events(struct gpiod_line_bulk *bulk,
-			       const struct gpiod_line_request_config *config)
-{
-	struct gpiod_line *line;
-	unsigned int off;
-	int rv, rev;
-
-	line_bulk_foreach_line(bulk, line, off) {
-		rv = line_request_event_single(line, config);
-		if (rv) {
-			for (rev = off - 1; rev >= 0; rev--) {
-				line = gpiod_line_bulk_get_line(bulk, rev);
-				gpiod_line_release(line);
-			}
-
-			return -1;
-		}
-	}
-
-	return 0;
-}
-
-GPIOD_API int gpiod_line_request(struct gpiod_line *line,
-				 const struct gpiod_line_request_config *config,
-				 int default_val)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_request_bulk(&bulk, config, &default_val);
-}
-
-static bool line_request_is_direction(int request)
-{
-	return request == GPIOD_LINE_REQUEST_DIRECTION_AS_IS ||
-	       request == GPIOD_LINE_REQUEST_DIRECTION_INPUT ||
-	       request == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
-}
-
-static bool line_request_is_events(int request)
-{
-	return request == GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE ||
-	       request == GPIOD_LINE_REQUEST_EVENT_RISING_EDGE ||
-	       request == GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
-}
-
-GPIOD_API int
-gpiod_line_request_bulk(struct gpiod_line_bulk *bulk,
-			const struct gpiod_line_request_config *config,
-			const int *vals)
-{
-	if (line_request_is_direction(config->request_type))
-		return line_request_values(bulk, config, vals);
-	else if (line_request_is_events(config->request_type))
-		return line_request_events(bulk, config);
-
-	errno = EINVAL;
-	return -1;
-}
-
-GPIOD_API void gpiod_line_release(struct gpiod_line *line)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	gpiod_line_release_bulk(&bulk);
-}
-
-GPIOD_API void gpiod_line_release_bulk(struct gpiod_line_bulk *bulk)
-{
-	struct gpiod_line *line;
-	unsigned int idx;
-
-	line_bulk_foreach_line(bulk, line, idx) {
-		if (line->state != LINE_FREE) {
-			line_fd_decref(line);
-			line->state = LINE_FREE;
-		}
-	}
-}
-
-GPIOD_API int gpiod_line_get_value(struct gpiod_line *line)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-	int rv, value;
-
-	rv = gpiod_line_get_value_bulk(&bulk, &value);
-	if (rv < 0)
-		return -1;
-
-	return value;
-}
-
-GPIOD_API int gpiod_line_get_value_bulk(struct gpiod_line_bulk *bulk,
-					int *values)
-{
-	struct gpio_v2_line_values lv;
-	struct gpiod_line *line;
-	unsigned int i;
-	int rv, fd;
-
-	if (!line_bulk_all_requested(bulk))
-		return -1;
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-
-	memset(&lv, 0, sizeof(lv));
-
-	if (line->state == LINE_REQUESTED_VALUES) {
-		for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
-			lines_bitmap_set_bit(&lv.mask, i);
-
-		fd = line_get_fd(line);
-
-		rv = ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &lv);
-		if (rv < 0)
-			return -1;
-
-		for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++)
-			values[i] = lines_bitmap_test_bit(lv.bits, i);
-
-	} else if (line->state == LINE_REQUESTED_EVENTS) {
-		lines_bitmap_set_bit(&lv.mask, 0);
-		for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
-			line = gpiod_line_bulk_get_line(bulk, i);
-
-			fd = line_get_fd(line);
-			rv = ioctl(fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &lv);
-			if (rv < 0)
-				return -1;
-			values[i] = lines_bitmap_test_bit(lv.bits, 0);
-		}
-	} else {
-		errno = EINVAL;
-		return -1;
-	}
-	return 0;
-}
-
-GPIOD_API int gpiod_line_set_value(struct gpiod_line *line, int value)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_set_value_bulk(&bulk, &value);
-}
-
-GPIOD_API int gpiod_line_set_value_bulk(struct gpiod_line_bulk *bulk,
-					const int *values)
-{
-	struct gpio_v2_line_values lv;
-	struct gpiod_line *line;
-	unsigned int i;
-	int rv, fd;
-
-	if (!line_bulk_all_requested(bulk))
-		return -1;
-
-	memset(&lv, 0, sizeof(lv));
-
-	for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
-		lines_bitmap_set_bit(&lv.mask, i);
-		lines_bitmap_assign_bit(&lv.bits, i, values && values[i]);
-	}
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	fd = line_get_fd(line);
-
-	rv = ioctl(fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &lv);
-	if (rv < 0)
-		return -1;
-
-	line_bulk_foreach_line(bulk, line, i)
-		line->output_value = lines_bitmap_test_bit(lv.bits, i);
-
-	return 0;
-}
-
-GPIOD_API int gpiod_line_set_config(struct gpiod_line *line, int direction,
-				    int flags, int value)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_set_config_bulk(&bulk, direction, flags, &value);
-}
-
-GPIOD_API int gpiod_line_set_config_bulk(struct gpiod_line_bulk *bulk,
-					 int direction, int flags,
-					 const int *values)
-{
-	struct gpio_v2_line_config hcfg;
-	struct gpiod_line *line;
-	unsigned int i;
-	int rv, fd;
-
-	if (!line_bulk_all_requested_values(bulk))
-		return -1;
-
-	if (!line_request_direction_is_valid(direction))
-		return -1;
-
-	memset(&hcfg, 0, sizeof(hcfg));
-
-	line_request_flag_to_gpio_v2_line_config(flags, &hcfg);
-	line_request_type_to_gpio_v2_line_config(direction, &hcfg);
-	if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT && values) {
-		hcfg.num_attrs = 1;
-		hcfg.attrs[0].attr.id = GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES;
-		for (i = 0; i < gpiod_line_bulk_num_lines(bulk); i++) {
-			lines_bitmap_assign_bit(&hcfg.attrs[0].mask, i, 1);
-			lines_bitmap_assign_bit(
-				&hcfg.attrs[0].attr.values, i, values[i]);
-		}
-	}
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	fd = line_get_fd(line);
-
-	rv = ioctl(fd, GPIO_V2_LINE_SET_CONFIG_IOCTL, &hcfg);
-	if (rv < 0)
-		return -1;
-
-	line_bulk_foreach_line(bulk, line, i) {
-		line->req_flags = flags;
-		if (direction == GPIOD_LINE_REQUEST_DIRECTION_OUTPUT)
-			line->output_value = lines_bitmap_test_bit(
-				hcfg.attrs[0].attr.values, i);
-
-		rv = line_update(line);
-		if (rv < 0)
-			return rv;
-	}
-	return 0;
-}
-
-GPIOD_API int gpiod_line_set_flags(struct gpiod_line *line, int flags)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_set_flags_bulk(&bulk, flags);
-}
-
-GPIOD_API int gpiod_line_set_flags_bulk(struct gpiod_line_bulk *bulk, int flags)
-{
-	struct gpiod_line *line;
-	int values[LINE_REQUEST_MAX_LINES];
-	unsigned int i;
-	int direction;
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	if (line->direction == GPIOD_LINE_DIRECTION_OUTPUT) {
-		line_bulk_foreach_line(bulk, line, i)
-			values[i] = line->output_value;
-
-		direction = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
-	} else {
-		direction = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
-	}
-
-	return gpiod_line_set_config_bulk(bulk, direction,
-					  flags, values);
-}
-
-GPIOD_API int gpiod_line_set_direction_input(struct gpiod_line *line)
-{
-	return gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-				     line->req_flags, 0);
-}
-
-GPIOD_API int gpiod_line_set_direction_input_bulk(struct gpiod_line_bulk *bulk)
-{
-	struct gpiod_line *line;
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	return gpiod_line_set_config_bulk(bulk,
-					  GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-					  line->req_flags, NULL);
-}
-
-GPIOD_API int gpiod_line_set_direction_output(struct gpiod_line *line,
-					      int value)
-{
-	return gpiod_line_set_config(line, GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-				     line->req_flags, value);
-}
-
-GPIOD_API int gpiod_line_set_direction_output_bulk(struct gpiod_line_bulk *bulk,
-						   const int *values)
-{
-	struct gpiod_line *line;
-
-	line = gpiod_line_bulk_get_line(bulk, 0);
-	return gpiod_line_set_config_bulk(bulk,
-					  GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-					  line->req_flags, values);
-}
-
-GPIOD_API int gpiod_line_event_wait(struct gpiod_line *line, uint64_t timeout)
-{
-	struct gpiod_line_bulk bulk = BULK_SINGLE_LINE_INIT(line);
-
-	return gpiod_line_event_wait_bulk(&bulk, timeout, NULL);
-}
-
-GPIOD_API int gpiod_line_event_wait_bulk(struct gpiod_line_bulk *bulk,
-					 uint64_t timeout,
-					 struct gpiod_line_bulk *event_bulk)
-{
-	struct pollfd fds[LINE_REQUEST_MAX_LINES];
-	unsigned int off, num_lines;
-	struct gpiod_line *line;
-	struct timespec ts;
-	int rv;
-
-	if (!line_bulk_all_requested(bulk))
-		return -1;
-
-	memset(fds, 0, sizeof(fds));
-	num_lines = gpiod_line_bulk_num_lines(bulk);
-
-	line_bulk_foreach_line(bulk, line, off) {
-		fds[off].fd = line_get_fd(line);
-		fds[off].events = POLLIN | POLLPRI;
-	}
-
-	ts.tv_sec = timeout / 1000000000ULL;
-	ts.tv_nsec = timeout % 1000000000ULL;
-
-	rv = ppoll(fds, num_lines, &ts, NULL);
-	if (rv < 0)
-		return -1;
-	else if (rv == 0)
-		return 0;
-
-	for (off = 0; off < num_lines; off++) {
-		if (fds[off].revents) {
-			if (fds[off].revents & POLLNVAL) {
-				errno = EINVAL;
-				return -1;
-			}
-
-			if (event_bulk) {
-				line = gpiod_line_bulk_get_line(bulk, off);
-				rv = gpiod_line_bulk_add_line(event_bulk, line);
-				if (rv)
-					return -1;
-			}
-
-			if (!--rv)
-				break;
-		}
-	}
-
-	return 1;
-}
-
-GPIOD_API int gpiod_line_event_get_fd(struct gpiod_line *line)
-{
-	if (line->state != LINE_REQUESTED_EVENTS) {
-		errno = EPERM;
-		return -1;
-	}
-
-	return line_get_fd(line);
-}
diff --git a/lib/event.c b/lib/event.c
index f4dfce8..03b2781 100644
--- a/lib/event.c
+++ b/lib/event.c
@@ -3,6 +3,7 @@
 
 #include <errno.h>
 #include <gpiod.h>
+#include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -169,48 +170,15 @@ gpiod_line_event_buffer_copy_event(struct gpiod_line_event_buffer *buf,
 	return event;
 }
 
-GPIOD_API int gpiod_line_event_read(struct gpiod_line *line,
-				    struct gpiod_line_event_buffer *buf)
-{
-	int ret;
-
-	ret = gpiod_line_event_read_multiple(line, buf, 1);
-	if (ret < 0)
-		return -1;
-
-	return 0;
-}
-
-GPIOD_API int
-gpiod_line_event_read_multiple(struct gpiod_line *line,
-			       struct gpiod_line_event_buffer *buf,
-			       unsigned int num_events)
-{
-	int fd;
-
-	fd = gpiod_line_event_get_fd(line);
-	if (fd < 0)
-		return -1;
-
-	return gpiod_line_event_read_fd_multiple(fd, buf, num_events);
-}
-
-GPIOD_API int gpiod_line_event_read_fd(int fd,
-				       struct gpiod_line_event_buffer *buf)
+GPIOD_API unsigned int
+gpiod_line_event_buffer_num_events(struct gpiod_line_event_buffer *buf)
 {
-	int ret;
-
-	ret = gpiod_line_event_read_fd_multiple(fd, buf, 1);
-	if (ret < 0)
-		return -1;
-
-	return 0;
+	return buf->num_events;
 }
 
 GPIOD_API int
-gpiod_line_event_read_fd_multiple(int fd,
-				  struct gpiod_line_event_buffer *buf,
-				  unsigned int max_events)
+gpiod_line_event_buffer_read_fd(int fd, struct gpiod_line_event_buffer *buf,
+				unsigned int max_events)
 {
 	struct gpio_v2_line_event *curr;
 	struct gpiod_line_event *event;
diff --git a/lib/handle.c b/lib/handle.c
new file mode 100644
index 0000000..abd08f9
--- /dev/null
+++ b/lib/handle.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line attribute data structure and functions. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include "internal.h"
+#include "uapi/gpio.h"
+
+struct gpiod_request_handle {
+	struct gpiod_refcount refcount;
+	int fd;
+};
+
+static void request_handle_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_request_handle *handle;
+
+	handle = gpiod_container_of(refcount,
+				    struct gpiod_request_handle, refcount);
+
+	close(handle->fd);
+	free(handle);
+}
+
+struct gpiod_request_handle *gpiod_request_handle_from_fd(int fd)
+{
+	struct gpiod_request_handle *handle;
+
+	handle = malloc(sizeof(*handle));
+	if (!handle)
+		return NULL;
+
+	memset(handle, 0, sizeof(*handle));
+	gpiod_refcount_init(&handle->refcount, request_handle_release);
+	handle->fd = fd;
+
+	return handle;
+}
+
+GPIOD_API struct gpiod_request_handle *
+gpiod_request_handle_ref(struct gpiod_request_handle *handle)
+{
+	gpiod_refcount_ref(&handle->refcount);
+	return handle;
+}
+
+GPIOD_API void gpiod_request_handle_unref(struct gpiod_request_handle *handle)
+{
+	gpiod_refcount_unref(&handle->refcount);
+}
+
+GPIOD_API int
+gpiod_request_handle_get_values(struct gpiod_request_handle *handle,
+				gpiod_line_mask *values,
+				gpiod_line_mask mask)
+{
+	struct gpio_v2_line_values valbuf;
+	int ret;
+
+	valbuf.bits = 0;
+	valbuf.mask = mask;
+
+	ret = ioctl(handle->fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &valbuf);
+	if (ret)
+		return -1;
+
+	*values = valbuf.bits;
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_request_handle_set_values(struct gpiod_request_handle *handle,
+				gpiod_line_mask values,
+				gpiod_line_mask mask)
+{
+	struct gpio_v2_line_values valbuf;
+
+	valbuf.bits = values;
+	valbuf.mask = mask;
+
+	return ioctl(handle->fd, GPIO_V2_LINE_SET_VALUES_IOCTL, &valbuf);
+}
+
+GPIOD_API int
+gpiod_request_handle_set_config(struct gpiod_request_handle *handle,
+				struct gpiod_line_config *config)
+{
+	struct gpio_v2_line_config cfgbuf;
+	int ret;
+
+	gpiod_line_config_to_kernel(config, &cfgbuf);
+
+	ret = ioctl(handle->fd, GPIO_V2_LINE_SET_CONFIG_IOCTL, &cfgbuf);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_request_handle_event_wait(struct gpiod_request_handle *handle,
+				uint64_t timeout)
+{
+	struct timespec ts;
+	struct pollfd pfd;
+	int ret;
+
+	memset(&pfd, 0, sizeof(pfd));
+	pfd.fd = handle->fd;
+	pfd.events = POLLIN | POLLPRI;
+
+	ts.tv_sec = timeout / 1000000000ULL;
+	ts.tv_nsec = timeout % 1000000000ULL;
+
+	ret = ppoll(&pfd, 1, &ts, NULL);
+	if (ret < 0)
+		return -1;
+	else if (ret == 0)
+		return 0;
+
+	return 1;
+}
+
+GPIOD_API int
+gpiod_request_handle_event_read(struct gpiod_request_handle *handle,
+				struct gpiod_line_event_buffer *buf,
+				unsigned int max_events)
+{
+	return gpiod_line_event_buffer_read_fd(handle->fd, buf, max_events);
+}
+
+GPIOD_API int gpiod_request_handle_get_fd(struct gpiod_request_handle *handle)
+{
+	return handle->fd;
+}
diff --git a/lib/helpers.c b/lib/helpers.c
deleted file mode 100644
index 6e15dcf..0000000
--- a/lib/helpers.c
+++ /dev/null
@@ -1,306 +0,0 @@
-// SPDX-License-Identifier: LGPL-2.1-or-later
-// SPDX-FileCopyrightText: 2017-2021 Bartosz Golaszewski <bartekgola@gmail.com>
-
-/*
- * More specific variants of the core API and misc functions that don't need
- * access to neither the internal library data structures nor the kernel UAPI.
- */
-
-#include <errno.h>
-#include <gpiod.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "internal.h"
-
-GPIOD_API struct gpiod_line_bulk *
-gpiod_chip_get_lines(struct gpiod_chip *chip,
-		     unsigned int *offsets, unsigned int num_offsets)
-{
-	struct gpiod_line_bulk *bulk;
-	struct gpiod_line *line;
-	unsigned int i;
-
-	bulk = gpiod_line_bulk_new(num_offsets);
-	if (!bulk)
-		return NULL;
-
-	for (i = 0; i < num_offsets; i++) {
-		line = gpiod_chip_get_line(chip, offsets[i]);
-		if (!line) {
-			gpiod_line_bulk_free(bulk);
-			return NULL;
-		}
-
-		gpiod_line_bulk_add_line(bulk, line);
-	}
-
-	return bulk;
-}
-
-GPIOD_API struct gpiod_line_bulk *
-gpiod_chip_get_all_lines(struct gpiod_chip *chip)
-{
-	struct gpiod_line_bulk *bulk;
-	struct gpiod_line *line;
-	unsigned int offset;
-
-	bulk = gpiod_line_bulk_new(gpiod_chip_get_num_lines(chip));
-	if (!bulk)
-		return NULL;
-
-	for (offset = 0; offset < gpiod_chip_get_num_lines(chip); offset++) {
-		line = gpiod_chip_get_line(chip, offset);
-		if (!line) {
-			gpiod_line_bulk_free(bulk);
-			return NULL;
-		}
-
-		gpiod_line_bulk_add_line(bulk, line);
-	}
-
-	return bulk;
-}
-
-GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
-{
-	unsigned int offset, num_lines;
-	struct gpiod_line_info *info;
-	const char *tmp;
-
-	num_lines = gpiod_chip_get_num_lines(chip);
-
-	for (offset = 0; offset < num_lines; offset++) {
-		info = gpiod_chip_get_line_info(chip, offset);
-		if (!info)
-			return -1;
-
-		tmp = gpiod_line_get_name(info);
-		if (tmp && strcmp(tmp, name) == 0) {
-			gpiod_line_info_unref(info);
-			return offset;
-		}
-
-		gpiod_line_info_unref(info);
-	}
-
-	errno = ENOENT;
-	return -1;
-}
-
-GPIOD_API int gpiod_line_request_input(struct gpiod_line *line,
-				       const char *consumer)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-	};
-
-	return gpiod_line_request(line, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_output(struct gpiod_line *line,
-					const char *consumer, int default_val)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-	};
-
-	return gpiod_line_request(line, &config, default_val);
-}
-
-GPIOD_API int gpiod_line_request_input_flags(struct gpiod_line *line,
-					     const char *consumer, int flags)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-		.flags = flags,
-	};
-
-	return gpiod_line_request(line, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_output_flags(struct gpiod_line *line,
-					      const char *consumer, int flags,
-					      int default_val)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-		.flags = flags,
-	};
-
-	return gpiod_line_request(line, &config, default_val);
-}
-
-static int line_event_request_type(struct gpiod_line *line,
-				   const char *consumer, int flags, int type)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = type,
-		.flags = flags,
-	};
-
-	return gpiod_line_request(line, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_rising_edge_events(struct gpiod_line *line,
-						    const char *consumer)
-{
-	return line_event_request_type(line, consumer, 0,
-				       GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
-}
-
-GPIOD_API int gpiod_line_request_falling_edge_events(struct gpiod_line *line,
-						     const char *consumer)
-{
-	return line_event_request_type(line, consumer, 0,
-				       GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
-}
-
-GPIOD_API int gpiod_line_request_both_edges_events(struct gpiod_line *line,
-						   const char *consumer)
-{
-	return line_event_request_type(line, consumer, 0,
-				       GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
-}
-
-GPIOD_API int
-gpiod_line_request_rising_edge_events_flags(struct gpiod_line *line,
-					    const char *consumer,
-					    int flags)
-{
-	return line_event_request_type(line, consumer, flags,
-				       GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_request_falling_edge_events_flags(struct gpiod_line *line,
-					     const char *consumer,
-					     int flags)
-{
-	return line_event_request_type(line, consumer, flags,
-				       GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_request_both_edges_events_flags(struct gpiod_line *line,
-					   const char *consumer, int flags)
-{
-	return line_event_request_type(line, consumer, flags,
-				       GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
-}
-
-GPIOD_API int gpiod_line_request_bulk_input(struct gpiod_line_bulk *bulk,
-					    const char *consumer)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_bulk_output(struct gpiod_line_bulk *bulk,
-					     const char *consumer,
-					     const int *default_vals)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, default_vals);
-}
-
-static int line_event_request_type_bulk(struct gpiod_line_bulk *bulk,
-					const char *consumer,
-					int flags, int type)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = type,
-		.flags = flags,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, 0);
-}
-
-GPIOD_API int
-gpiod_line_request_bulk_rising_edge_events(struct gpiod_line_bulk *bulk,
-					   const char *consumer)
-{
-	return line_event_request_type_bulk(bulk, consumer, 0,
-					GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_request_bulk_falling_edge_events(struct gpiod_line_bulk *bulk,
-					    const char *consumer)
-{
-	return line_event_request_type_bulk(bulk, consumer, 0,
-					GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
-}
-
-GPIOD_API int
-gpiod_line_request_bulk_both_edges_events(struct gpiod_line_bulk *bulk,
-					  const char *consumer)
-{
-	return line_event_request_type_bulk(bulk, consumer, 0,
-					GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
-}
-
-GPIOD_API int gpiod_line_request_bulk_input_flags(struct gpiod_line_bulk *bulk,
-						  const char *consumer,
-						  int flags)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT,
-		.flags = flags,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, 0);
-}
-
-GPIOD_API int gpiod_line_request_bulk_output_flags(struct gpiod_line_bulk *bulk,
-						   const char *consumer,
-						   int flags,
-						   const int *default_vals)
-{
-	struct gpiod_line_request_config config = {
-		.consumer = consumer,
-		.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT,
-		.flags = flags,
-	};
-
-	return gpiod_line_request_bulk(bulk, &config, default_vals);
-}
-
-GPIOD_API int gpiod_line_request_bulk_rising_edge_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags)
-{
-	return line_event_request_type_bulk(bulk, consumer, flags,
-					GPIOD_LINE_REQUEST_EVENT_RISING_EDGE);
-}
-
-GPIOD_API int gpiod_line_request_bulk_falling_edge_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags)
-{
-	return line_event_request_type_bulk(bulk, consumer, flags,
-					GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE);
-}
-
-GPIOD_API int gpiod_line_request_bulk_both_edges_events_flags(
-					struct gpiod_line_bulk *bulk,
-					const char *consumer, int flags)
-{
-	return line_event_request_type_bulk(bulk, consumer, flags,
-					GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES);
-}
diff --git a/lib/info.c b/lib/info.c
index 5f7c463..ef69309 100644
--- a/lib/info.c
+++ b/lib/info.c
@@ -2,6 +2,7 @@
 // SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
 
 #include <gpiod.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "internal.h"
diff --git a/lib/internal.c b/lib/internal.c
index 52b9461..06adcd2 100644
--- a/lib/internal.c
+++ b/lib/internal.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: LGPL-2.1-or-later
 // SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
 
+#include <errno.h>
+
 #include "internal.h"
 
 void gpiod_refcount_init(struct gpiod_refcount *refcount,
@@ -20,3 +22,102 @@ void gpiod_refcount_unref(struct gpiod_refcount *refcount)
 	if (--refcount->refcnt == 0)
 		refcount->release(refcount);
 }
+
+int gpiod_validate_request_type(int request_type)
+{
+	switch (request_type) {
+	case GPIOD_LINE_CONFIG_DIRECTION_AS_IS:
+	case GPIOD_LINE_CONFIG_DIRECTION_INPUT:
+	case GPIOD_LINE_CONFIG_DIRECTION_OUTPUT:
+	case GPIOD_LINE_CONFIG_EVENT_FALLING_EDGE:
+	case GPIOD_LINE_CONFIG_EVENT_RISING_EDGE:
+	case GPIOD_LINE_CONFIG_EVENT_BOTH_EDGES:
+		return 0;
+	}
+
+	errno = EINVAL;
+	return -1;
+}
+
+int gpiod_validate_drive(int drive)
+{
+	switch (drive) {
+	case GPIOD_LINE_CONFIG_DRIVE_PUSH_PULL:
+	case GPIOD_LINE_CONFIG_DRIVE_OPEN_DRAIN:
+	case GPIOD_LINE_CONFIG_DRIVE_OPEN_SOURCE:
+		return 0;
+	}
+
+	errno = EINVAL;
+	return -1;
+}
+
+int gpiod_validate_bias(int bias)
+{
+	switch (bias) {
+	case GPIOD_LINE_CONFIG_BIAS_DISABLED:
+	case GPIOD_LINE_CONFIG_BIAS_PULL_UP:
+	case GPIOD_LINE_CONFIG_BIAS_PULL_DOWN:
+		return 0;
+	}
+
+	errno = EINVAL;
+	return -1;
+}
+
+uint64_t gpiod_make_kernel_flags(int request_type, int drive, int bias,
+				 bool active_low, bool clock_realtime)
+{
+	uint64_t flags = 0;
+
+	switch (request_type) {
+	case GPIOD_LINE_CONFIG_DIRECTION_INPUT:
+		flags |= GPIO_V2_LINE_FLAG_INPUT;
+		break;
+	case GPIOD_LINE_CONFIG_DIRECTION_OUTPUT:
+		flags |= GPIO_V2_LINE_FLAG_OUTPUT;
+		break;
+	case GPIOD_LINE_CONFIG_EVENT_FALLING_EDGE:
+		flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
+			  GPIO_V2_LINE_FLAG_INPUT);
+		break;
+	case GPIOD_LINE_CONFIG_EVENT_RISING_EDGE:
+		flags |= (GPIO_V2_LINE_FLAG_EDGE_RISING |
+			  GPIO_V2_LINE_FLAG_INPUT);
+		break;
+	case GPIOD_LINE_CONFIG_EVENT_BOTH_EDGES:
+		flags |= (GPIO_V2_LINE_FLAG_EDGE_FALLING |
+			  GPIO_V2_LINE_FLAG_EDGE_RISING |
+			  GPIO_V2_LINE_FLAG_INPUT);
+		break;
+	}
+
+	switch (drive) {
+	case GPIOD_LINE_CONFIG_DRIVE_OPEN_DRAIN:
+		flags |= GPIO_V2_LINE_FLAG_OPEN_DRAIN;
+		break;
+	case GPIOD_LINE_CONFIG_DRIVE_OPEN_SOURCE:
+		flags |= GPIO_V2_LINE_FLAG_OPEN_SOURCE;
+		break;
+	}
+
+	switch (bias) {
+	case GPIOD_LINE_CONFIG_BIAS_DISABLED:
+		flags |= GPIO_V2_LINE_FLAG_BIAS_DISABLED;
+		break;
+	case GPIOD_LINE_CONFIG_BIAS_PULL_UP:
+		flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP;
+		break;
+	case GPIOD_LINE_CONFIG_BIAS_PULL_DOWN:
+		flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN;
+		break;
+	}
+
+	if (active_low)
+		flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW;
+
+	if (clock_realtime)
+		flags |= GPIO_V2_LINE_FLAG_EVENT_CLOCK_REALTIME;
+
+	return flags;
+}
diff --git a/lib/internal.h b/lib/internal.h
index 2d1627d..2677ee3 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -6,6 +6,7 @@
 
 #include <gpiod.h>
 #include <stddef.h>
+#include <stdint.h>
 
 #include "uapi/gpio.h"
 
@@ -32,5 +33,18 @@ void gpiod_refcount_unref(struct gpiod_refcount *refcount);
 
 struct gpiod_line_info *
 gpiod_line_info_from_kernel(struct gpio_v2_line_info *infobuf);
+void gpiod_request_config_to_kernel(struct gpiod_request_config *config,
+				    struct gpio_v2_line_request *reqbuf);
+void gpiod_line_config_to_kernel(struct gpiod_line_config *config,
+				 struct gpio_v2_line_config *cfgbuf);
+void gpiod_line_attr_to_kernel(struct gpiod_line_attr *attr,
+			       struct gpio_v2_line_config_attribute *attrbuf);
+uint64_t gpiod_make_kernel_flags(int request_type, int drive, int bias,
+				 bool active_low, bool clock_realtime);
+struct gpiod_request_handle *gpiod_request_handle_from_fd(int fd);
+
+int gpiod_validate_request_type(int request_type);
+int gpiod_validate_drive(int drive);
+int gpiod_validate_bias(int bias);
 
 #endif /* __LIBGPIOD_GPIOD_INTERNAL_H__ */
diff --git a/lib/mask.c b/lib/mask.c
new file mode 100644
index 0000000..0b24821
--- /dev/null
+++ b/lib/mask.c
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line mask bitmaps. */
+
+#include "internal.h"
+
+GPIOD_API void gpiod_line_mask_fill(gpiod_line_mask *mask)
+{
+	*mask = UINT64_MAX;
+}
+
+GPIOD_API void gpiod_line_mask_zero(gpiod_line_mask *mask)
+{
+	*mask = 0ULL;
+}
+
+GPIOD_API bool gpiod_line_mask_test_bit(gpiod_line_mask mask,
+					unsigned int offset)
+{
+	return mask & (1ULL << offset);
+}
+
+GPIOD_API void gpiod_line_mask_set_bit(gpiod_line_mask *mask,
+				       unsigned int offset)
+{
+	*mask |= (1ULL << offset);
+}
+
+GPIOD_API void gpiod_line_mask_clear_bit(gpiod_line_mask *mask,
+					 unsigned int offset)
+{
+	*mask &= ~(1ULL << offset);
+}
+
+GPIOD_API void gpiod_line_mask_assign_bit(gpiod_line_mask *mask,
+					  unsigned int offset, bool value)
+{
+	if (value)
+		gpiod_line_mask_set_bit(mask, offset);
+	else
+		gpiod_line_mask_clear_bit(mask, offset);
+}
diff --git a/lib/misc.c b/lib/misc.c
index 67654f9..0b945ef 100644
--- a/lib/misc.c
+++ b/lib/misc.c
@@ -3,10 +3,85 @@
 
 /* Misc code that didn't fit anywhere else. */
 
+#include <errno.h>
 #include <gpiod.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <sys/types.h>
+#include <unistd.h>
 
 #include "internal.h"
 
+GPIOD_API bool gpiod_is_gpiochip_device(const char *path)
+{
+	char *realname, *sysfsp, devpath[64];
+	struct stat statbuf;
+	bool ret = false;
+	int rv;
+
+	rv = lstat(path, &statbuf);
+	if (rv)
+		goto out;
+
+	/*
+	 * Is it a symbolic link? We have to resolve it before checking
+	 * the rest.
+	 */
+	realname = S_ISLNK(statbuf.st_mode) ? realpath(path, NULL)
+					    : strdup(path);
+	if (realname == NULL)
+		goto out;
+
+	rv = stat(realname, &statbuf);
+	if (rv)
+		goto out_free_realname;
+
+	/* Is it a character device? */
+	if (!S_ISCHR(statbuf.st_mode)) {
+		/*
+		 * Passing a file descriptor not associated with a character
+		 * device to ioctl() makes it set errno to ENOTTY. Let's do
+		 * the same in order to stay compatible with the versions of
+		 * libgpiod from before the introduction of this routine.
+		 */
+		errno = ENOTTY;
+		goto out_free_realname;
+	}
+
+	/* Is the device associated with the GPIO subsystem? */
+	snprintf(devpath, sizeof(devpath), "/sys/dev/char/%u:%u/subsystem",
+		 major(statbuf.st_rdev), minor(statbuf.st_rdev));
+
+	sysfsp = realpath(devpath, NULL);
+	if (!sysfsp)
+		goto out_free_realname;
+
+	if (strcmp(sysfsp, "/sys/bus/gpio") != 0) {
+		/*
+		 * This is a character device but not the one we're after.
+		 * Before the introduction of this function, we'd fail with
+		 * ENOTTY on the first GPIO ioctl() call for this file
+		 * descriptor. Let's stay compatible here and keep returning
+		 * the same error code.
+		 */
+		errno = ENOTTY;
+		goto out_free_sysfsp;
+	}
+
+	ret = true;
+
+out_free_sysfsp:
+	free(sysfsp);
+out_free_realname:
+	free(realname);
+out:
+	return ret;
+}
+
 GPIOD_API uint64_t gpiod_sec_to_nsec(uint64_t sec)
 {
 	return sec * 1000000000ULL;
diff --git a/lib/request.c b/lib/request.c
new file mode 100644
index 0000000..1b9ae03
--- /dev/null
+++ b/lib/request.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line configuration data structure and functions. */
+
+#include <errno.h>
+#include <gpiod.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+#include "uapi/gpio.h"
+
+struct gpiod_request_config {
+	struct gpiod_refcount refcount;
+	char consumer[GPIO_MAX_NAME_SIZE];
+	unsigned int offsets[GPIO_V2_LINES_MAX];
+	unsigned int num_lines;
+	unsigned int event_buffer_size;
+};
+
+static void request_config_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_request_config *config;
+
+	config = gpiod_container_of(refcount,
+				    struct gpiod_request_config, refcount);
+
+	free(config);
+}
+
+GPIOD_API struct gpiod_request_config *gpiod_request_config_new(void)
+{
+	struct gpiod_request_config *config;
+
+	config = malloc(sizeof(*config));
+	if (!config)
+		return NULL;
+
+	memset(config, 0, sizeof(*config));
+	gpiod_refcount_init(&config->refcount, request_config_release);
+
+	return config;
+}
+
+GPIOD_API struct gpiod_request_config *
+gpiod_request_config_ref(struct gpiod_request_config *config)
+{
+	gpiod_refcount_ref(&config->refcount);
+	return config;
+}
+
+GPIOD_API void gpiod_request_config_unref(struct gpiod_request_config *config)
+{
+	gpiod_refcount_unref(&config->refcount);
+}
+
+GPIOD_API int
+gpiod_request_config_set_consumer(struct gpiod_request_config *config,
+				  const char *consumer)
+{
+	if (strlen(consumer) >= GPIO_MAX_NAME_SIZE) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	strncpy(config->consumer, consumer, GPIO_MAX_NAME_SIZE - 1);
+	config->consumer[GPIO_MAX_NAME_SIZE - 1] = '\0';
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_request_config_set_offsets(struct gpiod_request_config *config,
+				 unsigned int num_lines,
+				 unsigned int *offsets)
+{
+	unsigned int i;
+
+	if (num_lines > GPIO_V2_LINES_MAX) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	for (i = 0; i < num_lines; i++)
+		config->offsets[i] = offsets[i];
+
+	config->num_lines = num_lines;
+
+	return 0;
+}
+
+GPIOD_API int
+gpiod_request_config_set_event_buffer_size(struct gpiod_request_config *config,
+					   unsigned int event_buffer_size)
+{
+	if (event_buffer_size == 0 ||
+	    event_buffer_size > (GPIO_V2_LINES_MAX * 16)) {
+		errno = EINVAL;
+		return -1;
+	}
+
+	config->event_buffer_size = event_buffer_size;
+	return 0;
+}
+
+void gpiod_request_config_to_kernel(struct gpiod_request_config *config,
+				    struct gpio_v2_line_request *reqbuf)
+{
+	unsigned int i;
+
+	for (i = 0; i < config->num_lines; i++)
+		reqbuf->offsets[i] = config->offsets[i];
+
+	reqbuf->num_lines = config->num_lines;
+	strcpy(reqbuf->consumer, config->consumer);
+	reqbuf->event_buffer_size = config->event_buffer_size;
+}
diff --git a/tests/tests-event.c b/tests/tests-event.c
index cb4aac4..6908b04 100644
--- a/tests/tests-event.c
+++ b/tests/tests-event.c
@@ -36,7 +36,7 @@ GPIOD_TEST_CASE(rising_edge_good, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -79,7 +79,7 @@ GPIOD_TEST_CASE(falling_edge_good, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -127,17 +127,17 @@ GPIOD_TEST_CASE(rising_edge_ignore_falling, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	ret = gpiod_line_event_read(line, event_buf0);
 	g_assert_cmpint(ret, ==, 0);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	ret = gpiod_line_event_read(line, event_buf1);
 	g_assert_cmpint(ret, ==, 0);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	ret = gpiod_line_event_read(line, event_buf2);
 	g_assert_cmpint(ret, ==, 0);
@@ -186,7 +186,7 @@ GPIOD_TEST_CASE(both_edges, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -199,7 +199,7 @@ GPIOD_TEST_CASE(both_edges, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -242,7 +242,7 @@ GPIOD_TEST_CASE(both_edges_active_low, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -255,7 +255,7 @@ GPIOD_TEST_CASE(both_edges_active_low, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -298,7 +298,7 @@ GPIOD_TEST_CASE(both_edges_bias_disable, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -311,7 +311,7 @@ GPIOD_TEST_CASE(both_edges_bias_disable, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -354,7 +354,7 @@ GPIOD_TEST_CASE(both_edges_bias_pull_down, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -367,7 +367,7 @@ GPIOD_TEST_CASE(both_edges_bias_pull_down, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -410,7 +410,7 @@ GPIOD_TEST_CASE(both_edges_bias_pull_up, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -423,7 +423,7 @@ GPIOD_TEST_CASE(both_edges_bias_pull_up, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -466,7 +466,7 @@ GPIOD_TEST_CASE(falling_edge_active_low, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -513,7 +513,7 @@ GPIOD_TEST_CASE(get_value, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -561,7 +561,7 @@ GPIOD_TEST_CASE(get_value_active_low, 0, { 8 })
 
 	ev_thread = gpiod_test_start_event_thread(0, 7, 100);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 
 	ret = gpiod_line_event_read(line, event_buf);
@@ -836,7 +836,7 @@ GPIOD_TEST_CASE(invalid_fd, 0, { 8 })
 	fd = gpiod_line_event_get_fd(line);
 	close(fd);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, -1);
 	g_assert_cmpint(errno, ==, EINVAL);
 
@@ -890,7 +890,7 @@ GPIOD_TEST_CASE(read_events_individually, 0, { 8 })
 	}
 
 	/* read them individually... */
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -905,7 +905,7 @@ GPIOD_TEST_CASE(read_events_individually, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -920,7 +920,7 @@ GPIOD_TEST_CASE(read_events_individually, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -935,7 +935,7 @@ GPIOD_TEST_CASE(read_events_individually, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(event), ==,
 			GPIOD_LINE_EVENT_FALLING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 0);
 }
 
@@ -978,7 +978,7 @@ GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
 		usleep(10000);
 	}
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -1001,7 +1001,7 @@ GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(events[2]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -1031,7 +1031,7 @@ GPIOD_TEST_CASE(read_multiple_events, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(events[3]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 0);
 }
 
@@ -1067,7 +1067,7 @@ GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
 		usleep(10000);
 	}
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -1093,7 +1093,7 @@ GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(events[2]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 1);
 	if (!ret)
 		return;
@@ -1123,6 +1123,6 @@ GPIOD_TEST_CASE(read_multiple_events_fd, 0, { 8 })
 	g_assert_cmpint(gpiod_line_event_get_event_type(events[3]), ==,
 			GPIOD_LINE_EVENT_RISING_EDGE);
 
-	ret = gpiod_line_event_wait(line, timeout);
+	ret = gpiod_request_handle_event_wait(line, timeout);
 	g_assert_cmpint(ret, ==, 0);
 }
diff --git a/tools/gpio-tools-test.bats b/tools/gpio-tools-test.bats
index a5b97e1..e1b79b4 100755
--- a/tools/gpio-tools-test.bats
+++ b/tools/gpio-tools-test.bats
@@ -370,7 +370,7 @@ teardown() {
 	run_tool gpioget "$(gpio_mockup_chip_name 1)" 0 1 2 3 4
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to retrieve GPIO lines from chip"
+	output_regex_match ".*unable to request lines.*"
 }
 
 @test "gpioget: same line twice" {
@@ -379,7 +379,7 @@ teardown() {
 	run_tool gpioget "$(gpio_mockup_chip_name 1)" 0 0
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request lines.*"
+	output_regex_match ".*unable to request lines"
 }
 
 @test "gpioget: invalid bias" {
@@ -578,7 +578,7 @@ teardown() {
 	run_tool gpioset "$(gpio_mockup_chip_name 1)" 0=1 1=1 2=1 3=1 4=1 5=1
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to retrieve GPIO lines from chip"
+	output_regex_match ".*unable to request lines.*"
 }
 
 @test "gpioset: use --sec without --mode=time" {
@@ -667,7 +667,7 @@ teardown() {
 	run_tool gpioset "$(gpio_mockup_chip_name 1)" 0=1 0=1
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request lines.*"
+	output_regex_match ".*unable to request lines"
 }
 
 #
@@ -887,7 +887,7 @@ teardown() {
 	run_tool gpiomon "$(gpio_mockup_chip_name 1)" 0 0
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to request GPIO lines for events"
+	output_regex_match ".*unable to request lines"
 }
 
 @test "gpiomon: no arguments" {
@@ -912,7 +912,7 @@ teardown() {
 	run_tool gpiomon "$(gpio_mockup_chip_name 0)" 5
 
 	test "$status" -eq "1"
-	output_regex_match ".*unable to retrieve GPIO lines from chip"
+	output_regex_match ".*unable to request lines"
 }
 
 @test "gpiomon: invalid bias" {
diff --git a/tools/gpiodetect.c b/tools/gpiodetect.c
index 10706e2..f053afa 100644
--- a/tools/gpiodetect.c
+++ b/tools/gpiodetect.c
@@ -6,6 +6,7 @@
 #include <getopt.h>
 #include <gpiod.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "tools-common.h"
@@ -66,14 +67,8 @@ int main(int argc, char **argv)
 
 	for (i = 0; i < num_chips; i++) {
 		chip = chip_open_by_name(entries[i]->d_name);
-		if (!chip) {
-			if (errno == EACCES)
-				printf("%s Permission denied\n",
-				       entries[i]->d_name);
-			else
-				die_perror("unable to open %s",
-					   entries[i]->d_name);
-		}
+		if (!chip)
+			die_perror("unable to open %s", entries[i]->d_name);
 
 		printf("%s [%s] (%u lines)\n",
 		       gpiod_chip_get_name(chip),
diff --git a/tools/gpiofind.c b/tools/gpiofind.c
index 32b7852..a3df67c 100644
--- a/tools/gpiofind.c
+++ b/tools/gpiofind.c
@@ -6,6 +6,7 @@
 #include <getopt.h>
 #include <gpiod.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "tools-common.h"
diff --git a/tools/gpioget.c b/tools/gpioget.c
index ceeec56..e404589 100644
--- a/tools/gpioget.c
+++ b/tools/gpioget.c
@@ -5,6 +5,7 @@
 #include <gpiod.h>
 #include <limits.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "tools-common.h"
@@ -38,11 +39,14 @@ static void print_help(void)
 
 int main(int argc, char **argv)
 {
-	struct gpiod_line_request_config config;
-	int *values, optc, opti, rv, flags = 0;
+	struct gpiod_request_config *req_cfg;
+	struct gpiod_request_handle *handle;
 	unsigned int *offsets, i, num_lines;
-	struct gpiod_line_bulk *lines;
+	struct gpiod_line_config *line_cfg;
+	gpiod_line_mask values, lines;
+	int optc, opti, bias = 0, ret;
 	struct gpiod_chip *chip;
+	bool active_low = false;
 	char *device, *end;
 
 	for (;;) {
@@ -58,10 +62,10 @@ int main(int argc, char **argv)
 			print_version();
 			return EXIT_SUCCESS;
 		case 'l':
-			flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+			active_low = true;
 			break;
 		case 'B':
-			flags |= bias_flags(optarg);
+			bias = parse_bias(optarg);
 			break;
 		case '?':
 			die("try %s --help", get_progname());
@@ -82,9 +86,8 @@ int main(int argc, char **argv)
 	device = argv[0];
 	num_lines = argc - 1;
 
-	values = malloc(sizeof(*values) * num_lines);
 	offsets = malloc(sizeof(*offsets) * num_lines);
-	if (!values || !offsets)
+	if (!offsets)
 		die("out of memory");
 
 	for (i = 0; i < num_lines; i++) {
@@ -97,35 +100,43 @@ int main(int argc, char **argv)
 	if (!chip)
 		die_perror("unable to open %s", device);
 
-	lines = gpiod_chip_get_lines(chip, offsets, num_lines);
-	if (!lines)
-		die_perror("unable to retrieve GPIO lines from chip");
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
 
-	memset(&config, 0, sizeof(config));
+	if (bias)
+		gpiod_line_config_set_bias(line_cfg, bias);
+	gpiod_line_config_set_active_low(line_cfg, active_low);
+	gpiod_line_config_set_request_type(line_cfg,
+					   GPIOD_LINE_CONFIG_DIRECTION_INPUT);
 
-	config.consumer = "gpioget";
-	config.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT;
-	config.flags = flags;
+	req_cfg = gpiod_request_config_new();
+	if (!req_cfg)
+		die_perror("unable to allocate the request config structure");
 
-	rv = gpiod_line_request_bulk(lines, &config, NULL);
-	if (rv)
+	gpiod_request_config_set_consumer(req_cfg, "gpioget");
+	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
+
+	handle = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	if (!handle)
 		die_perror("unable to request lines");
 
-	rv = gpiod_line_get_value_bulk(lines, values);
-	if (rv < 0)
-		die_perror("error reading GPIO values");
+	gpiod_line_mask_fill(&lines);
+	ret = gpiod_request_handle_get_values(handle, &values, lines);
+	if (ret)
+		die_perror("unable to read GPIO line values");
 
 	for (i = 0; i < num_lines; i++) {
-		printf("%d", values[i]);
+		printf("%d", !!gpiod_line_mask_test_bit(values, i));
 		if (i != num_lines - 1)
 			printf(" ");
 	}
 	printf("\n");
 
-	gpiod_line_release_bulk(lines);
+	gpiod_request_handle_unref(handle);
+	gpiod_request_config_unref(req_cfg);
+	gpiod_line_config_unref(line_cfg);
 	gpiod_chip_unref(chip);
-	gpiod_line_bulk_free(lines);
-	free(values);
 	free(offsets);
 
 	return EXIT_SUCCESS;
diff --git a/tools/gpioinfo.c b/tools/gpioinfo.c
index 7901036..fb14d89 100644
--- a/tools/gpioinfo.c
+++ b/tools/gpioinfo.c
@@ -7,6 +7,7 @@
 #include <gpiod.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 
 #include "tools-common.h"
@@ -221,14 +222,9 @@ int main(int argc, char **argv)
 
 		for (i = 0; i < num_chips; i++) {
 			chip = chip_open_by_name(entries[i]->d_name);
-			if (!chip) {
-				if (errno == EACCES)
-					printf("%s Permission denied\n",
-					       entries[i]->d_name);
-				else
-					die_perror("unable to open %s",
-						   entries[i]->d_name);
-			}
+			if (!chip)
+				die_perror("unable to open %s",
+					   entries[i]->d_name);
 
 			list_lines(chip);
 
diff --git a/tools/gpiomon.c b/tools/gpiomon.c
index 3e6b715..d426064 100644
--- a/tools/gpiomon.c
+++ b/tools/gpiomon.c
@@ -8,6 +8,7 @@
 #include <poll.h>
 #include <signal.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -152,20 +153,19 @@ static void handle_signal(int signum UNUSED)
 
 int main(int argc, char **argv)
 {
-	unsigned int offsets[64], num_lines = 0, offset,
-		     events_wanted = 0, events_done = 0, x;
-	bool watch_rising = false, watch_falling = false;
+	unsigned int offsets[64], num_lines = 0, offset, events_wanted = 0,
+		     events_done = 0;
+	bool watch_rising = false, watch_falling = false, active_low = false;
+	struct gpiod_line_event_buffer *event_buffer;
+	int optc, opti, ret, i, event_type, bias = 0;
 	uint64_t timeout = gpiod_sec_to_nsec(10);
-	int flags = 0;
-	int optc, opti, rv, i, y, event_type;
-	struct mon_ctx ctx;
+	struct gpiod_request_config *req_cfg;
+	struct gpiod_request_handle *handle;
+	struct gpiod_line_config *line_cfg;
+	struct gpiod_line_event *event;
 	struct gpiod_chip *chip;
-	struct gpiod_line_bulk *lines, *evlines;
+	struct mon_ctx ctx;
 	char *end;
-	struct gpiod_line_request_config config;
-	struct gpiod_line *line;
-	struct gpiod_line_event_buffer *event_buffer;
-	struct gpiod_line_event *event;
 
 	/*
 	 * FIXME: use signalfd once the API has been converted to using a single file
@@ -189,10 +189,10 @@ int main(int argc, char **argv)
 			print_version();
 			return EXIT_SUCCESS;
 		case 'l':
-			flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+			active_low = true;
 			break;
 		case 'B':
-			flags |= bias_flags(optarg);
+			bias = parse_bias(optarg);
 			break;
 		case 'n':
 			events_wanted = strtoul(optarg, &end, 10);
@@ -225,11 +225,11 @@ int main(int argc, char **argv)
 	argv += optind;
 
 	if (watch_rising && !watch_falling)
-		event_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE;
+		event_type = GPIOD_LINE_CONFIG_EVENT_RISING_EDGE;
 	else if (watch_falling && !watch_rising)
-		event_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE;
+		event_type = GPIOD_LINE_CONFIG_EVENT_FALLING_EDGE;
 	else
-		event_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES;
+		event_type = GPIOD_LINE_CONFIG_EVENT_BOTH_EDGES;
 
 	if (argc < 1)
 		die("gpiochip must be specified");
@@ -253,70 +253,65 @@ int main(int argc, char **argv)
 	if (!chip)
 		die_perror("unable to open %s", argv[0]);
 
-	lines = gpiod_chip_get_lines(chip, offsets, num_lines);
-	if (!lines)
-		die_perror("unable to retrieve GPIO lines from chip");
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
 
-	memset(&config, 0, sizeof(config));
+	if (bias)
+		gpiod_line_config_set_bias(line_cfg, bias);
+	gpiod_line_config_set_active_low(line_cfg, active_low);
+	gpiod_line_config_set_request_type(line_cfg, event_type);
 
-	config.consumer = "gpiomon";
-	config.request_type = event_type;
-	config.flags = flags;
+	req_cfg = gpiod_request_config_new();
+	if (!req_cfg)
+		die_perror("unable to allocate the request config structure");
 
-	rv = gpiod_line_request_bulk(lines, &config, NULL);
-	if (rv)
-		die_perror("unable to request GPIO lines for events");
+	gpiod_request_config_set_consumer(req_cfg, "gpiomon");
+	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
 
-	evlines = gpiod_line_bulk_new(num_lines);
-	if (!evlines)
-		die("out of memory");
+	handle = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	if (!handle)
+		die_perror("unable to request lines");
 
 	event_buffer = gpiod_line_event_buffer_new(EVENT_BUF_SIZE);
 	if (!event_buffer)
 		die_perror("unable to allocate the line event buffer");
 
 	for (;;) {
-		gpiod_line_bulk_reset(evlines);
-		rv = gpiod_line_event_wait_bulk(lines, timeout, evlines);
-		if (rv < 0)
+		ret = gpiod_request_handle_event_wait(handle, timeout);
+		if (ret < 0)
 			die_perror("error waiting for events");
-		if (rv == 0)
+		if (ret == 0)
 			continue;
 
-		num_lines = gpiod_line_bulk_num_lines(evlines);
-
-		for (x = 0; x < num_lines; x++) {
-			line = gpiod_line_bulk_get_line(evlines, x);
-
-			rv = gpiod_line_event_read_multiple(line, event_buffer,
-							    EVENT_BUF_SIZE);
-			if (rv < 0)
-				die_perror("error reading line events");
+		ret = gpiod_request_handle_event_read(handle, event_buffer,
+						      EVENT_BUF_SIZE);
+		if (ret < 0)
+			die_perror("error reading line events");
 
-			for (y = 0; y < rv; y++) {
-				event = gpiod_line_event_buffer_get_event(
-							event_buffer, y);
-				if (!event)
-					die_perror("unable to retrieve the event");
+		for (i = 0; i < ret; i++) {
+			event  = gpiod_line_event_buffer_get_event(event_buffer,
+								   i);
+			if (!event)
+				die_perror("unable to retrieve the event from the buffer");
 
-				handle_event(gpiod_line_offset(line),
-					gpiod_line_event_get_event_type(event),
-					gpiod_line_event_get_timestamp(event),
-					&ctx);
+			handle_event(gpiod_line_event_get_line_offset(event),
+				     gpiod_line_event_get_event_type(event),
+				     gpiod_line_event_get_timestamp(event),
+				     &ctx);
 
-				events_done++;
+			events_done++;
 
-				if (events_wanted &&
-				    events_done >= events_wanted)
-					goto done;
-			}
+			if (events_wanted && events_done >= events_wanted)
+				goto done;
 		}
 	}
 
 done:
-	gpiod_line_release_bulk(lines);
-	gpiod_line_bulk_free(lines);
-	gpiod_line_bulk_free(evlines);
+	gpiod_line_event_buffer_unref(event_buffer);
+	gpiod_request_handle_unref(handle);
+	gpiod_request_config_unref(req_cfg);
+	gpiod_line_config_unref(line_cfg);
 	gpiod_chip_unref(chip);
 
 	return EXIT_SUCCESS;
diff --git a/tools/gpioset.c b/tools/gpioset.c
index 7882b53..72f77ae 100644
--- a/tools/gpioset.c
+++ b/tools/gpioset.c
@@ -7,6 +7,7 @@
 #include <limits.h>
 #include <poll.h>
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
 #include <sys/select.h>
 #include <unistd.h>
@@ -175,12 +176,12 @@ static const struct mode_mapping *parse_mode(const char *mode)
 	return NULL;
 }
 
-static int drive_flags(const char *option)
+static int parse_drive(const char *option)
 {
 	if (strcmp(option, "open-drain") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN;
+		return GPIOD_LINE_CONFIG_DRIVE_OPEN_DRAIN;
 	if (strcmp(option, "open-source") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_OPEN_SOURCE;
+		return GPIOD_LINE_CONFIG_DRIVE_OPEN_SOURCE;
 	if (strcmp(option, "push-pull") != 0)
 		die("invalid drive: %s", option);
 	return 0;
@@ -189,12 +190,16 @@ static int drive_flags(const char *option)
 int main(int argc, char **argv)
 {
 	const struct mode_mapping *mode = &modes[MODE_EXIT];
-	struct gpiod_line_request_config config;
-	int *values, rv, optc, opti, flags = 0;
-	unsigned int *offsets, num_lines, i;
-	struct gpiod_line_bulk *lines;
+	unsigned int *offsets, num_lines, i, val;
+	int ret, optc, opti, bias = 0, drive = 0;
+	struct gpiod_request_config *req_cfg;
+	struct gpiod_request_handle *handle;
+	struct gpiod_line_config *line_cfg;
+	struct gpiod_line_attr *def_vals;
+	gpiod_line_mask values, mask;
 	struct callback_data cbdata;
 	struct gpiod_chip *chip;
+	bool active_low = false;
 	char *device, *end;
 
 	memset(&cbdata, 0, sizeof(cbdata));
@@ -212,13 +217,13 @@ int main(int argc, char **argv)
 			print_version();
 			return EXIT_SUCCESS;
 		case 'l':
-			flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+			active_low = true;
 			break;
 		case 'B':
-			flags |= bias_flags(optarg);
+			bias = parse_bias(optarg);
 			break;
 		case 'D':
-			flags |= drive_flags(optarg);
+			drive = parse_drive(optarg);
 			break;
 		case 'm':
 			mode = parse_mode(optarg);
@@ -268,48 +273,74 @@ int main(int argc, char **argv)
 	num_lines = argc - 1;
 
 	offsets = malloc(sizeof(*offsets) * num_lines);
-	values = malloc(sizeof(*values) * num_lines);
-	if (!values || !offsets)
+	if (!offsets)
 		die("out of memory");
 
+	gpiod_line_mask_zero(&values);
+	gpiod_line_mask_zero(&mask);
+
 	for (i = 0; i < num_lines; i++) {
-		rv = sscanf(argv[i + 1], "%u=%d", &offsets[i], &values[i]);
-		if (rv != 2)
+		ret = sscanf(argv[i + 1], "%u=%d", &offsets[i], &val);
+		if (ret != 2)
 			die("invalid offset<->value mapping: %s", argv[i + 1]);
 
-		if (values[i] != 0 && values[i] != 1)
+		if (val != 0 && val != 1)
 			die("value must be 0 or 1: %s", argv[i + 1]);
 
 		if (offsets[i] > INT_MAX)
 			die("invalid offset: %s", argv[i + 1]);
+
+		gpiod_line_mask_assign_bit(&values, i, val);
+		gpiod_line_mask_set_bit(&mask, i);
 	}
 
 	chip = chip_open_lookup(device);
 	if (!chip)
 		die_perror("unable to open %s", device);
 
-	lines = gpiod_chip_get_lines(chip, offsets, num_lines);
-	if (!lines)
-		die_perror("unable to retrieve GPIO lines from chip");
+	def_vals = gpiod_line_attr_new(GPIOD_LINE_ATTR_TYPE_OUTPUT_VALUES);
+	if (!def_vals)
+		die_perror("unable to allocate the line attribute structure");
+
+	gpiod_line_attr_set_output_values(def_vals, values);
+	gpiod_line_attr_set_line_mask(def_vals, mask);
+
+	line_cfg = gpiod_line_config_new();
+	if (!line_cfg)
+		die_perror("unable to allocate the line config structure");
+
+	if (bias)
+		gpiod_line_config_set_bias(line_cfg, bias);
+	if (drive)
+		gpiod_line_config_set_drive(line_cfg, drive);
+	gpiod_line_config_set_active_low(line_cfg, active_low);
+	gpiod_line_config_set_request_type(line_cfg,
+					   GPIOD_LINE_CONFIG_DIRECTION_OUTPUT);
+
+	ret = gpiod_line_config_add_attribute(line_cfg, def_vals);
+	if (ret)
+		die_perror("unable to set line attribute");
 
-	memset(&config, 0, sizeof(config));
+	req_cfg = gpiod_request_config_new();
+	if (!req_cfg)
+		die_perror("unable to allocate the request config structure");
 
-	config.consumer = "gpioset";
-	config.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT;
-	config.flags = flags;
+	gpiod_request_config_set_consumer(req_cfg, "gpioset");
+	gpiod_request_config_set_offsets(req_cfg, num_lines, offsets);
 
-	rv = gpiod_line_request_bulk(lines, &config, values);
-	if (rv)
+	handle = gpiod_chip_request_lines(chip, req_cfg, line_cfg);
+	if (!handle)
 		die_perror("unable to request lines");
 
 	if (mode->callback)
 		mode->callback(&cbdata);
 
-	gpiod_line_release_bulk(lines);
+	gpiod_request_handle_unref(handle);
+	gpiod_request_config_unref(req_cfg);
+	gpiod_line_config_unref(line_cfg);
+	gpiod_line_attr_unref(def_vals);
 	gpiod_chip_unref(chip);
-	gpiod_line_bulk_free(lines);
 	free(offsets);
-	free(values);
 
 	return EXIT_SUCCESS;
 }
diff --git a/tools/tools-common.c b/tools/tools-common.c
index 80087ee..36724d5 100644
--- a/tools/tools-common.c
+++ b/tools/tools-common.c
@@ -57,14 +57,14 @@ void print_version(void)
 	printf("There is NO WARRANTY, to the extent permitted by law.\n");
 }
 
-int bias_flags(const char *option)
+int parse_bias(const char *option)
 {
 	if (strcmp(option, "pull-down") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN;
+		return GPIOD_LINE_CONFIG_BIAS_PULL_DOWN;
 	if (strcmp(option, "pull-up") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP;
+		return GPIOD_LINE_CONFIG_BIAS_PULL_UP;
 	if (strcmp(option, "disable") == 0)
-		return GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLED;
+		return GPIOD_LINE_CONFIG_BIAS_DISABLED;
 	if (strcmp(option, "as-is") != 0)
 		die("invalid bias: %s", option);
 	return 0;
diff --git a/tools/tools-common.h b/tools/tools-common.h
index 5d5b505..f059440 100644
--- a/tools/tools-common.h
+++ b/tools/tools-common.h
@@ -25,7 +25,7 @@ const char *get_progname(void);
 void die(const char *fmt, ...) NORETURN PRINTF(1, 2);
 void die_perror(const char *fmt, ...) NORETURN PRINTF(1, 2);
 void print_version(void);
-int bias_flags(const char *option);
+int parse_bias(const char *option);
 void print_bias_help(void);
 int make_signalfd(void);
 int chip_dir_filter(const struct dirent *entry);
-- 
2.30.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* [libgpiod][RFC 6/6] core: implement line watch events
  2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
                   ` (4 preceding siblings ...)
  2021-04-10 14:51 ` [libgpiod][RFC 5/6] core: rework line requests Bartosz Golaszewski
@ 2021-04-10 14:51 ` Bartosz Golaszewski
  2021-04-14 14:15 ` [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Kent Gibson
  6 siblings, 0 replies; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-10 14:51 UTC (permalink / raw)
  To: Kent Gibson, Linus Walleij, Andy Shevchenko
  Cc: linux-gpio, Bartosz Golaszewski

This implement support for line status watch events in libgpiod. Unlike
line events, these are not expected to happen very often and continuously
so there's no need for a dedicated container.

Signed-off-by: Bartosz Golaszewski <brgl@bgdev.pl>
---
 include/gpiod.h | 103 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/Makefile.am |   3 +-
 lib/chip.c      |  65 ++++++++++++++++++++++++++----
 lib/handle.c    |  20 +---------
 lib/internal.c  |  24 +++++++++++
 lib/internal.h  |   4 ++
 lib/watch.c     |  96 ++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 288 insertions(+), 27 deletions(-)
 create mode 100644 lib/watch.c

diff --git a/include/gpiod.h b/include/gpiod.h
index 8831d4c..5202ede 100644
--- a/include/gpiod.h
+++ b/include/gpiod.h
@@ -41,6 +41,7 @@ extern "C" {
 
 struct gpiod_chip;
 struct gpiod_line_info;
+struct gpiod_watch_event;
 struct gpiod_line_attr;
 struct gpiod_line_config;
 struct gpiod_request_config;
@@ -107,6 +108,49 @@ unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip);
 struct gpiod_line_info *gpiod_chip_get_line_info(struct gpiod_chip *chip,
 						 unsigned int offset);
 
+/**
+ * @brief Get the current snapshot of information about the line at given
+ *        offset and start watching it for future changes.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return New GPIO line info object or NULL if an error occurred.
+ */
+struct gpiod_line_info *gpiod_chip_watch_line_info(struct gpiod_chip *chip,
+						   unsigned int offset);
+
+/**
+ * @brief Stop watching the line at given offset for status changes.
+ * @param chip GPIO chip object.
+ * @param offset The offset of the GPIO line.
+ * @return 0 on success, -1 on failure.
+ */
+int gpiod_chip_unwatch_line_info(struct gpiod_chip *chip, unsigned int offset);
+
+/**
+ * @brief Get the file descriptor associated with this chip.
+ * @param chip GPIO chip object.
+ * @return File descriptor number. This function never fails.
+ */
+int gpiod_chip_get_fd(struct gpiod_chip *chip);
+
+/**
+ * @brief Wait for line status events on any of the lines associated with
+ *        this chip.
+ * @param chip GPIO chip object.
+ * @param timeout Wait time limit in nanoseconds.
+ * @return 0 if wait timed out, -1 if an error occurred, 1 if an event is
+ *         pending.
+ */
+int gpiod_chip_watch_event_wait(struct gpiod_chip *chip, uint64_t timeout);
+
+/**
+ * @brief Read a single line status change event from this chip.
+ * @param chip GPIO chip object.
+ * @return Newly read watch event object or NULL on error.
+ * @note If no events are pending, this function will block.
+ */
+struct gpiod_watch_event *gpiod_chip_watch_event_read(struct gpiod_chip *chip);
+
 /**
  * @brief Map a GPIO line's name to its offset within the chip.
  * @param chip GPIO chip object.
@@ -295,6 +339,65 @@ bool gpiod_line_is_debounced(struct gpiod_line_info *info);
 unsigned long
 gpiod_line_get_debounce_period(struct gpiod_line_info *info);
 
+/**
+ * @}
+ *
+ * @defgroup line_watch Line status watch events
+ * @{
+ */
+
+/**
+ * @brief Possible line status change event types.
+ */
+enum {
+	GPIOD_WATCH_EVENT_LINE_REQUESTED = 1,
+	/**< Line has been requested. */
+	GPIOD_WATCH_EVENT_LINE_RELEASED,
+	/**< Previously requested line has been released. */
+	GPIOD_WATCH_EVENT_LINE_CONFIG_CHANGED,
+	/**< Line configuration has changed. */
+};
+
+/**
+ * @brief Increase the reference count of this line status watch event.
+ * @param event Line status watch event.
+ * @return Passed reference to this line watch event.
+ */
+struct gpiod_watch_event *
+gpiod_watch_event_ref(struct gpiod_watch_event *event);
+
+/**
+ * @brief Decrease the reference count of this line status watch event. If the
+ *        reference count reaches 0, release all associated resources.
+ * @param event Line status watch event.
+ */
+void gpiod_watch_event_unref(struct gpiod_watch_event *event);
+
+/**
+ * @brief Get the event type of this status change event.
+ * @param event Line status watch event.
+ * @return One of ::GPIOD_WATCH_EVENT_LINE_REQUESTED,
+ *         ::GPIOD_WATCH_EVENT_LINE_RELEASED or
+ *         ::GPIOD_WATCH_EVENT_LINE_CONFIG_CHANGED.
+ */
+int gpiod_watch_event_get_event_type(struct gpiod_watch_event *event);
+
+/**
+ * @brief Get the line info snapshot at the time of the watch event occurence.
+ * @param event Line status watch event.
+ * @return Reference to the line info object whose lifetime must be managed
+ *         by the caller.
+ */
+struct gpiod_line_info *
+gpiod_watch_event_get_line_info(struct gpiod_watch_event *event);
+
+/**
+ * @brief Get the timestamp of the event.
+ * @param event Line status watch event.
+ * @return Timestamp in nanoseconds.
+ */
+uint64_t gpiod_watch_event_get_timestamp(struct gpiod_watch_event *event);
+
 /**
  * @}
  *
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 8713d52..5d9b998 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -13,7 +13,8 @@ libgpiod_la_SOURCES =	attr.c \
 			mask.c \
 			misc.c \
 			request.c \
-			uapi/gpio.h
+			uapi/gpio.h \
+			watch.c
 
 libgpiod_la_CFLAGS = -Wall -Wextra -g -std=gnu89
 libgpiod_la_CFLAGS += -fvisibility=hidden -I$(top_srcdir)/include/
diff --git a/lib/chip.c b/lib/chip.c
index 9bacfe7..6fb4c1b 100644
--- a/lib/chip.c
+++ b/lib/chip.c
@@ -117,33 +117,84 @@ GPIOD_API unsigned int gpiod_chip_get_num_lines(struct gpiod_chip *chip)
 }
 
 static int chip_read_line_info(int fd, unsigned int offset,
-			       struct gpio_v2_line_info *infobuf)
+			       struct gpio_v2_line_info *infobuf, bool watch)
 {
-	int ret;
+	int ret, cmd;
 
 	memset(infobuf, 0, sizeof(*infobuf));
 	infobuf->offset = offset;
 
-	ret = ioctl(fd, GPIO_V2_GET_LINEINFO_IOCTL, infobuf);
+	cmd = watch ? GPIO_V2_GET_LINEINFO_WATCH_IOCTL :
+		      GPIO_V2_GET_LINEINFO_IOCTL;
+
+	ret = ioctl(fd, cmd, infobuf);
 	if (ret)
 		return -1;
 
 	return 0;
 }
 
-GPIOD_API struct gpiod_line_info *
-gpiod_chip_get_line_info(struct gpiod_chip *chip, unsigned int offset)
+static struct gpiod_line_info *
+chip_get_line_info(struct gpiod_chip *chip, unsigned int offset, bool watch)
 {
 	struct gpio_v2_line_info infobuf;
 	int ret;
 
-	ret = chip_read_line_info(chip->fd, offset, &infobuf);
+	ret = chip_read_line_info(chip->fd, offset, &infobuf, watch);
 	if (ret)
 		return NULL;
 
 	return gpiod_line_info_from_kernel(&infobuf);
 }
 
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_get_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+	return chip_get_line_info(chip, offset, false);
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_chip_watch_line_info(struct gpiod_chip *chip, unsigned int offset)
+{
+	return chip_get_line_info(chip, offset, true);
+}
+
+GPIOD_API int gpiod_chip_unwatch_line_info(struct gpiod_chip *chip,
+					   unsigned int offset)
+{
+	return ioctl(chip->fd, GPIO_GET_LINEINFO_UNWATCH_IOCTL, &offset);
+}
+
+GPIOD_API int gpiod_chip_get_fd(struct gpiod_chip *chip)
+{
+	return chip->fd;
+}
+
+GPIOD_API int gpiod_chip_watch_event_wait(struct gpiod_chip *chip,
+					  uint64_t timeout)
+{
+	return gpiod_poll_fd(chip->fd, timeout);
+}
+
+GPIOD_API struct gpiod_watch_event *
+gpiod_chip_watch_event_read(struct gpiod_chip *chip)
+{
+	struct gpio_v2_line_info_changed evbuf;
+	ssize_t rd;
+
+	memset(&evbuf, 0, sizeof(evbuf));
+
+	rd = read(chip->fd, &evbuf, sizeof(evbuf));
+	if (rd < 0) {
+		return NULL;
+	} else if ((unsigned int)rd < sizeof(evbuf)) {
+		errno = EIO;
+		return NULL;
+	}
+
+	return gpiod_watch_event_from_kernel(&evbuf);
+}
+
 GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
 {
 	struct gpio_v2_line_info infobuf;
@@ -151,7 +202,7 @@ GPIOD_API int gpiod_chip_find_line(struct gpiod_chip *chip, const char *name)
 	int ret;
 
 	for (offset = 0; offset < chip->num_lines; offset++) {
-		ret = chip_read_line_info(chip->fd, offset, &infobuf);
+		ret = chip_read_line_info(chip->fd, offset, &infobuf, false);
 		if (ret)
 			return -1;
 
diff --git a/lib/handle.c b/lib/handle.c
index abd08f9..9fccb7c 100644
--- a/lib/handle.c
+++ b/lib/handle.c
@@ -5,7 +5,6 @@
 
 #include <errno.h>
 #include <gpiod.h>
-#include <poll.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/ioctl.h>
@@ -110,24 +109,7 @@ GPIOD_API int
 gpiod_request_handle_event_wait(struct gpiod_request_handle *handle,
 				uint64_t timeout)
 {
-	struct timespec ts;
-	struct pollfd pfd;
-	int ret;
-
-	memset(&pfd, 0, sizeof(pfd));
-	pfd.fd = handle->fd;
-	pfd.events = POLLIN | POLLPRI;
-
-	ts.tv_sec = timeout / 1000000000ULL;
-	ts.tv_nsec = timeout % 1000000000ULL;
-
-	ret = ppoll(&pfd, 1, &ts, NULL);
-	if (ret < 0)
-		return -1;
-	else if (ret == 0)
-		return 0;
-
-	return 1;
+	return gpiod_poll_fd(handle->fd, timeout);
 }
 
 GPIOD_API int
diff --git a/lib/internal.c b/lib/internal.c
index 06adcd2..66a42e6 100644
--- a/lib/internal.c
+++ b/lib/internal.c
@@ -2,6 +2,8 @@
 // SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
 
 #include <errno.h>
+#include <poll.h>
+#include <string.h>
 
 #include "internal.h"
 
@@ -121,3 +123,25 @@ uint64_t gpiod_make_kernel_flags(int request_type, int drive, int bias,
 
 	return flags;
 }
+
+int gpiod_poll_fd(int fd, uint64_t timeout)
+{
+	struct timespec ts;
+	struct pollfd pfd;
+	int ret;
+
+	memset(&pfd, 0, sizeof(pfd));
+	pfd.fd = fd;
+	pfd.events = POLLIN | POLLPRI;
+
+	ts.tv_sec = timeout / 1000000000ULL;
+	ts.tv_nsec = timeout % 1000000000ULL;
+
+	ret = ppoll(&pfd, 1, &ts, NULL);
+	if (ret < 0)
+		return -1;
+	else if (ret == 0)
+		return 0;
+
+	return 1;
+}
diff --git a/lib/internal.h b/lib/internal.h
index 2677ee3..fa2bc04 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -42,9 +42,13 @@ void gpiod_line_attr_to_kernel(struct gpiod_line_attr *attr,
 uint64_t gpiod_make_kernel_flags(int request_type, int drive, int bias,
 				 bool active_low, bool clock_realtime);
 struct gpiod_request_handle *gpiod_request_handle_from_fd(int fd);
+struct gpiod_watch_event *
+gpiod_watch_event_from_kernel(struct gpio_v2_line_info_changed *evbuf);
 
 int gpiod_validate_request_type(int request_type);
 int gpiod_validate_drive(int drive);
 int gpiod_validate_bias(int bias);
 
+int gpiod_poll_fd(int fd, uint64_t timeout);
+
 #endif /* __LIBGPIOD_GPIOD_INTERNAL_H__ */
diff --git a/lib/watch.c b/lib/watch.c
new file mode 100644
index 0000000..bc1721d
--- /dev/null
+++ b/lib/watch.c
@@ -0,0 +1,96 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+// SPDX-FileCopyrightText: 2021 Bartosz Golaszewski <brgl@bgdev.pl>
+
+/* Line status watch. */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "internal.h"
+
+struct gpiod_watch_event {
+	struct gpiod_refcount refcount;
+	int event_type;
+	uint64_t timestamp;
+	struct gpiod_line_info *info;
+};
+
+static void watch_event_release(struct gpiod_refcount *refcount)
+{
+	struct gpiod_watch_event *event;
+
+	event = gpiod_container_of(refcount,
+				   struct gpiod_watch_event, refcount);
+
+	gpiod_line_info_unref(event->info);
+	free(event);
+}
+
+struct gpiod_watch_event *
+gpiod_watch_event_from_kernel(struct gpio_v2_line_info_changed *evbuf)
+{
+	struct gpiod_watch_event *event;
+
+	event = malloc(sizeof(*event));
+	if (!event)
+		return NULL;
+
+	memset(event, 0, sizeof(*event));
+	gpiod_refcount_init(&event->refcount, watch_event_release);
+	event->timestamp = evbuf->timestamp_ns;
+
+	switch (evbuf->event_type) {
+	case GPIOLINE_CHANGED_REQUESTED:
+		event->event_type = GPIOD_WATCH_EVENT_LINE_REQUESTED;
+		break;
+	case GPIOLINE_CHANGED_RELEASED:
+		event->event_type = GPIOD_WATCH_EVENT_LINE_RELEASED;
+		break;
+	case GPIOLINE_CHANGED_CONFIG:
+		event->event_type = GPIOD_WATCH_EVENT_LINE_CONFIG_CHANGED;
+		break;
+	default:
+		/* Can't happen unless there's a bug in the kernel. */
+		errno = ENOMSG;
+		free(event);
+		return NULL;
+	}
+
+	event->info = gpiod_line_info_from_kernel(&evbuf->info);
+	if (!event->info) {
+		free(event);
+		return NULL;
+	}
+
+	return event;
+}
+
+GPIOD_API struct gpiod_watch_event *
+gpiod_watch_event_ref(struct gpiod_watch_event *event)
+{
+	gpiod_refcount_ref(&event->refcount);
+	return event;
+}
+
+GPIOD_API void gpiod_watch_event_unref(struct gpiod_watch_event *event)
+{
+	gpiod_refcount_unref(&event->refcount);
+}
+
+GPIOD_API int gpiod_watch_event_get_event_type(struct gpiod_watch_event *event)
+{
+	return event->event_type;
+}
+
+GPIOD_API struct gpiod_line_info *
+gpiod_watch_event_get_line_info(struct gpiod_watch_event *event)
+{
+	return gpiod_line_info_ref(event->info);
+}
+
+GPIOD_API uint64_t
+gpiod_watch_event_get_timestamp(struct gpiod_watch_event *event)
+{
+	return event->timestamp;
+}
-- 
2.30.1


^ permalink raw reply related	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
                   ` (5 preceding siblings ...)
  2021-04-10 14:51 ` [libgpiod][RFC 6/6] core: implement line watch events Bartosz Golaszewski
@ 2021-04-14 14:15 ` Kent Gibson
  2021-04-16  9:36   ` Bartosz Golaszewski
  6 siblings, 1 reply; 22+ messages in thread
From: Kent Gibson @ 2021-04-14 14:15 UTC (permalink / raw)
  To: Bartosz Golaszewski; +Cc: Linus Walleij, Andy Shevchenko, linux-gpio

On Sat, Apr 10, 2021 at 04:51:51PM +0200, Bartosz Golaszewski wrote:
> This series introduces an entirely reworked API for the core C part of
> libgpiod. It's not fully functional as the bindings are not modified,
> and starting from patch 5, even the tests stop working. This is on
> purpose as I'd like to reach an agreement on the interface before
> spending time on reworking the tests.
> 
> My plan for the development of v2.0 is to keep the changes in a separate
> branch until all bits & pieces are complete and then rearrange them into
> a bisectable series that will be merged into the master branch.
> 
> A couple points regarding the design of the new API:
> - all objects have become opaque and can only be accessed using dedicated
>   functions
> - line objects as well as bulk containers have been removed
> - line requests are now configured using three types of structures: attributes,
>   line config and request config structures, which follows the kernel API
> - line request handles have now a lifetime separate from the parent chips to
>   leverage the separation of chip and request file descriptors
> - line events are now opaque but they can be stored in a dedicated container
>   so that memory allocations are not necessary everytime an event is read from
>   the kernel
> - the library code has been split into several compilation units for easier
>   maintenance
> 
> The new API is completely documented in include/gpiod.h doxygen comments.
> 
> Please let me know what you think. I am aware that these patches are huge and
> difficult to review but I'm really only expecting a general API review - all
> other implementation details can be improved later.
> 

In that vein, I'll lump my comments here, rather than scattering them
throughout the patches...

Overall it feels much tighter and clearer than the old API, though that
could just be personal bias, as it is also closer to the new uAPI.

I find the ownership and lifetime of objects confusing.  I presume you
want to use the reference counting to simplify the object lifecycle, but
that just formalises the possibility that objects are shared - further
confusing the ownership issue.
e.g. gpiod_line_config_add_attribute()
- who owns the attr after the call?  What happens if it is subsequently
modified? Is the attr copied or does ownership pass to the config?
As written it is neither - the attr becomes shared.  How is that
preferable?
Similarly gpiod_line_get_consumer() - who owns the returned pointer and
what is it's lifetime?
I would prefer the opaque objects to be able to be free()d normally, and
for any calls that involve a change of ownership to explicitly document
that fact.
For objects that require additional setup/cleanup, try to make use of 
open()/close() or request()/release() function name pairings.
So gpiod_chip would have open()/close(), gpiod_request_handle would have
request()/release(), and the others would all be new()/free()d.

Conceptually the config for each line can be considered to be independent
- the uAPI encoding using attributes and masks is only there to keep the
uAPI structs to a manageable size. 
At this level you can model it as config per line, and only map
between the uAPI structures when calling the ioctl()s.
So I would drop the gpiod_line_attr, and instead provide accessors and
mutators on the gpiod_line_config for each attr type.
That removes the whole lifecycle issue for attributes, and allows you to
provide a simpler abstraction of the config than that provided in the
uAPI.
For the mutators there can be two flavours, one that sets the config for
the whole set, and another that accepts a subset of applicable lines.
Accessors would provide the config attr for a particular line.
Both accessor and mutator would use chip offsets to identify the lines.

Not sure I like merging the direction and edge into the request_type.
I would tend to keep those separate, with set_direction and
set_edge_detection functions, with the latter forcing input direction.

I would rename gpiod_request_config to gpiod_request_options.  Config
is long lived and can be modified, whereas options are one-off.
And it would reduce any chance of confusion with gpiod_line_config.

gpiod_line_mask should highlight that the bits correspond to lines on
the request, in the order provided to gpiod_request_config_set_offsets(),
not line offsets on the chip.  Perhaps the gpiod_request_config or the
gpiod_request_handle should provide the mask functions for a given a
qchip offset, rather than require the user to track the mapping?
Then the gpiod_line_mask can be opaque as well.

I would rename gpiod_request_handle to gpiod_line_request.
And request.c to options.c, and handle.c to request.c.

gpiod_line_attr_set_debounce() can use a zero period to identify
disabling debounce, so the debounce flag is redundant.

Cheers,
Kent.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-14 14:15 ` [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Kent Gibson
@ 2021-04-16  9:36   ` Bartosz Golaszewski
  2021-04-17  7:23     ` Kent Gibson
  0 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-16  9:36 UTC (permalink / raw)
  To: Kent Gibson; +Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

Hi Kent!

Thanks a lot for looking at this.

On Wed, Apr 14, 2021 at 4:15 PM Kent Gibson <warthog618@gmail.com> wrote:
>
> On Sat, Apr 10, 2021 at 04:51:51PM +0200, Bartosz Golaszewski wrote:
> > This series introduces an entirely reworked API for the core C part of
> > libgpiod. It's not fully functional as the bindings are not modified,
> > and starting from patch 5, even the tests stop working. This is on
> > purpose as I'd like to reach an agreement on the interface before
> > spending time on reworking the tests.
> >
> > My plan for the development of v2.0 is to keep the changes in a separate
> > branch until all bits & pieces are complete and then rearrange them into
> > a bisectable series that will be merged into the master branch.
> >
> > A couple points regarding the design of the new API:
> > - all objects have become opaque and can only be accessed using dedicated
> >   functions
> > - line objects as well as bulk containers have been removed
> > - line requests are now configured using three types of structures: attributes,
> >   line config and request config structures, which follows the kernel API
> > - line request handles have now a lifetime separate from the parent chips to
> >   leverage the separation of chip and request file descriptors
> > - line events are now opaque but they can be stored in a dedicated container
> >   so that memory allocations are not necessary everytime an event is read from
> >   the kernel
> > - the library code has been split into several compilation units for easier
> >   maintenance
> >
> > The new API is completely documented in include/gpiod.h doxygen comments.
> >
> > Please let me know what you think. I am aware that these patches are huge and
> > difficult to review but I'm really only expecting a general API review - all
> > other implementation details can be improved later.
> >
>

Just to clarify why I did certain things the way I did: there are
certain semi-standardized guidelines to creating low-level C libraries
for linux user-space interfaces. They are gathered in a sample project
called libabc[1][2][3]. Most plumbing-layer libraries follow these
guidelines more or less rigorously. Many low-level programmers will
make certain assumptions based on their previous experiences when
working with libraries and it's good to be as little confusing as
possible.

> In that vein, I'll lump my comments here, rather than scattering them
> throughout the patches...
>
> Overall it feels much tighter and clearer than the old API, though that
> could just be personal bias, as it is also closer to the new uAPI.
>
> I find the ownership and lifetime of objects confusing.  I presume you
> want to use the reference counting to simplify the object lifecycle, but
> that just formalises the possibility that objects are shared - further
> confusing the ownership issue.

Interesting because in my mind reference counting actually simplifies
the life-time of objects - everytime someone (the caller or another
object) stores the address of the object, the reference count is
increased.

Reference counting is a de-facto standard in C libraries. Look at
libkmod, libudev, libpulse, all the systemd libraries, the whole of
GLib.

Reference counting makes higher-level programming easier. An object
lives as long as there's at least one user. In C++ bindings the
objects were refcounted using shared_ptr which causes some issues. Now
with refcounting implemented at the C library level, we'll be able to
use the PImpl pattern with unique_ptr and still keep the objects
copyable.

To summarize: I don't see many disadvantages to using reference
counting other than the fact that my documentation is not yet very
detailed and lacks some general information about how reference
counting works in libgpiod and needs some clarification.

> e.g. gpiod_line_config_add_attribute()
> - who owns the attr after the call?  What happens if it is subsequently
> modified? Is the attr copied or does ownership pass to the config?
> As written it is neither - the attr becomes shared.  How is that
> preferable?

Yes, it becomes shared. Its reference count is now 2. If the user
modifies it, the config will use a modified value at the time of the
request. To give you a real-life example of such behavior: in GLib
where all GObjects are reference counted, if you pass a reference to a
GFile to another user, then set its (GFile's) attribute, they'll be
changed for the user sharing the object too. Same with how Python or
Java handle references, except you don't need to manage them yourself.

> Similarly gpiod_line_get_consumer() - who owns the returned pointer and
> what is it's lifetime?

Since simple strings can't be refcounted and this function returns a
pointer to a constant string, the life-time of the string is obviously
tied to that of the line_info object. The doc should probably say:
Returns a pointer to the string stored in the line_info object.

> I would prefer the opaque objects to be able to be free()d normally, and
> for any calls that involve a change of ownership to explicitly document
> that fact.
> For objects that require additional setup/cleanup, try to make use of
> open()/close() or request()/release() function name pairings.
> So gpiod_chip would have open()/close(), gpiod_request_handle would have
> request()/release(), and the others would all be new()/free()d.
>

This makes you use a different set of resource management functions
depending on the object. Additionally some objects can't be created -
only retrieved from other objects.

Having a simple ref/unref pair for all opaque data types looks more
elegant to me. Another real-life example from yesterday: I'm working
on a small library to go with the new gpio-sim module (meant as a
replacement for libgpio-mockup) and when I tried to use libmount (to
verify if configfs is mounted and where) I was super confused by its
resource management which is similar to what you suggest but also some
resources need to be freed and some transfer their ownership to other
objects (without reference counting) and while it's all documented and
you can find info on that eventually, I still spent time debugging
strange double-free and other corruptions before I got things right. I
find libudev and libkmod with their consistent reference counting much
more instinctive to use.

> Conceptually the config for each line can be considered to be independent
> - the uAPI encoding using attributes and masks is only there to keep the
> uAPI structs to a manageable size.
> At this level you can model it as config per line, and only map
> between the uAPI structures when calling the ioctl()s.
> So I would drop the gpiod_line_attr, and instead provide accessors and
> mutators on the gpiod_line_config for each attr type.
> That removes the whole lifecycle issue for attributes, and allows you to
> provide a simpler abstraction of the config than that provided in the
> uAPI.
> For the mutators there can be two flavours, one that sets the config for
> the whole set, and another that accepts a subset of applicable lines.
> Accessors would provide the config attr for a particular line.
> Both accessor and mutator would use chip offsets to identify the lines.
>

I had exactly this in mind initially but couldn't find an elegant interface.

There are two options I see:

gpiod_line_config_set_drive(cfg, drive);
gpiod_line_config_set_drive_index(cfg, drive, index);

or:

gpiod_line_config_set_drive(cfg, drive);
unsigned int indices[] = { 0, 3, 4, 6 };
gpiod_line_config_set_drive_index(cfg, drive, indices);

The former would require a separate function call for each line, the
former needs a lot of boilerplate code because we'll usually have to
alloc & fill the indices array. Eventually I decided to go with
attributes but if you have some ideas/suggestions for that, they're
welcome!

> Not sure I like merging the direction and edge into the request_type.
> I would tend to keep those separate, with set_direction and
> set_edge_detection functions, with the latter forcing input direction.
>

If there's no way we can use output with edge detection, why even
bother with allowing the user to try to set output mode for
edge_detection or vice-versa? It's just more error checking for no
benefit, right? The user will still find out that the line is in input
mode with the line_info.

> I would rename gpiod_request_config to gpiod_request_options.  Config
> is long lived and can be modified, whereas options are one-off.
> And it would reduce any chance of confusion with gpiod_line_config.
>

Not sure about this one. With the current naming, it's being made
clear that we have two separate things to configure, the line(s) and
the request itself. Naming both "_config" makes a clear distinction.
Also: the request config can be reused too for separate requests.

> gpiod_line_mask should highlight that the bits correspond to lines on
> the request, in the order provided to gpiod_request_config_set_offsets(),
> not line offsets on the chip.  Perhaps the gpiod_request_config or the
> gpiod_request_handle should provide the mask functions for a given a
> qchip offset, rather than require the user to track the mapping?
> Then the gpiod_line_mask can be opaque as well.
>

Yes, this is one of those things I struggled with. I wasn't sure which
one's better because if the user wants to request 64 lines, then set a
separate config for 20 of them, it's a lot of function calls if we
allow to set line config by offset of a single line, or we get a
complicated API with allowing arrays of offsets.

Agreed for the line map functions, it's probably the most confusing
element of this new API. I'll come up with something.

> I would rename gpiod_request_handle to gpiod_line_request.
> And request.c to options.c, and handle.c to request.c.
>
> gpiod_line_attr_set_debounce() can use a zero period to identify
> disabling debounce, so the debounce flag is redundant.
>

Sounds good.

Bart

> Cheers,
> Kent.

[1] http://0pointer.de/blog/projects/libabc.html
[2] https://git.kernel.org/pub/scm/linux/kernel/git/kay/libabc.git
[3] https://git.kernel.org/pub/scm/linux/kernel/git/kay/libabc.git/plain/README

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-16  9:36   ` Bartosz Golaszewski
@ 2021-04-17  7:23     ` Kent Gibson
  2021-04-17 11:31       ` Bartosz Golaszewski
  0 siblings, 1 reply; 22+ messages in thread
From: Kent Gibson @ 2021-04-17  7:23 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Fri, Apr 16, 2021 at 11:36:08AM +0200, Bartosz Golaszewski wrote:
> Hi Kent!
> 
> Thanks a lot for looking at this.
> 
> On Wed, Apr 14, 2021 at 4:15 PM Kent Gibson <warthog618@gmail.com> wrote:
> >
> > On Sat, Apr 10, 2021 at 04:51:51PM +0200, Bartosz Golaszewski wrote:
> > > This series introduces an entirely reworked API for the core C part of
> > > libgpiod. It's not fully functional as the bindings are not modified,
> > > and starting from patch 5, even the tests stop working. This is on
> > > purpose as I'd like to reach an agreement on the interface before
> > > spending time on reworking the tests.
> > >
> > > My plan for the development of v2.0 is to keep the changes in a separate
> > > branch until all bits & pieces are complete and then rearrange them into
> > > a bisectable series that will be merged into the master branch.
> > >
> > > A couple points regarding the design of the new API:
> > > - all objects have become opaque and can only be accessed using dedicated
> > >   functions
> > > - line objects as well as bulk containers have been removed
> > > - line requests are now configured using three types of structures: attributes,
> > >   line config and request config structures, which follows the kernel API
> > > - line request handles have now a lifetime separate from the parent chips to
> > >   leverage the separation of chip and request file descriptors
> > > - line events are now opaque but they can be stored in a dedicated container
> > >   so that memory allocations are not necessary everytime an event is read from
> > >   the kernel
> > > - the library code has been split into several compilation units for easier
> > >   maintenance
> > >
> > > The new API is completely documented in include/gpiod.h doxygen comments.
> > >
> > > Please let me know what you think. I am aware that these patches are huge and
> > > difficult to review but I'm really only expecting a general API review - all
> > > other implementation details can be improved later.
> > >
> >
> 
> Just to clarify why I did certain things the way I did: there are
> certain semi-standardized guidelines to creating low-level C libraries
> for linux user-space interfaces. They are gathered in a sample project
> called libabc[1][2][3]. Most plumbing-layer libraries follow these
> guidelines more or less rigorously. Many low-level programmers will
> make certain assumptions based on their previous experiences when
> working with libraries and it's good to be as little confusing as
> possible.
> 

Fair enough, though I don't see anything in libabc that conflicts with
my suggestions, nor that require some of your design choices.

> > In that vein, I'll lump my comments here, rather than scattering them
> > throughout the patches...
> >
> > Overall it feels much tighter and clearer than the old API, though that
> > could just be personal bias, as it is also closer to the new uAPI.
> >
> > I find the ownership and lifetime of objects confusing.  I presume you
> > want to use the reference counting to simplify the object lifecycle, but
> > that just formalises the possibility that objects are shared - further
> > confusing the ownership issue.
> 
> Interesting because in my mind reference counting actually simplifies
> the life-time of objects - everytime someone (the caller or another
> object) stores the address of the object, the reference count is
> increased.
> 
> Reference counting is a de-facto standard in C libraries. Look at
> libkmod, libudev, libpulse, all the systemd libraries, the whole of
> GLib.
> 

I don't doubt that reference counting is out there, I question whether
it is necessary here.

In my mind reference counting is a tool to be used when you need to
share objects and it isn't clear who is responsible for freeing
them.  I don't see any need for sharing here at all - for all objects 
(excluding the gpiod_line_attr that I would remove) it is pretty clear
who is responsible for them at all times.

> Reference counting makes higher-level programming easier. An object
> lives as long as there's at least one user. In C++ bindings the
> objects were refcounted using shared_ptr which causes some issues. Now
> with refcounting implemented at the C library level, we'll be able to
> use the PImpl pattern with unique_ptr and still keep the objects
> copyable.
> 

That still sounds to me like you are sharing objects that shouldn't be
shared. Or copied.

> To summarize: I don't see many disadvantages to using reference
> counting other than the fact that my documentation is not yet very
> detailed and lacks some general information about how reference
> counting works in libgpiod and needs some clarification.
> 

I understand your argument - which is exactly what I had presumed.
But I still find the idea of having _unref() acting as the universal free
function to be distasteful.

> > e.g. gpiod_line_config_add_attribute()
> > - who owns the attr after the call?  What happens if it is subsequently
> > modified? Is the attr copied or does ownership pass to the config?
> > As written it is neither - the attr becomes shared.  How is that
> > preferable?
> 
> Yes, it becomes shared. Its reference count is now 2. If the user
> modifies it, the config will use a modified value at the time of the
> request. To give you a real-life example of such behavior: in GLib
> where all GObjects are reference counted, if you pass a reference to a
> GFile to another user, then set its (GFile's) attribute, they'll be
> changed for the user sharing the object too. Same with how Python or
> Java handle references, except you don't need to manage them yourself.
> 

This is all mute if you drop gpiod_line_attr, but I still don't see why
sharing is preferable in this case.  It feels like the worst of both
worlds - I need to alloc an attr for every add, and I need to unref them
as well.

> > Similarly gpiod_line_get_consumer() - who owns the returned pointer and
> > what is it's lifetime?
> 
> Since simple strings can't be refcounted and this function returns a
> pointer to a constant string, the life-time of the string is obviously
> tied to that of the line_info object. The doc should probably say:
> Returns a pointer to the string stored in the line_info object.
> 

I would dispute the "obviously", particularly since you seem to expect
everything else to be unref()ed.

> > I would prefer the opaque objects to be able to be free()d normally, and
> > for any calls that involve a change of ownership to explicitly document
> > that fact.
> > For objects that require additional setup/cleanup, try to make use of
> > open()/close() or request()/release() function name pairings.
> > So gpiod_chip would have open()/close(), gpiod_request_handle would have
> > request()/release(), and the others would all be new()/free()d.
> >
> 
> This makes you use a different set of resource management functions
> depending on the object. Additionally some objects can't be created -
> only retrieved from other objects.
> 

Yup, different objects have different lifecycles - even your way.

> Having a simple ref/unref pair for all opaque data types looks more
> elegant to me. Another real-life example from yesterday: I'm working
> on a small library to go with the new gpio-sim module (meant as a
> replacement for libgpio-mockup) and when I tried to use libmount (to
> verify if configfs is mounted and where) I was super confused by its
> resource management which is similar to what you suggest but also some
> resources need to be freed and some transfer their ownership to other
> objects (without reference counting) and while it's all documented and
> you can find info on that eventually, I still spent time debugging
> strange double-free and other corruptions before I got things right. I
> find libudev and libkmod with their consistent reference counting much
> more instinctive to use.
> 

I find dealing with repeated frees preferable to dealing with issues
related to unexpected side-effects when ownership is unclear.

> > Conceptually the config for each line can be considered to be independent
> > - the uAPI encoding using attributes and masks is only there to keep the
> > uAPI structs to a manageable size.
> > At this level you can model it as config per line, and only map
> > between the uAPI structures when calling the ioctl()s.
> > So I would drop the gpiod_line_attr, and instead provide accessors and
> > mutators on the gpiod_line_config for each attr type.
> > That removes the whole lifecycle issue for attributes, and allows you to
> > provide a simpler abstraction of the config than that provided in the
> > uAPI.
> > For the mutators there can be two flavours, one that sets the config for
> > the whole set, and another that accepts a subset of applicable lines.
> > Accessors would provide the config attr for a particular line.
> > Both accessor and mutator would use chip offsets to identify the lines.
> >
> 
> I had exactly this in mind initially but couldn't find an elegant interface.
> 
> There are two options I see:
> 
> gpiod_line_config_set_drive(cfg, drive);
> gpiod_line_config_set_drive_index(cfg, drive, index);
> 
> or:
> 
> gpiod_line_config_set_drive(cfg, drive);
> unsigned int indices[] = { 0, 3, 4, 6 };
> gpiod_line_config_set_drive_index(cfg, drive, indices);
> 
> The former would require a separate function call for each line, the
> former needs a lot of boilerplate code because we'll usually have to
> alloc & fill the indices array. Eventually I decided to go with
> attributes but if you have some ideas/suggestions for that, they're
> welcome!
> 

I generally avoid variadic functions, but this might be an application
where they make sense.  i.e. make your indices variadic.
Oh, and make them offsets ;-).

> > Not sure I like merging the direction and edge into the request_type.
> > I would tend to keep those separate, with set_direction and
> > set_edge_detection functions, with the latter forcing input direction.
> >
> 
> If there's no way we can use output with edge detection, why even
> bother with allowing the user to try to set output mode for
> edge_detection or vice-versa? It's just more error checking for no
> benefit, right? The user will still find out that the line is in input
> mode with the line_info.
> 

You are adding another concept for the user to grok - the request_type.
They will need to understand direction and edge detection anyway, why
add a new concept to the mix?

Oh, and wrt error checking, in my Go library, and as I suggest above,
setting edge detection overrides any previous direction setting and
forces input.  Similarly setting output direction cancels any edge
detection.  So the config presented to the kernel will always be
consistent and never trigger an EINVAL.  And that is all documented in
the corresponding config functions.

Similarly the drive settings.

The library policy is last-in-wins, which it can be as there are
distinct calls for each attribute, whereas at the uAPI they are merged
into flags.

> > I would rename gpiod_request_config to gpiod_request_options.  Config
> > is long lived and can be modified, whereas options are one-off.
> > And it would reduce any chance of confusion with gpiod_line_config.
> >
> 
> Not sure about this one. With the current naming, it's being made
> clear that we have two separate things to configure, the line(s) and
> the request itself. Naming both "_config" makes a clear distinction.
> Also: the request config can be reused too for separate requests.
> 

I meant one-off wrt a single request.  i.e. changing the options after
the lines have been requested can never impact the request, whereas a
changed config can be applied with the set_config().

> > gpiod_line_mask should highlight that the bits correspond to lines on
> > the request, in the order provided to gpiod_request_config_set_offsets(),
> > not line offsets on the chip.  Perhaps the gpiod_request_config or the
> > gpiod_request_handle should provide the mask functions for a given a
> > qchip offset, rather than require the user to track the mapping?
> > Then the gpiod_line_mask can be opaque as well.
> >
> 
> Yes, this is one of those things I struggled with. I wasn't sure which
> one's better because if the user wants to request 64 lines, then set a
> separate config for 20 of them, it's a lot of function calls if we
> allow to set line config by offset of a single line, or we get a
> complicated API with allowing arrays of offsets.
> 
> Agreed for the line map functions, it's probably the most confusing
> element of this new API. I'll come up with something.
> 

Ideally I would like to avoid the gpiod_line_mask totally, and always
deal in chip offsets.

The API should be simplest for the most common use cases.
Those far corner cases can be a pain to use, but must still be possible.
I would put the priority on handling the most common cases:
A. all lines
B. one line
C. a subset of lines

I would expect A to be the most common by far - that was all that the
previous API supported, then B and then C.
I had suggested providing A and C, with B being a special case of C.
But I could live with C being implemented as multiple calls to B.

A variadic function could manage all three, with no offsets meaning all
lines?  Though in practice I would probably provide distinct functions
for A and B, and reserve the variadic for C.

Cheers,
Kent.


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-17  7:23     ` Kent Gibson
@ 2021-04-17 11:31       ` Bartosz Golaszewski
  2021-04-18  3:48         ` Kent Gibson
  0 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-17 11:31 UTC (permalink / raw)
  To: Kent Gibson; +Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Sat, Apr 17, 2021 at 9:23 AM Kent Gibson <warthog618@gmail.com> wrote:
>
> On Fri, Apr 16, 2021 at 11:36:08AM +0200, Bartosz Golaszewski wrote:
> > Hi Kent!
> >
> > Thanks a lot for looking at this.
> >
> > On Wed, Apr 14, 2021 at 4:15 PM Kent Gibson <warthog618@gmail.com> wrote:
> > >
> > > On Sat, Apr 10, 2021 at 04:51:51PM +0200, Bartosz Golaszewski wrote:
> > > > This series introduces an entirely reworked API for the core C part of
> > > > libgpiod. It's not fully functional as the bindings are not modified,
> > > > and starting from patch 5, even the tests stop working. This is on
> > > > purpose as I'd like to reach an agreement on the interface before
> > > > spending time on reworking the tests.
> > > >
> > > > My plan for the development of v2.0 is to keep the changes in a separate
> > > > branch until all bits & pieces are complete and then rearrange them into
> > > > a bisectable series that will be merged into the master branch.
> > > >
> > > > A couple points regarding the design of the new API:
> > > > - all objects have become opaque and can only be accessed using dedicated
> > > >   functions
> > > > - line objects as well as bulk containers have been removed
> > > > - line requests are now configured using three types of structures: attributes,
> > > >   line config and request config structures, which follows the kernel API
> > > > - line request handles have now a lifetime separate from the parent chips to
> > > >   leverage the separation of chip and request file descriptors
> > > > - line events are now opaque but they can be stored in a dedicated container
> > > >   so that memory allocations are not necessary everytime an event is read from
> > > >   the kernel
> > > > - the library code has been split into several compilation units for easier
> > > >   maintenance
> > > >
> > > > The new API is completely documented in include/gpiod.h doxygen comments.
> > > >
> > > > Please let me know what you think. I am aware that these patches are huge and
> > > > difficult to review but I'm really only expecting a general API review - all
> > > > other implementation details can be improved later.
> > > >
> > >
> >
> > Just to clarify why I did certain things the way I did: there are
> > certain semi-standardized guidelines to creating low-level C libraries
> > for linux user-space interfaces. They are gathered in a sample project
> > called libabc[1][2][3]. Most plumbing-layer libraries follow these
> > guidelines more or less rigorously. Many low-level programmers will
> > make certain assumptions based on their previous experiences when
> > working with libraries and it's good to be as little confusing as
> > possible.
> >
>
> Fair enough, though I don't see anything in libabc that conflicts with
> my suggestions, nor that require some of your design choices.
>
> > > In that vein, I'll lump my comments here, rather than scattering them
> > > throughout the patches...
> > >
> > > Overall it feels much tighter and clearer than the old API, though that
> > > could just be personal bias, as it is also closer to the new uAPI.
> > >
> > > I find the ownership and lifetime of objects confusing.  I presume you
> > > want to use the reference counting to simplify the object lifecycle, but
> > > that just formalises the possibility that objects are shared - further
> > > confusing the ownership issue.
> >
> > Interesting because in my mind reference counting actually simplifies
> > the life-time of objects - everytime someone (the caller or another
> > object) stores the address of the object, the reference count is
> > increased.
> >
> > Reference counting is a de-facto standard in C libraries. Look at
> > libkmod, libudev, libpulse, all the systemd libraries, the whole of
> > GLib.
> >
>
> I don't doubt that reference counting is out there, I question whether
> it is necessary here.
>
> In my mind reference counting is a tool to be used when you need to
> share objects and it isn't clear who is responsible for freeing
> them.  I don't see any need for sharing here at all - for all objects
> (excluding the gpiod_line_attr that I would remove) it is pretty clear
> who is responsible for them at all times.
>
> > Reference counting makes higher-level programming easier. An object
> > lives as long as there's at least one user. In C++ bindings the
> > objects were refcounted using shared_ptr which causes some issues. Now
> > with refcounting implemented at the C library level, we'll be able to
> > use the PImpl pattern with unique_ptr and still keep the objects
> > copyable.
> >
>
> That still sounds to me like you are sharing objects that shouldn't be
> shared. Or copied.
>
> > To summarize: I don't see many disadvantages to using reference
> > counting other than the fact that my documentation is not yet very
> > detailed and lacks some general information about how reference
> > counting works in libgpiod and needs some clarification.
> >
>
> I understand your argument - which is exactly what I had presumed.
> But I still find the idea of having _unref() acting as the universal free
> function to be distasteful.
>
> > > e.g. gpiod_line_config_add_attribute()
> > > - who owns the attr after the call?  What happens if it is subsequently
> > > modified? Is the attr copied or does ownership pass to the config?
> > > As written it is neither - the attr becomes shared.  How is that
> > > preferable?
> >
> > Yes, it becomes shared. Its reference count is now 2. If the user
> > modifies it, the config will use a modified value at the time of the
> > request. To give you a real-life example of such behavior: in GLib
> > where all GObjects are reference counted, if you pass a reference to a
> > GFile to another user, then set its (GFile's) attribute, they'll be
> > changed for the user sharing the object too. Same with how Python or
> > Java handle references, except you don't need to manage them yourself.
> >
>
> This is all mute if you drop gpiod_line_attr, but I still don't see why
> sharing is preferable in this case.  It feels like the worst of both
> worlds - I need to alloc an attr for every add, and I need to unref them
> as well.
>
> > > Similarly gpiod_line_get_consumer() - who owns the returned pointer and
> > > what is it's lifetime?
> >
> > Since simple strings can't be refcounted and this function returns a
> > pointer to a constant string, the life-time of the string is obviously
> > tied to that of the line_info object. The doc should probably say:
> > Returns a pointer to the string stored in the line_info object.
> >
>
> I would dispute the "obviously", particularly since you seem to expect
> everything else to be unref()ed.
>

No, the ref/unref mechanism is customary for opaque structures. Const
string pointers are always stored in some other object because passing
const char * to free will result in a warning. In libc all functions
that return a malloc()'ed string return char *.

> > > I would prefer the opaque objects to be able to be free()d normally, and
> > > for any calls that involve a change of ownership to explicitly document
> > > that fact.
> > > For objects that require additional setup/cleanup, try to make use of
> > > open()/close() or request()/release() function name pairings.
> > > So gpiod_chip would have open()/close(), gpiod_request_handle would have
> > > request()/release(), and the others would all be new()/free()d.
> > >
> >
> > This makes you use a different set of resource management functions
> > depending on the object. Additionally some objects can't be created -
> > only retrieved from other objects.
> >
>
> Yup, different objects have different lifecycles - even your way.
>
> > Having a simple ref/unref pair for all opaque data types looks more
> > elegant to me. Another real-life example from yesterday: I'm working
> > on a small library to go with the new gpio-sim module (meant as a
> > replacement for libgpio-mockup) and when I tried to use libmount (to
> > verify if configfs is mounted and where) I was super confused by its
> > resource management which is similar to what you suggest but also some
> > resources need to be freed and some transfer their ownership to other
> > objects (without reference counting) and while it's all documented and
> > you can find info on that eventually, I still spent time debugging
> > strange double-free and other corruptions before I got things right. I
> > find libudev and libkmod with their consistent reference counting much
> > more instinctive to use.
> >
>
> I find dealing with repeated frees preferable to dealing with issues
> related to unexpected side-effects when ownership is unclear.
>
> > > Conceptually the config for each line can be considered to be independent
> > > - the uAPI encoding using attributes and masks is only there to keep the
> > > uAPI structs to a manageable size.
> > > At this level you can model it as config per line, and only map
> > > between the uAPI structures when calling the ioctl()s.
> > > So I would drop the gpiod_line_attr, and instead provide accessors and
> > > mutators on the gpiod_line_config for each attr type.
> > > That removes the whole lifecycle issue for attributes, and allows you to
> > > provide a simpler abstraction of the config than that provided in the
> > > uAPI.
> > > For the mutators there can be two flavours, one that sets the config for
> > > the whole set, and another that accepts a subset of applicable lines.
> > > Accessors would provide the config attr for a particular line.
> > > Both accessor and mutator would use chip offsets to identify the lines.
> > >
> >
> > I had exactly this in mind initially but couldn't find an elegant interface.
> >
> > There are two options I see:
> >
> > gpiod_line_config_set_drive(cfg, drive);
> > gpiod_line_config_set_drive_index(cfg, drive, index);
> >
> > or:
> >
> > gpiod_line_config_set_drive(cfg, drive);
> > unsigned int indices[] = { 0, 3, 4, 6 };
> > gpiod_line_config_set_drive_index(cfg, drive, indices);
> >
> > The former would require a separate function call for each line, the
> > former needs a lot of boilerplate code because we'll usually have to
> > alloc & fill the indices array. Eventually I decided to go with
> > attributes but if you have some ideas/suggestions for that, they're
> > welcome!
> >
>
> I generally avoid variadic functions, but this might be an application
> where they make sense.  i.e. make your indices variadic.
> Oh, and make them offsets ;-).
>
> > > Not sure I like merging the direction and edge into the request_type.
> > > I would tend to keep those separate, with set_direction and
> > > set_edge_detection functions, with the latter forcing input direction.
> > >
> >
> > If there's no way we can use output with edge detection, why even
> > bother with allowing the user to try to set output mode for
> > edge_detection or vice-versa? It's just more error checking for no
> > benefit, right? The user will still find out that the line is in input
> > mode with the line_info.
> >
>
> You are adding another concept for the user to grok - the request_type.
> They will need to understand direction and edge detection anyway, why
> add a new concept to the mix?
>
> Oh, and wrt error checking, in my Go library, and as I suggest above,
> setting edge detection overrides any previous direction setting and
> forces input.  Similarly setting output direction cancels any edge
> detection.  So the config presented to the kernel will always be
> consistent and never trigger an EINVAL.  And that is all documented in
> the corresponding config functions.
>
> Similarly the drive settings.
>
> The library policy is last-in-wins, which it can be as there are
> distinct calls for each attribute, whereas at the uAPI they are merged
> into flags.
>
> > > I would rename gpiod_request_config to gpiod_request_options.  Config
> > > is long lived and can be modified, whereas options are one-off.
> > > And it would reduce any chance of confusion with gpiod_line_config.
> > >
> >
> > Not sure about this one. With the current naming, it's being made
> > clear that we have two separate things to configure, the line(s) and
> > the request itself. Naming both "_config" makes a clear distinction.
> > Also: the request config can be reused too for separate requests.
> >
>
> I meant one-off wrt a single request.  i.e. changing the options after
> the lines have been requested can never impact the request, whereas a
> changed config can be applied with the set_config().
>
> > > gpiod_line_mask should highlight that the bits correspond to lines on
> > > the request, in the order provided to gpiod_request_config_set_offsets(),
> > > not line offsets on the chip.  Perhaps the gpiod_request_config or the
> > > gpiod_request_handle should provide the mask functions for a given a
> > > qchip offset, rather than require the user to track the mapping?
> > > Then the gpiod_line_mask can be opaque as well.
> > >
> >
> > Yes, this is one of those things I struggled with. I wasn't sure which
> > one's better because if the user wants to request 64 lines, then set a
> > separate config for 20 of them, it's a lot of function calls if we
> > allow to set line config by offset of a single line, or we get a
> > complicated API with allowing arrays of offsets.
> >
> > Agreed for the line map functions, it's probably the most confusing
> > element of this new API. I'll come up with something.
> >
>
> Ideally I would like to avoid the gpiod_line_mask totally, and always
> deal in chip offsets.
>
> The API should be simplest for the most common use cases.
> Those far corner cases can be a pain to use, but must still be possible.
> I would put the priority on handling the most common cases:
> A. all lines
> B. one line
> C. a subset of lines
>
> I would expect A to be the most common by far - that was all that the
> previous API supported, then B and then C.
> I had suggested providing A and C, with B being a special case of C.
> But I could live with C being implemented as multiple calls to B.
>
> A variadic function could manage all three, with no offsets meaning all
> lines?  Though in practice I would probably provide distinct functions
> for A and B, and reserve the variadic for C.
>
> Cheers,
> Kent.
>

Thank you for your insight and suggestions. You are right about how
the config should be handled and the example with priorities is
spot-on. I'm still not sure about the naming for config structures but
that's a minor detail.

I was on the fence wrt reference counting but then realized that in
C++ or Python we still need to provide a mechanism for unconditional
closing of chips and releasing of requests. For the former it's
because otherwise we'd need to make the object go out of scope
manually (probably by storing it in another object that would be
"closed" -> pointless abstraction) and in the latter case: Python
doesn't even guarantee that the destructor will be called at any
specific point.

So it seems that it is the right way to go after all and these objects
simply will not be shared. In C++ this will be addressed by providing
move constructors and operators, in Python reference counting is
natural so there's no problem. Same for GLib bindings I want to
implement before providing the dbus API.

You haven't commented on the line & watch events. I would love to hear
your opinion on that too.

For v2 I intend to apply the renaming of chip accessors to master and
then squash all patches adding the new API into one because splitting
them doesn't make the reviewing any easier. Except maybe for the line
watch patch which is logically separate.

Best regards,
Bartosz Golaszewski

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-17 11:31       ` Bartosz Golaszewski
@ 2021-04-18  3:48         ` Kent Gibson
  2021-04-18 21:12           ` Bartosz Golaszewski
  0 siblings, 1 reply; 22+ messages in thread
From: Kent Gibson @ 2021-04-18  3:48 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Sat, Apr 17, 2021 at 01:31:01PM +0200, Bartosz Golaszewski wrote:
> On Sat, Apr 17, 2021 at 9:23 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> > On Fri, Apr 16, 2021 at 11:36:08AM +0200, Bartosz Golaszewski wrote:
> > > Hi Kent!
> > >
> > > Thanks a lot for looking at this.
> > >
> > > On Wed, Apr 14, 2021 at 4:15 PM Kent Gibson <warthog618@gmail.com> wrote:
> > > >
> > > > On Sat, Apr 10, 2021 at 04:51:51PM +0200, Bartosz Golaszewski wrote:
> > > > > This series introduces an entirely reworked API for the core C part of
> > > > > libgpiod. It's not fully functional as the bindings are not modified,
> > > > > and starting from patch 5, even the tests stop working. This is on
> > > > > purpose as I'd like to reach an agreement on the interface before
> > > > > spending time on reworking the tests.
> > > > >
> > > > > My plan for the development of v2.0 is to keep the changes in a separate
> > > > > branch until all bits & pieces are complete and then rearrange them into
> > > > > a bisectable series that will be merged into the master branch.
> > > > >
> > > > > A couple points regarding the design of the new API:
> > > > > - all objects have become opaque and can only be accessed using dedicated
> > > > >   functions
> > > > > - line objects as well as bulk containers have been removed
> > > > > - line requests are now configured using three types of structures: attributes,
> > > > >   line config and request config structures, which follows the kernel API
> > > > > - line request handles have now a lifetime separate from the parent chips to
> > > > >   leverage the separation of chip and request file descriptors
> > > > > - line events are now opaque but they can be stored in a dedicated container
> > > > >   so that memory allocations are not necessary everytime an event is read from
> > > > >   the kernel
> > > > > - the library code has been split into several compilation units for easier
> > > > >   maintenance
> > > > >
> > > > > The new API is completely documented in include/gpiod.h doxygen comments.
> > > > >
> > > > > Please let me know what you think. I am aware that these patches are huge and
> > > > > difficult to review but I'm really only expecting a general API review - all
> > > > > other implementation details can be improved later.
> > > > >
> > > >
> > >
> > > Just to clarify why I did certain things the way I did: there are
> > > certain semi-standardized guidelines to creating low-level C libraries
> > > for linux user-space interfaces. They are gathered in a sample project
> > > called libabc[1][2][3]. Most plumbing-layer libraries follow these
> > > guidelines more or less rigorously. Many low-level programmers will
> > > make certain assumptions based on their previous experiences when
> > > working with libraries and it's good to be as little confusing as
> > > possible.
> > >
> >
> > Fair enough, though I don't see anything in libabc that conflicts with
> > my suggestions, nor that require some of your design choices.
> >
> > > > In that vein, I'll lump my comments here, rather than scattering them
> > > > throughout the patches...
> > > >
> > > > Overall it feels much tighter and clearer than the old API, though that
> > > > could just be personal bias, as it is also closer to the new uAPI.
> > > >
> > > > I find the ownership and lifetime of objects confusing.  I presume you
> > > > want to use the reference counting to simplify the object lifecycle, but
> > > > that just formalises the possibility that objects are shared - further
> > > > confusing the ownership issue.
> > >
> > > Interesting because in my mind reference counting actually simplifies
> > > the life-time of objects - everytime someone (the caller or another
> > > object) stores the address of the object, the reference count is
> > > increased.
> > >
> > > Reference counting is a de-facto standard in C libraries. Look at
> > > libkmod, libudev, libpulse, all the systemd libraries, the whole of
> > > GLib.
> > >
> >
> > I don't doubt that reference counting is out there, I question whether
> > it is necessary here.
> >
> > In my mind reference counting is a tool to be used when you need to
> > share objects and it isn't clear who is responsible for freeing
> > them.  I don't see any need for sharing here at all - for all objects
> > (excluding the gpiod_line_attr that I would remove) it is pretty clear
> > who is responsible for them at all times.
> >
> > > Reference counting makes higher-level programming easier. An object
> > > lives as long as there's at least one user. In C++ bindings the
> > > objects were refcounted using shared_ptr which causes some issues. Now
> > > with refcounting implemented at the C library level, we'll be able to
> > > use the PImpl pattern with unique_ptr and still keep the objects
> > > copyable.
> > >
> >
> > That still sounds to me like you are sharing objects that shouldn't be
> > shared. Or copied.
> >
> > > To summarize: I don't see many disadvantages to using reference
> > > counting other than the fact that my documentation is not yet very
> > > detailed and lacks some general information about how reference
> > > counting works in libgpiod and needs some clarification.
> > >
> >
> > I understand your argument - which is exactly what I had presumed.
> > But I still find the idea of having _unref() acting as the universal free
> > function to be distasteful.
> >
> > > > e.g. gpiod_line_config_add_attribute()
> > > > - who owns the attr after the call?  What happens if it is subsequently
> > > > modified? Is the attr copied or does ownership pass to the config?
> > > > As written it is neither - the attr becomes shared.  How is that
> > > > preferable?
> > >
> > > Yes, it becomes shared. Its reference count is now 2. If the user
> > > modifies it, the config will use a modified value at the time of the
> > > request. To give you a real-life example of such behavior: in GLib
> > > where all GObjects are reference counted, if you pass a reference to a
> > > GFile to another user, then set its (GFile's) attribute, they'll be
> > > changed for the user sharing the object too. Same with how Python or
> > > Java handle references, except you don't need to manage them yourself.
> > >
> >
> > This is all mute if you drop gpiod_line_attr, but I still don't see why
> > sharing is preferable in this case.  It feels like the worst of both
> > worlds - I need to alloc an attr for every add, and I need to unref them
> > as well.
> >
> > > > Similarly gpiod_line_get_consumer() - who owns the returned pointer and
> > > > what is it's lifetime?
> > >
> > > Since simple strings can't be refcounted and this function returns a
> > > pointer to a constant string, the life-time of the string is obviously
> > > tied to that of the line_info object. The doc should probably say:
> > > Returns a pointer to the string stored in the line_info object.
> > >
> >
> > I would dispute the "obviously", particularly since you seem to expect
> > everything else to be unref()ed.
> >
> 
> No, the ref/unref mechanism is customary for opaque structures. Const
> string pointers are always stored in some other object because passing
> const char * to free will result in a warning. In libc all functions
> that return a malloc()'ed string return char *.
> 
> > > > I would prefer the opaque objects to be able to be free()d normally, and
> > > > for any calls that involve a change of ownership to explicitly document
> > > > that fact.
> > > > For objects that require additional setup/cleanup, try to make use of
> > > > open()/close() or request()/release() function name pairings.
> > > > So gpiod_chip would have open()/close(), gpiod_request_handle would have
> > > > request()/release(), and the others would all be new()/free()d.
> > > >
> > >
> > > This makes you use a different set of resource management functions
> > > depending on the object. Additionally some objects can't be created -
> > > only retrieved from other objects.
> > >
> >
> > Yup, different objects have different lifecycles - even your way.
> >
> > > Having a simple ref/unref pair for all opaque data types looks more
> > > elegant to me. Another real-life example from yesterday: I'm working
> > > on a small library to go with the new gpio-sim module (meant as a
> > > replacement for libgpio-mockup) and when I tried to use libmount (to
> > > verify if configfs is mounted and where) I was super confused by its
> > > resource management which is similar to what you suggest but also some
> > > resources need to be freed and some transfer their ownership to other
> > > objects (without reference counting) and while it's all documented and
> > > you can find info on that eventually, I still spent time debugging
> > > strange double-free and other corruptions before I got things right. I
> > > find libudev and libkmod with their consistent reference counting much
> > > more instinctive to use.
> > >
> >
> > I find dealing with repeated frees preferable to dealing with issues
> > related to unexpected side-effects when ownership is unclear.
> >
> > > > Conceptually the config for each line can be considered to be independent
> > > > - the uAPI encoding using attributes and masks is only there to keep the
> > > > uAPI structs to a manageable size.
> > > > At this level you can model it as config per line, and only map
> > > > between the uAPI structures when calling the ioctl()s.
> > > > So I would drop the gpiod_line_attr, and instead provide accessors and
> > > > mutators on the gpiod_line_config for each attr type.
> > > > That removes the whole lifecycle issue for attributes, and allows you to
> > > > provide a simpler abstraction of the config than that provided in the
> > > > uAPI.
> > > > For the mutators there can be two flavours, one that sets the config for
> > > > the whole set, and another that accepts a subset of applicable lines.
> > > > Accessors would provide the config attr for a particular line.
> > > > Both accessor and mutator would use chip offsets to identify the lines.
> > > >
> > >
> > > I had exactly this in mind initially but couldn't find an elegant interface.
> > >
> > > There are two options I see:
> > >
> > > gpiod_line_config_set_drive(cfg, drive);
> > > gpiod_line_config_set_drive_index(cfg, drive, index);
> > >
> > > or:
> > >
> > > gpiod_line_config_set_drive(cfg, drive);
> > > unsigned int indices[] = { 0, 3, 4, 6 };
> > > gpiod_line_config_set_drive_index(cfg, drive, indices);
> > >
> > > The former would require a separate function call for each line, the
> > > former needs a lot of boilerplate code because we'll usually have to
> > > alloc & fill the indices array. Eventually I decided to go with
> > > attributes but if you have some ideas/suggestions for that, they're
> > > welcome!
> > >
> >
> > I generally avoid variadic functions, but this might be an application
> > where they make sense.  i.e. make your indices variadic.
> > Oh, and make them offsets ;-).
> >
> > > > Not sure I like merging the direction and edge into the request_type.
> > > > I would tend to keep those separate, with set_direction and
> > > > set_edge_detection functions, with the latter forcing input direction.
> > > >
> > >
> > > If there's no way we can use output with edge detection, why even
> > > bother with allowing the user to try to set output mode for
> > > edge_detection or vice-versa? It's just more error checking for no
> > > benefit, right? The user will still find out that the line is in input
> > > mode with the line_info.
> > >
> >
> > You are adding another concept for the user to grok - the request_type.
> > They will need to understand direction and edge detection anyway, why
> > add a new concept to the mix?
> >
> > Oh, and wrt error checking, in my Go library, and as I suggest above,
> > setting edge detection overrides any previous direction setting and
> > forces input.  Similarly setting output direction cancels any edge
> > detection.  So the config presented to the kernel will always be
> > consistent and never trigger an EINVAL.  And that is all documented in
> > the corresponding config functions.
> >
> > Similarly the drive settings.
> >
> > The library policy is last-in-wins, which it can be as there are
> > distinct calls for each attribute, whereas at the uAPI they are merged
> > into flags.
> >
> > > > I would rename gpiod_request_config to gpiod_request_options.  Config
> > > > is long lived and can be modified, whereas options are one-off.
> > > > And it would reduce any chance of confusion with gpiod_line_config.
> > > >
> > >
> > > Not sure about this one. With the current naming, it's being made
> > > clear that we have two separate things to configure, the line(s) and
> > > the request itself. Naming both "_config" makes a clear distinction.
> > > Also: the request config can be reused too for separate requests.
> > >
> >
> > I meant one-off wrt a single request.  i.e. changing the options after
> > the lines have been requested can never impact the request, whereas a
> > changed config can be applied with the set_config().
> >
> > > > gpiod_line_mask should highlight that the bits correspond to lines on
> > > > the request, in the order provided to gpiod_request_config_set_offsets(),
> > > > not line offsets on the chip.  Perhaps the gpiod_request_config or the
> > > > gpiod_request_handle should provide the mask functions for a given a
> > > > qchip offset, rather than require the user to track the mapping?
> > > > Then the gpiod_line_mask can be opaque as well.
> > > >
> > >
> > > Yes, this is one of those things I struggled with. I wasn't sure which
> > > one's better because if the user wants to request 64 lines, then set a
> > > separate config for 20 of them, it's a lot of function calls if we
> > > allow to set line config by offset of a single line, or we get a
> > > complicated API with allowing arrays of offsets.
> > >
> > > Agreed for the line map functions, it's probably the most confusing
> > > element of this new API. I'll come up with something.
> > >
> >
> > Ideally I would like to avoid the gpiod_line_mask totally, and always
> > deal in chip offsets.
> >
> > The API should be simplest for the most common use cases.
> > Those far corner cases can be a pain to use, but must still be possible.
> > I would put the priority on handling the most common cases:
> > A. all lines
> > B. one line
> > C. a subset of lines
> >
> > I would expect A to be the most common by far - that was all that the
> > previous API supported, then B and then C.
> > I had suggested providing A and C, with B being a special case of C.
> > But I could live with C being implemented as multiple calls to B.
> >
> > A variadic function could manage all three, with no offsets meaning all
> > lines?  Though in practice I would probably provide distinct functions
> > for A and B, and reserve the variadic for C.
> >
> > Cheers,
> > Kent.
> >
> 
> Thank you for your insight and suggestions. You are right about how
> the config should be handled and the example with priorities is
> spot-on. I'm still not sure about the naming for config structures but
> that's a minor detail.
> 

I forgot to add that wrt the config mutators, you need to allow
overriding of existing config, rather than returning an error on
conflict, so that you can change config for the set_config ioctl().
Hence the last-in-wins approach.  And as a consequence the mutator is
always right and so needs no return code.

And you might want to add a copy() for config to allow the user to
easily create two slightly different configurations.

> I was on the fence wrt reference counting but then realized that in
> C++ or Python we still need to provide a mechanism for unconditional
> closing of chips and releasing of requests. For the former it's
> because otherwise we'd need to make the object go out of scope
> manually (probably by storing it in another object that would be
> "closed" -> pointless abstraction) and in the latter case: Python
> doesn't even guarantee that the destructor will be called at any
> specific point.
> 

Hmmm, ok, I was assuming the C++ bindings would wrap the C objects in C++
objects, and the C++ destructor would release any associated resources.

For Python I would probably go with an explicit close() or equivalent.
My Go library has to use that approach as there are no destructors in Go.

> So it seems that it is the right way to go after all and these objects
> simply will not be shared. In C++ this will be addressed by providing
> move constructors and operators, in Python reference counting is
> natural so there's no problem. Same for GLib bindings I want to
> implement before providing the dbus API.
> 
> You haven't commented on the line & watch events. I would love to hear
> your opinion on that too.
> 

Didn't have any major problems with them on the first pass - nothing
worth commenting on anyway.

I presume that the info events don't get a buffer as they are
expected to be low rate.

A zero timeout in the wait() will not block, and just return the event
availability?

Perhaps the wait() and read() should be strictly orthogonal, i.e. the
read() should never block (blocking functions are bad, as per libabc).
At least the wait() will eventually timeout (assuming a zero timeout
doesn't block forever).
Or the two could be combined?

Oh, and in the @return for gpiod_line_event_buffer_copy_event(),
"decreases" should be "decreased".

> For v2 I intend to apply the renaming of chip accessors to master and
> then squash all patches adding the new API into one because splitting
> them doesn't make the reviewing any easier. Except maybe for the line
> watch patch which is logically separate.
> 

Fair enough. In this case I applied all the patches and looked at the
resulting gpiod.h, as I wasn't terribly interested in the changes - only
the final result.

Cheers,
Kent.

> Best regards,
> Bartosz Golaszewski

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-18  3:48         ` Kent Gibson
@ 2021-04-18 21:12           ` Bartosz Golaszewski
  2021-04-19  1:17             ` Kent Gibson
  0 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-18 21:12 UTC (permalink / raw)
  To: Kent Gibson; +Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Sun, Apr 18, 2021 at 5:48 AM Kent Gibson <warthog618@gmail.com> wrote:
>
> On Sat, Apr 17, 2021 at 01:31:01PM +0200, Bartosz Golaszewski wrote:
> > On Sat, Apr 17, 2021 at 9:23 AM Kent Gibson <warthog618@gmail.com> wrote:
> > >
> > > On Fri, Apr 16, 2021 at 11:36:08AM +0200, Bartosz Golaszewski wrote:
> > > > Hi Kent!
> > > >
> > > > Thanks a lot for looking at this.
> > > >
> > > > On Wed, Apr 14, 2021 at 4:15 PM Kent Gibson <warthog618@gmail.com> wrote:
> > > > >
> > > > > On Sat, Apr 10, 2021 at 04:51:51PM +0200, Bartosz Golaszewski wrote:
> > > > > > This series introduces an entirely reworked API for the core C part of
> > > > > > libgpiod. It's not fully functional as the bindings are not modified,
> > > > > > and starting from patch 5, even the tests stop working. This is on
> > > > > > purpose as I'd like to reach an agreement on the interface before
> > > > > > spending time on reworking the tests.
> > > > > >
> > > > > > My plan for the development of v2.0 is to keep the changes in a separate
> > > > > > branch until all bits & pieces are complete and then rearrange them into
> > > > > > a bisectable series that will be merged into the master branch.
> > > > > >
> > > > > > A couple points regarding the design of the new API:
> > > > > > - all objects have become opaque and can only be accessed using dedicated
> > > > > >   functions
> > > > > > - line objects as well as bulk containers have been removed
> > > > > > - line requests are now configured using three types of structures: attributes,
> > > > > >   line config and request config structures, which follows the kernel API
> > > > > > - line request handles have now a lifetime separate from the parent chips to
> > > > > >   leverage the separation of chip and request file descriptors
> > > > > > - line events are now opaque but they can be stored in a dedicated container
> > > > > >   so that memory allocations are not necessary everytime an event is read from
> > > > > >   the kernel
> > > > > > - the library code has been split into several compilation units for easier
> > > > > >   maintenance
> > > > > >
> > > > > > The new API is completely documented in include/gpiod.h doxygen comments.
> > > > > >
> > > > > > Please let me know what you think. I am aware that these patches are huge and
> > > > > > difficult to review but I'm really only expecting a general API review - all
> > > > > > other implementation details can be improved later.
> > > > > >
> > > > >
> > > >
> > > > Just to clarify why I did certain things the way I did: there are
> > > > certain semi-standardized guidelines to creating low-level C libraries
> > > > for linux user-space interfaces. They are gathered in a sample project
> > > > called libabc[1][2][3]. Most plumbing-layer libraries follow these
> > > > guidelines more or less rigorously. Many low-level programmers will
> > > > make certain assumptions based on their previous experiences when
> > > > working with libraries and it's good to be as little confusing as
> > > > possible.
> > > >
> > >
> > > Fair enough, though I don't see anything in libabc that conflicts with
> > > my suggestions, nor that require some of your design choices.
> > >
> > > > > In that vein, I'll lump my comments here, rather than scattering them
> > > > > throughout the patches...
> > > > >
> > > > > Overall it feels much tighter and clearer than the old API, though that
> > > > > could just be personal bias, as it is also closer to the new uAPI.
> > > > >
> > > > > I find the ownership and lifetime of objects confusing.  I presume you
> > > > > want to use the reference counting to simplify the object lifecycle, but
> > > > > that just formalises the possibility that objects are shared - further
> > > > > confusing the ownership issue.
> > > >
> > > > Interesting because in my mind reference counting actually simplifies
> > > > the life-time of objects - everytime someone (the caller or another
> > > > object) stores the address of the object, the reference count is
> > > > increased.
> > > >
> > > > Reference counting is a de-facto standard in C libraries. Look at
> > > > libkmod, libudev, libpulse, all the systemd libraries, the whole of
> > > > GLib.
> > > >
> > >
> > > I don't doubt that reference counting is out there, I question whether
> > > it is necessary here.
> > >
> > > In my mind reference counting is a tool to be used when you need to
> > > share objects and it isn't clear who is responsible for freeing
> > > them.  I don't see any need for sharing here at all - for all objects
> > > (excluding the gpiod_line_attr that I would remove) it is pretty clear
> > > who is responsible for them at all times.
> > >
> > > > Reference counting makes higher-level programming easier. An object
> > > > lives as long as there's at least one user. In C++ bindings the
> > > > objects were refcounted using shared_ptr which causes some issues. Now
> > > > with refcounting implemented at the C library level, we'll be able to
> > > > use the PImpl pattern with unique_ptr and still keep the objects
> > > > copyable.
> > > >
> > >
> > > That still sounds to me like you are sharing objects that shouldn't be
> > > shared. Or copied.
> > >
> > > > To summarize: I don't see many disadvantages to using reference
> > > > counting other than the fact that my documentation is not yet very
> > > > detailed and lacks some general information about how reference
> > > > counting works in libgpiod and needs some clarification.
> > > >
> > >
> > > I understand your argument - which is exactly what I had presumed.
> > > But I still find the idea of having _unref() acting as the universal free
> > > function to be distasteful.
> > >
> > > > > e.g. gpiod_line_config_add_attribute()
> > > > > - who owns the attr after the call?  What happens if it is subsequently
> > > > > modified? Is the attr copied or does ownership pass to the config?
> > > > > As written it is neither - the attr becomes shared.  How is that
> > > > > preferable?
> > > >
> > > > Yes, it becomes shared. Its reference count is now 2. If the user
> > > > modifies it, the config will use a modified value at the time of the
> > > > request. To give you a real-life example of such behavior: in GLib
> > > > where all GObjects are reference counted, if you pass a reference to a
> > > > GFile to another user, then set its (GFile's) attribute, they'll be
> > > > changed for the user sharing the object too. Same with how Python or
> > > > Java handle references, except you don't need to manage them yourself.
> > > >
> > >
> > > This is all mute if you drop gpiod_line_attr, but I still don't see why
> > > sharing is preferable in this case.  It feels like the worst of both
> > > worlds - I need to alloc an attr for every add, and I need to unref them
> > > as well.
> > >
> > > > > Similarly gpiod_line_get_consumer() - who owns the returned pointer and
> > > > > what is it's lifetime?
> > > >
> > > > Since simple strings can't be refcounted and this function returns a
> > > > pointer to a constant string, the life-time of the string is obviously
> > > > tied to that of the line_info object. The doc should probably say:
> > > > Returns a pointer to the string stored in the line_info object.
> > > >
> > >
> > > I would dispute the "obviously", particularly since you seem to expect
> > > everything else to be unref()ed.
> > >
> >
> > No, the ref/unref mechanism is customary for opaque structures. Const
> > string pointers are always stored in some other object because passing
> > const char * to free will result in a warning. In libc all functions
> > that return a malloc()'ed string return char *.
> >
> > > > > I would prefer the opaque objects to be able to be free()d normally, and
> > > > > for any calls that involve a change of ownership to explicitly document
> > > > > that fact.
> > > > > For objects that require additional setup/cleanup, try to make use of
> > > > > open()/close() or request()/release() function name pairings.
> > > > > So gpiod_chip would have open()/close(), gpiod_request_handle would have
> > > > > request()/release(), and the others would all be new()/free()d.
> > > > >
> > > >
> > > > This makes you use a different set of resource management functions
> > > > depending on the object. Additionally some objects can't be created -
> > > > only retrieved from other objects.
> > > >
> > >
> > > Yup, different objects have different lifecycles - even your way.
> > >
> > > > Having a simple ref/unref pair for all opaque data types looks more
> > > > elegant to me. Another real-life example from yesterday: I'm working
> > > > on a small library to go with the new gpio-sim module (meant as a
> > > > replacement for libgpio-mockup) and when I tried to use libmount (to
> > > > verify if configfs is mounted and where) I was super confused by its
> > > > resource management which is similar to what you suggest but also some
> > > > resources need to be freed and some transfer their ownership to other
> > > > objects (without reference counting) and while it's all documented and
> > > > you can find info on that eventually, I still spent time debugging
> > > > strange double-free and other corruptions before I got things right. I
> > > > find libudev and libkmod with their consistent reference counting much
> > > > more instinctive to use.
> > > >
> > >
> > > I find dealing with repeated frees preferable to dealing with issues
> > > related to unexpected side-effects when ownership is unclear.
> > >
> > > > > Conceptually the config for each line can be considered to be independent
> > > > > - the uAPI encoding using attributes and masks is only there to keep the
> > > > > uAPI structs to a manageable size.
> > > > > At this level you can model it as config per line, and only map
> > > > > between the uAPI structures when calling the ioctl()s.
> > > > > So I would drop the gpiod_line_attr, and instead provide accessors and
> > > > > mutators on the gpiod_line_config for each attr type.
> > > > > That removes the whole lifecycle issue for attributes, and allows you to
> > > > > provide a simpler abstraction of the config than that provided in the
> > > > > uAPI.
> > > > > For the mutators there can be two flavours, one that sets the config for
> > > > > the whole set, and another that accepts a subset of applicable lines.
> > > > > Accessors would provide the config attr for a particular line.
> > > > > Both accessor and mutator would use chip offsets to identify the lines.
> > > > >
> > > >
> > > > I had exactly this in mind initially but couldn't find an elegant interface.
> > > >
> > > > There are two options I see:
> > > >
> > > > gpiod_line_config_set_drive(cfg, drive);
> > > > gpiod_line_config_set_drive_index(cfg, drive, index);
> > > >
> > > > or:
> > > >
> > > > gpiod_line_config_set_drive(cfg, drive);
> > > > unsigned int indices[] = { 0, 3, 4, 6 };
> > > > gpiod_line_config_set_drive_index(cfg, drive, indices);
> > > >
> > > > The former would require a separate function call for each line, the
> > > > former needs a lot of boilerplate code because we'll usually have to
> > > > alloc & fill the indices array. Eventually I decided to go with
> > > > attributes but if you have some ideas/suggestions for that, they're
> > > > welcome!
> > > >
> > >
> > > I generally avoid variadic functions, but this might be an application
> > > where they make sense.  i.e. make your indices variadic.
> > > Oh, and make them offsets ;-).
> > >
> > > > > Not sure I like merging the direction and edge into the request_type.
> > > > > I would tend to keep those separate, with set_direction and
> > > > > set_edge_detection functions, with the latter forcing input direction.
> > > > >
> > > >
> > > > If there's no way we can use output with edge detection, why even
> > > > bother with allowing the user to try to set output mode for
> > > > edge_detection or vice-versa? It's just more error checking for no
> > > > benefit, right? The user will still find out that the line is in input
> > > > mode with the line_info.
> > > >
> > >
> > > You are adding another concept for the user to grok - the request_type.
> > > They will need to understand direction and edge detection anyway, why
> > > add a new concept to the mix?
> > >
> > > Oh, and wrt error checking, in my Go library, and as I suggest above,
> > > setting edge detection overrides any previous direction setting and
> > > forces input.  Similarly setting output direction cancels any edge
> > > detection.  So the config presented to the kernel will always be
> > > consistent and never trigger an EINVAL.  And that is all documented in
> > > the corresponding config functions.
> > >
> > > Similarly the drive settings.
> > >
> > > The library policy is last-in-wins, which it can be as there are
> > > distinct calls for each attribute, whereas at the uAPI they are merged
> > > into flags.
> > >
> > > > > I would rename gpiod_request_config to gpiod_request_options.  Config
> > > > > is long lived and can be modified, whereas options are one-off.
> > > > > And it would reduce any chance of confusion with gpiod_line_config.
> > > > >
> > > >
> > > > Not sure about this one. With the current naming, it's being made
> > > > clear that we have two separate things to configure, the line(s) and
> > > > the request itself. Naming both "_config" makes a clear distinction.
> > > > Also: the request config can be reused too for separate requests.
> > > >
> > >
> > > I meant one-off wrt a single request.  i.e. changing the options after
> > > the lines have been requested can never impact the request, whereas a
> > > changed config can be applied with the set_config().
> > >
> > > > > gpiod_line_mask should highlight that the bits correspond to lines on
> > > > > the request, in the order provided to gpiod_request_config_set_offsets(),
> > > > > not line offsets on the chip.  Perhaps the gpiod_request_config or the
> > > > > gpiod_request_handle should provide the mask functions for a given a
> > > > > qchip offset, rather than require the user to track the mapping?
> > > > > Then the gpiod_line_mask can be opaque as well.
> > > > >
> > > >
> > > > Yes, this is one of those things I struggled with. I wasn't sure which
> > > > one's better because if the user wants to request 64 lines, then set a
> > > > separate config for 20 of them, it's a lot of function calls if we
> > > > allow to set line config by offset of a single line, or we get a
> > > > complicated API with allowing arrays of offsets.
> > > >
> > > > Agreed for the line map functions, it's probably the most confusing
> > > > element of this new API. I'll come up with something.
> > > >
> > >
> > > Ideally I would like to avoid the gpiod_line_mask totally, and always
> > > deal in chip offsets.
> > >
> > > The API should be simplest for the most common use cases.
> > > Those far corner cases can be a pain to use, but must still be possible.
> > > I would put the priority on handling the most common cases:
> > > A. all lines
> > > B. one line
> > > C. a subset of lines
> > >
> > > I would expect A to be the most common by far - that was all that the
> > > previous API supported, then B and then C.
> > > I had suggested providing A and C, with B being a special case of C.
> > > But I could live with C being implemented as multiple calls to B.
> > >
> > > A variadic function could manage all three, with no offsets meaning all
> > > lines?  Though in practice I would probably provide distinct functions
> > > for A and B, and reserve the variadic for C.
> > >
> > > Cheers,
> > > Kent.
> > >
> >
> > Thank you for your insight and suggestions. You are right about how
> > the config should be handled and the example with priorities is
> > spot-on. I'm still not sure about the naming for config structures but
> > that's a minor detail.
> >
>
> I forgot to add that wrt the config mutators, you need to allow
> overriding of existing config, rather than returning an error on
> conflict, so that you can change config for the set_config ioctl().
> Hence the last-in-wins approach.  And as a consequence the mutator is
> always right and so needs no return code.
>

This sounds good in theory but how do we handle a situation that
requires more than 10 attributes? Override the first one? The last
one? What if the line offsets passed to the request config repeat
themselves? I think some sanitization of input is in order.

Regarding offsets: I was thinking about how to approach referring to
lines in configs and requests by offsets only (in order to hide the
whole masking logic) and while for a request (for example: when
setting/reading line) this is straightforward (as long as we make sure
the offsets are never duplicated), the line config structure doesn't
really know the concept of offsets. So when we set a config option for
a specific line, we need to carry the offset information somehow in
the structure until the request is actually made. How do you deal with
this in your library? Did you expose any of the bitmap details in your
API? Can we really avoid dealing with indexing of lines in a request?

> And you might want to add a copy() for config to allow the user to
> easily create two slightly different configurations.
>
> > I was on the fence wrt reference counting but then realized that in
> > C++ or Python we still need to provide a mechanism for unconditional
> > closing of chips and releasing of requests. For the former it's
> > because otherwise we'd need to make the object go out of scope
> > manually (probably by storing it in another object that would be
> > "closed" -> pointless abstraction) and in the latter case: Python
> > doesn't even guarantee that the destructor will be called at any
> > specific point.
> >
>
> Hmmm, ok, I was assuming the C++ bindings would wrap the C objects in C++
> objects, and the C++ destructor would release any associated resources.
>

Yes, but what if the user wants to close the chip or release the
request without the underlying object going out of scope? I think we
need to keep that possibility.

> For Python I would probably go with an explicit close() or equivalent.
> My Go library has to use that approach as there are no destructors in Go.
>
> > So it seems that it is the right way to go after all and these objects
> > simply will not be shared. In C++ this will be addressed by providing
> > move constructors and operators, in Python reference counting is
> > natural so there's no problem. Same for GLib bindings I want to
> > implement before providing the dbus API.
> >
> > You haven't commented on the line & watch events. I would love to hear
> > your opinion on that too.
> >
>
> Didn't have any major problems with them on the first pass - nothing
> worth commenting on anyway.
>
> I presume that the info events don't get a buffer as they are
> expected to be low rate.
>

Indeed.

> A zero timeout in the wait() will not block, and just return the event
> availability?
>

Yes.

> Perhaps the wait() and read() should be strictly orthogonal, i.e. the
> read() should never block (blocking functions are bad, as per libabc).
> At least the wait() will eventually timeout (assuming a zero timeout
> doesn't block forever).
> Or the two could be combined?
>

Good point, but I'd prefer to keep that logic separate. I've never
seen a combined poll()/read() in any lib. I will see about that
non-blocking read() though.

Bart

> Oh, and in the @return for gpiod_line_event_buffer_copy_event(),
> "decreases" should be "decreased".
>
> > For v2 I intend to apply the renaming of chip accessors to master and
> > then squash all patches adding the new API into one because splitting
> > them doesn't make the reviewing any easier. Except maybe for the line
> > watch patch which is logically separate.
> >
>
> Fair enough. In this case I applied all the patches and looked at the
> resulting gpiod.h, as I wasn't terribly interested in the changes - only
> the final result.
>
> Cheers,
> Kent.
>
> > Best regards,
> > Bartosz Golaszewski

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-18 21:12           ` Bartosz Golaszewski
@ 2021-04-19  1:17             ` Kent Gibson
  2021-04-21 20:04               ` Bartosz Golaszewski
  0 siblings, 1 reply; 22+ messages in thread
From: Kent Gibson @ 2021-04-19  1:17 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Sun, Apr 18, 2021 at 11:12:24PM +0200, Bartosz Golaszewski wrote:
> On Sun, Apr 18, 2021 at 5:48 AM Kent Gibson <warthog618@gmail.com> wrote:
> >

[snip!]

> >
> > I forgot to add that wrt the config mutators, you need to allow
> > overriding of existing config, rather than returning an error on
> > conflict, so that you can change config for the set_config ioctl().
> > Hence the last-in-wins approach.  And as a consequence the mutator is
> > always right and so needs no return code.
> >
> 
> This sounds good in theory but how do we handle a situation that
> requires more than 10 attributes? Override the first one? The last
> one? What if the line offsets passed to the request config repeat
> themselves? I think some sanitization of input is in order.
> 

Repeating of lines is equivalent to repeatedly setting a bit, so the
subsequent instances are ignored. In practice I don't even need to check
- if the user includes the line multiple times then it gets set multiple
times - to the same thing.

The case where a complex config can't be mapped to the uAPI, e.g. due to
too many attributes on too many lines, is handled at the time of the
request_lines() or set_config() itself when that mapping is performed.
Those will return an "overly complex config" error.

> Regarding offsets: I was thinking about how to approach referring to
> lines in configs and requests by offsets only (in order to hide the
> whole masking logic) and while for a request (for example: when
> setting/reading line) this is straightforward (as long as we make sure
> the offsets are never duplicated), the line config structure doesn't
> really know the concept of offsets. So when we set a config option for
> a specific line, we need to carry the offset information somehow in
> the structure until the request is actually made. How do you deal with
> this in your library? Did you expose any of the bitmap details in your
> API? Can we really avoid dealing with indexing of lines in a request?
> 

In the request config I use a map of offset to line config to avoid 
duplication. A config change that alters any existing setting just
overwrites the old.

The line config is similar to your struct gpiod_line_config.
The line config for a particular line is only created and added to the
map if there is a config change specific to that line.
Each attribute has a "not set" value, in which case the request-wide
default is used.

The request-wide default config is stored separately from the map.
And there is a function to reset a line config back to the default,
i.e. drop that line config from the map.

The request_lines() and set_config(), that accept the config, also have
the list of offsets available (provided to the request_lines() and
subsequently stored as part of the request struct for the set_config())
and so can map from offsets to indices to build the bitmap.
The bitmap and indices themselves are never exposed.

That is a high level description - the details are actually a little
different as the Go implementation uses functional options, so the
initial config settings become parameters to the request, and bundles
the config into the request object itself.

> > And you might want to add a copy() for config to allow the user to
> > easily create two slightly different configurations.
> >
> > > I was on the fence wrt reference counting but then realized that in
> > > C++ or Python we still need to provide a mechanism for unconditional
> > > closing of chips and releasing of requests. For the former it's
> > > because otherwise we'd need to make the object go out of scope
> > > manually (probably by storing it in another object that would be
> > > "closed" -> pointless abstraction) and in the latter case: Python
> > > doesn't even guarantee that the destructor will be called at any
> > > specific point.
> > >
> >
> > Hmmm, ok, I was assuming the C++ bindings would wrap the C objects in C++
> > objects, and the C++ destructor would release any associated resources.
> >
> 
> Yes, but what if the user wants to close the chip or release the
> request without the underlying object going out of scope? I think we
> need to keep that possibility.
> 

Then you also provide a close() method.  They aren't mutually exclusive.

Cheers,
Kent.


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-19  1:17             ` Kent Gibson
@ 2021-04-21 20:04               ` Bartosz Golaszewski
  2021-04-22  2:32                 ` Kent Gibson
  0 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-21 20:04 UTC (permalink / raw)
  To: Kent Gibson; +Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Mon, Apr 19, 2021 at 3:17 AM Kent Gibson <warthog618@gmail.com> wrote:
>

[snip -> long discussion on the libgpiod C API]

Hi Kent,

I was working on the next iteration of the code and I'm struggling
with the implementation of some elements without the concept of
attributes.

So I initially liked the idea of variadic functions but they won't
work for language bindings so that's a no go. On that note: I wanted
to get some inspiration from your go library but your elegant API
makes use of go features (like interfaces) we don't have in C.

The problem we have is basically about implementing primary and
secondary line configuration objects - where the primary contains the
default config for all requested lines and the secondary can override
certain config options (should zeroed values for enumerated types mean
- don't override?) for certain lines.

The basic line config structure (let's call it struct
gpiod_line_config) can be very simple and have the following mutators:

struct gpiod_line_config *cfg = gpiod_line_config_new();

gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_OUTPUT);
gpiod_line_config_set_active_low(cfg, true);

and so on for for drive, bias, edge, debounce, realtime clock.

Output values would be set like this:

unsigned int values[] = { 0, 1, 1, 0 }, num_lines = 4;
gpiod_line_config_set_output_values(cfg, num_lines, values);

One can imagine a simple request with the same config for all lines as:

gpiod_chip_request_lines(chip, req_cfg, line_cfg);

Where req_cfg configures request-specific options, and line_cfg
contains the above line config. I'm still not convinced that
gpiod_request_options is the better name, I think I prefer the
juxtaposition of the two names: line_config and request_config.

Now how do we pass a composite line config with overridden values in C
without interfaces etc.?

One idea I have is to add a new object called struct
gpiod_line_config_ext (for extended) that would take one primary
config and an arbitrary number of secondary configs with the following
example use-case:

struct gpiod_line_config_ext *ext_cfg = gpiod_line_config_ext_new();
unsigned int offsets[] = { 2, 3 };

/* Add the default config for this request. */
gpiod_line_config_ext_set_primary_config(ext_cfg, line_cfg);
/* Add a secondary config for 2 lines with offsets: 2 and 3. */
gpiod_line_config_ext_add_secondary_config(ext_cfg, other_line_cfg, 2, offsets);

gpiod_chip_request_lines_ext(chip, req_cfg, ext_cfg);

Does this make sense? I'm worried about the resource management here.
Who should be responsible for freeing the config structures? Should
the extended config take ownership? Should the user remain
responsible? Back to reference counting for these objects? Is this
even a good idea?

Please let me know what you think, I could use some advice.

Best Regards,
Bartosz

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-21 20:04               ` Bartosz Golaszewski
@ 2021-04-22  2:32                 ` Kent Gibson
  2021-04-22  9:24                   ` Bartosz Golaszewski
  0 siblings, 1 reply; 22+ messages in thread
From: Kent Gibson @ 2021-04-22  2:32 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Wed, Apr 21, 2021 at 10:04:04PM +0200, Bartosz Golaszewski wrote:
> On Mon, Apr 19, 2021 at 3:17 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> 
> [snip -> long discussion on the libgpiod C API]
> 
> Hi Kent,
> 
> I was working on the next iteration of the code and I'm struggling
> with the implementation of some elements without the concept of
> attributes.
> 
> So I initially liked the idea of variadic functions but they won't
> work for language bindings so that's a no go. On that note: I wanted
> to get some inspiration from your go library but your elegant API
> makes use of go features (like interfaces) we don't have in C.
> 

It is the functional options that is the big difference between my Go
implementation and what I would do in C.  I happen to use interfaces to
implement those options.  You could do something similar in C (cos what
can't you do in C) but it would be weird, so lets not go there.

You can still provide the variadic forms for C users.
And couldn't the language bindings use the "v" variant, as libabc
suggests?:

"Be careful with variadic functions
  - It's great if you provide them, but you must accompany them with
    "v" variants (i.e. functions taking a va_arg object), and provide
    non-variadic variants as well. This is important to get language
    wrappers right."

> The problem we have is basically about implementing primary and
> secondary line configuration objects - where the primary contains the
> default config for all requested lines and the secondary can override
> certain config options (should zeroed values for enumerated types mean
> - don't override?) for certain lines.
> 

Yep, use the 0 value to mean "defaulted".
For the secondary that means use the primary value.
For the primary that means use the kernel default, so the primary is
initialised with the kernel defaults.

Note that accessors, if provided, generally wouldn't return the 0 value to
the user - they follow the secondary -> primary chain and return the
effective setting.

> The basic line config structure (let's call it struct
> gpiod_line_config) can be very simple and have the following mutators:
> 

This is where you are immediately off into the weeds, so I obviously
didn't communicate my suggestion very well...
.
The opaque config object presented to the user is not the simple line
config object - it is the container for the whole request config (which
is different from the request options - which is exactly why I would like
the options not to be called request_config).

The user never sees the simple line config, which is internal, only the
request config.
The accessors always work on the request config at attribute level, and
there is never a need to apply or return the whole config for a particular
line. You could - but it is not necessary for core functionality, so for
now don't.

I realise this makes the internal modelling of config much more
complicated, but the goal is to provide a simplified interface for the
user - so it should be expected that the majority of the complexity will
end up in the library rather than user code.

In my Go implementation I merge the request config into the request
object itself. You could also do that, though the semantics might be
clearer if you keep them separate (more on that later).

> struct gpiod_line_config *cfg = gpiod_line_config_new();
> 
> gpiod_line_config_set_active_low(cfg, true);

I would provide two functions for active level - set_active_high() and
set_active_low().  Even if you don't, the function here should be
set_active_level(), in case you want to add them later.

> gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_OUTPUT);
> 

That is the A case, i.e. the whole request.  How would you name the B
and C cases?

_set_<attr>_line and _set_<attr>_lines?

or _set_line_<attr> and _set_lines_<attr>?

or something else?

It might be worthwhile providing separate set_input() and
set_output() functions - as the output case requires the initial value.
You could have the user call set_output_values(), but why force them to
make another call?

> and so on for for drive, bias, edge, debounce, realtime clock.
> 
> Output values would be set like this:
> 
> unsigned int values[] = { 0, 1, 1, 0 }, num_lines = 4;
> gpiod_line_config_set_output_values(cfg, num_lines, values);
> 

I don't like the implicit identification of lines based on request
ordering here.  I realise my Go API does that, but that is an option to
the request itself, so the requested lines are also present and visible
to the casual reader, whereas this is a standalone call.

So it should require the list of lines that you are setting the values
for. i.e. it is the mutator for a subset of lines, so the C case.

And it implicitly sets those lines to outputs, so it can be more clearly
named set_output(value) (that is the A case, btw).

> One can imagine a simple request with the same config for all lines as:
> 
> gpiod_chip_request_lines(chip, req_cfg, line_cfg);
> 
> Where req_cfg configures request-specific options, and line_cfg
> contains the above line config. I'm still not convinced that
> gpiod_request_options is the better name, I think I prefer the
> juxtaposition of the two names: line_config and request_config.
> 

That's ok - I'm pretty sure you'll get there eventually ;-).

> Now how do we pass a composite line config with overridden values in C
> without interfaces etc.?
>

As above, the req_cfg is the composite line config, so

req = gpiod_chip_request_lines(chip, req_options, req_cfg);

Or if you were to merge the request config, and even the options, into the
request:

unsigned int lines[] = { 0, 4, 12, 54 }, num_lines = 4;
req = gpiod_line_request_new(num_lines, lines); // also variadic forms??
// call req option and config mutators here...
gpiod_line_request_set_active_low(req);
gpiod_line_request_set_output(req, 1);
gpiod_line_request_set_line_input(req, 12);
gpiod_line_request_set_event_buffer_size(req, 42);
...
// then actually request the lines...
err = gpiod_chip_request_lines(chip, req);

which may error for various reasons, such as lines already being
requested or overly complex config.

Merging everything into the request means fewer opaque objects and
interactions for the user to have to deal with, which is always a good
thing.
The downside is that changes to options and config, such as the
gpiod_line_request_set_active_low() etc here, are not applied until
either the gpiod_chip_request_lines() or the set_config() call, which
could be confusing.  Though the more I think about it the more I think
the resulting simplification of the API wins out.  i.e. these objects:

struct gpiod_line_attr;
struct gpiod_line_config;
struct gpiod_request_config;
struct gpiod_request_handle;

all get collapsed into:

struct gpiod_line_request;

which significantly reduces the cognitive load on the user.

The set_config() would probably be called something like:

err = gpiod_line_request_reconfigure(req)

to distinguish it from the mutators which use the _set_ naming.
(and it would also align with my Go library ;-)

> One idea I have is to add a new object called struct
> gpiod_line_config_ext (for extended) that would take one primary
> config and an arbitrary number of secondary configs with the following
> example use-case:
> 
> struct gpiod_line_config_ext *ext_cfg = gpiod_line_config_ext_new();
> unsigned int offsets[] = { 2, 3 };
> 
> /* Add the default config for this request. */
> gpiod_line_config_ext_set_primary_config(ext_cfg, line_cfg);
> /* Add a secondary config for 2 lines with offsets: 2 and 3. */
> gpiod_line_config_ext_add_secondary_config(ext_cfg, other_line_cfg, 2, offsets);
> 
> gpiod_chip_request_lines_ext(chip, req_cfg, ext_cfg);
> 

Please, no _ext objects - that is an admission of failure right there.

> Does this make sense? I'm worried about the resource management here.
> Who should be responsible for freeing the config structures? Should
> the extended config take ownership? Should the user remain
> responsible? Back to reference counting for these objects? Is this
> even a good idea?
> 

The user is responsible for freeing the request config.
The request config is responsible for freeing the line configs, if there
are any - the user isn't even aware of them so they obviously can't.

Similarly if you merge the config into the request.

> Please let me know what you think, I could use some advice.

Hopefully I've communicated my meaning a little more clearly this time?

Cheers,
Kent.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-22  2:32                 ` Kent Gibson
@ 2021-04-22  9:24                   ` Bartosz Golaszewski
  2021-04-23  1:38                     ` Kent Gibson
  0 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-22  9:24 UTC (permalink / raw)
  To: Kent Gibson; +Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Thu, Apr 22, 2021 at 4:32 AM Kent Gibson <warthog618@gmail.com> wrote:
>
> On Wed, Apr 21, 2021 at 10:04:04PM +0200, Bartosz Golaszewski wrote:
> > On Mon, Apr 19, 2021 at 3:17 AM Kent Gibson <warthog618@gmail.com> wrote:
> > >
> >
> > [snip -> long discussion on the libgpiod C API]
> >
> > Hi Kent,
> >
> > I was working on the next iteration of the code and I'm struggling
> > with the implementation of some elements without the concept of
> > attributes.
> >
> > So I initially liked the idea of variadic functions but they won't
> > work for language bindings so that's a no go. On that note: I wanted
> > to get some inspiration from your go library but your elegant API
> > makes use of go features (like interfaces) we don't have in C.
> >
>
> It is the functional options that is the big difference between my Go
> implementation and what I would do in C.  I happen to use interfaces to
> implement those options.  You could do something similar in C (cos what
> can't you do in C) but it would be weird, so lets not go there.
>
> You can still provide the variadic forms for C users.
> And couldn't the language bindings use the "v" variant, as libabc
> suggests?:
>
> "Be careful with variadic functions
>   - It's great if you provide them, but you must accompany them with
>     "v" variants (i.e. functions taking a va_arg object), and provide
>     non-variadic variants as well. This is important to get language
>     wrappers right."
>

The "v" functions do nothing for language bindings - you can't
construct the required va_list out of thin air. What you do usually is
this (example taken from GLib):

gpointer
g_object_new(GType object_type,
             const gchar *first_property_name,
             ...);

has a non-variadic counterpart:

GObject *
g_object_new_with_properties(GType object_type,
                             guint n_properties,
                             const char *names[],
                             const GValue values[]);

I would prefer to simply not do variadic functions. The low-level API
should in general be wrapped in high-level languages. Most people that
mail me on libgpiod seem to use Python anyway.

> > The problem we have is basically about implementing primary and
> > secondary line configuration objects - where the primary contains the
> > default config for all requested lines and the secondary can override
> > certain config options (should zeroed values for enumerated types mean
> > - don't override?) for certain lines.
> >
>
> Yep, use the 0 value to mean "defaulted".
> For the secondary that means use the primary value.
> For the primary that means use the kernel default, so the primary is
> initialised with the kernel defaults.
>
> Note that accessors, if provided, generally wouldn't return the 0 value to
> the user - they follow the secondary -> primary chain and return the
> effective setting.
>
> > The basic line config structure (let's call it struct
> > gpiod_line_config) can be very simple and have the following mutators:
> >
>
> This is where you are immediately off into the weeds, so I obviously
> didn't communicate my suggestion very well...
> .
> The opaque config object presented to the user is not the simple line
> config object - it is the container for the whole request config (which
> is different from the request options - which is exactly why I would like
> the options not to be called request_config).
>
> The user never sees the simple line config, which is internal, only the
> request config.
> The accessors always work on the request config at attribute level, and
> there is never a need to apply or return the whole config for a particular
> line. You could - but it is not necessary for core functionality, so for
> now don't.
>
> I realise this makes the internal modelling of config much more
> complicated, but the goal is to provide a simplified interface for the
> user - so it should be expected that the majority of the complexity will
> end up in the library rather than user code.
>

Yeah, I understood this alright but I figured that this will bloat the
library interface with three variants of mutators per every config
option (or more if we provide two functions for direction and active
level) more than having simpler config objects and applying them
globally or per line.

> In my Go implementation I merge the request config into the request
> object itself. You could also do that, though the semantics might be
> clearer if you keep them separate (more on that later).
>

My goal was to logically split the config into elements that can be
reconfigured and those that cannot. More on that later.

> > struct gpiod_line_config *cfg = gpiod_line_config_new();
> >
> > gpiod_line_config_set_active_low(cfg, true);
>
> I would provide two functions for active level - set_active_high() and
> set_active_low().  Even if you don't, the function here should be
> set_active_level(), in case you want to add them later.
>
> > gpiod_line_config_set_direction(cfg, GPIOD_LINE_CONFIG_DIRECTION_OUTPUT);
> >
>
> That is the A case, i.e. the whole request.  How would you name the B
> and C cases?
>
> _set_<attr>_line and _set_<attr>_lines?
>
> or _set_line_<attr> and _set_lines_<attr>?
>
> or something else?
>

These are what I had in mind and also: _offset and _offsets.

> It might be worthwhile providing separate set_input() and
> set_output() functions - as the output case requires the initial value.
> You could have the user call set_output_values(), but why force them to
> make another call?
>
> > and so on for for drive, bias, edge, debounce, realtime clock.
> >
> > Output values would be set like this:
> >
> > unsigned int values[] = { 0, 1, 1, 0 }, num_lines = 4;
> > gpiod_line_config_set_output_values(cfg, num_lines, values);
> >
>
> I don't like the implicit identification of lines based on request
> ordering here.  I realise my Go API does that, but that is an option to
> the request itself, so the requested lines are also present and visible
> to the casual reader, whereas this is a standalone call.
>
> So it should require the list of lines that you are setting the values
> for. i.e. it is the mutator for a subset of lines, so the C case.
>
> And it implicitly sets those lines to outputs, so it can be more clearly
> named set_output(value) (that is the A case, btw).
>

I can imagine the B case like:

gpiod_line_config_set_output_value(config, offset, value);

But how would exactly the call for the A case look like? Two arrays
with offset -> value mapping?

unsigned int offsets[] = { 0, 2, 5 }, values[] = { 0, 1 ,1 };
gpiod_line_config_set_output_values_offsets(config, 3, offsets, values);

?

> > One can imagine a simple request with the same config for all lines as:
> >
> > gpiod_chip_request_lines(chip, req_cfg, line_cfg);
> >
> > Where req_cfg configures request-specific options, and line_cfg
> > contains the above line config. I'm still not convinced that
> > gpiod_request_options is the better name, I think I prefer the
> > juxtaposition of the two names: line_config and request_config.
> >
>
> That's ok - I'm pretty sure you'll get there eventually ;-).
>
> > Now how do we pass a composite line config with overridden values in C
> > without interfaces etc.?
> >
>
> As above, the req_cfg is the composite line config, so
>
> req = gpiod_chip_request_lines(chip, req_options, req_cfg);
>
> Or if you were to merge the request config, and even the options, into the
> request:
>
> unsigned int lines[] = { 0, 4, 12, 54 }, num_lines = 4;
> req = gpiod_line_request_new(num_lines, lines); // also variadic forms??
> // call req option and config mutators here...
> gpiod_line_request_set_active_low(req);
> gpiod_line_request_set_output(req, 1);
> gpiod_line_request_set_line_input(req, 12);
> gpiod_line_request_set_event_buffer_size(req, 42);
> ...
> // then actually request the lines...
> err = gpiod_chip_request_lines(chip, req);
>
> which may error for various reasons, such as lines already being
> requested or overly complex config.
>
> Merging everything into the request means fewer opaque objects and
> interactions for the user to have to deal with, which is always a good
> thing.
> The downside is that changes to options and config, such as the
> gpiod_line_request_set_active_low() etc here, are not applied until
> either the gpiod_chip_request_lines() or the set_config() call, which
> could be confusing.  Though the more I think about it the more I think
> the resulting simplification of the API wins out.  i.e. these objects:
>
> struct gpiod_line_attr;
> struct gpiod_line_config;
> struct gpiod_request_config;
> struct gpiod_request_handle;
>
> all get collapsed into:
>
> struct gpiod_line_request;
>
> which significantly reduces the cognitive load on the user.
>
> The set_config() would probably be called something like:
>
> err = gpiod_line_request_reconfigure(req)
>

This lack of splitting of options into configurable and constant ones
visually suggests that you can change all request options later on
which is not true. I think that at least for the C API, we should
split the responsibilities of objects and keep the division into
request config, line config *and* the line handle whose lifetime is
from the moment the lines get requested until they're released.

> to distinguish it from the mutators which use the _set_ naming.
> (and it would also align with my Go library ;-)
>
> > One idea I have is to add a new object called struct
> > gpiod_line_config_ext (for extended) that would take one primary
> > config and an arbitrary number of secondary configs with the following
> > example use-case:
> >
> > struct gpiod_line_config_ext *ext_cfg = gpiod_line_config_ext_new();
> > unsigned int offsets[] = { 2, 3 };
> >
> > /* Add the default config for this request. */
> > gpiod_line_config_ext_set_primary_config(ext_cfg, line_cfg);
> > /* Add a secondary config for 2 lines with offsets: 2 and 3. */
> > gpiod_line_config_ext_add_secondary_config(ext_cfg, other_line_cfg, 2, offsets);
> >
> > gpiod_chip_request_lines_ext(chip, req_cfg, ext_cfg);
> >
>
> Please, no _ext objects - that is an admission of failure right there.
>

I wanted to protest but then realized that if you need _ext interfaces
then it means your non-extended, initial design is already flawed. :)

Ok so let's try again.

How about:

Three structs:

struct gpiod_line_config;
struct gpiod_request_config;
struct gpiod_line_request;

The first one holds the composite of primary and secondary configs and
is modified using mutators according to this scheme:

gpiod_line_config_set_<attr>(config, attr);
gpiod_line_config_set_<attr>_offset(config, attr, offset);
gpiod_line_config_set_<attr>_offsets(config, attr, num_offsets, offsets);

With notable exceptions for:

gpiod_line_config_set_[input|active_low|active_high](config);
gpiod_line_config_set_[input|active_low|active_high]_offset(config, offset);
gpiod_line_config_set_[input|active_low|active_high]_offsets(config,
num_offsets, offsets);

and:

gpiod_line_config_set_output(config, num_lines, offsets, values);
gpiod_line_config_set_output_offset(config, offset, value);

The request function takes a single line config and a request config
and returns a new gpiod_line_request like in the first iteration.

Then the lines can be set like this:

// num_lines refers to the number of lines to set, not the number of
// lines in the request
gpiod_line_request_set_values(req, num_lines, offsets, values);

Bartosz

> > Does this make sense? I'm worried about the resource management here.
> > Who should be responsible for freeing the config structures? Should
> > the extended config take ownership? Should the user remain
> > responsible? Back to reference counting for these objects? Is this
> > even a good idea?
> >
>
> The user is responsible for freeing the request config.
> The request config is responsible for freeing the line configs, if there
> are any - the user isn't even aware of them so they obviously can't.
>
> Similarly if you merge the config into the request.
>
> > Please let me know what you think, I could use some advice.
>
> Hopefully I've communicated my meaning a little more clearly this time?
>
> Cheers,
> Kent.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-22  9:24                   ` Bartosz Golaszewski
@ 2021-04-23  1:38                     ` Kent Gibson
  2021-04-28  9:19                       ` Bartosz Golaszewski
  0 siblings, 1 reply; 22+ messages in thread
From: Kent Gibson @ 2021-04-23  1:38 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Thu, Apr 22, 2021 at 11:24:34AM +0200, Bartosz Golaszewski wrote:
> On Thu, Apr 22, 2021 at 4:32 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> > On Wed, Apr 21, 2021 at 10:04:04PM +0200, Bartosz Golaszewski wrote:
> > > On Mon, Apr 19, 2021 at 3:17 AM Kent Gibson <warthog618@gmail.com> wrote:
> > > >
> > >
> > > [snip -> long discussion on the libgpiod C API]
> > >
> > > Hi Kent,
> > >
> > > I was working on the next iteration of the code and I'm struggling
> > > with the implementation of some elements without the concept of
> > > attributes.
> > >
> > > So I initially liked the idea of variadic functions but they won't
> > > work for language bindings so that's a no go. On that note: I wanted
> > > to get some inspiration from your go library but your elegant API
> > > makes use of go features (like interfaces) we don't have in C.
> > >
> >
> > It is the functional options that is the big difference between my Go
> > implementation and what I would do in C.  I happen to use interfaces to
> > implement those options.  You could do something similar in C (cos what
> > can't you do in C) but it would be weird, so lets not go there.
> >
> > You can still provide the variadic forms for C users.
> > And couldn't the language bindings use the "v" variant, as libabc
> > suggests?:
> >
> > "Be careful with variadic functions
> >   - It's great if you provide them, but you must accompany them with
> >     "v" variants (i.e. functions taking a va_arg object), and provide
> >     non-variadic variants as well. This is important to get language
> >     wrappers right."
> >
> 
> The "v" functions do nothing for language bindings - you can't
> construct the required va_list out of thin air. What you do usually is
> this (example taken from GLib):
> 

You are right - I was thinking you could build and pass in the the va_list
like Python args and kwargs, but you can only use it to decode an
existing call stack :-|.

[snip]

> > So it should require the list of lines that you are setting the values
> > for. i.e. it is the mutator for a subset of lines, so the C case.
> >
> > And it implicitly sets those lines to outputs, so it can be more clearly
> > named set_output(value) (that is the A case, btw).
> >
> 
> I can imagine the B case like:
> 
> gpiod_line_config_set_output_value(config, offset, value);
> 
> But how would exactly the call for the A case look like? Two arrays
> with offset -> value mapping?
> 

No - the A case sets ALL lines to one value.
B is one line to one value.
C is a set of lines to one value.

A set of lines to a set of values is a new case.
And yes - two arrays as per your set_values() below.

> unsigned int offsets[] = { 0, 2, 5 }, values[] = { 0, 1 ,1 };
> gpiod_line_config_set_output_values_offsets(config, 3, offsets, values);
> 
> ?
> 
> > > One can imagine a simple request with the same config for all lines as:
> > >
> > > gpiod_chip_request_lines(chip, req_cfg, line_cfg);
> > >
> > > Where req_cfg configures request-specific options, and line_cfg
> > > contains the above line config. I'm still not convinced that
> > > gpiod_request_options is the better name, I think I prefer the
> > > juxtaposition of the two names: line_config and request_config.
> > >
> >
> > That's ok - I'm pretty sure you'll get there eventually ;-).
> >
> > > Now how do we pass a composite line config with overridden values in C
> > > without interfaces etc.?
> > >
> >
> > As above, the req_cfg is the composite line config, so
> >
> > req = gpiod_chip_request_lines(chip, req_options, req_cfg);
> >
> > Or if you were to merge the request config, and even the options, into the
> > request:
> >
> > unsigned int lines[] = { 0, 4, 12, 54 }, num_lines = 4;
> > req = gpiod_line_request_new(num_lines, lines); // also variadic forms??
> > // call req option and config mutators here...
> > gpiod_line_request_set_active_low(req);
> > gpiod_line_request_set_output(req, 1);
> > gpiod_line_request_set_line_input(req, 12);
> > gpiod_line_request_set_event_buffer_size(req, 42);
> > ...
> > // then actually request the lines...
> > err = gpiod_chip_request_lines(chip, req);
> >
> > which may error for various reasons, such as lines already being
> > requested or overly complex config.
> >
> > Merging everything into the request means fewer opaque objects and
> > interactions for the user to have to deal with, which is always a good
> > thing.
> > The downside is that changes to options and config, such as the
> > gpiod_line_request_set_active_low() etc here, are not applied until
> > either the gpiod_chip_request_lines() or the set_config() call, which
> > could be confusing.  Though the more I think about it the more I think
> > the resulting simplification of the API wins out.  i.e. these objects:
> >
> > struct gpiod_line_attr;
> > struct gpiod_line_config;
> > struct gpiod_request_config;
> > struct gpiod_request_handle;
> >
> > all get collapsed into:
> >
> > struct gpiod_line_request;
> >
> > which significantly reduces the cognitive load on the user.
> >
> > The set_config() would probably be called something like:
> >
> > err = gpiod_line_request_reconfigure(req)
> >
> 
> This lack of splitting of options into configurable and constant ones
> visually suggests that you can change all request options later on
> which is not true.

Yup, as I said, the semantics for the unified object are more confusing.

In the Go implementation, the request options can be passed to the
request_lines(), but not the set_config(), cos interfaces.

There is no good way to flag that in C at compile time. For a runtime
check you could add a return code to the option mutators and return an
error if the lines have already been requested.

> I think that at least for the C API, we should
> split the responsibilities of objects and keep the division into
> request config, line config *and* the line handle whose lifetime is
> from the moment the lines get requested until they're released.
> 
> > to distinguish it from the mutators which use the _set_ naming.
> > (and it would also align with my Go library ;-)
> >
> > > One idea I have is to add a new object called struct
> > > gpiod_line_config_ext (for extended) that would take one primary
> > > config and an arbitrary number of secondary configs with the following
> > > example use-case:
> > >
> > > struct gpiod_line_config_ext *ext_cfg = gpiod_line_config_ext_new();
> > > unsigned int offsets[] = { 2, 3 };
> > >
> > > /* Add the default config for this request. */
> > > gpiod_line_config_ext_set_primary_config(ext_cfg, line_cfg);
> > > /* Add a secondary config for 2 lines with offsets: 2 and 3. */
> > > gpiod_line_config_ext_add_secondary_config(ext_cfg, other_line_cfg, 2, offsets);
> > >
> > > gpiod_chip_request_lines_ext(chip, req_cfg, ext_cfg);
> > >
> >
> > Please, no _ext objects - that is an admission of failure right there.
> >
> 
> I wanted to protest but then realized that if you need _ext interfaces
> then it means your non-extended, initial design is already flawed. :)
> 
> Ok so let's try again.
> 
> How about:
> 
> Three structs:
> 
> struct gpiod_line_config;
> struct gpiod_request_config;
> struct gpiod_line_request;
> 

The user manages the lifecycle of all three?

I can live with that, though I would probably still lean towards the
unified object approach - with the option mutators getting return codes.

> The first one holds the composite of primary and secondary configs and
> is modified using mutators according to this scheme:
> 

Which is why it should be called request_config, not line_config.
line_config is misleading - it says line but its scope is request.

And of course request_config should be request_options ;-).

> gpiod_line_config_set_<attr>(config, attr);
> gpiod_line_config_set_<attr>_offset(config, attr, offset);
> gpiod_line_config_set_<attr>_offsets(config, attr, num_offsets, offsets);
> 

I personally prefer the _set_line_<attr> style as it reads better, but I
can live with this - I know you prefer suffixes for variants.

> With notable exceptions for:
> 
> gpiod_line_config_set_[input|active_low|active_high](config);
> gpiod_line_config_set_[input|active_low|active_high]_offset(config, offset);
> gpiod_line_config_set_[input|active_low|active_high]_offsets(config,
> num_offsets, offsets);
> 
> and:
> 
> gpiod_line_config_set_output(config, num_lines, offsets, values);
> gpiod_line_config_set_output_offset(config, offset, value);
> 
> The request function takes a single line config and a request config
> and returns a new gpiod_line_request like in the first iteration.
> 

Where are the set of requested lines specified?

Do null config ptrs result in something useful, but guaranteed harmless,
such as requesting lines as input?  Or are they required non-null?

> Then the lines can be set like this:
> 
> // num_lines refers to the number of lines to set, not the number of
> // lines in the request
> gpiod_line_request_set_values(req, num_lines, offsets, values);
> 

At first glance that feels a bit odd, being on the request while the
others all operate on the config, but it maps to the SET_VALUES ioctl(),
not the SET_CONFIG, so that makes sense.

There is a corresponding get_values(req, num_lines, offsets, values)?

And a reconfigure(req, cfg)?

Cheers,
Kent.


^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-23  1:38                     ` Kent Gibson
@ 2021-04-28  9:19                       ` Bartosz Golaszewski
  2021-04-28 10:34                         ` Kent Gibson
  0 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-28  9:19 UTC (permalink / raw)
  To: Kent Gibson; +Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Fri, Apr 23, 2021 at 3:39 AM Kent Gibson <warthog618@gmail.com> wrote:
>

[snip]

>
> > > So it should require the list of lines that you are setting the values
> > > for. i.e. it is the mutator for a subset of lines, so the C case.
> > >
> > > And it implicitly sets those lines to outputs, so it can be more clearly
> > > named set_output(value) (that is the A case, btw).
> > >
> >
> > I can imagine the B case like:
> >
> > gpiod_line_config_set_output_value(config, offset, value);
> >
> > But how would exactly the call for the A case look like? Two arrays
> > with offset -> value mapping?
> >
>
> No - the A case sets ALL lines to one value.

Apart from a single line request - what could possibly be the use-case for that?

> B is one line to one value.
> C is a set of lines to one value.

This makes a bit more sense but ...

>
> A set of lines to a set of values is a new case.
> And yes - two arrays as per your set_values() below.
>

... to me this and B are the most sensible options. Do we really need
A & C for line reading and setting? Do you find use for that in your
Go lib?

> > unsigned int offsets[] = { 0, 2, 5 }, values[] = { 0, 1 ,1 };
> > gpiod_line_config_set_output_values_offsets(config, 3, offsets, values);
> >
> > ?
> >
> > > > One can imagine a simple request with the same config for all lines as:
> > > >
> > > > gpiod_chip_request_lines(chip, req_cfg, line_cfg);
> > > >
> > > > Where req_cfg configures request-specific options, and line_cfg
> > > > contains the above line config. I'm still not convinced that
> > > > gpiod_request_options is the better name, I think I prefer the
> > > > juxtaposition of the two names: line_config and request_config.
> > > >
> > >
> > > That's ok - I'm pretty sure you'll get there eventually ;-).
> > >
> > > > Now how do we pass a composite line config with overridden values in C
> > > > without interfaces etc.?
> > > >
> > >
> > > As above, the req_cfg is the composite line config, so
> > >
> > > req = gpiod_chip_request_lines(chip, req_options, req_cfg);
> > >
> > > Or if you were to merge the request config, and even the options, into the
> > > request:
> > >
> > > unsigned int lines[] = { 0, 4, 12, 54 }, num_lines = 4;
> > > req = gpiod_line_request_new(num_lines, lines); // also variadic forms??
> > > // call req option and config mutators here...
> > > gpiod_line_request_set_active_low(req);
> > > gpiod_line_request_set_output(req, 1);
> > > gpiod_line_request_set_line_input(req, 12);
> > > gpiod_line_request_set_event_buffer_size(req, 42);
> > > ...
> > > // then actually request the lines...
> > > err = gpiod_chip_request_lines(chip, req);
> > >
> > > which may error for various reasons, such as lines already being
> > > requested or overly complex config.
> > >
> > > Merging everything into the request means fewer opaque objects and
> > > interactions for the user to have to deal with, which is always a good
> > > thing.
> > > The downside is that changes to options and config, such as the
> > > gpiod_line_request_set_active_low() etc here, are not applied until
> > > either the gpiod_chip_request_lines() or the set_config() call, which
> > > could be confusing.  Though the more I think about it the more I think
> > > the resulting simplification of the API wins out.  i.e. these objects:
> > >
> > > struct gpiod_line_attr;
> > > struct gpiod_line_config;
> > > struct gpiod_request_config;
> > > struct gpiod_request_handle;
> > >
> > > all get collapsed into:
> > >
> > > struct gpiod_line_request;
> > >
> > > which significantly reduces the cognitive load on the user.
> > >
> > > The set_config() would probably be called something like:
> > >
> > > err = gpiod_line_request_reconfigure(req)
> > >
> >
> > This lack of splitting of options into configurable and constant ones
> > visually suggests that you can change all request options later on
> > which is not true.
>
> Yup, as I said, the semantics for the unified object are more confusing.
>
> In the Go implementation, the request options can be passed to the
> request_lines(), but not the set_config(), cos interfaces.
>
> There is no good way to flag that in C at compile time. For a runtime
> check you could add a return code to the option mutators and return an
> error if the lines have already been requested.
>

I agree that it doesn't map well to C and this is why I think it would
be less confusing if we went with two structs instead.

> > I think that at least for the C API, we should
> > split the responsibilities of objects and keep the division into
> > request config, line config *and* the line handle whose lifetime is
> > from the moment the lines get requested until they're released.
> >
> > > to distinguish it from the mutators which use the _set_ naming.
> > > (and it would also align with my Go library ;-)
> > >
> > > > One idea I have is to add a new object called struct
> > > > gpiod_line_config_ext (for extended) that would take one primary
> > > > config and an arbitrary number of secondary configs with the following
> > > > example use-case:
> > > >
> > > > struct gpiod_line_config_ext *ext_cfg = gpiod_line_config_ext_new();
> > > > unsigned int offsets[] = { 2, 3 };
> > > >
> > > > /* Add the default config for this request. */
> > > > gpiod_line_config_ext_set_primary_config(ext_cfg, line_cfg);
> > > > /* Add a secondary config for 2 lines with offsets: 2 and 3. */
> > > > gpiod_line_config_ext_add_secondary_config(ext_cfg, other_line_cfg, 2, offsets);
> > > >
> > > > gpiod_chip_request_lines_ext(chip, req_cfg, ext_cfg);
> > > >
> > >
> > > Please, no _ext objects - that is an admission of failure right there.
> > >
> >
> > I wanted to protest but then realized that if you need _ext interfaces
> > then it means your non-extended, initial design is already flawed. :)
> >
> > Ok so let's try again.
> >
> > How about:
> >
> > Three structs:
> >
> > struct gpiod_line_config;
> > struct gpiod_request_config;
> > struct gpiod_line_request;
> >
>
> The user manages the lifecycle of all three?

Yes.

>
> I can live with that, though I would probably still lean towards the
> unified object approach - with the option mutators getting return codes.
>
> > The first one holds the composite of primary and secondary configs and
> > is modified using mutators according to this scheme:
> >
>
> Which is why it should be called request_config, not line_config.
> line_config is misleading - it says line but its scope is request.
>

It depends on how you look at it really. Its scope are *the lines* in
the request, not the request itself (unlike the event buffer size or
line offsets). It says line because gpiod_lines_config would look
bizarre.

> And of course request_config should be request_options ;-).

I'm still not there yet.

>
> > gpiod_line_config_set_<attr>(config, attr);
> > gpiod_line_config_set_<attr>_offset(config, attr, offset);
> > gpiod_line_config_set_<attr>_offsets(config, attr, num_offsets, offsets);
> >
>
> I personally prefer the _set_line_<attr> style as it reads better, but I
> can live with this - I know you prefer suffixes for variants.
>
> > With notable exceptions for:
> >
> > gpiod_line_config_set_[input|active_low|active_high](config);
> > gpiod_line_config_set_[input|active_low|active_high]_offset(config, offset);
> > gpiod_line_config_set_[input|active_low|active_high]_offsets(config,
> > num_offsets, offsets);
> >
> > and:
> >
> > gpiod_line_config_set_output(config, num_lines, offsets, values);
> > gpiod_line_config_set_output_offset(config, offset, value);
> >
> > The request function takes a single line config and a request config
> > and returns a new gpiod_line_request like in the first iteration.
> >
>
> Where are the set of requested lines specified?
>

They map the uAPI in that the offsets are set in struct gpiod_request_config:

gpiod_request_config_set_line_offsets(config, num_lines, offsets); :)

or

gpiod_request_options_set_line_offsets(config, num_lines, offsets); :(

> Do null config ptrs result in something useful, but guaranteed harmless,
> such as requesting lines as input?  Or are they required non-null?
>

I normally expect users to pass a valid pointer and don't make the
functions null aware. In this case - it's a good question. I'm
wondering if it is useful at all? The user should IMO specify what
they want to do with the lines? I would lean towards non-null line
configfs.

> > Then the lines can be set like this:
> >
> > // num_lines refers to the number of lines to set, not the number of
> > // lines in the request
> > gpiod_line_request_set_values(req, num_lines, offsets, values);
> >
>
> At first glance that feels a bit odd, being on the request while the
> others all operate on the config, but it maps to the SET_VALUES ioctl(),
> not the SET_CONFIG, so that makes sense.
>
> There is a corresponding get_values(req, num_lines, offsets, values)?
>

Sure, just didn't include it in the example.

> And a reconfigure(req, cfg)?
>

Sure, just haven't decided on the name yet.
gpiod_line_request_reconfigure() would be what you're suggesting but
it sounds like it would reconfigure the request and not modify the
line configuration. I would prefer something closer to
gpiod_line_request_set_line_config() which is as verbose as it gets.
Maybe even gpiod_line_request_change_line_config()? Or
gpiod_line_request_modify_line_config()?

Bartosz

> Cheers,
> Kent.
>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-28  9:19                       ` Bartosz Golaszewski
@ 2021-04-28 10:34                         ` Kent Gibson
  2021-04-30 17:52                           ` Bartosz Golaszewski
  0 siblings, 1 reply; 22+ messages in thread
From: Kent Gibson @ 2021-04-28 10:34 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Wed, Apr 28, 2021 at 11:19:05AM +0200, Bartosz Golaszewski wrote:
> On Fri, Apr 23, 2021 at 3:39 AM Kent Gibson <warthog618@gmail.com> wrote:
> >
> 
> [snip]
> 
> >
> > > > So it should require the list of lines that you are setting the values
> > > > for. i.e. it is the mutator for a subset of lines, so the C case.
> > > >
> > > > And it implicitly sets those lines to outputs, so it can be more clearly
> > > > named set_output(value) (that is the A case, btw).
> > > >
> > >
> > > I can imagine the B case like:
> > >
> > > gpiod_line_config_set_output_value(config, offset, value);
> > >
> > > But how would exactly the call for the A case look like? Two arrays
> > > with offset -> value mapping?
> > >
> >
> > No - the A case sets ALL lines to one value.
> 
> Apart from a single line request - what could possibly be the use-case for that?
> 

The A, B, C nomenclature originated for attributes, for which blanket
sets (A) make more sense.

The case for using A for outputs would be if the vast majority of your
lines default to one value.  You would then only have to use the other
(B, C or whatever) for the non-default lines.

> > B is one line to one value.
> > C is a set of lines to one value.
> 
> This makes a bit more sense but ...
> 
> >
> > A set of lines to a set of values is a new case.
> > And yes - two arrays as per your set_values() below.
> >

For future reference, lets call this case D.

> 
> ... to me this and B are the most sensible options. Do we really need
> A & C for line reading and setting? Do you find use for that in your
> Go lib?
> 

No need for all four - two would be sufficient - probably B and D as you
suggest.

The Go interface is a little different because it is being passed as
options.  It doesn't provide all four.  It provides D. And setting
all offsets in the request to specific values as part of the request
contructor - similar to the set in your initial draft.  And it defaults
lines to inactive if not specified otherwise.

For gets and sets it only supports all the requested lines.
Strictly speaking it can request a subset of the lines - but only the
first n lines not a sparse subset. It could be extended to the D case,
but so far no one has asked.

> > > unsigned int offsets[] = { 0, 2, 5 }, values[] = { 0, 1 ,1 };
> > > gpiod_line_config_set_output_values_offsets(config, 3, offsets, values);
> > >
> > > ?
> > >
> > > > > One can imagine a simple request with the same config for all lines as:
> > > > >
> > > > > gpiod_chip_request_lines(chip, req_cfg, line_cfg);
> > > > >
> > > > > Where req_cfg configures request-specific options, and line_cfg
> > > > > contains the above line config. I'm still not convinced that
> > > > > gpiod_request_options is the better name, I think I prefer the
> > > > > juxtaposition of the two names: line_config and request_config.
> > > > >
> > > >
> > > > That's ok - I'm pretty sure you'll get there eventually ;-).
> > > >
> > > > > Now how do we pass a composite line config with overridden values in C
> > > > > without interfaces etc.?
> > > > >
> > > >
> > > > As above, the req_cfg is the composite line config, so
> > > >
> > > > req = gpiod_chip_request_lines(chip, req_options, req_cfg);
> > > >
> > > > Or if you were to merge the request config, and even the options, into the
> > > > request:
> > > >
> > > > unsigned int lines[] = { 0, 4, 12, 54 }, num_lines = 4;
> > > > req = gpiod_line_request_new(num_lines, lines); // also variadic forms??
> > > > // call req option and config mutators here...
> > > > gpiod_line_request_set_active_low(req);
> > > > gpiod_line_request_set_output(req, 1);
> > > > gpiod_line_request_set_line_input(req, 12);
> > > > gpiod_line_request_set_event_buffer_size(req, 42);
> > > > ...
> > > > // then actually request the lines...
> > > > err = gpiod_chip_request_lines(chip, req);
> > > >
> > > > which may error for various reasons, such as lines already being
> > > > requested or overly complex config.
> > > >
> > > > Merging everything into the request means fewer opaque objects and
> > > > interactions for the user to have to deal with, which is always a good
> > > > thing.
> > > > The downside is that changes to options and config, such as the
> > > > gpiod_line_request_set_active_low() etc here, are not applied until
> > > > either the gpiod_chip_request_lines() or the set_config() call, which
> > > > could be confusing.  Though the more I think about it the more I think
> > > > the resulting simplification of the API wins out.  i.e. these objects:
> > > >
> > > > struct gpiod_line_attr;
> > > > struct gpiod_line_config;
> > > > struct gpiod_request_config;
> > > > struct gpiod_request_handle;
> > > >
> > > > all get collapsed into:
> > > >
> > > > struct gpiod_line_request;
> > > >
> > > > which significantly reduces the cognitive load on the user.
> > > >
> > > > The set_config() would probably be called something like:
> > > >
> > > > err = gpiod_line_request_reconfigure(req)
> > > >
> > >
> > > This lack of splitting of options into configurable and constant ones
> > > visually suggests that you can change all request options later on
> > > which is not true.
> >
> > Yup, as I said, the semantics for the unified object are more confusing.
> >
> > In the Go implementation, the request options can be passed to the
> > request_lines(), but not the set_config(), cos interfaces.
> >
> > There is no good way to flag that in C at compile time. For a runtime
> > check you could add a return code to the option mutators and return an
> > error if the lines have already been requested.
> >
> 
> I agree that it doesn't map well to C and this is why I think it would
> be less confusing if we went with two structs instead.
> 

I know.

My concern is that the simplest get use case is 7 function calls:
1. create request_config
2. create line_config
3. request lines
4. do the actual work
5. release request
6. free line_config
7. free request_config

And that is ignoring the chip open/close functions.

> > > I think that at least for the C API, we should
> > > split the responsibilities of objects and keep the division into
> > > request config, line config *and* the line handle whose lifetime is
> > > from the moment the lines get requested until they're released.
> > >
> > > > to distinguish it from the mutators which use the _set_ naming.
> > > > (and it would also align with my Go library ;-)
> > > >
> > > > > One idea I have is to add a new object called struct
> > > > > gpiod_line_config_ext (for extended) that would take one primary
> > > > > config and an arbitrary number of secondary configs with the following
> > > > > example use-case:
> > > > >
> > > > > struct gpiod_line_config_ext *ext_cfg = gpiod_line_config_ext_new();
> > > > > unsigned int offsets[] = { 2, 3 };
> > > > >
> > > > > /* Add the default config for this request. */
> > > > > gpiod_line_config_ext_set_primary_config(ext_cfg, line_cfg);
> > > > > /* Add a secondary config for 2 lines with offsets: 2 and 3. */
> > > > > gpiod_line_config_ext_add_secondary_config(ext_cfg, other_line_cfg, 2, offsets);
> > > > >
> > > > > gpiod_chip_request_lines_ext(chip, req_cfg, ext_cfg);
> > > > >
> > > >
> > > > Please, no _ext objects - that is an admission of failure right there.
> > > >
> > >
> > > I wanted to protest but then realized that if you need _ext interfaces
> > > then it means your non-extended, initial design is already flawed. :)
> > >
> > > Ok so let's try again.
> > >
> > > How about:
> > >
> > > Three structs:
> > >
> > > struct gpiod_line_config;
> > > struct gpiod_request_config;
> > > struct gpiod_line_request;
> > >
> >
> > The user manages the lifecycle of all three?
> 
> Yes.
> 
> >
> > I can live with that, though I would probably still lean towards the
> > unified object approach - with the option mutators getting return codes.
> >
> > > The first one holds the composite of primary and secondary configs and
> > > is modified using mutators according to this scheme:
> > >
> >
> > Which is why it should be called request_config, not line_config.
> > line_config is misleading - it says line but its scope is request.
> >
> 
> It depends on how you look at it really. Its scope are *the lines* in
> the request, not the request itself (unlike the event buffer size or
> line offsets). It says line because gpiod_lines_config would look
> bizarre.
> 
> > And of course request_config should be request_options ;-).
> 
> I'm still not there yet.
> 
> >
> > > gpiod_line_config_set_<attr>(config, attr);
> > > gpiod_line_config_set_<attr>_offset(config, attr, offset);
> > > gpiod_line_config_set_<attr>_offsets(config, attr, num_offsets, offsets);
> > >
> >
> > I personally prefer the _set_line_<attr> style as it reads better, but I
> > can live with this - I know you prefer suffixes for variants.
> >
> > > With notable exceptions for:
> > >
> > > gpiod_line_config_set_[input|active_low|active_high](config);
> > > gpiod_line_config_set_[input|active_low|active_high]_offset(config, offset);
> > > gpiod_line_config_set_[input|active_low|active_high]_offsets(config,
> > > num_offsets, offsets);
> > >
> > > and:
> > >
> > > gpiod_line_config_set_output(config, num_lines, offsets, values);
> > > gpiod_line_config_set_output_offset(config, offset, value);
> > >
> > > The request function takes a single line config and a request config
> > > and returns a new gpiod_line_request like in the first iteration.
> > >
> >
> > Where are the set of requested lines specified?
> >
> 
> They map the uAPI in that the offsets are set in struct gpiod_request_config:
> 
> gpiod_request_config_set_line_offsets(config, num_lines, offsets); :)
> 
> or
> 
> gpiod_request_options_set_line_offsets(config, num_lines, offsets); :(
> 

Alternatively it could be in one of the constructors -
gpiod_request_config_new(), gpiod_line_config_new() or
gpiod_chip_request_lines()?

> > Do null config ptrs result in something useful, but guaranteed harmless,
> > such as requesting lines as input?  Or are they required non-null?
> >
> 
> I normally expect users to pass a valid pointer and don't make the
> functions null aware. In this case - it's a good question. I'm
> wondering if it is useful at all? The user should IMO specify what
> they want to do with the lines? I would lean towards non-null line
> configfs.
> 

Sure doesn't if you have to set the offsets in the config.
But might do if you provide them to the gpiod_chip_request_lines().

The null case could then request the lines as-is?

There seems to be a bit of interest in as-is of late, and the simplest
case would then be three function calls.

> > > Then the lines can be set like this:
> > >
> > > // num_lines refers to the number of lines to set, not the number of
> > > // lines in the request
> > > gpiod_line_request_set_values(req, num_lines, offsets, values);
> > >
> >
> > At first glance that feels a bit odd, being on the request while the
> > others all operate on the config, but it maps to the SET_VALUES ioctl(),
> > not the SET_CONFIG, so that makes sense.
> >
> > There is a corresponding get_values(req, num_lines, offsets, values)?
> >
> 
> Sure, just didn't include it in the example.
> 
> > And a reconfigure(req, cfg)?
> >
> 
> Sure, just haven't decided on the name yet.
> gpiod_line_request_reconfigure() would be what you're suggesting but
> it sounds like it would reconfigure the request and not modify the
> line configuration. I would prefer something closer to
> gpiod_line_request_set_line_config() which is as verbose as it gets.
> Maybe even gpiod_line_request_change_line_config()? Or
> gpiod_line_request_modify_line_config()?
> 

gpiod_line_request_reconfigure(req, cfg) is not modifying the line_config
- it is modifying the request WITH the modified config.

If gpiod_line_request_<splat>_line_config(req, cfg) works better for you
then I'd prefer something along the lines of _apply_ or _commit_, as it is
taking the line_config, modified by sets, and applying it to the
requested lines.

Cheers,
Kent.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-28 10:34                         ` Kent Gibson
@ 2021-04-30 17:52                           ` Bartosz Golaszewski
  2021-05-01  0:15                             ` Kent Gibson
  0 siblings, 1 reply; 22+ messages in thread
From: Bartosz Golaszewski @ 2021-04-30 17:52 UTC (permalink / raw)
  To: Kent Gibson; +Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Wed, Apr 28, 2021 at 12:34 PM Kent Gibson <warthog618@gmail.com> wrote:
>
> On Wed, Apr 28, 2021 at 11:19:05AM +0200, Bartosz Golaszewski wrote:
> > On Fri, Apr 23, 2021 at 3:39 AM Kent Gibson <warthog618@gmail.com> wrote:
> > >
> >
> > [snip]
> >
> > >
> > > > > So it should require the list of lines that you are setting the values
> > > > > for. i.e. it is the mutator for a subset of lines, so the C case.
> > > > >
> > > > > And it implicitly sets those lines to outputs, so it can be more clearly
> > > > > named set_output(value) (that is the A case, btw).
> > > > >
> > > >
> > > > I can imagine the B case like:
> > > >
> > > > gpiod_line_config_set_output_value(config, offset, value);
> > > >
> > > > But how would exactly the call for the A case look like? Two arrays
> > > > with offset -> value mapping?
> > > >
> > >
> > > No - the A case sets ALL lines to one value.
> >
> > Apart from a single line request - what could possibly be the use-case for that?
> >
>
> The A, B, C nomenclature originated for attributes, for which blanket
> sets (A) make more sense.
>
> The case for using A for outputs would be if the vast majority of your
> lines default to one value.  You would then only have to use the other
> (B, C or whatever) for the non-default lines.
>
> > > B is one line to one value.
> > > C is a set of lines to one value.
> >
> > This makes a bit more sense but ...
> >
> > >
> > > A set of lines to a set of values is a new case.
> > > And yes - two arrays as per your set_values() below.
> > >
>
> For future reference, lets call this case D.
>
> >
> > ... to me this and B are the most sensible options. Do we really need
> > A & C for line reading and setting? Do you find use for that in your
> > Go lib?
> >
>
> No need for all four - two would be sufficient - probably B and D as you
> suggest.
>

Agreed.

[snip]

> > > >
> > > > This lack of splitting of options into configurable and constant ones
> > > > visually suggests that you can change all request options later on
> > > > which is not true.
> > >
> > > Yup, as I said, the semantics for the unified object are more confusing.
> > >
> > > In the Go implementation, the request options can be passed to the
> > > request_lines(), but not the set_config(), cos interfaces.
> > >
> > > There is no good way to flag that in C at compile time. For a runtime
> > > check you could add a return code to the option mutators and return an
> > > error if the lines have already been requested.
> > >
> >
> > I agree that it doesn't map well to C and this is why I think it would
> > be less confusing if we went with two structs instead.
> >
>
> I know.
>
> My concern is that the simplest get use case is 7 function calls:
> 1. create request_config
> 2. create line_config
> 3. request lines
> 4. do the actual work
> 5. release request
> 6. free line_config
> 7. free request_config
>
> And that is ignoring the chip open/close functions.
>

This doesn't bother me all that much. This is the low-level C API.
It's supposed to be a bit clunky. It'll get way simpler in the
high-level bindings.

In Python a simple case would look something like this:

with gpiod.Chip("/dev/gpiochip0") as chip:
    with chip.request_lines(gpiod.RequestConfig(offsets=( 0, 2, 3 )),

gpiod.LineConfig(direction=gpiod.DIRECTION_INPUT)) as req:
        values = req.get_values() # Read all values
        values = req.get_values(offsets=( 2, 3)) # Read values of specific lines

[snip]

> > >
> > > Where are the set of requested lines specified?
> > >
> >
> > They map the uAPI in that the offsets are set in struct gpiod_request_config:
> >
> > gpiod_request_config_set_line_offsets(config, num_lines, offsets); :)
> >
> > or
> >
> > gpiod_request_options_set_line_offsets(config, num_lines, offsets); :(
> >
>
> Alternatively it could be in one of the constructors -
> gpiod_request_config_new(), gpiod_line_config_new() or
> gpiod_chip_request_lines()?
>

I want to make the struct reusable/modifiable so setting it only in
the constructor would disallow it and having both would be redundant.
And the last one: the less function arguments the better IMO. So I'm
for having it in the request_config.

> > > Do null config ptrs result in something useful, but guaranteed harmless,
> > > such as requesting lines as input?  Or are they required non-null?
> > >
> >
> > I normally expect users to pass a valid pointer and don't make the
> > functions null aware. In this case - it's a good question. I'm
> > wondering if it is useful at all? The user should IMO specify what
> > they want to do with the lines? I would lean towards non-null line
> > configfs.
> >
>
> Sure doesn't if you have to set the offsets in the config.
> But might do if you provide them to the gpiod_chip_request_lines().
>
> The null case could then request the lines as-is?
>
> There seems to be a bit of interest in as-is of late, and the simplest
> case would then be three function calls.
>

Ok, makes sense. Null line-config -> request lines as is.

> > > > Then the lines can be set like this:
> > > >
> > > > // num_lines refers to the number of lines to set, not the number of
> > > > // lines in the request
> > > > gpiod_line_request_set_values(req, num_lines, offsets, values);
> > > >
> > >
> > > At first glance that feels a bit odd, being on the request while the
> > > others all operate on the config, but it maps to the SET_VALUES ioctl(),
> > > not the SET_CONFIG, so that makes sense.
> > >
> > > There is a corresponding get_values(req, num_lines, offsets, values)?
> > >
> >
> > Sure, just didn't include it in the example.
> >
> > > And a reconfigure(req, cfg)?
> > >
> >
> > Sure, just haven't decided on the name yet.
> > gpiod_line_request_reconfigure() would be what you're suggesting but
> > it sounds like it would reconfigure the request and not modify the
> > line configuration. I would prefer something closer to
> > gpiod_line_request_set_line_config() which is as verbose as it gets.
> > Maybe even gpiod_line_request_change_line_config()? Or
> > gpiod_line_request_modify_line_config()?
> >
>
> gpiod_line_request_reconfigure(req, cfg) is not modifying the line_config
> - it is modifying the request WITH the modified config.
>
> If gpiod_line_request_<splat>_line_config(req, cfg) works better for you
> then I'd prefer something along the lines of _apply_ or _commit_, as it is
> taking the line_config, modified by sets, and applying it to the
> requested lines.
>

How about gpiod_line_request_reconfigure_lines()?

Bart

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API
  2021-04-30 17:52                           ` Bartosz Golaszewski
@ 2021-05-01  0:15                             ` Kent Gibson
  0 siblings, 0 replies; 22+ messages in thread
From: Kent Gibson @ 2021-05-01  0:15 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: Linus Walleij, Andy Shevchenko, open list:GPIO SUBSYSTEM

On Fri, Apr 30, 2021 at 07:52:05PM +0200, Bartosz Golaszewski wrote:
> On Wed, Apr 28, 2021 at 12:34 PM Kent Gibson <warthog618@gmail.com> wrote:
> >
> > On Wed, Apr 28, 2021 at 11:19:05AM +0200, Bartosz Golaszewski wrote:
> > > On Fri, Apr 23, 2021 at 3:39 AM Kent Gibson <warthog618@gmail.com> wrote:
> > > >
> > >

[snip]

> >
> > If gpiod_line_request_<splat>_line_config(req, cfg) works better for you
> > then I'd prefer something along the lines of _apply_ or _commit_, as it is
> > taking the line_config, modified by sets, and applying it to the
> > requested lines.
> >
> 
> How about gpiod_line_request_reconfigure_lines()?
> 

That works for me.

Cheers,
Kent.

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2021-05-01  0:16 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-04-10 14:51 [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 1/6] treewide: rename chip property accessors Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 2/6] core: add refcounting helpers Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 3/6] core: implement line_info objects Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 4/6] core: rework line events Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 5/6] core: rework line requests Bartosz Golaszewski
2021-04-10 14:51 ` [libgpiod][RFC 6/6] core: implement line watch events Bartosz Golaszewski
2021-04-14 14:15 ` [libgpiod][RFC 0/6] first draft of libgpiod v2.0 API Kent Gibson
2021-04-16  9:36   ` Bartosz Golaszewski
2021-04-17  7:23     ` Kent Gibson
2021-04-17 11:31       ` Bartosz Golaszewski
2021-04-18  3:48         ` Kent Gibson
2021-04-18 21:12           ` Bartosz Golaszewski
2021-04-19  1:17             ` Kent Gibson
2021-04-21 20:04               ` Bartosz Golaszewski
2021-04-22  2:32                 ` Kent Gibson
2021-04-22  9:24                   ` Bartosz Golaszewski
2021-04-23  1:38                     ` Kent Gibson
2021-04-28  9:19                       ` Bartosz Golaszewski
2021-04-28 10:34                         ` Kent Gibson
2021-04-30 17:52                           ` Bartosz Golaszewski
2021-05-01  0:15                             ` Kent Gibson

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.