* [PATCH] gpio: shared-proxy: Fix race between concurrent first users
@ 2026-10-15 12:27 Shawn Guo
2026-10-15 12:37 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Shawn Guo @ 2026-10-15 12:27 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: linux-gpio, linux-arm-msm, linux-kernel, Shawn Guo
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@oss.qualcomm.com>
---
drivers/gpio/gpio-shared-proxy.c | 72 ++++++++++++++++++++++++++++----
drivers/gpio/gpiolib-shared.h | 1 +
2 files changed, 65 insertions(+), 8 deletions(-)
diff --git a/drivers/gpio/gpio-shared-proxy.c b/drivers/gpio/gpio-shared-proxy.c
index bc69b8729d19..0d9ef98a4229 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);
}
@@ -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
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH] gpio: shared-proxy: Fix race between concurrent first users
2026-10-15 12:27 [PATCH] gpio: shared-proxy: Fix race between concurrent first users Shawn Guo
@ 2026-10-15 12:37 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-10-15 12:37 UTC (permalink / raw)
To: Shawn Guo; +Cc: linux-gpio
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] gpio: shared-proxy: integer underflow of votecnt on proxy free error path
- [Low] gpio: shared-proxy: spurious warning and logic bypass in set_config
--
commit 2a16d83da9ca16fcf3fc8f06a64d08bb431d1954
Author: Shawn Guo <shengchao.guo@oss.qualcomm.com>
gpio: shared-proxy: Fix race between concurrent first users
This commit addresses a race condition where concurrent consumers
requesting a shared proxy line could both observe the use count as 2
before any configuration occurs, resulting in a failure. It resolves
this by introducing a configuration counter and a per-proxy flag to
track actual configuration changes rather than just requests.
> diff --git a/drivers/gpio/gpio-shared-proxy.c b/drivers/gpio/gpio-shared-proxy.c
> index bc69b8729d196..0d9ef98a4229e 100644
> --- a/drivers/gpio/gpio-shared-proxy.c
> +++ b/drivers/gpio/gpio-shared-proxy.c
[ ... ]
> @@ -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--;
> + }
> +
[Severity: High]
If the hardware write fails in the earlier gpio_shared_proxy_set_unlocked()
call inside this function, proxy->voted_change remains true:
if (proxy->voted_change) {
ret = gpio_shared_proxy_set_unlocked(proxy, shared_desc->def_val);
if (ret)
dev_err(proxy->dev, ...);
}
When we unconditionally clear proxy->configured and decrement cfgcnt
here despite the failure, can this allow a new proxy to bypass checks and
reset the global votecnt to 0?
If cfgcnt drops to 0 and votecnt is reset, a later reuse of this proxy
could see voted_change still set. If votecnt is 0 at that point,
calling gpio_shared_proxy_set_unlocked() again could skip the hardware
write but decrement votecnt, causing it to underflow to UINT_MAX.
Could we ensure voted_change is cleared, or configured is not cleared
if the write fails?
[ ... ]
> diff --git a/drivers/gpio/gpiolib-shared.h b/drivers/gpio/gpiolib-shared.h
> index 618756f6c6aaf..ae10506b7029f 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 */
[Severity: Low]
Since concurrent probes are now tracked using cfgcnt rather than usecnt
to determine if the line has been initialized, does
gpio_shared_proxy_set_config() also need to be updated?
If two consumers concurrently request the line but haven't configured it,
usecnt is 2. If one of them then calls gpio_shared_proxy_set_config(),
it still checks usecnt > 1. This could cause a spurious conflict warning
if the requested configuration differs from the uninitialized state, or it
might bypass the hardware configuration entirely if they match, erroneously
assuming another consumer already applied it.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261015122740.971792-1-shengchao.guo@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-15 12:37 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 12:27 [PATCH] gpio: shared-proxy: Fix race between concurrent first users Shawn Guo
2026-10-15 12:37 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox