drivers/gpio/gpiolib.c | 96 +++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 53 deletions(-)
Since commit aab5c6f20023 ("gpio: set device type for GPIO chips"),
`gdev->dev.release` is unset. As a result, the reference count to
`gdev->dev` isn't dropped on the error handling paths.
Drop the reference on errors.
Also reorder the instructions to make the error handling simpler.
Now gpiochip_add_data_with_key() roughly looks like:
>>> Some memory allocation. Go to ERR ZONE 1 on errors.
>>> device_initialize().
(gpiodev_release() takes over the responsibility for freeing the
resources of `gdev->dev`. The subsequent error handling paths
shouldn't go through ERR ZONE 1 again which leads to double free.)
>>> Some initialization mainly on `gdev`.
>>> The rest of initialization. Go to ERR ZONE 2 on errors.
>>> Chip registration success and exit.
>>> ERR ZONE 2. gpio_device_put() and exit.
>>> ERR ZONE 1.
Cc: stable@vger.kernel.org
Fixes: aab5c6f20023 ("gpio: set device type for GPIO chips")
Signed-off-by: Tzung-Bi Shih <tzungbi@kernel.org>
---
v2:
- Reorder the instructions again to make the error handling simpler which
fixes https://lore.kernel.org/all/20260116081036.352286-2-tzungbi@kernel.org
too.
- Modify the commit message slightly.
v1: https://lore.kernel.org/all/20260116081036.352286-4-tzungbi@kernel.org
drivers/gpio/gpiolib.c | 96 +++++++++++++++++++-----------------------
1 file changed, 43 insertions(+), 53 deletions(-)
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index c52200eaaaff..039cd3e56baf 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -893,13 +893,15 @@ static const struct device_type gpio_dev_type = {
#define gcdev_unregister(gdev) device_del(&(gdev)->dev)
#endif
+/*
+ * An initial reference count has been held in gpiochip_add_data_with_key().
+ * The caller should drop the reference via gpio_device_put() on errors.
+ */
static int gpiochip_setup_dev(struct gpio_device *gdev)
{
struct fwnode_handle *fwnode = dev_fwnode(&gdev->dev);
int ret;
- device_initialize(&gdev->dev);
-
/*
* If fwnode doesn't belong to another device, it's safe to clear its
* initialized flag.
@@ -965,9 +967,11 @@ static void gpiochip_setup_devs(void)
list_for_each_entry_srcu(gdev, &gpio_devices, list,
srcu_read_lock_held(&gpio_devices_srcu)) {
ret = gpiochip_setup_dev(gdev);
- if (ret)
+ if (ret) {
+ gpio_device_put(gdev);
dev_err(&gdev->dev,
"Failed to initialize gpio device (%d)\n", ret);
+ }
}
}
@@ -1048,71 +1052,67 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
int base = 0;
int ret;
- /*
- * First: allocate and populate the internal stat container, and
- * set up the struct device.
- */
gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
if (!gdev)
return -ENOMEM;
-
- gdev->dev.type = &gpio_dev_type;
- gdev->dev.bus = &gpio_bus_type;
- gdev->dev.parent = gc->parent;
- rcu_assign_pointer(gdev->chip, gc);
-
gc->gpiodev = gdev;
gpiochip_set_data(gc, data);
- device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc));
-
ret = ida_alloc(&gpio_ida, GFP_KERNEL);
if (ret < 0)
goto err_free_gdev;
gdev->id = ret;
- ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id);
+ ret = init_srcu_struct(&gdev->srcu);
if (ret)
goto err_free_ida;
+ rcu_assign_pointer(gdev->chip, gc);
- if (gc->parent && gc->parent->driver)
- gdev->owner = gc->parent->driver->owner;
- else if (gc->owner)
- /* TODO: remove chip->owner */
- gdev->owner = gc->owner;
- else
- gdev->owner = THIS_MODULE;
+ ret = init_srcu_struct(&gdev->desc_srcu);
+ if (ret)
+ goto err_cleanup_gdev_srcu;
+
+ ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id);
+ if (ret)
+ goto err_cleanup_desc_srcu;
+
+ device_initialize(&gdev->dev);
+ gdev->dev.type = &gpio_dev_type;
+ gdev->dev.bus = &gpio_bus_type;
+ gdev->dev.parent = gc->parent;
+ device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc));
ret = gpiochip_get_ngpios(gc, &gdev->dev);
if (ret)
- goto err_free_dev_name;
+ goto err_put_device;
+ gdev->ngpio = gc->ngpio;
gdev->descs = kcalloc(gc->ngpio, sizeof(*gdev->descs), GFP_KERNEL);
if (!gdev->descs) {
ret = -ENOMEM;
- goto err_free_dev_name;
+ goto err_put_device;
}
gdev->label = kstrdup_const(gc->label ?: "unknown", GFP_KERNEL);
if (!gdev->label) {
ret = -ENOMEM;
- goto err_free_descs;
+ goto err_put_device;
}
- gdev->ngpio = gc->ngpio;
gdev->can_sleep = gc->can_sleep;
-
rwlock_init(&gdev->line_state_lock);
RAW_INIT_NOTIFIER_HEAD(&gdev->line_state_notifier);
BLOCKING_INIT_NOTIFIER_HEAD(&gdev->device_notifier);
-
- ret = init_srcu_struct(&gdev->srcu);
- if (ret)
- goto err_free_label;
-
- ret = init_srcu_struct(&gdev->desc_srcu);
- if (ret)
- goto err_cleanup_gdev_srcu;
+#ifdef CONFIG_PINCTRL
+ INIT_LIST_HEAD(&gdev->pin_ranges);
+#endif
+ if (gc->parent && gc->parent->driver)
+ gdev->owner = gc->parent->driver->owner;
+ else if (gc->owner)
+ /* TODO: remove chip->owner */
+ gdev->owner = gc->owner;
+ else
+ gdev->owner = THIS_MODULE;
scoped_guard(mutex, &gpio_devices_lock) {
/*
@@ -1128,7 +1128,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
if (base < 0) {
ret = base;
base = 0;
- goto err_cleanup_desc_srcu;
+ goto err_put_device;
}
/*
@@ -1148,14 +1148,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
ret = gpiodev_add_to_list_unlocked(gdev);
if (ret) {
gpiochip_err(gc, "GPIO integer space overlap, cannot add chip\n");
- goto err_cleanup_desc_srcu;
+ goto err_put_device;
}
}
-#ifdef CONFIG_PINCTRL
- INIT_LIST_HEAD(&gdev->pin_ranges);
-#endif
-
if (gc->names)
gpiochip_set_desc_names(gc);
@@ -1249,25 +1245,19 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
scoped_guard(mutex, &gpio_devices_lock)
list_del_rcu(&gdev->list);
synchronize_srcu(&gpio_devices_srcu);
- if (gdev->dev.release) {
- /* release() has been registered by gpiochip_setup_dev() */
- gpio_device_put(gdev);
- goto err_print_message;
- }
+err_put_device:
+ gpio_device_put(gdev);
+ goto err_print_message;
+
err_cleanup_desc_srcu:
cleanup_srcu_struct(&gdev->desc_srcu);
err_cleanup_gdev_srcu:
cleanup_srcu_struct(&gdev->srcu);
-err_free_label:
- kfree_const(gdev->label);
-err_free_descs:
- kfree(gdev->descs);
-err_free_dev_name:
- kfree(dev_name(&gdev->dev));
err_free_ida:
ida_free(&gpio_ida, gdev->id);
err_free_gdev:
kfree(gdev);
+
err_print_message:
/* failures here can mean systems won't boot... */
if (ret != -EPROBE_DEFER) {
--
2.53.0.rc2.204.g2597b5adb4-goog
On Tue, Feb 3, 2026 at 7:02 AM Tzung-Bi Shih <tzungbi@kernel.org> wrote:
> Since commit aab5c6f20023 ("gpio: set device type for GPIO chips"),
> `gdev->dev.release` is unset. As a result, the reference count to
> `gdev->dev` isn't dropped on the error handling paths.
>
> Drop the reference on errors.
>
> Also reorder the instructions to make the error handling simpler.
> Now gpiochip_add_data_with_key() roughly looks like:
>
> >>> Some memory allocation. Go to ERR ZONE 1 on errors.
> >>> device_initialize().
>
> (gpiodev_release() takes over the responsibility for freeing the
> resources of `gdev->dev`. The subsequent error handling paths
> shouldn't go through ERR ZONE 1 again which leads to double free.)
This doesn't need to be parenthesized, it should be pointed out
so people can follow it, also:
> @@ -1048,71 +1052,67 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
> int base = 0;
> int ret;
>
> - /*
> - * First: allocate and populate the internal stat container, and
> - * set up the struct device.
> - */
> gdev = kzalloc(sizeof(*gdev), GFP_KERNEL);
> if (!gdev)
> return -ENOMEM;
> -
> - gdev->dev.type = &gpio_dev_type;
> - gdev->dev.bus = &gpio_bus_type;
> - gdev->dev.parent = gc->parent;
> - rcu_assign_pointer(gdev->chip, gc);
> -
> gc->gpiodev = gdev;
> gpiochip_set_data(gc, data);
>
> - device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc));
> -
> ret = ida_alloc(&gpio_ida, GFP_KERNEL);
> if (ret < 0)
> goto err_free_gdev;
> gdev->id = ret;
>
> - ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id);
> + ret = init_srcu_struct(&gdev->srcu);
> if (ret)
> goto err_free_ida;
> + rcu_assign_pointer(gdev->chip, gc);
>
> - if (gc->parent && gc->parent->driver)
> - gdev->owner = gc->parent->driver->owner;
> - else if (gc->owner)
> - /* TODO: remove chip->owner */
> - gdev->owner = gc->owner;
> - else
> - gdev->owner = THIS_MODULE;
> + ret = init_srcu_struct(&gdev->desc_srcu);
> + if (ret)
> + goto err_cleanup_gdev_srcu;
> +
> + ret = dev_set_name(&gdev->dev, GPIOCHIP_NAME "%d", gdev->id);
> + if (ret)
> + goto err_cleanup_desc_srcu;
> +
> + device_initialize(&gdev->dev);
> + gdev->dev.type = &gpio_dev_type;
This is the point where, from this point on, gpiodev_release() will
get called from the type.
This is worth a comment in the code don't you think?
Something like:
/*
* After this point any allocated resources will be free():d by
* gpiodev_release(), if you add new resources then make sure
* they get free():ed there.
*/
gdev->dev.type = &gpio_dev_type;
This helps developers to do the right thing.
With that added comment:
Reviewed-by: Linus Walleij <linusw@kernel.org>
Yours,
Linus Walleij
On Tue, Feb 03, 2026 at 10:35:14AM +0100, Linus Walleij wrote: > On Tue, Feb 3, 2026 at 7:02 AM Tzung-Bi Shih <tzungbi@kernel.org> wrote: > > Also reorder the instructions to make the error handling simpler. > > Now gpiochip_add_data_with_key() roughly looks like: > > > > >>> Some memory allocation. Go to ERR ZONE 1 on errors. > > >>> device_initialize(). > > > > (gpiodev_release() takes over the responsibility for freeing the > > resources of `gdev->dev`. The subsequent error handling paths > > shouldn't go through ERR ZONE 1 again which leads to double free.) > > This doesn't need to be parenthesized, it should be pointed out > so people can follow it, also: > [...] > > + device_initialize(&gdev->dev); > > + gdev->dev.type = &gpio_dev_type; > > > This is the point where, from this point on, gpiodev_release() will > get called from the type. > > This is worth a comment in the code don't you think? > > Something like: > > /* > * After this point any allocated resources will be free():d by > * gpiodev_release(), if you add new resources then make sure > * they get free():ed there. > */ > gdev->dev.type = &gpio_dev_type; > > This helps developers to do the right thing. > > With that added comment: > Reviewed-by: Linus Walleij <linusw@kernel.org> Thanks for your review. Fix both of them in v3 https://lore.kernel.org/all/20260205092840.2574840-1-tzungbi@kernel.org/.
© 2016 - 2026 Red Hat, Inc.