mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays
@ 2026-11-04  3:40 Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 1/7] gpiolib: fix GPIO descriptor leak in gpiod_get_array() Dmitry Torokhov
                   ` (6 more replies)
  0 siblings, 7 replies; 8+ messages in thread
From: Dmitry Torokhov @ 2026-11-04  3:40 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel,
	Andy Shevchenko

This series fixes a GPIO descriptor leak and a fast-path cancellation
bug in gpiod_get_array(), fixes the !CONFIG_OF_GPIO stub for
of_gpio_count() to return -ENOENT instead of 0, introduces
DEFINE_FREE() scope-based cleanup helpers for GPIO descriptors
(gpiod_put and gpiod_put_array), converts gpiod_get_array() to use
them, reworks descriptor array allocation to avoid krealloc(), and
switches the fastpath bitmaps in struct gpio_array to bitmap_zalloc()
and bitmap_free().

In the final patch, each of the three fastpath bitmaps (get_mask,
set_mask, and invert_mask) is allocated with its own bitmap_zalloc()
call rather than packing all three into a single bitmap_zalloc()
allocation. While Andy suggested a single 3x bitmap_zalloc() call to
avoid multiple allocations, slicing one bitmap into three pointers
still requires rounding the bit count up to BITS_PER_LONG and doing
manual unsigned long pointer arithmetic (which would still break if
bitmaps ever gained a header or tail), while still adding a second
allocation and extra bitmap_free() paths on fastpath cancellation and
teardown. Using three separate bitmap_zalloc() calls achieves full
bitmap API abstraction at the cost of three extra slab allocations when
the fastpath is used, and is kept as a separate final patch so the
maintainers can decide whether they prefer the single contiguous
allocation in patch 6/7 or the strict bitmap abstraction in patch 7/7.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
Changes in v2:
- Added cover letter for the series.
- Added a patch fixing the !CONFIG_OF_GPIO of_gpio_count() stub to
  return -ENOENT instead of 0, and dropped the count == 0 check in
  gpiod_get_array().
- In patch 6/7, separated array_info assignment from declaration in
  gpio_descs_check_fastpath() immediately before NULL check to preserve
  reverse-christmas-tree order.
- Added patch 7/7 switching fastpath bitmaps in struct gpio_array to
  individual bitmap_zalloc() and bitmap_free() calls per Andy's review
  feedback.
- Link to v1: https://patch.msgid.link/20261021190212.2237804-1-dmitry.torokhov@gmail.com

---
Dmitry Torokhov (7):
      gpiolib: fix GPIO descriptor leak in gpiod_get_array()
      gpiolib: fix fast-path cancellation in gpiod_get_array()
      gpiolib: of: return -ENOENT from of_gpio_count() stub
      gpiolib: add support for scope-based management of GPIO descriptors
      gpiolib: use __free(gpiod_put_array) in gpiod_get_array()
      gpiolib: avoid krealloc() in gpiod_get_array()
      gpiolib: use bitmap_zalloc() for fastpath bitmaps

 drivers/gpio/gpiolib-of.h     |   2 +-
 drivers/gpio/gpiolib.c        | 224 ++++++++++++++++++++++++------------------
 drivers/gpio/gpiolib.h        |   2 +-
 include/linux/gpio/consumer.h |   4 +
 4 files changed, 134 insertions(+), 98 deletions(-)
---
base-commit: d6bcfc94eb4da6e089c5473970cd96727292fd97
change-id: 20261021-b4-gpiolib-free-74d92ea91503

Thanks.

-- 
Dmitry


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

* [PATCH v2 1/7] gpiolib: fix GPIO descriptor leak in gpiod_get_array()
  2026-11-04  3:40 [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
@ 2026-11-04  3:40 ` Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 2/7] gpiolib: fix fast-path cancellation " Dmitry Torokhov
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Dmitry Torokhov @ 2026-11-04  3:40 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel

In gpiod_get_array(), descs->ndescs is used as the loop induction
variable and is only incremented at the end of each iteration. When
krealloc() fails on the first iteration (when index is 0), descs->desc[0]
has already been acquired via gpiod_get_index(), but descs->ndescs is
still 0. Consequently, gpiod_put_array() frees descs without releasing
descs->desc[0], leaking the descriptor.

Fix this by using a dedicated loop induction variable and incrementing
descs->ndescs immediately upon storing the retrieved descriptor.
Additionally, since the fast-path array_info setup only executes for
the first element (index 0), simplify the bitmap_set() calls to start at
bit 0 for count bits instead of calculating offsets from the loop index.

Fixes: bf9346f5d47b ("gpiolib: Identify arrays matching GPIO hardware")
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/gpio/gpiolib.c | 36 +++++++++++++++---------------------
 1 file changed, 15 insertions(+), 21 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index fbdeda2f2787..f33033dd9473 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -5230,7 +5230,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 	struct gpio_descs *descs;
 	struct gpio_device *gdev;
 	struct gpio_array *array_info = NULL;
-	int count, bitmap_size;
+	int i, count, bitmap_size;
 	unsigned long dflags;
 	size_t descs_size;
 
@@ -5243,21 +5243,21 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 	if (!descs)
 		return ERR_PTR(-ENOMEM);
 
-	for (descs->ndescs = 0; descs->ndescs < count; descs->ndescs++) {
-		desc = gpiod_get_index(dev, con_id, descs->ndescs, flags);
+	for (i = 0; i < count; i++) {
+		desc = gpiod_get_index(dev, con_id, i, flags);
 		if (IS_ERR(desc)) {
 			gpiod_put_array(descs);
 			return ERR_CAST(desc);
 		}
 
-		descs->desc[descs->ndescs] = desc;
+		descs->desc[descs->ndescs++] = desc;
 
 		gdev = gpiod_to_gpio_device(desc);
 		/*
 		 * If pin hardware number of array member 0 is also 0, select
 		 * its chip as a candidate for fast bitmap processing path.
 		 */
-		if (descs->ndescs == 0 && gpiod_hwgpio(desc) == 0) {
+		if (i == 0 && gpiod_hwgpio(desc) == 0) {
 			struct gpio_descs *array;
 
 			bitmap_size = BITS_TO_LONGS(gdev->ngpio > count ?
@@ -5282,10 +5282,8 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 			array_info->desc = descs->desc;
 			array_info->size = count;
 			array_info->gdev = gdev;
-			bitmap_set(array_info->get_mask, descs->ndescs,
-				   count - descs->ndescs);
-			bitmap_set(array_info->set_mask, descs->ndescs,
-				   count - descs->ndescs);
+			bitmap_set(array_info->get_mask, 0, count);
+			bitmap_set(array_info->set_mask, 0, count);
 			descs->info = array_info;
 		}
 
@@ -5295,38 +5293,34 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 
 		/* Unmark array members which don't belong to the 'fast' chip */
 		if (array_info->gdev != gdev) {
-			__clear_bit(descs->ndescs, array_info->get_mask);
-			__clear_bit(descs->ndescs, array_info->set_mask);
+			__clear_bit(i, array_info->get_mask);
+			__clear_bit(i, array_info->set_mask);
 		}
 		/*
 		 * Detect array members which belong to the 'fast' chip
 		 * but their pins are not in hardware order.
 		 */
-		else if (gpiod_hwgpio(desc) != descs->ndescs) {
+		else if (gpiod_hwgpio(desc) != i) {
 			/*
 			 * Don't use fast path if all array members processed so
 			 * far belong to the same chip as this one but its pin
 			 * hardware number is different from its array index.
 			 */
-			if (bitmap_full(array_info->get_mask, descs->ndescs)) {
+			if (bitmap_full(array_info->get_mask, i)) {
 				array_info = NULL;
 			} else {
-				__clear_bit(descs->ndescs,
-					    array_info->get_mask);
-				__clear_bit(descs->ndescs,
-					    array_info->set_mask);
+				__clear_bit(i, array_info->get_mask);
+				__clear_bit(i, array_info->set_mask);
 			}
 		} else {
 			dflags = READ_ONCE(desc->flags);
 			/* Exclude open drain or open source from fast output */
 			if (test_bit(GPIOD_FLAG_OPEN_DRAIN, &dflags) ||
 			    test_bit(GPIOD_FLAG_OPEN_SOURCE, &dflags))
-				__clear_bit(descs->ndescs,
-					    array_info->set_mask);
+				__clear_bit(i, array_info->set_mask);
 			/* Identify 'fast' pins which require invertion */
 			if (gpiod_is_active_low(desc))
-				__set_bit(descs->ndescs,
-					  array_info->invert_mask);
+				__set_bit(i, array_info->invert_mask);
 		}
 	}
 	if (array_info)

-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v2 2/7] gpiolib: fix fast-path cancellation in gpiod_get_array()
  2026-11-04  3:40 [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 1/7] gpiolib: fix GPIO descriptor leak in gpiod_get_array() Dmitry Torokhov
@ 2026-11-04  3:40 ` Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 3/7] gpiolib: of: return -ENOENT from of_gpio_count() stub Dmitry Torokhov
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Dmitry Torokhov @ 2026-11-04  3:40 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel

In gpiod_get_array(), when a pin on the same chip is not in hardware
order and the preceding pins form a contiguous block, the fast path is
cancelled by setting array_info to NULL. However, descs->info was
already populated with the array_info pointer during the first
iteration, leaving descs->info pointing to the aborted array_info.
Consequently, subsequent array operations still use the fast path with
remaining bits set in the masks and wrong hardware pin numbers.

Clear descs->info alongside array_info when cancelling the fast path so
callers do not erroneously use the aborted array_info.

Fixes: c4c958aa64f3 ("gpiolib: Fix array members of same chip processed separately")
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/gpio/gpiolib.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index f33033dd9473..9a850f5c1917 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -5307,6 +5307,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 			 * hardware number is different from its array index.
 			 */
 			if (bitmap_full(array_info->get_mask, i)) {
+				descs->info = NULL;
 				array_info = NULL;
 			} else {
 				__clear_bit(i, array_info->get_mask);

-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v2 3/7] gpiolib: of: return -ENOENT from of_gpio_count() stub
  2026-11-04  3:40 [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 1/7] gpiolib: fix GPIO descriptor leak in gpiod_get_array() Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 2/7] gpiolib: fix fast-path cancellation " Dmitry Torokhov
@ 2026-11-04  3:40 ` Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 4/7] gpiolib: add support for scope-based management of GPIO descriptors Dmitry Torokhov
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Dmitry Torokhov @ 2026-11-04  3:40 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel

gpiod_count() and its firmware backends are expected to return either a
positive number of GPIOs or a negative error code (-ENOENT) when no
GPIOs are assigned. However, the !CONFIG_OF_GPIO stub for
of_gpio_count() returns 0 instead of -ENOENT. This causes gpiod_count()
to skip the platform_gpio_count() fallback (which triggers when
count < 0) and erroneously return 0 to callers.

Change the stub to return -ENOENT to match the real of_gpio_count()
implementation and the other error-returning stubs in gpiolib-of.h.

Fixes: f626d6dfb709 ("gpio: of: Break out OF-only code")
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/gpio/gpiolib-of.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpio/gpiolib-of.h b/drivers/gpio/gpiolib-of.h
index 218cfe5bc4ac..9ca540d93f93 100644
--- a/drivers/gpio/gpiolib-of.h
+++ b/drivers/gpio/gpiolib-of.h
@@ -46,7 +46,7 @@ static inline bool of_gpiochip_instance_match(struct gpio_chip *gc,
 static inline int of_gpio_count(const struct fwnode_handle *fwnode,
 				const char *con_id)
 {
-	return 0;
+	return -ENOENT;
 }
 static inline int of_gpiochip_get_lflags(struct gpio_chip *chip,
 					 struct fwnode_reference_args *gpiospec,

-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v2 4/7] gpiolib: add support for scope-based management of GPIO descriptors
  2026-11-04  3:40 [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
                   ` (2 preceding siblings ...)
  2026-11-04  3:40 ` [PATCH v2 3/7] gpiolib: of: return -ENOENT from of_gpio_count() stub Dmitry Torokhov
@ 2026-11-04  3:40 ` Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 5/7] gpiolib: use __free(gpiod_put_array) in gpiod_get_array() Dmitry Torokhov
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 8+ messages in thread
From: Dmitry Torokhov @ 2026-11-04  3:40 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel

Add DEFINE_FREE() definitions for gpiod_put and gpiod_put_array to
allow using __free(gpiod_put) and __free(gpiod_put_array) for automatic,
scope-based cleanup of individual GPIO descriptors and descriptor
arrays. Place these definitions after the CONFIG_GPIOLIB check so that
they are available regardless of whether CONFIG_GPIOLIB is enabled.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 include/linux/gpio/consumer.h | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h
index fceeefd5f893..ee183a97575c 100644
--- a/include/linux/gpio/consumer.h
+++ b/include/linux/gpio/consumer.h
@@ -3,6 +3,7 @@
 #define __LINUX_GPIO_CONSUMER_H
 
 #include <linux/bits.h>
+#include <linux/cleanup.h>
 #include <linux/err.h>
 #include <linux/types.h>
 
@@ -578,6 +579,9 @@ gpiod_is_equal(const struct gpio_desc *desc, const struct gpio_desc *other)
 
 #endif /* CONFIG_GPIOLIB */
 
+DEFINE_FREE(gpiod_put, struct gpio_desc *, if (!IS_ERR_OR_NULL(_T)) gpiod_put(_T))
+DEFINE_FREE(gpiod_put_array, struct gpio_descs *, if (!IS_ERR_OR_NULL(_T)) gpiod_put_array(_T))
+
 #if IS_ENABLED(CONFIG_GPIOLIB) && IS_ENABLED(CONFIG_HTE)
 int gpiod_enable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags);
 int gpiod_disable_hw_timestamp_ns(struct gpio_desc *desc, unsigned long flags);

-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v2 5/7] gpiolib: use __free(gpiod_put_array) in gpiod_get_array()
  2026-11-04  3:40 [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
                   ` (3 preceding siblings ...)
  2026-11-04  3:40 ` [PATCH v2 4/7] gpiolib: add support for scope-based management of GPIO descriptors Dmitry Torokhov
@ 2026-11-04  3:40 ` Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 6/7] gpiolib: avoid krealloc() " Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 7/7] gpiolib: use bitmap_zalloc() for fastpath bitmaps Dmitry Torokhov
  6 siblings, 0 replies; 8+ messages in thread
From: Dmitry Torokhov @ 2026-11-04  3:40 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel

Switch to using __free(gpiod_put_array) scope-based cleanup facility
in gpiod_get_array(), simplifying error unwinding and eliminating manual
gpiod_put_array() calls.

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/gpio/gpiolib.c | 17 ++++++-----------
 1 file changed, 6 insertions(+), 11 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 9a850f5c1917..e71f8865559f 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -5227,28 +5227,24 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 						enum gpiod_flags flags)
 {
 	struct gpio_desc *desc;
-	struct gpio_descs *descs;
 	struct gpio_device *gdev;
 	struct gpio_array *array_info = NULL;
 	int i, count, bitmap_size;
 	unsigned long dflags;
-	size_t descs_size;
 
 	count = gpiod_count(dev, con_id);
 	if (count < 0)
 		return ERR_PTR(count);
 
-	descs_size = struct_size(descs, desc, count);
-	descs = kzalloc(descs_size, GFP_KERNEL);
+	struct gpio_descs *descs __free(gpiod_put_array) =
+		kzalloc_flex(*descs, desc, count);
 	if (!descs)
 		return ERR_PTR(-ENOMEM);
 
 	for (i = 0; i < count; i++) {
 		desc = gpiod_get_index(dev, con_id, i, flags);
-		if (IS_ERR(desc)) {
-			gpiod_put_array(descs);
+		if (IS_ERR(desc))
 			return ERR_CAST(desc);
-		}
 
 		descs->desc[descs->ndescs++] = desc;
 
@@ -5259,6 +5255,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 		 */
 		if (i == 0 && gpiod_hwgpio(desc) == 0) {
 			struct gpio_descs *array;
+			size_t descs_size = struct_size(descs, desc, count);
 
 			bitmap_size = BITS_TO_LONGS(gdev->ngpio > count ?
 						    gdev->ngpio : count);
@@ -5266,10 +5263,8 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 			array = krealloc(descs, descs_size +
 					 struct_size(array_info, invert_mask, 3 * bitmap_size),
 					 GFP_KERNEL | __GFP_ZERO);
-			if (!array) {
-				gpiod_put_array(descs);
+			if (!array)
 				return ERR_PTR(-ENOMEM);
-			}
 
 			descs = array;
 
@@ -5330,7 +5325,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 			array_info->gdev->label, array_info->size,
 			*array_info->get_mask, *array_info->set_mask,
 			*array_info->invert_mask);
-	return descs;
+	return_ptr(descs);
 }
 EXPORT_SYMBOL_GPL(gpiod_get_array);
 

-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v2 6/7] gpiolib: avoid krealloc() in gpiod_get_array()
  2026-11-04  3:40 [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
                   ` (4 preceding siblings ...)
  2026-11-04  3:40 ` [PATCH v2 5/7] gpiolib: use __free(gpiod_put_array) in gpiod_get_array() Dmitry Torokhov
@ 2026-11-04  3:40 ` Dmitry Torokhov
  2026-11-04  3:40 ` [PATCH v2 7/7] gpiolib: use bitmap_zalloc() for fastpath bitmaps Dmitry Torokhov
  6 siblings, 0 replies; 8+ messages in thread
From: Dmitry Torokhov @ 2026-11-04  3:40 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel

Instead of allocating descs before the loop and reallocating it via
krealloc() when candidate hardware array matching is detected on the
first iteration, defer allocation of descs until the first descriptor
is retrieved.

Factor descriptor allocation and fast-path validation into helper
functions gpio_descs_alloc() and gpio_descs_check_fastpath(). Inside
the loop, acquire the descriptor using __free(gpiod_put), allocate
descs once on the first iteration, check fast-path candidate
suitability, and transfer descriptor ownership using no_free_ptr().

Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/gpio/gpiolib.c | 181 ++++++++++++++++++++++++++-----------------------
 1 file changed, 98 insertions(+), 83 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index e71f8865559f..cf311c1a707c 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -5208,6 +5208,91 @@ static void gpiochip_free_hogs(struct gpio_chip *gc)
 		gpiochip_free_own_desc(desc);
 }
 
+static struct gpio_descs *gpio_descs_alloc(unsigned int count, struct gpio_desc *first_desc)
+{
+	struct gpio_device *gdev = gpiod_to_gpio_device(first_desc);
+	struct gpio_array *array_info;
+	struct gpio_descs *descs;
+	size_t descs_size, alloc_size;
+	unsigned int bitmap_size;
+
+	/*
+	 * If pin hardware number of array member 0 is also 0, select
+	 * its chip as a candidate for fast bitmap processing path.
+	 */
+	if (gpiod_hwgpio(first_desc) != 0)
+		return kzalloc_flex(*descs, desc, count);
+
+	descs_size = struct_size(descs, desc, count);
+	bitmap_size = BITS_TO_LONGS(max(gdev->ngpio, count));
+	alloc_size = descs_size + struct_size(array_info, invert_mask, 3 * bitmap_size);
+
+	descs = kzalloc(alloc_size, GFP_KERNEL);
+	if (!descs)
+		return NULL;
+
+	array_info = (void *)descs + descs_size;
+	array_info->get_mask = array_info->invert_mask + bitmap_size;
+	array_info->set_mask = array_info->get_mask + bitmap_size;
+
+	array_info->desc = descs->desc;
+	array_info->size = count;
+	array_info->gdev = gdev;
+	bitmap_set(array_info->get_mask, 0, count);
+	bitmap_set(array_info->set_mask, 0, count);
+	descs->info = array_info;
+
+	return descs;
+}
+
+static void gpio_descs_check_fastpath(struct gpio_descs *descs,
+				      struct gpio_desc *desc)
+{
+	unsigned int idx = descs->ndescs;
+	struct gpio_array *array_info;
+	unsigned long dflags;
+
+	array_info = descs->info;
+	if (!array_info)
+		return;
+
+	/* Unmark array members which don't belong to the 'fast' chip */
+	if (array_info->gdev != gpiod_to_gpio_device(desc)) {
+		__clear_bit(idx, array_info->get_mask);
+		__clear_bit(idx, array_info->set_mask);
+		return;
+	}
+
+	/*
+	 * Detect array members which belong to the 'fast' chip
+	 * but their pins are not in hardware order.
+	 */
+	if (gpiod_hwgpio(desc) != idx) {
+		/*
+		 * Don't use fast path if all array members processed so
+		 * far belong to the same chip as this one but its pin
+		 * hardware number is different from its array index.
+		 */
+		if (bitmap_full(array_info->get_mask, idx)) {
+			descs->info = NULL;
+		} else {
+			__clear_bit(idx, array_info->get_mask);
+			__clear_bit(idx, array_info->set_mask);
+		}
+		return;
+	}
+
+	dflags = READ_ONCE(desc->flags);
+	/* Exclude open drain or open source from fast output */
+	if (test_bit(GPIOD_FLAG_OPEN_DRAIN, &dflags) ||
+	    test_bit(GPIOD_FLAG_OPEN_SOURCE, &dflags))
+		__clear_bit(idx, array_info->set_mask);
+
+	/* Identify 'fast' pins which require invertion */
+	if (gpiod_is_active_low(desc))
+		__set_bit(idx, array_info->invert_mask);
+}
+
 /**
  * gpiod_get_array - obtain multiple GPIOs from a multi-index GPIO function
  * @dev:	GPIO consumer, can be NULL for system-global GPIOs
@@ -5226,105 +5311,35 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
 						const char *con_id,
 						enum gpiod_flags flags)
 {
-	struct gpio_desc *desc;
-	struct gpio_device *gdev;
-	struct gpio_array *array_info = NULL;
-	int i, count, bitmap_size;
-	unsigned long dflags;
+	struct gpio_descs *descs __free(gpiod_put_array) = NULL;
+	int i, count;
 
 	count = gpiod_count(dev, con_id);
 	if (count < 0)
 		return ERR_PTR(count);
 
-	struct gpio_descs *descs __free(gpiod_put_array) =
-		kzalloc_flex(*descs, desc, count);
-	if (!descs)
-		return ERR_PTR(-ENOMEM);
-
 	for (i = 0; i < count; i++) {
-		desc = gpiod_get_index(dev, con_id, i, flags);
+		struct gpio_desc *desc __free(gpiod_put) =
+			gpiod_get_index(dev, con_id, i, flags);
 		if (IS_ERR(desc))
 			return ERR_CAST(desc);
 
-		descs->desc[descs->ndescs++] = desc;
-
-		gdev = gpiod_to_gpio_device(desc);
-		/*
-		 * If pin hardware number of array member 0 is also 0, select
-		 * its chip as a candidate for fast bitmap processing path.
-		 */
-		if (i == 0 && gpiod_hwgpio(desc) == 0) {
-			struct gpio_descs *array;
-			size_t descs_size = struct_size(descs, desc, count);
-
-			bitmap_size = BITS_TO_LONGS(gdev->ngpio > count ?
-						    gdev->ngpio : count);
-
-			array = krealloc(descs, descs_size +
-					 struct_size(array_info, invert_mask, 3 * bitmap_size),
-					 GFP_KERNEL | __GFP_ZERO);
-			if (!array)
+		if (i == 0) {
+			descs = gpio_descs_alloc(count, desc);
+			if (!descs)
 				return ERR_PTR(-ENOMEM);
-
-			descs = array;
-
-			array_info = (void *)descs + descs_size;
-			array_info->get_mask = array_info->invert_mask +
-						  bitmap_size;
-			array_info->set_mask = array_info->get_mask +
-						  bitmap_size;
-
-			array_info->desc = descs->desc;
-			array_info->size = count;
-			array_info->gdev = gdev;
-			bitmap_set(array_info->get_mask, 0, count);
-			bitmap_set(array_info->set_mask, 0, count);
-			descs->info = array_info;
 		}
 
-		/* If there is no cache for fast bitmap processing path, continue */
-		if (!array_info)
-			continue;
+		gpio_descs_check_fastpath(descs, desc);
 
-		/* Unmark array members which don't belong to the 'fast' chip */
-		if (array_info->gdev != gdev) {
-			__clear_bit(i, array_info->get_mask);
-			__clear_bit(i, array_info->set_mask);
-		}
-		/*
-		 * Detect array members which belong to the 'fast' chip
-		 * but their pins are not in hardware order.
-		 */
-		else if (gpiod_hwgpio(desc) != i) {
-			/*
-			 * Don't use fast path if all array members processed so
-			 * far belong to the same chip as this one but its pin
-			 * hardware number is different from its array index.
-			 */
-			if (bitmap_full(array_info->get_mask, i)) {
-				descs->info = NULL;
-				array_info = NULL;
-			} else {
-				__clear_bit(i, array_info->get_mask);
-				__clear_bit(i, array_info->set_mask);
-			}
-		} else {
-			dflags = READ_ONCE(desc->flags);
-			/* Exclude open drain or open source from fast output */
-			if (test_bit(GPIOD_FLAG_OPEN_DRAIN, &dflags) ||
-			    test_bit(GPIOD_FLAG_OPEN_SOURCE, &dflags))
-				__clear_bit(i, array_info->set_mask);
-			/* Identify 'fast' pins which require invertion */
-			if (gpiod_is_active_low(desc))
-				__set_bit(i, array_info->invert_mask);
-		}
+		descs->desc[descs->ndescs++] = no_free_ptr(desc);
 	}
-	if (array_info)
+	if (descs->info)
 		dev_dbg(dev,
 			"GPIO array info: chip=%s, size=%d, get_mask=%lx, set_mask=%lx, invert_mask=%lx\n",
-			array_info->gdev->label, array_info->size,
-			*array_info->get_mask, *array_info->set_mask,
-			*array_info->invert_mask);
+			descs->info->gdev->label, descs->info->size,
+			*descs->info->get_mask, *descs->info->set_mask,
+			*descs->info->invert_mask);
 	return_ptr(descs);
 }
 EXPORT_SYMBOL_GPL(gpiod_get_array);

-- 
2.56.0.385.gac671c819a-goog


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

* [PATCH v2 7/7] gpiolib: use bitmap_zalloc() for fastpath bitmaps
  2026-11-04  3:40 [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
                   ` (5 preceding siblings ...)
  2026-11-04  3:40 ` [PATCH v2 6/7] gpiolib: avoid krealloc() " Dmitry Torokhov
@ 2026-11-04  3:40 ` Dmitry Torokhov
  6 siblings, 0 replies; 8+ messages in thread
From: Dmitry Torokhov @ 2026-11-04  3:40 UTC (permalink / raw)
  To: Linus Walleij, Bartosz Golaszewski
  Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel,
	Andy Shevchenko

Currently, struct gpio_array embeds invert_mask[] as a flexible array
member, and gpio_descs_alloc() allocates the descriptor array,
struct gpio_array, and all three fastpath bitmaps in a single kzalloc()
call using manual BITS_TO_LONGS() calculations and pointer arithmetic.
This exposes gpiolib to the underlying unsigned long representation
used by bitmaps rather than respecting the bitmap abstraction.

Convert invert_mask in struct gpio_array to a pointer and allocate each
of the three fastpath bitmaps individually via bitmap_zalloc(), freeing
them via bitmap_free() on fastpath cancellation and array disposal.

Although this adds three slab allocations when the fastpath is active,
packing all three masks into a single bitmap_zalloc() call would still
require rounding the bit count up to BITS_PER_LONG and slicing the
buffer with unsigned long pointer arithmetic, adding a second
allocation and extra free paths without achieving bitmap API
abstraction.

Suggested-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
 drivers/gpio/gpiolib.c | 47 +++++++++++++++++++++++++++++++++++++----------
 drivers/gpio/gpiolib.h |  2 +-
 2 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index cf311c1a707c..bba4e5bb125f 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -5208,13 +5208,28 @@ static void gpiochip_free_hogs(struct gpio_chip *gc)
 		gpiochip_free_own_desc(desc);
 }
 
-static struct gpio_descs *gpio_descs_alloc(unsigned int count, struct gpio_desc *first_desc)
+static void gpio_descs_free_fastpath(struct gpio_descs *descs)
+{
+	struct gpio_array *array_info;
+
+	array_info = descs->info;
+	if (!array_info)
+		return;
+
+	bitmap_free(array_info->get_mask);
+	bitmap_free(array_info->set_mask);
+	bitmap_free(array_info->invert_mask);
+	descs->info = NULL;
+}
+
+static struct gpio_descs *gpio_descs_alloc(unsigned int count,
+					   struct gpio_desc *first_desc)
 {
 	struct gpio_device *gdev = gpiod_to_gpio_device(first_desc);
+	struct gpio_descs *descs __free(gpiod_put_array) = NULL;
 	struct gpio_array *array_info;
-	struct gpio_descs *descs;
 	size_t descs_size, alloc_size;
-	unsigned int bitmap_size;
+	unsigned int nbits;
 
 	/*
 	 * If pin hardware number of array member 0 is also 0, select
@@ -5224,25 +5239,36 @@ static struct gpio_descs *gpio_descs_alloc(unsigned int count, struct gpio_desc
 		return kzalloc_flex(*descs, desc, count);
 
 	descs_size = struct_size(descs, desc, count);
-	bitmap_size = BITS_TO_LONGS(max(gdev->ngpio, count));
-	alloc_size = descs_size + struct_size(array_info, invert_mask, 3 * bitmap_size);
+	alloc_size = descs_size + sizeof(*array_info);
 
 	descs = kzalloc(alloc_size, GFP_KERNEL);
 	if (!descs)
 		return NULL;
 
 	array_info = (void *)descs + descs_size;
-	array_info->get_mask = array_info->invert_mask + bitmap_size;
-	array_info->set_mask = array_info->get_mask + bitmap_size;
+	descs->info = array_info;
+
+	nbits = max(gdev->ngpio, count);
+
+	array_info->get_mask = bitmap_zalloc(nbits, GFP_KERNEL);
+	if (!array_info->get_mask)
+		return NULL;
+
+	array_info->set_mask = bitmap_zalloc(nbits, GFP_KERNEL);
+	if (!array_info->set_mask)
+		return NULL;
+
+	array_info->invert_mask = bitmap_zalloc(nbits, GFP_KERNEL);
+	if (!array_info->invert_mask)
+		return NULL;
 
 	array_info->desc = descs->desc;
 	array_info->size = count;
 	array_info->gdev = gdev;
 	bitmap_set(array_info->get_mask, 0, count);
 	bitmap_set(array_info->set_mask, 0, count);
-	descs->info = array_info;
 
-	return descs;
+	return_ptr(descs);
 }
 
 static void gpio_descs_check_fastpath(struct gpio_descs *descs,
@@ -5274,7 +5300,7 @@ static void gpio_descs_check_fastpath(struct gpio_descs *descs,
 		 * hardware number is different from its array index.
 		 */
 		if (bitmap_full(array_info->get_mask, idx)) {
-			descs->info = NULL;
+			gpio_descs_free_fastpath(descs);
 		} else {
 			__clear_bit(idx, array_info->get_mask);
 			__clear_bit(idx, array_info->set_mask);
@@ -5397,6 +5423,7 @@ void gpiod_put_array(struct gpio_descs *descs)
 	for (i = 0; i < descs->ndescs; i++)
 		gpiod_put(descs->desc[i]);
 
+	gpio_descs_free_fastpath(descs);
 	kfree(descs);
 }
 EXPORT_SYMBOL_GPL(gpiod_put_array);
diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h
index 650a702741df..de1795c9be3d 100644
--- a/drivers/gpio/gpiolib.h
+++ b/drivers/gpio/gpiolib.h
@@ -135,7 +135,7 @@ struct gpio_array {
 	struct gpio_device	*gdev;
 	unsigned long		*get_mask;
 	unsigned long		*set_mask;
-	unsigned long		invert_mask[];
+	unsigned long		*invert_mask;
 };
 
 #define for_each_gpio_desc(gc, desc)					\

-- 
2.56.0.385.gac671c819a-goog


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

end of thread, other threads:[~2026-11-04  3:40 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04  3:40 [PATCH v2 0/7] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
2026-11-04  3:40 ` [PATCH v2 1/7] gpiolib: fix GPIO descriptor leak in gpiod_get_array() Dmitry Torokhov
2026-11-04  3:40 ` [PATCH v2 2/7] gpiolib: fix fast-path cancellation " Dmitry Torokhov
2026-11-04  3:40 ` [PATCH v2 3/7] gpiolib: of: return -ENOENT from of_gpio_count() stub Dmitry Torokhov
2026-11-04  3:40 ` [PATCH v2 4/7] gpiolib: add support for scope-based management of GPIO descriptors Dmitry Torokhov
2026-11-04  3:40 ` [PATCH v2 5/7] gpiolib: use __free(gpiod_put_array) in gpiod_get_array() Dmitry Torokhov
2026-11-04  3:40 ` [PATCH v2 6/7] gpiolib: avoid krealloc() " Dmitry Torokhov
2026-11-04  3:40 ` [PATCH v2 7/7] gpiolib: use bitmap_zalloc() for fastpath bitmaps Dmitry Torokhov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®