lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-Id: <20251222-gpio-shared-reset-gpio-proxy-v1-3-8d4bba7d8c14@oss.qualcomm.com>
Date: Mon, 22 Dec 2025 11:01:28 +0100
From: Bartosz Golaszewski <bartosz.golaszewski@....qualcomm.com>
To: Linus Walleij <linusw@...nel.org>, Bartosz Golaszewski <brgl@...nel.org>
Cc: linux-gpio@...r.kernel.org, linux-kernel@...r.kernel.org,
        Linus Walleij <linus.walleij@...aro.org>,
        Bartosz Golaszewski <bartosz.golaszewski@....qualcomm.com>
Subject: [PATCH 3/3] gpio: shared: allow sharing a reset-gpios pin between
 reset-gpio and gpiolib

We currently support sharing GPIOs between multiple devices whose drivers
use either the GPIOLIB API *OR* the reset control API but not both at
the same time.

There's an unlikely corner-case where a reset-gpios pin can be shared by
one driver using the GPIOLIB API and a second using the reset API. This
will currently not work as the reset-gpio consumers are not described in
firmware at the time of scanning so the shared GPIO just chooses one of
the proxies created for the consumers when the reset-gpio driver performs
the lookup. This can of course conflict in the case described above.

In order to fix it: if we deal with the "reset-gpios" pin that's shared
acconding to the firmware node setup, create a proxy for each described
consumer as well as another one for the potential reset-gpio device. To
that end: rework the matching to take this into account.

Fixes: 7b78b26757e0 ("gpio: shared: handle the reset-gpios corner case")
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@....qualcomm.com>
---
 drivers/gpio/gpiolib-shared.c | 182 ++++++++++++++++++++++++++++++------------
 1 file changed, 129 insertions(+), 53 deletions(-)

diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index f589109590c7c6bc9c0c1828ea15ab9003846523..baf7e07a3bb887dab8155078666a15779e304409 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -76,6 +76,56 @@ gpio_shared_find_entry(struct fwnode_handle *controller_node,
 	return NULL;
 }
 
+static struct gpio_shared_ref *gpio_shared_make_ref(struct fwnode_handle *fwnode,
+						    const char *con_id,
+						    enum gpiod_flags flags)
+{
+	char *con_id_cpy __free(kfree) = NULL;
+
+	struct gpio_shared_ref *ref __free(kfree) = kzalloc(sizeof(*ref), GFP_KERNEL);
+	if (!ref)
+		return NULL;
+
+	if (con_id) {
+		con_id_cpy = kstrdup(con_id, GFP_KERNEL);
+		if (!con_id_cpy)
+			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;
+	mutex_init(&ref->lock);
+
+	return no_free_ptr(ref);
+}
+
+static int gpio_shared_setup_reset_proxy(struct gpio_shared_entry *entry,
+					 enum gpiod_flags flags)
+{
+	struct gpio_shared_ref *ref;
+
+	list_for_each_entry(ref, &entry->refs, list) {
+		if (!ref->fwnode && ref->con_id && strcmp(ref->con_id, "reset") == 0)
+			return 0;
+	}
+
+	ref = gpio_shared_make_ref(NULL, "reset", flags);
+	if (!ref)
+		return -ENOMEM;
+
+	list_add_tail(&ref->list, &entry->refs);
+
+	pr_debug("Created a secondary shared GPIO reference for potential reset-gpio device for GPIO %u at %s\n",
+		 entry->offset, fwnode_get_name(entry->fwnode));
+
+	return 0;
+}
+
 /* Handle all special nodes that we should ignore. */
 static bool gpio_shared_of_node_ignore(struct device_node *node)
 {
@@ -106,6 +156,7 @@ static int gpio_shared_of_traverse(struct device_node *curr)
 	size_t con_id_len, suffix_len;
 	struct fwnode_handle *fwnode;
 	struct of_phandle_args args;
+	struct gpio_shared_ref *ref;
 	struct property *prop;
 	unsigned int offset;
 	const char *suffix;
@@ -138,6 +189,7 @@ static int gpio_shared_of_traverse(struct device_node *curr)
 
 		for (i = 0; i < count; i++) {
 			struct device_node *np __free(device_node) = NULL;
+			char *con_id __free(kfree) = NULL;
 
 			ret = of_parse_phandle_with_args(curr, prop->name,
 							 "#gpio-cells", i,
@@ -182,15 +234,6 @@ static int gpio_shared_of_traverse(struct device_node *curr)
 				list_add_tail(&entry->list, &gpio_shared_list);
 			}
 
-			struct gpio_shared_ref *ref __free(kfree) =
-					kzalloc(sizeof(*ref), GFP_KERNEL);
-			if (!ref)
-				return -ENOMEM;
-
-			ref->fwnode = fwnode_handle_get(of_fwnode_handle(curr));
-			ref->flags = args.args[1];
-			mutex_init(&ref->lock);
-
 			if (strends(prop->name, "gpios"))
 				suffix = "-gpios";
 			else if (strends(prop->name, "gpio"))
@@ -202,27 +245,32 @@ static int gpio_shared_of_traverse(struct device_node *curr)
 
 			/* We only set con_id if there's actually one. */
 			if (strcmp(prop->name, "gpios") && strcmp(prop->name, "gpio")) {
-				ref->con_id = kstrdup(prop->name, GFP_KERNEL);
-				if (!ref->con_id)
+				con_id = kstrdup(prop->name, GFP_KERNEL);
+				if (!con_id)
 					return -ENOMEM;
 
-				con_id_len = strlen(ref->con_id);
+				con_id_len = strlen(con_id);
 				suffix_len = strlen(suffix);
 
-				ref->con_id[con_id_len - suffix_len] = '\0';
+				con_id[con_id_len - suffix_len] = '\0';
 			}
 
-			ref->dev_id = ida_alloc(&gpio_shared_ida, GFP_KERNEL);
-			if (ref->dev_id < 0) {
-				kfree(ref->con_id);
+			ref = gpio_shared_make_ref(fwnode_handle_get(of_fwnode_handle(curr)),
+						   con_id, args.args[1]);
+			if (!ref)
 				return -ENOMEM;
-			}
 
 			if (!list_empty(&entry->refs))
 				pr_debug("GPIO %u at %s is shared by multiple firmware nodes\n",
 					 entry->offset, fwnode_get_name(entry->fwnode));
 
-			list_add_tail(&no_free_ptr(ref)->list, &entry->refs);
+			list_add_tail(&ref->list, &entry->refs);
+
+			if (strcmp(prop->name, "reset-gpios") == 0) {
+				ret = gpio_shared_setup_reset_proxy(entry, args.args[1]);
+				if (ret)
+					return ret;
+			}
 		}
 	}
 
@@ -306,20 +354,16 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
 	struct fwnode_handle *reset_fwnode = dev_fwnode(consumer);
 	struct fwnode_reference_args ref_args, aux_args;
 	struct device *parent = consumer->parent;
+	struct gpio_shared_ref *real_ref;
 	bool match;
 	int ret;
 
+	lockdep_assert_held(&ref->lock);
+
 	/* The reset-gpio device must have a parent AND a firmware node. */
 	if (!parent || !reset_fwnode)
 		return false;
 
-	/*
-	 * FIXME: use device_is_compatible() once the reset-gpio drivers gains
-	 * a compatible string which it currently does not have.
-	 */
-	if (!strstarts(dev_name(consumer), "reset.gpio."))
-		return false;
-
 	/*
 	 * Parent of the reset-gpio auxiliary device is the GPIO chip whose
 	 * fwnode we stored in the entry structure.
@@ -328,33 +372,56 @@ static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
 		return false;
 
 	/*
-	 * The device associated with the shared reference's firmware node is
-	 * the consumer of the reset control exposed by the reset-gpio device.
-	 * It must have a "reset-gpios" property that's referencing the entry's
-	 * firmware node.
-	 *
-	 * The reference args must agree between the real consumer and the
-	 * auxiliary reset-gpio device.
+	 * Now we need to find the actual pin we want to assign to this
+	 * reset-gpio device. To that end: iterate over the list of references
+	 * of this entry and see if there's one, whose reset-gpios property's
+	 * arguments match the ones from this consumer's node.
 	 */
-	ret = fwnode_property_get_reference_args(ref->fwnode, "reset-gpios",
-						 NULL, 2, 0, &ref_args);
-	if (ret)
-		return false;
+	list_for_each_entry(real_ref, &entry->refs, list) {
+		if (!real_ref->fwnode)
+			continue;
+
+		/*
+		 * The device associated with the shared reference's firmware
+		 * node is the consumer of the reset control exposed by the
+		 * reset-gpio device. It must have a "reset-gpios" property
+		 * that's referencing the entry's firmware node.
+		 *
+		 * The reference args must agree between the real consumer and
+		 * the auxiliary reset-gpio device.
+		 */
+		ret = fwnode_property_get_reference_args(real_ref->fwnode,
+							 "reset-gpios",
+							 NULL, 2, 0, &ref_args);
+		if (ret)
+			continue;
+
+		ret = fwnode_property_get_reference_args(reset_fwnode, "reset-gpios",
+							 NULL, 2, 0, &aux_args);
+		if (ret) {
+			fwnode_handle_put(ref_args.fwnode);
+			continue;
+		}
+
+		match = ((ref_args.fwnode == entry->fwnode) &&
+			 (aux_args.fwnode == entry->fwnode) &&
+			 (ref_args.args[0] == aux_args.args[0]));
 
-	ret = fwnode_property_get_reference_args(reset_fwnode, "reset-gpios",
-						 NULL, 2, 0, &aux_args);
-	if (ret) {
 		fwnode_handle_put(ref_args.fwnode);
-		return false;
-	}
+		fwnode_handle_put(aux_args.fwnode);
+
+		if (!match)
+			continue;
 
-	match = ((ref_args.fwnode == entry->fwnode) &&
-		 (aux_args.fwnode == entry->fwnode) &&
-		 (ref_args.args[0] == aux_args.args[0]));
+		/*
+		 * Reuse the fwnode of the real device, next time we'll use it
+		 * in the normal path.
+		 */
+		ref->fwnode = fwnode_handle_get(real_ref->fwnode);
+		return true;
+	}
 
-	fwnode_handle_put(ref_args.fwnode);
-	fwnode_handle_put(aux_args.fwnode);
-	return match;
+	return false;
 }
 #else
 static bool gpio_shared_dev_is_reset_gpio(struct device *consumer,
@@ -379,12 +446,20 @@ int gpio_shared_add_proxy_lookup(struct device *consumer, const char *con_id,
 
 	list_for_each_entry(entry, &gpio_shared_list, list) {
 		list_for_each_entry(ref, &entry->refs, list) {
-			if (!device_match_fwnode(consumer, ref->fwnode) &&
-			    !gpio_shared_dev_is_reset_gpio(consumer, entry, ref))
-				continue;
-
 			guard(mutex)(&ref->lock);
 
+			/*
+			 * FIXME: use device_is_compatible() once the reset-gpio
+			 * drivers gains a compatible string which it currently
+			 * does not have.
+			 */
+			if (!ref->fwnode && strstarts(dev_name(consumer), "reset.gpio.")) {
+				if (!gpio_shared_dev_is_reset_gpio(consumer, entry, ref))
+					continue;
+			} else if (!device_match_fwnode(consumer, ref->fwnode)) {
+				continue;
+			}
+
 			if ((!con_id && ref->con_id) || (con_id && !ref->con_id) ||
 			    (con_id && ref->con_id && strcmp(con_id, ref->con_id) != 0))
 				continue;
@@ -471,8 +546,9 @@ int gpio_device_setup_shared(struct gpio_device *gdev)
 			 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\n",
-				 fwnode_get_name(ref->fwnode));
+			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)

-- 
2.47.3


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ