* [PATCH v9 0/5] gpio: shared: replace platform lookup with dummy software nodes
@ 2026-11-02 12:50 Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 1/5] gpio: shared: factor out per-entry teardown helper Bartosz Golaszewski
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-11-02 12:50 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski
Abel reported to me a crash on Eliza linked to fwnode GPIO lookup for
shared GPIOs. Turns out we don't support no-device, fwnode-based lookup
for shared GPIOs. With the NULL-pointer dereference now fixed, this is
a rework that implements support for this use-case.
Sashiko pointed out several issues under v4. Some of them were bogus and
I fixed the rest.
Tested on Eliza and made sure the fwnode-only lookup works correctly.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
Changes in v9:
- Fix software node life-time issues by using a managed software nodes
for shared proxy devices
- Avoid mixing gotos and lock guards in the same function
- Fix a typo in comment
- Link to v8: https://patch.msgid.link/20261029-gpio-shared-fwnode-lookup-v8-0-d15d75cd560f@oss.qualcomm.com
Changes in v8:
- Move the auxiliary device infrastructure (including swnode struct)
into a separate structure staying alive until the release of the
device
- Link to v7: https://patch.msgid.link/20261028-gpio-shared-fwnode-lookup-v7-0-acb453b6fbd5@oss.qualcomm.com
Changes in v7:
- Fix race conditions in teardown path (sashiko)
- Don't allow passing a NULL fwnode to gpio_device_find_by_fwnode()
- Link to v6: https://patch.msgid.link/20261021-gpio-shared-fwnode-lookup-v6-0-6af7d8eefd25@oss.qualcomm.com
Changes in v6:
- Add patches fixing the error path in shared proxy setup (sashiko)
- Link to v5: https://patch.msgid.link/20261020-gpio-shared-fwnode-lookup-v5-0-f4a2dfc059d7@oss.qualcomm.com
Changes in v5:
- Drop fixes already applied upstream
- Check ref->proxy_dev for NULL before dereferencing it
- Assign ref->proxy_fwnode with the shared reference lock taken
- Don't return uninitialized ret from gpio_shared_make_proxy_dev()
- Simplify error handling in gpio_shared_make_proxy_dev()
- Link to v4: https://patch.msgid.link/20261014-gpio-shared-fwnode-lookup-v4-0-2253ad645fd6@oss.qualcomm.com
Changes in v4:
- Fix arguments passed to ida_free() in error path
- Fix memory leak in error path
- Link to v3: https://patch.msgid.link/20261013-gpio-shared-fwnode-lookup-v3-0-4553c1a7d80f@oss.qualcomm.com
Changes in v3:
- Move struct auxiliary_device out of struct gpio_shared_ref
- Link to v2: https://patch.msgid.link/20261001-gpio-shared-fwnode-lookup-v2-0-9a40681713cc@oss.qualcomm.com
Changes in v2:
- Rework the shared GPIO management to replace platform lookup with
software nodes associated with proxy GPIO controllers
- Link to v1: https://patch.msgid.link/20260929-gpio-shared-fwnode-lookup-v1-0-57d54811de1a@oss.qualcomm.com
---
Bartosz Golaszewski (5):
gpio: shared: factor out per-entry teardown helper
gpio: shared: factor out per-entry setup helper
gpio: shared: unwind partial failures in gpiochip_setup_shared()
gpio: shared: detach the proxy device from struct gpio_shared_ref
gpio: shared: convert to using software nodes for GPIO shared proxy lookup
drivers/gpio/gpiolib-shared.c | 302 ++++++++++++++++++++++++------------------
drivers/gpio/gpiolib-shared.h | 15 +--
drivers/gpio/gpiolib.c | 20 +--
3 files changed, 181 insertions(+), 156 deletions(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20260929-gpio-shared-fwnode-lookup-e866faeb5bde
Best regards,
--
Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v9 1/5] gpio: shared: factor out per-entry teardown helper
2026-11-02 12:50 [PATCH v9 0/5] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
@ 2026-11-02 12:50 ` Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 2/5] gpio: shared: factor out per-entry setup helper Bartosz Golaszewski
` (3 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-11-02 12:50 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski
Move the body of the loop in gpio_device_teardown_shared() into
gpio_shared_teardown_entry(). This is a preparation for unwinding a
partially completed gpiochip_setup_shared(), which will need to tear
down individual entries. No functional change.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 37 ++++++++++++++++++++++---------------
1 file changed, 22 insertions(+), 15 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 8c9ac9d9330490b17fb3936b8758beb97a308a15..64bb450f0a8461d214a45aa97e2474ff0d1857ce 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -517,6 +517,27 @@ static void gpio_shared_remove_adev(struct auxiliary_device *adev)
auxiliary_device_uninit(adev);
}
+static void gpio_shared_teardown_entry(struct gpio_device *gdev,
+ struct gpio_shared_entry *entry)
+{
+ struct gpio_shared_ref *ref;
+
+ scoped_guard(mutex, &entry->lock)
+ gpiod_free_commit(&gdev->descs[entry->offset]);
+
+ list_for_each_entry(ref, &entry->refs, list) {
+ if (ref->lookup) {
+ gpiod_remove_lookup_table(ref->lookup);
+ kfree(ref->lookup->table[0].key);
+ kfree(ref->lookup->dev_id);
+ kfree(ref->lookup);
+ ref->lookup = NULL;
+ }
+
+ gpio_shared_remove_adev(&ref->adev);
+ }
+}
+
int gpiochip_setup_shared(struct gpio_chip *gc)
{
struct gpio_device *gdev = gc->gpiodev;
@@ -606,26 +627,12 @@ int gpiochip_setup_shared(struct gpio_chip *gc)
void gpio_device_teardown_shared(struct gpio_device *gdev)
{
struct gpio_shared_entry *entry;
- struct gpio_shared_ref *ref;
list_for_each_entry(entry, &gpio_shared_list, list) {
if (!device_match_fwnode(&gdev->dev, entry->fwnode))
continue;
- scoped_guard(mutex, &entry->lock)
- gpiod_free_commit(&gdev->descs[entry->offset]);
-
- list_for_each_entry(ref, &entry->refs, list) {
- if (ref->lookup) {
- gpiod_remove_lookup_table(ref->lookup);
- kfree(ref->lookup->table[0].key);
- kfree(ref->lookup->dev_id);
- kfree(ref->lookup);
- ref->lookup = NULL;
- }
-
- gpio_shared_remove_adev(&ref->adev);
- }
+ gpio_shared_teardown_entry(gdev, entry);
}
}
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v9 2/5] gpio: shared: factor out per-entry setup helper
2026-11-02 12:50 [PATCH v9 0/5] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 1/5] gpio: shared: factor out per-entry teardown helper Bartosz Golaszewski
@ 2026-11-02 12:50 ` Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 3/5] gpio: shared: unwind partial failures in gpiochip_setup_shared() Bartosz Golaszewski
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-11-02 12:50 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski
Move the per-entry part of gpiochip_setup_shared() into
gpio_shared_setup_entry(). This is a preparation for making the setup
all-or-nothing, which requires handling the failure of a single entry
in isolation. No functional change.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 112 ++++++++++++++++++++++++------------------
1 file changed, 63 insertions(+), 49 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 64bb450f0a8461d214a45aa97e2474ff0d1857ce..c9500c2b904ca53dd1e3cfe1bdb921d767d18b3d 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -538,12 +538,71 @@ static void gpio_shared_teardown_entry(struct gpio_device *gdev,
}
}
+static int gpio_shared_setup_entry(struct gpio_chip *gc,
+ struct gpio_shared_entry *entry)
+{
+ struct gpio_device *gdev = gc->gpiodev;
+ struct gpio_shared_ref *ref;
+ struct gpio_desc *desc;
+ int ret;
+
+ scoped_guard(mutex, &entry->lock) {
+#if IS_ENABLED(CONFIG_OF)
+ if (is_of_node(entry->fwnode) && gc->of_xlate) {
+ /*
+ * This is the earliest that we can translate the
+ * devicetree offset to the chip offset.
+ */
+ struct of_phandle_args gpiospec = { };
+
+ gpiospec.np = to_of_node(entry->fwnode);
+ gpiospec.args_count = 2;
+ gpiospec.args[0] = entry->offset;
+
+ ret = gc->of_xlate(gc, &gpiospec, NULL);
+ if (ret < 0)
+ return ret;
+
+ entry->offset = ret;
+ }
+#endif /* CONFIG_OF */
+
+ desc = &gdev->descs[entry->offset];
+
+ __set_bit(GPIOD_FLAG_SHARED, &desc->flags);
+ /*
+ * Shared GPIOs are not requested via the normal path. Make
+ * them inaccessible to anyone even before we register the
+ * chip.
+ */
+ ret = gpiod_request_commit(desc, "shared");
+ if (ret)
+ return ret;
+
+ pr_debug("GPIO %u owned by %s is shared by multiple consumers\n",
+ entry->offset, gpio_device_get_label(gdev));
+ }
+
+ list_for_each_entry(ref, &entry->refs, list) {
+ pr_debug("Setting up a shared GPIO entry for %s (con_id: '%s')\n",
+ fwnode_get_name(ref->fwnode) ?: "(no fwnode)",
+ ref->con_id ?: "(none)");
+
+ ret = gpio_shared_make_adev(gdev, entry, ref);
+ if (ret) {
+ gpiod_free_commit(desc);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
int gpiochip_setup_shared(struct gpio_chip *gc)
{
struct gpio_device *gdev = gc->gpiodev;
struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref;
- struct gpio_desc *desc;
int ret;
list_for_each_entry(entry, &gpio_shared_list, list) {
@@ -571,54 +630,9 @@ int gpiochip_setup_shared(struct gpio_chip *gc)
if (list_count_nodes(&entry->refs) <= 1)
continue;
- scoped_guard(mutex, &entry->lock) {
-#if IS_ENABLED(CONFIG_OF)
- if (is_of_node(entry->fwnode) && gc->of_xlate) {
- /*
- * This is the earliest that we can tranlate the
- * devicetree offset to the chip offset.
- */
- struct of_phandle_args gpiospec = { };
-
- gpiospec.np = to_of_node(entry->fwnode);
- gpiospec.args_count = 2;
- gpiospec.args[0] = entry->offset;
-
- ret = gc->of_xlate(gc, &gpiospec, NULL);
- if (ret < 0)
- return ret;
-
- entry->offset = ret;
- }
-#endif /* CONFIG_OF */
-
- desc = &gdev->descs[entry->offset];
-
- __set_bit(GPIOD_FLAG_SHARED, &desc->flags);
- /*
- * Shared GPIOs are not requested via the normal path. Make
- * them inaccessible to anyone even before we register the
- * chip.
- */
- ret = gpiod_request_commit(desc, "shared");
- if (ret)
- return ret;
-
- pr_debug("GPIO %u owned by %s is shared by multiple consumers\n",
- entry->offset, gpio_device_get_label(gdev));
- }
-
- list_for_each_entry(ref, &entry->refs, list) {
- pr_debug("Setting up a shared GPIO entry for %s (con_id: '%s')\n",
- fwnode_get_name(ref->fwnode) ?: "(no fwnode)",
- ref->con_id ?: "(none)");
-
- ret = gpio_shared_make_adev(gdev, entry, ref);
- if (ret) {
- gpiod_free_commit(desc);
- return ret;
- }
- }
+ ret = gpio_shared_setup_entry(gc, entry);
+ if (ret)
+ return ret;
}
return 0;
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v9 3/5] gpio: shared: unwind partial failures in gpiochip_setup_shared()
2026-11-02 12:50 [PATCH v9 0/5] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 1/5] gpio: shared: factor out per-entry teardown helper Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 2/5] gpio: shared: factor out per-entry setup helper Bartosz Golaszewski
@ 2026-11-02 12:50 ` Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 4/5] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 5/5] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
4 siblings, 0 replies; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-11-02 12:50 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski
If gpio_shared_make_adev() fails for one of the references of a shared
GPIO entry, the auxiliary devices already registered for the preceding
references are never removed. Similarly, when setting up an entry fails,
the entries that were successfully set up before it keep their requested
descriptors and proxy devices, as the caller does not call
gpio_device_teardown_shared() when gpiochip_setup_shared() fails.
Make gpiochip_setup_shared() all-or-nothing. Have
gpio_shared_setup_entry() undo its own partial work: free the descriptor
and remove only the auxiliary devices of the references that precede the
failed one, so that no device is removed that was never registered. On
failure of an entry, tear down the previously completed entries in
reverse order using the same helper as the regular removal path.
Fixes: a060b8c511ab ("gpiolib: implement low-level, shared GPIO support")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index c9500c2b904ca53dd1e3cfe1bdb921d767d18b3d..6b183864933870e54780715c4dd9179faf800d09 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -591,6 +591,9 @@ static int gpio_shared_setup_entry(struct gpio_chip *gc,
ret = gpio_shared_make_adev(gdev, entry, ref);
if (ret) {
gpiod_free_commit(desc);
+ list_for_each_entry_continue_reverse(ref, &entry->refs, list)
+ gpio_shared_remove_adev(&ref->adev);
+
return ret;
}
}
@@ -598,6 +601,13 @@ static int gpio_shared_setup_entry(struct gpio_chip *gc,
return 0;
}
+static bool gpio_shared_entry_needs_setup(struct gpio_device *gdev,
+ struct gpio_shared_entry *entry)
+{
+ return device_match_fwnode(&gdev->dev, entry->fwnode) &&
+ list_count_nodes(&entry->refs) > 1;
+}
+
int gpiochip_setup_shared(struct gpio_chip *gc)
{
struct gpio_device *gdev = gc->gpiodev;
@@ -624,15 +634,18 @@ int gpiochip_setup_shared(struct gpio_chip *gc)
* exposing shared pins. Find them and create the proxy devices.
*/
list_for_each_entry(entry, &gpio_shared_list, list) {
- if (!device_match_fwnode(&gdev->dev, entry->fwnode))
- continue;
-
- if (list_count_nodes(&entry->refs) <= 1)
+ if (!gpio_shared_entry_needs_setup(gdev, entry))
continue;
ret = gpio_shared_setup_entry(gc, entry);
- if (ret)
+ if (ret) {
+ list_for_each_entry_continue_reverse(entry, &gpio_shared_list, list) {
+ if (gpio_shared_entry_needs_setup(gdev, entry))
+ gpio_shared_teardown_entry(gdev, entry);
+ }
+
return ret;
+ }
}
return 0;
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v9 4/5] gpio: shared: detach the proxy device from struct gpio_shared_ref
2026-11-02 12:50 [PATCH v9 0/5] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
` (2 preceding siblings ...)
2026-11-02 12:50 ` [PATCH v9 3/5] gpio: shared: unwind partial failures in gpiochip_setup_shared() Bartosz Golaszewski
@ 2026-11-02 12:50 ` Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 5/5] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
4 siblings, 0 replies; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-11-02 12:50 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski
Sashiko reported a pre-existing issue with the reference-counted
auxiliary device being embedded in struct gpio_shared_ref. We
unconditionally memset() the struct when setting up the GPIO shared
proxy parent while there may still be references to it held by users.
While in practice this would never happen and sashiko seems to have
confused the parent auxiliary device and the separately allocated struct
device embedded in struct gpio_device, it's still a bad pattern and
could lead to memory corruption if a user deliberately took reference to
the auxiliary device via one of the lookup functions and kept it over
the GPIO parent's teardown.
Move the auxiliary device struct out of the shared reference structure
and allocate its memory separately on proxy setup.
Since we're already touching that field: change the name to something
more meaningful: it's more important to point out this is the proxy
device instead of indicating its type in the name.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 69 +++++++++++++++++++++++++++----------------
1 file changed, 43 insertions(+), 26 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 6b183864933870e54780715c4dd9179faf800d09..9d73a02e998909746f5216f40dfe4d82f7be9e1c 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -35,11 +35,10 @@ struct gpio_shared_ref {
/* GPIO flags this consumer uses for the request. */
enum gpiod_flags flags;
char *con_id;
- int dev_id;
/* Protects the auxiliary device struct and the lookup table. */
struct mutex lock;
struct lock_class_key lock_key;
- struct auxiliary_device adev;
+ struct auxiliary_device *proxy_dev;
struct gpiod_lookup_table *lookup;
bool is_reset_gpio;
};
@@ -94,10 +93,6 @@ static struct gpio_shared_ref *gpio_shared_make_ref(struct fwnode_handle *fwnode
return NULL;
}
- ref->dev_id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
- if (ref->dev_id < 0)
- return NULL;
-
ref->flags = flags;
ref->con_id = no_free_ptr(con_id_cpy);
ref->fwnode = fwnode;
@@ -306,31 +301,43 @@ static int gpio_shared_of_scan(void)
}
#endif /* CONFIG_OF */
-static void gpio_shared_adev_release(struct device *dev)
+static void gpio_shared_proxy_dev_release(struct device *dev)
{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+ ida_free(&gpio_shared_ida, adev->id);
+ kfree(adev);
}
-static int gpio_shared_make_adev(struct gpio_device *gdev,
- struct gpio_shared_entry *entry,
- struct gpio_shared_ref *ref)
+static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
+ struct gpio_shared_entry *entry,
+ struct gpio_shared_ref *ref)
{
- struct auxiliary_device *adev = &ref->adev;
- int ret;
+ struct auxiliary_device *adev;
+ int ret, id;
- guard(mutex)(&ref->lock);
+ adev = kzalloc_obj(*adev);
+ if (!adev)
+ return -ENOMEM;
- memset(adev, 0, sizeof(*adev));
+ id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
+ if (id < 0) {
+ kfree(adev);
+ return id;
+ }
- adev->id = ref->dev_id;
+ adev->id = id;
adev->name = "proxy";
adev->dev.parent = gdev->dev.parent;
adev->dev.platform_data = entry;
- adev->dev.release = gpio_shared_adev_release;
+ adev->dev.release = gpio_shared_proxy_dev_release;
ret = auxiliary_device_init(adev);
- if (ret)
+ if (ret) {
+ ida_free(&gpio_shared_ida, id);
+ kfree(adev);
return ret;
+ }
ret = auxiliary_device_add(adev);
if (ret) {
@@ -341,6 +348,10 @@ static int gpio_shared_make_adev(struct gpio_device *gdev,
pr_debug("Created an auxiliary GPIO proxy %s for GPIO device %s\n",
dev_name(&adev->dev), gpio_device_get_label(gdev));
+ scoped_guard(mutex, &ref->lock) {
+ ref->proxy_dev = adev;
+ }
+
return 0;
}
@@ -480,7 +491,7 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
char *key __free(kfree) =
kasprintf(GFP_KERNEL,
KBUILD_MODNAME ".proxy.%u",
- ref->adev.id);
+ ref->proxy_dev->id);
if (!key)
return -ENOMEM;
@@ -511,10 +522,17 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
return -ENOENT;
}
-static void gpio_shared_remove_adev(struct auxiliary_device *adev)
+static void gpio_shared_remove_proxy_dev(struct gpio_shared_ref *ref)
{
- auxiliary_device_delete(adev);
- auxiliary_device_uninit(adev);
+ struct auxiliary_device *proxy;
+
+ scoped_guard(mutex, &ref->lock) {
+ proxy = ref->proxy_dev;
+ ref->proxy_dev = NULL;
+ }
+
+ auxiliary_device_delete(proxy);
+ auxiliary_device_uninit(proxy);
}
static void gpio_shared_teardown_entry(struct gpio_device *gdev,
@@ -534,7 +552,7 @@ static void gpio_shared_teardown_entry(struct gpio_device *gdev,
ref->lookup = NULL;
}
- gpio_shared_remove_adev(&ref->adev);
+ gpio_shared_remove_proxy_dev(ref);
}
}
@@ -588,11 +606,11 @@ static int gpio_shared_setup_entry(struct gpio_chip *gc,
fwnode_get_name(ref->fwnode) ?: "(no fwnode)",
ref->con_id ?: "(none)");
- ret = gpio_shared_make_adev(gdev, entry, ref);
+ ret = gpio_shared_make_proxy_dev(gdev, entry, ref);
if (ret) {
gpiod_free_commit(desc);
list_for_each_entry_continue_reverse(ref, &entry->refs, list)
- gpio_shared_remove_adev(&ref->adev);
+ gpio_shared_remove_proxy_dev(ref);
return ret;
}
@@ -617,7 +635,7 @@ int gpiochip_setup_shared(struct gpio_chip *gc)
list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) {
- if (gdev->dev.parent == &ref->adev.dev) {
+ if (ref->proxy_dev && gdev->dev.parent == &ref->proxy_dev->dev) {
/*
* This is a shared GPIO proxy. Mark its
* descriptor as such and return here.
@@ -752,7 +770,6 @@ static void gpio_shared_drop_ref(struct gpio_shared_ref *ref)
mutex_destroy(&ref->lock);
lockdep_unregister_key(&ref->lock_key);
kfree(ref->con_id);
- ida_free(&gpio_shared_ida, ref->dev_id);
fwnode_handle_put(ref->fwnode);
kfree(ref);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v9 5/5] gpio: shared: convert to using software nodes for GPIO shared proxy lookup
2026-11-02 12:50 [PATCH v9 0/5] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
` (3 preceding siblings ...)
2026-11-02 12:50 ` [PATCH v9 4/5] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
@ 2026-11-02 12:50 ` Bartosz Golaszewski
2026-11-02 13:12 ` sashiko-bot
4 siblings, 1 reply; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-11-02 12:50 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski, Abel Vesa
Cc: linux-arm-msm, linux-gpio, linux-kernel, Bartosz Golaszewski
Automated management of shared GPIOs only allows to request shared pins
when they're assigned to a real struct device. Firmware node, no-device
lookup currently isn't supported and can't be because we're using
platform lookup tables to point the callers to the shared proxy GPIO
controllers.
Rework the GPIO sharing by using dummy managed software nodes assigned
to the proxy devices which allow us - once the correct struct
gpio_shared_ref is found - to find the target GPIO device by its firmware
node.
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
---
drivers/gpio/gpiolib-shared.c | 133 ++++++++++++++++++++----------------------
drivers/gpio/gpiolib-shared.h | 15 +++--
drivers/gpio/gpiolib.c | 20 +------
3 files changed, 71 insertions(+), 97 deletions(-)
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index 9d73a02e998909746f5216f40dfe4d82f7be9e1c..57d8fd0bf2e1bc2997c1ed64b937100ce745136f 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -35,11 +35,11 @@ struct gpio_shared_ref {
/* GPIO flags this consumer uses for the request. */
enum gpiod_flags flags;
char *con_id;
- /* Protects the auxiliary device struct and the lookup table. */
+ /* Protects the auxiliary device struct and fwnode. */
struct mutex lock;
- struct lock_class_key lock_key;
struct auxiliary_device *proxy_dev;
- struct gpiod_lookup_table *lookup;
+ struct fwnode_handle *proxy_fwnode;
+ struct lock_class_key lock_key;
bool is_reset_gpio;
};
@@ -303,56 +303,70 @@ static int gpio_shared_of_scan(void)
static void gpio_shared_proxy_dev_release(struct device *dev)
{
- struct auxiliary_device *adev = to_auxiliary_dev(dev);
+ struct auxiliary_device *proxy = to_auxiliary_dev(dev);
- ida_free(&gpio_shared_ida, adev->id);
- kfree(adev);
+ ida_free(&gpio_shared_ida, proxy->id);
+ kfree(proxy);
}
static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
struct gpio_shared_entry *entry,
struct gpio_shared_ref *ref)
{
- struct auxiliary_device *adev;
+ struct property_entry properties = { };
+ struct auxiliary_device *proxy;
+ struct device *dev;
int ret, id;
- adev = kzalloc_obj(*adev);
- if (!adev)
+ proxy = kzalloc_obj(*proxy);
+ if (!proxy)
return -ENOMEM;
id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
if (id < 0) {
- kfree(adev);
- return id;
+ ret = id;
+ goto err_free_proxy;
}
- adev->id = id;
- adev->name = "proxy";
- adev->dev.parent = gdev->dev.parent;
- adev->dev.platform_data = entry;
- adev->dev.release = gpio_shared_proxy_dev_release;
+ dev = &proxy->dev;
+ proxy->id = id;
+ proxy->name = "proxy";
+ dev->parent = gdev->dev.parent;
+ dev->platform_data = entry;
+ dev->release = gpio_shared_proxy_dev_release;
- ret = auxiliary_device_init(adev);
+ ret = auxiliary_device_init(proxy);
+ if (ret)
+ goto err_free_ida;
+
+ ret = device_create_managed_software_node(dev, &properties, NULL);
if (ret) {
- ida_free(&gpio_shared_ida, id);
- kfree(adev);
+ auxiliary_device_uninit(proxy);
return ret;
}
- ret = auxiliary_device_add(adev);
+ ret = auxiliary_device_add(proxy);
if (ret) {
- auxiliary_device_uninit(adev);
+ auxiliary_device_uninit(proxy);
return ret;
}
pr_debug("Created an auxiliary GPIO proxy %s for GPIO device %s\n",
- dev_name(&adev->dev), gpio_device_get_label(gdev));
+ dev_name(&proxy->dev), gpio_device_get_label(gdev));
scoped_guard(mutex, &ref->lock) {
- ref->proxy_dev = adev;
+ ref->proxy_dev = proxy;
+ ref->proxy_fwnode = dev_fwnode(dev);
}
return 0;
+
+err_free_ida:
+ ida_free(&gpio_shared_ida, id);
+err_free_proxy:
+ kfree(proxy);
+
+ return ret;
}
#if IS_ENABLED(CONFIG_RESET_GPIO)
@@ -457,23 +471,19 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
}
#endif /* CONFIG_RESET_GPIO */
-int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *fwnode,
- const char *con_id, unsigned long lflags)
+struct gpio_desc *
+gpio_shared_get_proxy(struct device *consumer, struct fwnode_handle *fwnode,
+ const char *con_id)
{
struct gpio_shared_entry *entry;
struct gpio_shared_ref *ref;
- const char *dev_id;
-
- if (!consumer)
- return -EOPNOTSUPP;
-
- dev_id = dev_name(consumer);
list_for_each_entry(entry, &gpio_shared_list, list) {
list_for_each_entry(ref, &entry->refs, list) {
guard(mutex)(&ref->lock);
- if (!ref->fwnode && device_is_compatible(consumer, "reset-gpio")) {
+ if (!ref->fwnode && consumer &&
+ device_is_compatible(consumer, "reset-gpio")) {
if (!gpio_shared_dev_is_reset_gpio(consumer, entry, ref))
continue;
} else if (fwnode != ref->fwnode) {
@@ -484,42 +494,31 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, struct fwnode_handle *
(con_id && ref->con_id && strcmp(con_id, ref->con_id) != 0))
continue;
- /* We've already done that on a previous request. */
- if (ref->lookup)
- return 0;
-
- char *key __free(kfree) =
- kasprintf(GFP_KERNEL,
- KBUILD_MODNAME ".proxy.%u",
- ref->proxy_dev->id);
- if (!key)
- return -ENOMEM;
-
- struct gpiod_lookup_table *lookup __free(kfree) =
- kzalloc_flex(*lookup, table, 2);
- if (!lookup)
- return -ENOMEM;
-
- pr_debug("Adding machine lookup entry for a shared GPIO for consumer %s, with key '%s' and con_id '%s'\n",
- dev_id, key, ref->con_id ?: "none");
-
- lookup->dev_id = kstrdup(dev_id, GFP_KERNEL);
- if (!lookup->dev_id)
- return -ENOMEM;
-
- lookup->table[0] = GPIO_LOOKUP(no_free_ptr(key), 0,
- ref->con_id, lflags);
+ if (!ref->proxy_fwnode)
+ /*
+ * There's a matching shared reference but the
+ * associated auxiliary device has not been
+ * created yet.
+ */
+ return ERR_PTR(-EPROBE_DEFER);
- ref->lookup = no_free_ptr(lookup);
- gpiod_add_lookup_table(ref->lookup);
+ struct gpio_device *gdev __free(gpio_device_put) =
+ gpio_device_find_by_fwnode(ref->proxy_fwnode);
+ if (!gdev)
+ /*
+ * There's a matching shared reference but the
+ * associated GPIO controller has not been
+ * registered yet.
+ */
+ return ERR_PTR(-EPROBE_DEFER);
- return 0;
+ return gpio_device_get_desc(gdev, 0);
}
}
/* We warn here because this can only happen if the programmer borked. */
WARN_ON(1);
- return -ENOENT;
+ return ERR_PTR(-ENOENT);
}
static void gpio_shared_remove_proxy_dev(struct gpio_shared_ref *ref)
@@ -529,6 +528,7 @@ static void gpio_shared_remove_proxy_dev(struct gpio_shared_ref *ref)
scoped_guard(mutex, &ref->lock) {
proxy = ref->proxy_dev;
ref->proxy_dev = NULL;
+ ref->proxy_fwnode = NULL;
}
auxiliary_device_delete(proxy);
@@ -543,17 +543,8 @@ static void gpio_shared_teardown_entry(struct gpio_device *gdev,
scoped_guard(mutex, &entry->lock)
gpiod_free_commit(&gdev->descs[entry->offset]);
- list_for_each_entry(ref, &entry->refs, list) {
- if (ref->lookup) {
- gpiod_remove_lookup_table(ref->lookup);
- kfree(ref->lookup->table[0].key);
- kfree(ref->lookup->dev_id);
- kfree(ref->lookup);
- ref->lookup = NULL;
- }
-
+ list_for_each_entry(ref, &entry->refs, list)
gpio_shared_remove_proxy_dev(ref);
- }
}
static int gpio_shared_setup_entry(struct gpio_chip *gc,
diff --git a/drivers/gpio/gpiolib-shared.h b/drivers/gpio/gpiolib-shared.h
index 618756f6c6aafd087df267a4a32732ccc03af38e..27fd2a40c5cb77a19c258e3fbf429d3f6e7d4b32 100644
--- a/drivers/gpio/gpiolib-shared.h
+++ b/drivers/gpio/gpiolib-shared.h
@@ -14,9 +14,9 @@ struct fwnode_handle;
int gpiochip_setup_shared(struct gpio_chip *gc);
void gpio_device_teardown_shared(struct gpio_device *gdev);
-int gpio_shared_add_proxy_lookup(struct device *consumer,
- struct fwnode_handle *fwnode,
- const char *con_id, unsigned long lflags);
+struct gpio_desc *
+gpio_shared_get_proxy(struct device *consumer, struct fwnode_handle *fwnode,
+ const char *con_id);
#else
@@ -27,12 +27,11 @@ static inline int gpiochip_setup_shared(struct gpio_chip *gc)
static inline void gpio_device_teardown_shared(struct gpio_device *gdev) { }
-static inline int gpio_shared_add_proxy_lookup(struct device *consumer,
- struct fwnode_handle *fwnode,
- const char *con_id,
- unsigned long lflags)
+static inline struct gpio_desc *
+gpio_shared_get_proxy(struct device *consumer, struct fwnode_handle *fwnode,
+ const char *con_id)
{
- return 0;
+ return NULL;
}
#endif /* CONFIG_GPIO_SHARED */
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index fbdeda2f278713a47c2e3d2a6640f387f6d92227..402de2e106f19fd110d67eeecd3c07794b7a5cdd 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -4841,24 +4841,8 @@ struct gpio_desc *gpiod_find_and_request(struct device *consumer,
scoped_guard(srcu, &gpio_devices_srcu) {
desc = gpiod_fwnode_lookup(fwnode, consumer, con_id, idx,
&flags, &lookupflags);
- if (!IS_ERR_OR_NULL(desc) &&
- test_bit(GPIOD_FLAG_SHARED, &desc->flags)) {
- /*
- * We're dealing with a GPIO shared by multiple
- * consumers. This is the moment to add the machine
- * lookup table for the proxy device as previously
- * we only knew the consumer's fwnode.
- */
- ret = gpio_shared_add_proxy_lookup(consumer, fwnode,
- con_id, lookupflags);
- if (ret)
- return ERR_PTR(ret);
-
- /* Trigger platform lookup for shared GPIO proxy. */
- desc = ERR_PTR(-ENOENT);
- /* Trigger it even for fwnode-only gpiod_get(). */
- platform_lookup_allowed = true;
- }
+ if (!IS_ERR_OR_NULL(desc) && test_bit(GPIOD_FLAG_SHARED, &desc->flags))
+ desc = gpio_shared_get_proxy(consumer, fwnode, con_id);
if (gpiod_not_found(desc) && platform_lookup_allowed) {
/*
--
2.47.3
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v9 5/5] gpio: shared: convert to using software nodes for GPIO shared proxy lookup
2026-11-02 12:50 ` [PATCH v9 5/5] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
@ 2026-11-02 13:12 ` sashiko-bot
2026-11-03 12:10 ` Bartosz Golaszewski
0 siblings, 1 reply; 8+ messages in thread
From: sashiko-bot @ 2026-11-02 13:12 UTC (permalink / raw)
To: Bartosz Golaszewski; +Cc: linux-gpio
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] gpio: shared: use-after-free in software_node_notify_remove() on proxy removal
- [Medium] gpio: shared: software node leak in gpio_shared_make_proxy_dev() on add error
--
commit 8540cb961814db62ab8d7cec3ac6bee73e8c8520
Author: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
gpio: shared: convert to using software nodes for GPIO shared proxy lookup
This commit replaces platform lookup tables for shared GPIO proxy devices
with managed software nodes attached to the proxy auxiliary devices,
allowing shared GPIO lookup to work via firmware nodes even when no
consumer struct device is present.
> diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
> index 9d73a02e99890..57d8fd0bf2e1b 100644
> --- a/drivers/gpio/gpiolib-shared.c
> +++ b/drivers/gpio/gpiolib-shared.c
[ ... ]
> @@ -303,56 +303,70 @@ static int gpio_shared_of_scan(void)
>
> static void gpio_shared_proxy_dev_release(struct device *dev)
> {
> - struct auxiliary_device *adev = to_auxiliary_dev(dev);
> + struct auxiliary_device *proxy = to_auxiliary_dev(dev);
>
> - ida_free(&gpio_shared_ida, adev->id);
> - kfree(adev);
> + ida_free(&gpio_shared_ida, proxy->id);
> + kfree(proxy);
> }
>
> static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
> struct gpio_shared_entry *entry,
> struct gpio_shared_ref *ref)
> {
> - struct auxiliary_device *adev;
> + struct property_entry properties = { };
> + struct auxiliary_device *proxy;
> + struct device *dev;
> int ret, id;
>
> - adev = kzalloc_obj(*adev);
> - if (!adev)
> + proxy = kzalloc_obj(*proxy);
> + if (!proxy)
> return -ENOMEM;
>
> id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
> if (id < 0) {
> - kfree(adev);
> - return id;
> + ret = id;
> + goto err_free_proxy;
> }
>
> - adev->id = id;
> - adev->name = "proxy";
> - adev->dev.parent = gdev->dev.parent;
> - adev->dev.platform_data = entry;
> - adev->dev.release = gpio_shared_proxy_dev_release;
> + dev = &proxy->dev;
> + proxy->id = id;
> + proxy->name = "proxy";
> + dev->parent = gdev->dev.parent;
> + dev->platform_data = entry;
> + dev->release = gpio_shared_proxy_dev_release;
>
> - ret = auxiliary_device_init(adev);
> + ret = auxiliary_device_init(proxy);
> + if (ret)
> + goto err_free_ida;
> +
> + ret = device_create_managed_software_node(dev, &properties, NULL);
> if (ret) {
> - ida_free(&gpio_shared_ida, id);
> - kfree(adev);
> + auxiliary_device_uninit(proxy);
> return ret;
> }
[Severity: Critical]
As reported by Sashiko on v8 [1], can using
device_create_managed_software_node() here cause a use-after-free and
refcount underflow in software_node_notify_remove() when the proxy device
is removed or unbound?
In gpio_shared_make_proxy_dev(), device_create_managed_software_node()
creates a struct swnode with refcount 1, sets swnode->managed = true, and
attaches it to proxy->dev. When auxiliary_device_add() registers
proxy->dev, software_node_notify() increments the swnode refcount to 2.
Later, when gpio_shared_proxy_probe() registers the proxy gpio_chip with
gc->parent = &proxy->dev and gc->fwnode = NULL,
gpiochip_add_data_with_key() propagates the parent's fwnode to the child
gdev->dev:
drivers/gpio/gpiolib.c:gpiochip_add_data_with_key() {
...
gdev->dev.parent = gc->parent;
device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc));
...
}
When gpiochip_setup_dev() calls device_add(&gdev->dev),
software_node_notify(&gdev->dev) increments the same managed swnode's
refcount to 3.
However, managed software nodes cannot be shared across devices because
software_node_notify_remove() drops two references whenever
swnode->managed is true:
drivers/base/swnode.c:software_node_notify_remove() {
...
swnode_put(swnode);
if (swnode->managed) {
set_secondary_fwnode(dev, NULL);
swnode_put(swnode);
}
}
When gpio_shared_remove_proxy_dev() calls auxiliary_device_delete(proxy),
unbinding gpio-shared-proxy first removes gdev->dev, which runs
software_node_notify_remove(&gdev->dev) and drops the swnode refcount from
3 to 1. Removing proxy->dev then runs
software_node_notify_remove(&proxy->dev), where the first swnode_put()
drops the refcount to 0 and frees swnode, and the subsequent
swnode->managed check and second swnode_put() access freed memory.
>
> - ret = auxiliary_device_add(adev);
> + ret = auxiliary_device_add(proxy);
> if (ret) {
> - auxiliary_device_uninit(adev);
> + auxiliary_device_uninit(proxy);
> return ret;
> }
[Severity: Medium]
Does this error path leak the software node if auxiliary_device_add()
fails before device_platform_notify() is reached in device_add()?
Because device_create_managed_software_node() is called before the device
is registered, the struct swnode is allocated and added to swnode_kset
with refcount 1, relying on device_platform_notify_remove() ->
software_node_notify_remove() to release it once device_platform_notify()
has run.
If auxiliary_device_add() fails in dev_set_name() or early in device_add()
before device_platform_notify() is called, software_node_notify_remove()
is never invoked, and auxiliary_device_uninit() ->
gpio_shared_proxy_dev_release() frees proxy without calling
device_remove_software_node(dev).
[1] https://lore.kernel.org/r/20261029-gpio-shared-fwnode-lookup-v8-5-d15d75cd560f@oss.qualcomm.com
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261102-gpio-shared-fwnode-lookup-v9-0-a1d6cc501098@oss.qualcomm.com?part=5
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v9 5/5] gpio: shared: convert to using software nodes for GPIO shared proxy lookup
2026-11-02 13:12 ` sashiko-bot
@ 2026-11-03 12:10 ` Bartosz Golaszewski
0 siblings, 0 replies; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-11-03 12:10 UTC (permalink / raw)
To: sashiko-reviews; +Cc: Bartosz Golaszewski, linux-gpio
On Mon, Nov 02, 2026 at 01:12:40PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Critical] gpio: shared: use-after-free in software_node_notify_remove() on proxy removal
> - [Medium] gpio: shared: software node leak in gpio_shared_make_proxy_dev() on add error
> --
>
> commit 8540cb961814db62ab8d7cec3ac6bee73e8c8520
> Author: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
>
> gpio: shared: convert to using software nodes for GPIO shared proxy lookup
>
> This commit replaces platform lookup tables for shared GPIO proxy devices
> with managed software nodes attached to the proxy auxiliary devices,
> allowing shared GPIO lookup to work via firmware nodes even when no
> consumer struct device is present.
>
> > diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
> > index 9d73a02e99890..57d8fd0bf2e1b 100644
> > --- a/drivers/gpio/gpiolib-shared.c
> > +++ b/drivers/gpio/gpiolib-shared.c
> [ ... ]
> > @@ -303,56 +303,70 @@ static int gpio_shared_of_scan(void)
> >
> > static void gpio_shared_proxy_dev_release(struct device *dev)
> > {
> > - struct auxiliary_device *adev = to_auxiliary_dev(dev);
> > + struct auxiliary_device *proxy = to_auxiliary_dev(dev);
> >
> > - ida_free(&gpio_shared_ida, adev->id);
> > - kfree(adev);
> > + ida_free(&gpio_shared_ida, proxy->id);
> > + kfree(proxy);
> > }
> >
> > static int gpio_shared_make_proxy_dev(struct gpio_device *gdev,
> > struct gpio_shared_entry *entry,
> > struct gpio_shared_ref *ref)
> > {
> > - struct auxiliary_device *adev;
> > + struct property_entry properties = { };
> > + struct auxiliary_device *proxy;
> > + struct device *dev;
> > int ret, id;
> >
> > - adev = kzalloc_obj(*adev);
> > - if (!adev)
> > + proxy = kzalloc_obj(*proxy);
> > + if (!proxy)
> > return -ENOMEM;
> >
> > id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
> > if (id < 0) {
> > - kfree(adev);
> > - return id;
> > + ret = id;
> > + goto err_free_proxy;
> > }
> >
> > - adev->id = id;
> > - adev->name = "proxy";
> > - adev->dev.parent = gdev->dev.parent;
> > - adev->dev.platform_data = entry;
> > - adev->dev.release = gpio_shared_proxy_dev_release;
> > + dev = &proxy->dev;
> > + proxy->id = id;
> > + proxy->name = "proxy";
> > + dev->parent = gdev->dev.parent;
> > + dev->platform_data = entry;
> > + dev->release = gpio_shared_proxy_dev_release;
> >
> > - ret = auxiliary_device_init(adev);
> > + ret = auxiliary_device_init(proxy);
> > + if (ret)
> > + goto err_free_ida;
> > +
> > + ret = device_create_managed_software_node(dev, &properties, NULL);
> > if (ret) {
> > - ida_free(&gpio_shared_ida, id);
> > - kfree(adev);
> > + auxiliary_device_uninit(proxy);
> > return ret;
> > }
>
> [Severity: Critical]
> As reported by Sashiko on v8 [1], can using
> device_create_managed_software_node() here cause a use-after-free and
> refcount underflow in software_node_notify_remove() when the proxy device
> is removed or unbound?
>
> In gpio_shared_make_proxy_dev(), device_create_managed_software_node()
> creates a struct swnode with refcount 1, sets swnode->managed = true, and
> attaches it to proxy->dev. When auxiliary_device_add() registers
> proxy->dev, software_node_notify() increments the swnode refcount to 2.
>
> Later, when gpio_shared_proxy_probe() registers the proxy gpio_chip with
> gc->parent = &proxy->dev and gc->fwnode = NULL,
> gpiochip_add_data_with_key() propagates the parent's fwnode to the child
> gdev->dev:
>
> drivers/gpio/gpiolib.c:gpiochip_add_data_with_key() {
> ...
> gdev->dev.parent = gc->parent;
> device_set_node(&gdev->dev, gpiochip_choose_fwnode(gc));
> ...
> }
>
> When gpiochip_setup_dev() calls device_add(&gdev->dev),
> software_node_notify(&gdev->dev) increments the same managed swnode's
> refcount to 3.
>
> However, managed software nodes cannot be shared across devices because
> software_node_notify_remove() drops two references whenever
> swnode->managed is true:
>
> drivers/base/swnode.c:software_node_notify_remove() {
> ...
> swnode_put(swnode);
>
> if (swnode->managed) {
> set_secondary_fwnode(dev, NULL);
> swnode_put(swnode);
> }
> }
>
> When gpio_shared_remove_proxy_dev() calls auxiliary_device_delete(proxy),
> unbinding gpio-shared-proxy first removes gdev->dev, which runs
> software_node_notify_remove(&gdev->dev) and drops the swnode refcount from
> 3 to 1. Removing proxy->dev then runs
> software_node_notify_remove(&proxy->dev), where the first swnode_put()
> drops the refcount to 0 and frees swnode, and the subsequent
> swnode->managed check and second swnode_put() access freed memory.
>
FWIW this would be fixed by the patch I submitted[1] but in thhe
meantime I can switch to non-managed swnodes.
Bart
> >
> > - ret = auxiliary_device_add(adev);
> > + ret = auxiliary_device_add(proxy);
> > if (ret) {
> > - auxiliary_device_uninit(adev);
> > + auxiliary_device_uninit(proxy);
> > return ret;
> > }
>
> [Severity: Medium]
> Does this error path leak the software node if auxiliary_device_add()
> fails before device_platform_notify() is reached in device_add()?
>
> Because device_create_managed_software_node() is called before the device
> is registered, the struct swnode is allocated and added to swnode_kset
> with refcount 1, relying on device_platform_notify_remove() ->
> software_node_notify_remove() to release it once device_platform_notify()
> has run.
>
> If auxiliary_device_add() fails in dev_set_name() or early in device_add()
> before device_platform_notify() is called, software_node_notify_remove()
> is never invoked, and auxiliary_device_uninit() ->
> gpio_shared_proxy_dev_release() frees proxy without calling
> device_remove_software_node(dev).
>
> [1] https://lore.kernel.org/r/20261029-gpio-shared-fwnode-lookup-v8-5-d15d75cd560f@oss.qualcomm.com
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/20261102-gpio-shared-fwnode-lookup-v9-0-a1d6cc501098@oss.qualcomm.com?part=5
[1] https://lore.kernel.org/all/20261102-swnode-only-owner-release-v1-1-ee4c9022a629@oss.qualcomm.com/
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-11-03 12:10 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 12:50 [PATCH v9 0/5] gpio: shared: replace platform lookup with dummy software nodes Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 1/5] gpio: shared: factor out per-entry teardown helper Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 2/5] gpio: shared: factor out per-entry setup helper Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 3/5] gpio: shared: unwind partial failures in gpiochip_setup_shared() Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 4/5] gpio: shared: detach the proxy device from struct gpio_shared_ref Bartosz Golaszewski
2026-11-02 12:50 ` [PATCH v9 5/5] gpio: shared: convert to using software nodes for GPIO shared proxy lookup Bartosz Golaszewski
2026-11-02 13:12 ` sashiko-bot
2026-11-03 12:10 ` Bartosz Golaszewski
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.