Skip to contentSkip to conversation

treewide: rework struct gpiod_line_bulk

PATCH Superseded Bartosz Golaszewski 4 participants 11 messages Oct 23, 2020 – Oct 25, 2020

Subsystems (2)

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 treewide: rework struct gpiod_line_bulk

From: Bartosz Golaszewski <[email protected]>

Current implementation of struct gpiod_line_bulk uses stack memory excessively. The structure is big: it's an array 64 pointers + 4 bytes size. That amounts to 260 bytes on 32-bit and 516 bytes on 64-bit architectures respectively. It's also used everywhere as all functions dealing with single lines eventually end up calling bulk counterparts.

The rework addresses it by making the bulk structure opaque and providing appropriate interfaces for library users while retaining a way for internal users to allocate single line bulks on the stack.

The macro-based loop has been removed. In its place we provide a function iterating over all lines held by a bulk and calling the provided callback function for each line as well as a new line bulk iterator which works similarily to chip and line iterators.

Since bulk operations can now fail, a bunch of test-cases has been added to cover the relevant code.

While at it: using the word offset both when referring to line's HW offset in a chip as well as the offset in a bulk leads to confusion. This patch renames the bulk offset to index.

---
 bindings/cxx/gpiod.hpp        |  18 ++-
 bindings/cxx/line_bulk.cpp    | 112 +++++++++--------
 bindings/python/gpiodmodule.c | 182 +++++++++++++++++++--------
 include/gpiod.h               | 169 +++++++++++++------------
 lib/core.c                    | 178 +++++++++++++++++++--------
 lib/ctxless.c                 |  53 +++++---
 lib/helpers.c                 |  61 +++++----
 lib/iter.c                    |  33 +++++
 tests/Makefile.am             |   1 +
 tests/gpiod-test.h            |   5 +
 tests/tests-bulk.c            | 158 ++++++++++++++++++++++++
 tests/tests-chip.c            |  59 +++++----
 tests/tests-event.c           |  73 +++++++----
 tests/tests-iter.c            |  26 ++++
 tests/tests-line.c            | 225 +++++++++++++---------------------
 15 files changed, 888 insertions(+), 465 deletions(-)
 create mode 100644 tests/tests-bulk.c
bindings/cxx/gpiod.hpp +13 -5
index 8dfc172..83f543f 100644
@@ -626,18 +626,18 @@ public:
 
 	/**
 	 * @brief Get the line at given offset.
-	 * @param offset Offset of the line to get.
+	 * @param index Offset of the line to get.
 	 * @return Reference to the line object.
 	 */
-	GPIOD_API line& get(unsigned int offset);
+	GPIOD_API line& get(unsigned int index);
 
 	/**
 	 * @brief Get the line at given offset without bounds checking.
-	 * @param offset Offset of the line to get.
+	 * @param index Offset of the line to get.
 	 * @return Reference to the line object.
 	 * @note No bounds checking is performed.
 	 */
-	GPIOD_API line& operator[](unsigned int offset);
+	GPIOD_API line& operator[](unsigned int index);
 
 	/**
 	 * @brief Get the number of lines currently held by this object.
@@ -839,8 +839,16 @@ public:
 
 private:
 
+	struct line_bulk_deleter
+	{
+		void operator()(::gpiod_line_bulk *bulk);
+	};
+
 	void throw_if_empty(void) const;
-	void to_line_bulk(::gpiod_line_bulk* bulk) const;
+
+	using line_bulk_ptr = ::std::unique_ptr<::gpiod_line_bulk, line_bulk_deleter>;
+
+	line_bulk_ptr to_line_bulk(void) const;
 
 	::std::vector<line> _m_bulk;
 };
bindings/cxx/line_bulk.cpp +63 -49

235 lines not on this page · raw message

bindings/python/gpiodmodule.c +132 -50

395 lines not on this page · raw message

include/gpiod.h +89 -80

243 lines not on this page · raw message

lib/core.c +130 -48

316 lines not on this page · raw message

lib/ctxless.c +36 -17

156 lines not on this page · raw message

lib/helpers.c +40 -21

106 lines not on this page · raw message

lib/iter.c +33 -0
index bfd2852..c9a117c 100644
@@ -23,6 +23,12 @@ struct gpiod_line_iter {
 	unsigned int offset;
 };
 
+struct gpiod_line_bulk_iter {
+	struct gpiod_line_bulk *bulk;
+	unsigned int num_lines;
+	unsigned int index;
+};
+
 static int dir_filter(const struct dirent *dir)
 {
 	return !strncmp(dir->d_name, "gpiochip", 8);
@@ -169,3 +175,30 @@ struct gpiod_line *gpiod_line_iter_next(struct gpiod_line_iter *iter)
 	return iter->offset < (iter->num_lines)
 					? iter->lines[iter->offset++] : NULL;
 }
+
+struct gpiod_line_bulk_iter *
+gpiod_line_bulk_iter_new(struct gpiod_line_bulk *bulk)
+{
+	struct gpiod_line_bulk_iter *iter;
+
+	iter = malloc(sizeof(*iter));
+	if (!iter)
+		return NULL;
+
+	iter->bulk = bulk;
+	iter->index = 0;
+	iter->num_lines = gpiod_line_bulk_num_lines(bulk);
+
+	return iter;
+}
+
+void gpiod_line_bulk_iter_free(struct gpiod_line_bulk_iter *iter)
+{
+	free(iter);
+}
+
+struct gpiod_line *gpiod_line_bulk_iter_next(struct gpiod_line_bulk_iter *iter)
+{
+	return iter->index < iter->num_lines ?
+		gpiod_line_bulk_get_line(iter->bulk, iter->index++) : NULL;
+}
tests/Makefile.am +1 -0
index f79f8d5..4a3befd 100644
@@ -22,6 +22,7 @@ bin_PROGRAMS = gpiod-test
 gpiod_test_SOURCES =			\
 		gpiod-test.c		\
 		gpiod-test.h		\
+		tests-bulk.c		\
 		tests-chip.c		\
 		tests-ctxless.c		\
 		tests-event.c		\
tests/gpiod-test.h +5 -0
index d4a8c5f..d930c04 100644
@@ -24,12 +24,17 @@
  * regular 'struct typename' syntax.
  */
 typedef struct gpiod_chip gpiod_chip_struct;
+typedef struct gpiod_line_bulk gpiod_line_bulk_struct;
 typedef struct gpiod_chip_iter gpiod_chip_iter_struct;
 typedef struct gpiod_line_iter gpiod_line_iter_struct;
+typedef struct gpiod_line_bulk_iter gpiod_line_bulk_iter_struct;
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_chip_struct, gpiod_chip_close);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_bulk_struct, gpiod_line_bulk_free);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_chip_iter_struct, gpiod_chip_iter_free);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_iter_struct, gpiod_line_iter_free);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(gpiod_line_bulk_iter_struct,
+			      gpiod_line_bulk_iter_free);
 
 /* These are private definitions and should not be used directly. */
 typedef void (*_gpiod_test_func)(void);
tests/tests-bulk.c new file +158 -0

158 lines not on this page · raw message

tests/tests-chip.c +29 -30

114 lines not on this page · raw message

tests/tests-event.c +47 -26

199 lines not on this page · raw message

tests/tests-iter.c +26 -0
index 8deee8e..18263af 100644
@@ -121,3 +121,29 @@ GPIOD_TEST_CASE(line_iter, 0, { 8 })
 
 	g_assert_cmpuint(i, ==, 8);
 }
+
+GPIOD_TEST_CASE(line_bulk_iter, 0, { 4 })
+{
+	g_autoptr(gpiod_line_bulk_iter_struct) iter = NULL;
+	g_autoptr(gpiod_line_bulk_struct) bulk = NULL;
+	g_autoptr(gpiod_chip_struct) chip = NULL;
+	struct gpiod_line *line;
+	gint i = 0;
+
+	chip = gpiod_chip_open(gpiod_test_chip_path(0));
+	g_assert_nonnull(chip);
+	gpiod_test_return_if_failed();
+
+	bulk = gpiod_chip_get_all_lines(chip);
+	g_assert_nonnull(bulk);
+	gpiod_test_return_if_failed();
+
+	iter = gpiod_line_bulk_iter_new(bulk);
+	g_assert_nonnull(iter);
+	gpiod_test_return_if_failed();
+
+	gpiod_line_bulk_iter_foreach_line(iter, line)
+		g_assert_cmpint(i++, ==, gpiod_line_offset(line));
+
+	g_assert_cmpint(i, ==, 4);
+}
tests/tests-line.c +86 -139

415 lines not on this page · raw message

Signature · 1 line
2.28.0
#1 lore raw
Quoted 9 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Current implementation of struct gpiod_line_bulk uses stack memory excessively. The structure is big: it's an array 64 pointers + 4 bytes size. That amounts to 260 bytes on 32-bit and 516 bytes on 64-bit architectures respectively. It's also used everywhere as all functions dealing with single lines eventually end up calling bulk counterparts.

Quoted 8 lines

The rework addresses it by making the bulk structure opaque and providing appropriate interfaces for library users while retaining a way for internal users to allocate single line bulks on the stack.

The macro-based loop has been removed. In its place we provide a function iterating over all lines held by a bulk and calling the provided callback function for each line as well as a new line bulk iterator which works similarily to chip and line iterators.

similarly

Quoted 6 lines

Since bulk operations can now fail, a bunch of test-cases has been added to cover the relevant code.

While at it: using the word offset both when referring to line's HW offset in a chip as well as the offset in a bulk leads to confusion. This patch renames the bulk offset to index.

...

lib/core.c struct gpiod_chip { · line 87

Quoted 5 lines
lib/core.c +5 -0
+struct gpiod_line_bulk {
+       struct gpiod_chip *owner;
+       unsigned int num_lines;
+       unsigned int max_lines;
+       struct gpiod_line *lines[1];

Why not '[]' as we do in the kernel?

lib/core.c struct gpiod_chip { · line 91

Quoted 4 lines
lib/core.c +3 -0
+};
 
+#define BULK_SINGLE_LINE_INIT(line) \
+               { gpiod_line_get_chip(line), 1, 1, { (line) } }

Hmm... Perhaps union can help here?

union {
struct *lines[];
struct *line;
}

num_lines == 1 exactly defines this.

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#2 lore raw
Quoted 3 lines · Andy Shevchenko
Quoted 1 line · Bartosz Golaszewski

[snip]

Quoted 9 lines

lib/core.c struct gpiod_chip { · line 87

Quoted 6 lines
lib/core.c +5 -0
+struct gpiod_line_bulk {
+       struct gpiod_chip *owner;
+       unsigned int num_lines;
+       unsigned int max_lines;
+       struct gpiod_line *lines[1];

Why not '[]' as we do in the kernel?

Because I want to be able to store a single line in this structure if I do:

    struct gpiod_line_bulk bulk;

Using [] makes the size of this structure not take into account the last array member.

Quoted 5 lines

lib/core.c struct gpiod_chip { · line 91

Quoted 4 lines
lib/core.c +3 -0
+};
 
+#define BULK_SINGLE_LINE_INIT(line) \
+               { gpiod_line_get_chip(line), 1, 1, { (line) } }
Quoted 9 lines

Hmm... Perhaps union can help here?

union {
struct *lines[];
struct *line;
}

num_lines == 1 exactly defines this.

Nope because gcc will scream:

error: flexible array member in union

Bartosz

#3 lore raw
Quoted 3 lines · Bartosz Golaszewski
Quoted 1 line · Andy Shevchenko

On Fri, Oct 23, 2020 at 12:31 PM Bartosz Golaszewski <[email protected]> wrote:

...

Quoted 28 lines
Quoted 8 lines

lib/core.c struct gpiod_chip { · line 87

Quoted 5 lines
lib/core.c +5 -0
+struct gpiod_line_bulk {
+       struct gpiod_chip *owner;
+       unsigned int num_lines;
+       unsigned int max_lines;
+       struct gpiod_line *lines[1];

Why not '[]' as we do in the kernel?

Because I want to be able to store a single line in this structure if I do:

    struct gpiod_line_bulk bulk;

Using [] makes the size of this structure not take into account the last array member.

Quoted 12 lines

lib/core.c struct gpiod_chip { · line 91

Quoted 4 lines
lib/core.c +3 -0
+};
 
+#define BULK_SINGLE_LINE_INIT(line) \
+               { gpiod_line_get_chip(line), 1, 1, { (line) } }

Hmm... Perhaps union can help here?

union {
struct *lines[];
struct *line;
}

Quoted 6 lines
Quoted 2 lines

num_lines == 1 exactly defines this.

Nope because gcc will scream:

error: flexible array member in union

Ah, of course. Should be
struct ... **lines;

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#4 lore raw
Quoted 3 lines · Andy Shevchenko
Quoted 2 lines · Bartosz Golaszewski

On Fri, Oct 23, 2020 at 12:24 PM Andy Shevchenko
<[email protected]> wrote:

...

Quoted 6 lines
Quoted 3 lines

Nope because gcc will scream:

error: flexible array member in union

Ah, of course. Should be
struct ... **lines;

But it is not gonna work... we need an array here. or just one member

struct *lines;

bulk:
lines = malloc(num_lines * sizeof(lines));
xxx->lines = lines;

single:
xxx->lines = line;

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#5 lore raw
Quoted 18 lines · Andy Shevchenko
Quoted 3 lines · Andy Shevchenko
Quoted 2 lines · Bartosz Golaszewski

On Fri, Oct 23, 2020 at 12:24 PM Andy Shevchenko
<[email protected]> wrote:

...

Quoted 6 lines
Quoted 3 lines

Nope because gcc will scream:

error: flexible array member in union

Ah, of course. Should be
struct ... **lines;

But it is not gonna work... we need an array here. or just one member

Quoted 9 lines

struct *lines;

bulk:
lines = malloc(num_lines * sizeof(lines));
xxx->lines = lines;

single:
xxx->lines = line;

The definition I used is clearer - it's explicit about using an array with a single member by default and can be extended as needed when allocating.

Bartosz

#6 lore raw
Quoted 26 lines · Bartosz Golaszewski
Quoted 24 lines · Andy Shevchenko
Quoted 3 lines · Andy Shevchenko
Quoted 2 lines · Bartosz Golaszewski

On Fri, Oct 23, 2020 at 12:24 PM Andy Shevchenko
<[email protected]> wrote:

...

Quoted 6 lines
Quoted 3 lines

Nope because gcc will scream:

error: flexible array member in union

Ah, of course. Should be
struct ... **lines;

But it is not gonna work... we need an array here. or just one member

struct *lines;

bulk:
lines = malloc(num_lines * sizeof(lines));
xxx->lines = lines;

Quoted 6 lines
Quoted 2 lines

single:
xxx->lines = line;

The definition I used is clearer - it's explicit about using an array with a single member by default and can be extended as needed when allocating.

According to [1] it makes harder to avoid sizeof() type of calculation mistakes for such struct.

From my point of view extending something that has been already predefined is not cool. But it's up to you to decide.

[1]: https://www.kernel.org/doc/html/v5.9-rc1/process/deprecated.html#zero-length-and-one-element-arrays

Signature · 2 lines
With Best Regards,
Andy Shevchenko
#7 lore raw
Quoted 35 lines · Andy Shevchenko
Quoted 26 lines · Bartosz Golaszewski
Quoted 24 lines · Andy Shevchenko
Quoted 3 lines · Andy Shevchenko
Quoted 2 lines · Bartosz Golaszewski

On Fri, Oct 23, 2020 at 12:24 PM Andy Shevchenko
<[email protected]> wrote:

...

Quoted 6 lines
Quoted 3 lines

Nope because gcc will scream:

error: flexible array member in union

Ah, of course. Should be
struct ... **lines;

But it is not gonna work... we need an array here. or just one member

struct *lines;

bulk:
lines = malloc(num_lines * sizeof(lines));
xxx->lines = lines;

Quoted 6 lines
Quoted 2 lines

single:
xxx->lines = line;

The definition I used is clearer - it's explicit about using an array with a single member by default and can be extended as needed when allocating.

Quoted 8 lines

According to [1] it makes harder to avoid sizeof() type of calculation mistakes for such struct.

From my point of view extending something that has been already predefined is not cool. But it's up to you to decide.

[1]: https://www.kernel.org/doc/html/v5.9-rc1/process/deprecated.html#zero-length-and-one-element-arrays

Your point is valid but we're not talking about a structure commonly used across a vact code base that is the linux kernel. We're talking about a structure that is private to a single .c file and its size is calculated exactly once right next to where it's defined. Risk of misusing it is miniscule IMO.

I think it's fine for what we need here: a struct that can be extended when allocated dynamically but also which can be used on the stack for the very precise use-case - holding a single line.

Bartosz

#8 lore raw
In reply to Bartosz Golaszewski
Quoted 49 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

Current implementation of struct gpiod_line_bulk uses stack memory excessively. The structure is big: it's an array 64 pointers + 4 bytes size. That amounts to 260 bytes on 32-bit and 516 bytes on 64-bit architectures respectively. It's also used everywhere as all functions dealing with single lines eventually end up calling bulk counterparts.

The rework addresses it by making the bulk structure opaque and providing appropriate interfaces for library users while retaining a way for internal users to allocate single line bulks on the stack.

The macro-based loop has been removed. In its place we provide a function iterating over all lines held by a bulk and calling the provided callback function for each line as well as a new line bulk iterator which works similarily to chip and line iterators.

Since bulk operations can now fail, a bunch of test-cases has been added to cover the relevant code.

While at it: using the word offset both when referring to line's HW offset in a chip as well as the offset in a bulk leads to confusion. This patch renames the bulk offset to index.

---
 bindings/cxx/gpiod.hpp        |  18 ++-
 bindings/cxx/line_bulk.cpp    | 112 +++++++++--------
 bindings/python/gpiodmodule.c | 182 +++++++++++++++++++--------
 include/gpiod.h               | 169 +++++++++++++------------
 lib/core.c                    | 178 +++++++++++++++++++--------
 lib/ctxless.c                 |  53 +++++---
 lib/helpers.c                 |  61 +++++----
 lib/iter.c                    |  33 +++++
 tests/Makefile.am             |   1 +
 tests/gpiod-test.h            |   5 +
 tests/tests-bulk.c            | 158 ++++++++++++++++++++++++
 tests/tests-chip.c            |  59 +++++----
 tests/tests-event.c           |  73 +++++++----
 tests/tests-iter.c            |  26 ++++
 tests/tests-line.c            | 225 +++++++++++++---------------------
 15 files changed, 888 insertions(+), 465 deletions(-)
 create mode 100644 tests/tests-bulk.c
bindings/cxx/gpiod.hpp +0 -0
index 8dfc172..83f543f 100644

bindings/cxx/gpiod.hpp public: · line 631

Quoted 7 lines
bindings/cxx/gpiod.hpp +1 -1
@@ -626,18 +626,18 @@ public:
 
 	/**
 	 * @brief Get the line at given offset.
-	 * @param offset Offset of the line to get.
+	 * @param index Offset of the line to get.
 	 * @return Reference to the line object.
 	 */

The comment still says "Offset" and so is still confusing. Change it to something like: "Index of the line within the bulk"?

And document what happens if index >= size()?

Similarly elsewhere.

That is just the one thing that got my attention - I'll try to find time to go over the whole patch in the next few days.

Cheers,
Kent.

#9 lore raw
In reply to Bartosz Golaszewski
Quoted 2 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

[snip]

Quoted 7 lines
lib/core.c +3 -0
@@ -74,6 +74,106 @@ struct gpiod_chip {
 	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

lib/core.c struct gpiod_chip { · line 89

Quoted 10 lines
lib/core.c +10 -0
+ * 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 {
+	struct gpiod_chip *owner;
+	unsigned int num_lines;
+	unsigned int max_lines;
+	struct gpiod_line *lines[1];
+};
+

owner is only used to check that added lines are on same chip. Just compare with lines[0]->chip in gpiod_line_bulk_add_line() instead?

Also, line_bulk_same_chip() is now redundant as lines can only be added to the bulk via gpiod_line_bulk_add_line() which performs the check, so remove it and all uses of it throughout.

[snip]

Quoted 31 lines
lib/iter.c +23 -0
+struct gpiod_line_bulk_iter {
+	struct gpiod_line_bulk *bulk;
+	unsigned int num_lines;
+	unsigned int index;
+};
+
 static int dir_filter(const struct dirent *dir)
 {
 	return !strncmp(dir->d_name, "gpiochip", 8);
@@ -169,3 +175,30 @@ struct gpiod_line *gpiod_line_iter_next(struct gpiod_line_iter *iter)
 	return iter->offset < (iter->num_lines)
 					? iter->lines[iter->offset++] : NULL;
 }
+
+struct gpiod_line_bulk_iter *
+gpiod_line_bulk_iter_new(struct gpiod_line_bulk *bulk)
+{
+	struct gpiod_line_bulk_iter *iter;
+
+	iter = malloc(sizeof(*iter));
+	if (!iter)
+		return NULL;
+
+	iter->bulk = bulk;
+	iter->index = 0;
+	iter->num_lines = gpiod_line_bulk_num_lines(bulk);
+
+	return iter;
+}
+

lib/iter.c struct gpiod_line *gpiod_line_iter_next(struct gpiod_line_iter *iter) · line 204

Quoted 10 lines
lib/iter.c +10 -0
+void gpiod_line_bulk_iter_free(struct gpiod_line_bulk_iter *iter)
+{
+	free(iter);
+}
+
+struct gpiod_line *gpiod_line_bulk_iter_next(struct gpiod_line_bulk_iter *iter)
+{
+	return iter->index < iter->num_lines ?
+		gpiod_line_bulk_get_line(iter->bulk, iter->index++) : NULL;
+}

I question the value of the struct gpiod_line_bulk_iter, and also struct gpiod_line_iter.
They seem worse than the user performing a for-loop over the bulk indicies or chip offsets and getting each line themselves. They add a malloc overhead, in the case of gpiod_line_iter both a malloc and a calloc, as well as the corresponding frees.

What benefit do they provide?

Similarly gpiod_line_bulk_foreach_line().

And I'm not sure about the utility of the struct gpiod_chip_iter either as it bails if opening any of the chips fails. There a several reasons that could occur, e.g. permissions or unplugging, so you might want to leave it to the user to decide what to do.

So I would prefer an iter that provides prospective chip names, or just a scandir() wrapper that returns an array of names.

Wrt rethinking the libgpiod API for v2, I'd like to either reduce the API to a minimal function set, for example stripping out the iters, or at least identify the minimal set that everything else is built on, and then look at how to rework those to expose the uAPI v2 features. e.g. given lines in a bulk now have to all be on the same chip, and given uAPI v2 supports per-line configs even within a bulk, the whole line and bulk lifecycle, as well as their relationship the chip and the line request, can be rethought.

I'd also like to see ALL libgpiod types become opaque.

But that is getting way outside the scope of this patch...

Cheers,
Kent.

#10 lore raw
Quoted 20 lines · Kent Gibson
Quoted 2 lines · Bartosz Golaszewski

From: Bartosz Golaszewski <[email protected]>

[snip]

Quoted 13 lines
lib/core.c +9 -0
@@ -74,6 +74,106 @@ struct gpiod_chip {
      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 {
+     struct gpiod_chip *owner;
+     unsigned int num_lines;
Quoted 9 lines

lib/core.c struct gpiod_chip { · line 89

Quoted 4 lines
lib/core.c +4 -0
+     unsigned int max_lines;
+     struct gpiod_line *lines[1];
+};
+

owner is only used to check that added lines are on same chip. Just compare with lines[0]->chip in gpiod_line_bulk_add_line() instead?

Yes, of course.

Quoted 4 lines

Also, line_bulk_same_chip() is now redundant as lines can only be added to the bulk via gpiod_line_bulk_add_line() which performs the check, so remove it and all uses of it throughout.

Sure.

[snip]

Quoted 12 lines

I question the value of the struct gpiod_line_bulk_iter, and also struct gpiod_line_iter.
They seem worse than the user performing a for-loop over the bulk indicies or chip offsets and getting each line themselves. They add a malloc overhead, in the case of gpiod_line_iter both a malloc and a calloc, as well as the corresponding frees.

What benefit do they provide?

Similarly gpiod_line_bulk_foreach_line().

Quoted 8 lines

And I'm not sure about the utility of the struct gpiod_chip_iter either as it bails if opening any of the chips fails. There a several reasons that could occur, e.g. permissions or unplugging, so you might want to leave it to the user to decide what to do.

So I would prefer an iter that provides prospective chip names, or just a scandir() wrapper that returns an array of names.

I can think of a couple reasons to keep iterators in some form (while of course addressing the issues you mentioned) but I'll start a separate thread for discussion about changes in the API.

Quoted 9 lines

Wrt rethinking the libgpiod API for v2, I'd like to either reduce the API to a minimal function set, for example stripping out the iters, or at least identify the minimal set that everything else is built on, and then look at how to rework those to expose the uAPI v2 features. e.g. given lines in a bulk now have to all be on the same chip, and given uAPI v2 supports per-line configs even within a bulk, the whole line and bulk lifecycle, as well as their relationship the chip and the line request, can be rethought.

True. I'd also consider removing the entire ctxless family of functions and data structures. Throughout the entirety of libgpiod's lifetime I've received a lot of emails from users with various questions but nobody has ever asked me about the ctxless functions which makes me think that nobody uses it. If so - there's no need to keep it.

Quoted 2 lines

I'd also like to see ALL libgpiod types become opaque.

I'm not sure this is desirable, not for event structs anyway. While it's true that not exposing any structures to users is recommended in C libraries, this would impose a significant malloc overhead when reading events. While it's acceptable in Python bindings - there's no way around allocating Python objects anyway - I think that in low-level C interface reading events into a buffer provided by the user is still preferable.

We could probably live with the config structure being opaque - though for most cases I think just adding additional padding would be enough to keep ABI compatible. Opaque config structure would mean a lot of unnecessary churn for getters/setters.

Agreed for all other types though.

Quoted 2 lines

But that is getting way outside the scope of this patch...

Yes, let's discuss this in a dedicated thread. I'd really like to get most stuff right this time to be able to guarantee ABI and API compatibility over all feature releases in v2.x series.

Bartosz