[PATCH v4 1/4] gpio: shared-proxy: Fix race between concurrent first users

From: Shawn Guo

Date: Sat Oct 17 2026 - 00:37:13 EST


The proxy lets a consumer set the line direction and reset the
default value and vote tally only when usecnt == 1, i.e. when it is
the sole requester. But request() and direction_output() take the
descriptor mutex separately. When two consumers probe concurrently,
both request the line before either configures it, both see
usecnt == 2, both fall into the shared path, read back the still
unconfigured direction (input) and fail with -EPERM.

On Nord RRD the two DAC supplies are reg-fixed-voltage regulators
sharing tlmm GPIO 41. They probe asynchronously and intermittently
both fail, leaving the codecs without supplies:

reg-fixed-voltage regulator-3p3-dac: setup of GPIO (default) failed: -1
reg-fixed-voltage regulator-5p0-dac: setup of GPIO (default) failed: -1

Requesting the line is not evidence that a consumer has claimed it.
Instead, count proxies that have actually configured the line (set its
direction or voted for a value) in a new cfgcnt, with a per-proxy
configured flag, and allow (re)initialization only while no other
proxy has done so. A consumer obtained with GPIOD_ASIS that votes via
gpiod_set_value() without ever setting the direction counts as
configured, so a later user can never reset def_val/votecnt behind its
vote. The count is dropped when the proxy frees the line.

Fixes: e992d54c6f97 ("gpio: shared-proxy: implement the shared GPIO proxy driver")
Assisted-by: LLM
Signed-off-by: Shawn Guo <shengchao.guo@xxxxxxxxxxxxxxxx>
---
drivers/gpio/gpio-shared-proxy.c | 74 ++++++++++++++++++++++++++++----
drivers/gpio/gpiolib-shared.h | 1 +
2 files changed, 66 insertions(+), 9 deletions(-)

diff --git a/drivers/gpio/gpio-shared-proxy.c b/drivers/gpio/gpio-shared-proxy.c
index bc69b8729d19..0d1d724a6a37 100644
--- a/drivers/gpio/gpio-shared-proxy.c
+++ b/drivers/gpio/gpio-shared-proxy.c
@@ -21,9 +21,33 @@ struct gpio_shared_proxy_data {
struct gpio_chip gc;
struct gpio_shared_desc *shared_desc;
struct device *dev;
+ bool configured;
bool voted_change;
};

+/*
+ * A proxy may (re)initialize the line - set its direction and reset the
+ * default value and the vote tally - only while no other proxy has set the
+ * direction or voted for a value. Requesting the line is not enough: consumers
+ * may request concurrently, so counting requests lets every one of them
+ * conclude that it is not first.
+ */
+static bool gpio_shared_proxy_may_init(struct gpio_shared_proxy_data *proxy)
+{
+ unsigned int cfgcnt = proxy->shared_desc->cfgcnt;
+
+ return !cfgcnt || (cfgcnt == 1 && proxy->configured);
+}
+
+static void gpio_shared_proxy_mark_configured(struct gpio_shared_proxy_data *proxy)
+{
+ if (proxy->configured)
+ return;
+
+ proxy->configured = true;
+ proxy->shared_desc->cfgcnt++;
+}
+
static int
gpio_shared_proxy_set_unlocked(struct gpio_shared_proxy_data *proxy, int value)
{
@@ -117,6 +141,11 @@ static void gpio_shared_proxy_free(struct gpio_chip *gc, unsigned int offset)

proxy->shared_desc->usecnt--;

+ if (proxy->configured) {
+ proxy->configured = false;
+ shared_desc->cfgcnt--;
+ }
+
dev_dbg(proxy->dev, "Shared GPIO freed, number of users: %u\n",
proxy->shared_desc->usecnt);
}
@@ -131,7 +160,7 @@ static int gpio_shared_proxy_set_config(struct gpio_chip *gc,

guard(mutex)(&shared_desc->mutex);

- if (shared_desc->usecnt > 1) {
+ if (!gpio_shared_proxy_may_init(proxy)) {
if (shared_desc->cfg != cfg) {
dev_dbg(proxy->dev,
"Shared GPIO's configuration already set, accepting changes but users may conflict!!\n");
@@ -155,15 +184,21 @@ static int gpio_shared_proxy_direction_input(struct gpio_chip *gc,
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
struct gpio_shared_desc *shared_desc = proxy->shared_desc;
struct gpio_desc *desc = shared_desc->desc;
- int dir;
+ int ret, dir;

guard(mutex)(&shared_desc->mutex);

- if (shared_desc->usecnt == 1) {
+ if (gpio_shared_proxy_may_init(proxy)) {
dev_dbg(proxy->dev,
- "Only one user of this shared GPIO, allowing to set direction to input\n");
+ "No other user configured this shared GPIO, allowing to set direction to input\n");

- return gpiod_direction_input(desc);
+ ret = gpiod_direction_input(desc);
+ if (ret)
+ return ret;
+
+ gpio_shared_proxy_mark_configured(proxy);
+
+ return 0;
}

dir = gpiod_get_direction(desc);
@@ -176,6 +211,8 @@ static int gpio_shared_proxy_direction_input(struct gpio_chip *gc,
return -EPERM;
}

+ gpio_shared_proxy_mark_configured(proxy);
+
return 0;
}

@@ -189,18 +226,20 @@ static int gpio_shared_proxy_direction_output(struct gpio_chip *gc,

guard(mutex)(&shared_desc->mutex);

- if (shared_desc->usecnt == 1) {
+ if (gpio_shared_proxy_may_init(proxy)) {
dev_dbg(proxy->dev,
- "Only one user of this shared GPIO, allowing to set direction to output with value '%s'\n",
+ "No other user configured this shared GPIO, allowing to set direction to output with value '%s'\n",
str_high_low(value));

ret = gpiod_direction_output(desc, value);
if (ret)
return ret;

+ /* Only this proxy can have voted, so the tally is ours to reset */
shared_desc->def_val = value;
shared_desc->votecnt = 0;
proxy->voted_change = false;
+ gpio_shared_proxy_mark_configured(proxy);

return 0;
}
@@ -215,7 +254,13 @@ static int gpio_shared_proxy_direction_output(struct gpio_chip *gc,
return -EPERM;
}

- return gpio_shared_proxy_set_unlocked(proxy, value);
+ ret = gpio_shared_proxy_set_unlocked(proxy, value);
+ if (ret)
+ return ret;
+
+ gpio_shared_proxy_mark_configured(proxy);
+
+ return 0;
}

static int gpio_shared_proxy_get_cansleep(struct gpio_chip *gc,
@@ -230,10 +275,21 @@ static int gpio_shared_proxy_set_cansleep(struct gpio_chip *gc,
unsigned int offset, int value)
{
struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
+ int ret;

guard(mutex)(&proxy->shared_desc->mutex);

- return gpio_shared_proxy_set_unlocked(proxy, value);
+ /*
+ * A GPIOD_ASIS consumer may vote without ever setting the direction;
+ * count it as configured so a later user can't reset its vote.
+ */
+ ret = gpio_shared_proxy_set_unlocked(proxy, value);
+ if (ret)
+ return ret;
+
+ gpio_shared_proxy_mark_configured(proxy);
+
+ return 0;
}

static int gpio_shared_proxy_get_direction(struct gpio_chip *gc,
diff --git a/drivers/gpio/gpiolib-shared.h b/drivers/gpio/gpiolib-shared.h
index 618756f6c6aa..ae10506b7029 100644
--- a/drivers/gpio/gpiolib-shared.h
+++ b/drivers/gpio/gpiolib-shared.h
@@ -41,6 +41,7 @@ struct gpio_shared_desc {
struct gpio_desc *desc;
unsigned long cfg;
unsigned int usecnt;
+ unsigned int cfgcnt;
unsigned int votecnt;
int def_val;
struct mutex mutex; /* serializes all proxy operations on this descriptor */
--
2.43.0