* [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays
@ 2026-11-06 6:30 Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 1/8] gpiolib: fix GPIO descriptor leak in gpiod_get_array() Dmitry Torokhov
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 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(),
switches fastpath bitmaps in struct gpio_array to bitmap_zalloc()
and bitmap_free(), and uses the %*pb format specifier when printing
array masks.
In patch 7/8, 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 patch so the
maintainers can decide whether they prefer the single contiguous
allocation in patch 6/8 or the strict bitmap abstraction in patch 7/8.
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
Changes in v3:
- In patch 1/8, declared loop induction variable in for loop header and
carried it through the series.
- In patch 5/8, placed descs definition and kzalloc_flex()
initialization on a single line, and preserved reverse Christmas
tree order.
- In patch 6/8, moved descs definition after count < 0 error check.
- In patch 7/8, deferred descs definition in gpio_descs_alloc() until
after the early return, using struct_size_t() and a single statement
initialization at the kzalloc() call site.
- Added patch 8/8 using %*pb format specifier when printing array masks.
- Added Reviewed-by tags from Andy Shevchenko and Linus Walleij.
- Link to v2: https://patch.msgid.link/20261103-b4-gpiolib-free-v2-0-ea46767c9cf8@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 (8):
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
gpiolib: use %*pb format specifier when printing array masks
drivers/gpio/gpiolib-of.h | 2 +-
drivers/gpio/gpiolib.c | 225 ++++++++++++++++++++++++------------------
drivers/gpio/gpiolib.h | 2 +-
include/linux/gpio/consumer.h | 4 +
4 files changed, 135 insertions(+), 98 deletions(-)
---
base-commit: ddfe51b0a0f5dc9e415cc2c8ad8db17532dd426b
change-id: 20261021-b4-gpiolib-free-74d92ea91503
Thanks.
--
Dmitry
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 1/8] gpiolib: fix GPIO descriptor leak in gpiod_get_array()
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
@ 2026-11-06 6:30 ` Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 2/8] gpiolib: fix fast-path cancellation " Dmitry Torokhov
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel,
Andy Shevchenko
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")
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/gpio/gpiolib.c | 34 ++++++++++++++--------------------
1 file changed, 14 insertions(+), 20 deletions(-)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index fbdeda2f2787..a5a519c0f001 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -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 (int 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.506.g506a7eabc5-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 2/8] gpiolib: fix fast-path cancellation in gpiod_get_array()
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 1/8] gpiolib: fix GPIO descriptor leak in gpiod_get_array() Dmitry Torokhov
@ 2026-11-06 6:30 ` Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 3/8] gpiolib: of: return -ENOENT from of_gpio_count() stub Dmitry Torokhov
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel,
Andy Shevchenko
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")
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
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 a5a519c0f001..e095aaef0a09 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.506.g506a7eabc5-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 3/8] gpiolib: of: return -ENOENT from of_gpio_count() stub
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 1/8] gpiolib: fix GPIO descriptor leak in gpiod_get_array() Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 2/8] gpiolib: fix fast-path cancellation " Dmitry Torokhov
@ 2026-11-06 6:30 ` Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 4/8] gpiolib: add support for scope-based management of GPIO descriptors Dmitry Torokhov
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel,
Andy Shevchenko
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")
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
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.506.g506a7eabc5-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 4/8] gpiolib: add support for scope-based management of GPIO descriptors
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
` (2 preceding siblings ...)
2026-11-06 6:30 ` [PATCH v3 3/8] gpiolib: of: return -ENOENT from of_gpio_count() stub Dmitry Torokhov
@ 2026-11-06 6:30 ` Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 5/8] gpiolib: use __free(gpiod_put_array) in gpiod_get_array() Dmitry Torokhov
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel,
Andy Shevchenko
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.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
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.506.g506a7eabc5-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 5/8] gpiolib: use __free(gpiod_put_array) in gpiod_get_array()
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
` (3 preceding siblings ...)
2026-11-06 6:30 ` [PATCH v3 4/8] gpiolib: add support for scope-based management of GPIO descriptors Dmitry Torokhov
@ 2026-11-06 6:30 ` Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 6/8] gpiolib: avoid krealloc() " Dmitry Torokhov
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel,
Andy Shevchenko
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.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Reviewed-by: Linus Walleij <linusw@kernel.org>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/gpio/gpiolib.c | 16 +++++-----------
1 file changed, 5 insertions(+), 11 deletions(-)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index e095aaef0a09..1da91e52e51c 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -5227,28 +5227,23 @@ 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 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 (int 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;
@@ -5258,6 +5253,7 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
* its chip as a candidate for fast bitmap processing path.
*/
if (i == 0 && gpiod_hwgpio(desc) == 0) {
+ size_t descs_size = struct_size(descs, desc, count);
struct gpio_descs *array;
bitmap_size = BITS_TO_LONGS(gdev->ngpio > count ?
@@ -5266,10 +5262,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 +5324,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.506.g506a7eabc5-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 6/8] gpiolib: avoid krealloc() in gpiod_get_array()
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
` (4 preceding siblings ...)
2026-11-06 6:30 ` [PATCH v3 5/8] gpiolib: use __free(gpiod_put_array) in gpiod_get_array() Dmitry Torokhov
@ 2026-11-06 6:30 ` Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 7/8] gpiolib: use bitmap_zalloc() for fastpath bitmaps Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 8/8] gpiolib: use %*pb format specifier when printing array masks Dmitry Torokhov
7 siblings, 0 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 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 | 179 +++++++++++++++++++++++++++----------------------
1 file changed, 98 insertions(+), 81 deletions(-)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 1da91e52e51c..9b5024d30060 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,104 +5311,36 @@ 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 count, bitmap_size;
- unsigned long dflags;
+ int 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);
+ struct gpio_descs *descs __free(gpiod_put_array) = NULL;
for (int 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) {
- size_t descs_size = struct_size(descs, desc, count);
- struct gpio_descs *array;
-
- 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.506.g506a7eabc5-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 7/8] gpiolib: use bitmap_zalloc() for fastpath bitmaps
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
` (5 preceding siblings ...)
2026-11-06 6:30 ` [PATCH v3 6/8] gpiolib: avoid krealloc() " Dmitry Torokhov
@ 2026-11-06 6:30 ` Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 8/8] gpiolib: use %*pb format specifier when printing array masks Dmitry Torokhov
7 siblings, 0 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 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>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/gpio/gpiolib.c | 52 +++++++++++++++++++++++++++++++++++++-------------
drivers/gpio/gpiolib.h | 2 +-
2 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 9b5024d30060..83edb89a364a 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -5208,41 +5208,66 @@ 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_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
* its chip as a candidate for fast bitmap processing path.
*/
if (gpiod_hwgpio(first_desc) != 0)
- return kzalloc_flex(*descs, desc, count);
+ return kzalloc_flex(struct gpio_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_size = struct_size_t(struct gpio_descs, desc, count);
+ alloc_size = descs_size + sizeof(*array_info);
- descs = kzalloc(alloc_size, GFP_KERNEL);
+ struct gpio_descs *descs __free(gpiod_put_array) = 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 +5299,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);
@@ -5398,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.506.g506a7eabc5-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 8/8] gpiolib: use %*pb format specifier when printing array masks
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
` (6 preceding siblings ...)
2026-11-06 6:30 ` [PATCH v3 7/8] gpiolib: use bitmap_zalloc() for fastpath bitmaps Dmitry Torokhov
@ 2026-11-06 6:30 ` Dmitry Torokhov
7 siblings, 0 replies; 9+ messages in thread
From: Dmitry Torokhov @ 2026-11-06 6:30 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Andy Shevchenko, Janusz Krzysztofik, linux-gpio, linux-kernel,
Andy Shevchenko
In gpiod_get_array(), the dev_dbg() statement prints get_mask, set_mask,
and invert_mask using %lx after dereferencing them as raw unsigned long
pointers. This truncates masks wider than BITS_PER_LONG to their first
word and relies on the raw unsigned long pointer representation of
bitmaps.
Switch to the %*pb format specifier, passing the mask size and pointer
for each mask.
Suggested-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com>
---
drivers/gpio/gpiolib.c | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 83edb89a364a..079b5a715eaa 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -5362,10 +5362,11 @@ struct gpio_descs *__must_check gpiod_get_array(struct device *dev,
}
if (descs->info)
dev_dbg(dev,
- "GPIO array info: chip=%s, size=%d, get_mask=%lx, set_mask=%lx, invert_mask=%lx\n",
+ "GPIO array info: chip=%s, size=%d, get_mask=%*pb, set_mask=%*pb, invert_mask=%*pb\n",
descs->info->gdev->label, descs->info->size,
- *descs->info->get_mask, *descs->info->set_mask,
- *descs->info->invert_mask);
+ descs->info->size, descs->info->get_mask,
+ descs->info->size, descs->info->set_mask,
+ descs->info->size, descs->info->invert_mask);
return_ptr(descs);
}
EXPORT_SYMBOL_GPL(gpiod_get_array);
--
2.56.0.506.g506a7eabc5-goog
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-11-06 6:30 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 6:30 [PATCH v3 0/8] gpiolib: scope-based management and cleanup for GPIO descriptor arrays Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 1/8] gpiolib: fix GPIO descriptor leak in gpiod_get_array() Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 2/8] gpiolib: fix fast-path cancellation " Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 3/8] gpiolib: of: return -ENOENT from of_gpio_count() stub Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 4/8] gpiolib: add support for scope-based management of GPIO descriptors Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 5/8] gpiolib: use __free(gpiod_put_array) in gpiod_get_array() Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 6/8] gpiolib: avoid krealloc() " Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 7/8] gpiolib: use bitmap_zalloc() for fastpath bitmaps Dmitry Torokhov
2026-11-06 6:30 ` [PATCH v3 8/8] gpiolib: use %*pb format specifier when printing array masks 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®