* [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping
@ 2026-06-10 15:32 ` Viacheslav Bocharov
2026-06-10 15:32 ` [PATCH 1/2] gpio: shared-proxy: always serialize with a sleeping mutex Viacheslav Bocharov
` (3 more replies)
0 siblings, 4 replies; 8+ messages in thread
From: Viacheslav Bocharov @ 2026-06-10 15:32 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Marek Szyprowski, Robin Murphy, Diederik de Haas, linux-gpio,
linux-arm-kernel, linux-amlogic, linux-kernel
gpio-shared-proxy chooses its descriptor lock (mutex vs spinlock) from
the underlying chip's can_sleep, but under that lock it calls config and
direction ops that reach sleeping pinctrl paths. On a controller with
non-sleeping MMIO value ops the lock is a spinlock, so a sleeping call
runs from atomic context:
BUG: sleeping function called from invalid context
... pinctrl_gpio_set_config <- gpiochip_generic_config
<- gpio_shared_proxy_set_config (voting spinlock held)
<- ... <- mmc_pwrseq_simple_probe
This was reported on Khadas VIM3 and worked around for Amlogic by
commit 28f240683871 ("pinctrl: meson: mark the GPIO controller as
sleeping"), which marked the whole meson controller sleeping. That
workaround broke atomic value-path consumers: w1-gpio (1-Wire bitbang)
no longer detects devices, because its IRQ-disabled read slot calls the
non-cansleep gpiod_*_value() and now hits WARN_ON(can_sleep) per bit.
Patch 1 fixes the proxy locking generically (always a sleeping mutex).
Patch 2 then restores meson can_sleep=false, fixing 1-Wire.
Patch 1 has a trade-off: a proxied GPIO becomes sleeping, so consumers
gating on gpiod_cansleep() change behaviour. No current device needs
atomic (non-cansleep) value access on a shared GPIO -- every report
(Khadas VIM3, ODROID-M1, my test on JetHub D1+) is a shared reset line
(eMMC/SDIO pwrseq or PCIe reset) driven through the cansleep accessors,
which is what the proxy exists to vote on. An alternative that keeps
atomic value access (split locking) is possible but adds a second lock
and new race windows. I went with the simpler, verified approach and
would appreciate guidance on whether the atomic value path must be
preserved.
The two are a unit: patch 2 must not be applied without patch 1,
otherwise the original VIM3 splat returns on boards that share a meson
GPIO -- please keep the order. I have not Cc'd stable; I will request
stable backports separately once both patches have landed.
Viacheslav Bocharov (2):
gpio: shared-proxy: always serialize with a sleeping mutex
pinctrl: meson: restore non-sleeping GPIO access
drivers/gpio/gpio-shared-proxy.c | 43 ++++++---------------------
drivers/gpio/gpiolib-shared.c | 9 ++----
drivers/gpio/gpiolib-shared.h | 31 ++++++++-----------
drivers/pinctrl/meson/pinctrl-meson.c | 2 +-
4 files changed, 24 insertions(+), 61 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH 1/2] gpio: shared-proxy: always serialize with a sleeping mutex
2026-06-10 15:32 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Viacheslav Bocharov
@ 2026-06-10 15:32 ` Viacheslav Bocharov
2026-06-16 12:53 ` Bartosz Golaszewski
2026-06-10 15:32 ` [PATCH 2/2] pinctrl: meson: restore non-sleeping GPIO access Viacheslav Bocharov
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Viacheslav Bocharov @ 2026-06-10 15:32 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Marek Szyprowski, Robin Murphy, Diederik de Haas, linux-gpio,
linux-arm-kernel, linux-amlogic, linux-kernel
The shared GPIO descriptor used either a mutex or a spinlock, chosen at
runtime from the underlying chip's can_sleep:
shared_desc->can_sleep = gpiod_cansleep(shared_desc->desc);
... if (can_sleep) mutex_lock(); else spin_lock_irqsave();
can_sleep describes only the value path (->get/->set). Under the same
lock, however, the proxy may call gpiod_set_config() and
gpiod_direction_*(), which can reach pinctrl paths that take a mutex
(e.g. gpiod_set_config() -> gpiochip_generic_config() ->
pinctrl_gpio_set_config()), independent of can_sleep. On a controller
with non-sleeping MMIO value ops the descriptor lock was a spinlock, so
the sleeping pinctrl call ran from atomic context. Reproduced on an
Amlogic A113X board with the workaround from commit 28f240683871
("pinctrl: meson: mark the GPIO controller as sleeping") reverted; the
original Khadas VIM3 report hit the same path:
BUG: sleeping function called from invalid context
__mutex_lock
pinctrl_get_device_gpio_range
pinctrl_gpio_set_config
gpiochip_generic_config
gpiod_set_config
gpio_shared_proxy_set_config <- voting spinlock held
...
mmc_pwrseq_simple_probe
The spinlock existed to take the value vote from atomic context, but the
vote and the (possibly sleeping) control operations share the same state
and lock, so this scheme cannot serialize config under a mutex and still
offer atomic value access. Always serialize the shared descriptor with a
mutex instead and mark the proxy a sleeping gpiochip, driving the
underlying GPIO through the cansleep value accessors: those are valid
for both sleeping and non-sleeping chips, so value access keeps working
on fast controllers, at the cost of no longer being atomic.
This is observable: consumers gating on gpiod_cansleep() take their
sleeping branch on a proxied GPIO (mmc-pwrseq-emmc skips its
emergency-restart reset handler; its normal reset is unaffected), and
consumers that reject sleeping GPIOs (pwm-gpio, ps2-gpio, ...) would
fail to probe. Such atomic users do not share a pin through the proxy,
whose purpose is voting on shared reset/enable lines. The same narrowing
already applies on Amlogic since that workaround, and rockchip
addressed the identical splat per-driver in commit 7ca497be0016 ("gpio:
rockchip: Stop calling pinctrl for set_direction"); fixing the proxy
addresses the locking error once, for every controller.
The lock type was added by commit a060b8c511ab ("gpiolib: implement
low-level, shared GPIO support"); the sleeping call under it arrived with
the proxy driver.
Fixes: e992d54c6f97 ("gpio: shared-proxy: implement the shared GPIO proxy driver")
Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
Closes: https://lore.kernel.org/all/00107523-7737-4b92-a785-14ce4e93b8cb@samsung.com/
Signed-off-by: Viacheslav Bocharov <v@baodeep.com>
---
drivers/gpio/gpio-shared-proxy.c | 43 +++++++-------------------------
drivers/gpio/gpiolib-shared.c | 9 ++-----
drivers/gpio/gpiolib-shared.h | 31 +++++++++--------------
3 files changed, 23 insertions(+), 60 deletions(-)
diff --git a/drivers/gpio/gpio-shared-proxy.c b/drivers/gpio/gpio-shared-proxy.c
index 6941e4be6cf1..856e5b9d6163 100644
--- a/drivers/gpio/gpio-shared-proxy.c
+++ b/drivers/gpio/gpio-shared-proxy.c
@@ -109,7 +109,7 @@ static void gpio_shared_proxy_free(struct gpio_chip *gc, unsigned int offset)
if (proxy->voted_high) {
ret = gpio_shared_proxy_set_unlocked(proxy,
- shared_desc->can_sleep ? gpiod_set_value_cansleep : gpiod_set_value, 0);
+ gpiod_set_value_cansleep, 0);
if (ret)
dev_err(proxy->dev,
"Failed to unset the shared GPIO value on release: %d\n", ret);
@@ -222,13 +222,6 @@ static int gpio_shared_proxy_direction_output(struct gpio_chip *gc,
return gpio_shared_proxy_set_unlocked(proxy, gpiod_direction_output, value);
}
-static int gpio_shared_proxy_get(struct gpio_chip *gc, unsigned int offset)
-{
- struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
-
- return gpiod_get_value(proxy->shared_desc->desc);
-}
-
static int gpio_shared_proxy_get_cansleep(struct gpio_chip *gc,
unsigned int offset)
{
@@ -237,29 +230,15 @@ static int gpio_shared_proxy_get_cansleep(struct gpio_chip *gc,
return gpiod_get_value_cansleep(proxy->shared_desc->desc);
}
-static int gpio_shared_proxy_do_set(struct gpio_shared_proxy_data *proxy,
- int (*set_func)(struct gpio_desc *desc, int value),
- int value)
-{
- guard(gpio_shared_desc_lock)(proxy->shared_desc);
-
- return gpio_shared_proxy_set_unlocked(proxy, set_func, value);
-}
-
-static int gpio_shared_proxy_set(struct gpio_chip *gc, unsigned int offset,
- int value)
-{
- struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
-
- return gpio_shared_proxy_do_set(proxy, gpiod_set_value, value);
-}
-
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);
- return gpio_shared_proxy_do_set(proxy, gpiod_set_value_cansleep, value);
+ guard(gpio_shared_desc_lock)(proxy->shared_desc);
+
+ return gpio_shared_proxy_set_unlocked(proxy, gpiod_set_value_cansleep,
+ value);
}
static int gpio_shared_proxy_get_direction(struct gpio_chip *gc,
@@ -302,20 +281,16 @@ static int gpio_shared_proxy_probe(struct auxiliary_device *adev,
gc->label = dev_name(dev);
gc->parent = dev;
gc->owner = THIS_MODULE;
- gc->can_sleep = shared_desc->can_sleep;
+ /* Always a sleeping gpiochip: see the lock comment in gpiolib-shared.h. */
+ gc->can_sleep = true;
gc->request = gpio_shared_proxy_request;
gc->free = gpio_shared_proxy_free;
gc->set_config = gpio_shared_proxy_set_config;
gc->direction_input = gpio_shared_proxy_direction_input;
gc->direction_output = gpio_shared_proxy_direction_output;
- if (gc->can_sleep) {
- gc->set = gpio_shared_proxy_set_cansleep;
- gc->get = gpio_shared_proxy_get_cansleep;
- } else {
- gc->set = gpio_shared_proxy_set;
- gc->get = gpio_shared_proxy_get;
- }
+ gc->set = gpio_shared_proxy_set_cansleep;
+ gc->get = gpio_shared_proxy_get_cansleep;
gc->get_direction = gpio_shared_proxy_get_direction;
gc->to_irq = gpio_shared_proxy_to_irq;
diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
index de72776fb154..495bd3d0ddf0 100644
--- a/drivers/gpio/gpiolib-shared.c
+++ b/drivers/gpio/gpiolib-shared.c
@@ -627,8 +627,7 @@ static void gpio_shared_release(struct kref *kref)
shared_desc = entry->shared_desc;
gpio_device_put(shared_desc->desc->gdev);
- if (shared_desc->can_sleep)
- mutex_destroy(&shared_desc->mutex);
+ mutex_destroy(&shared_desc->mutex);
kfree(shared_desc);
entry->shared_desc = NULL;
}
@@ -659,11 +658,7 @@ gpiod_shared_desc_create(struct gpio_shared_entry *entry)
}
shared_desc->desc = &gdev->descs[entry->offset];
- shared_desc->can_sleep = gpiod_cansleep(shared_desc->desc);
- if (shared_desc->can_sleep)
- mutex_init(&shared_desc->mutex);
- else
- spin_lock_init(&shared_desc->spinlock);
+ mutex_init(&shared_desc->mutex);
return shared_desc;
}
diff --git a/drivers/gpio/gpiolib-shared.h b/drivers/gpio/gpiolib-shared.h
index 15e72a8dcdb1..5c725118b1af 100644
--- a/drivers/gpio/gpiolib-shared.h
+++ b/drivers/gpio/gpiolib-shared.h
@@ -6,7 +6,6 @@
#include <linux/cleanup.h>
#include <linux/lockdep.h>
#include <linux/mutex.h>
-#include <linux/spinlock.h>
struct gpio_device;
struct gpio_desc;
@@ -42,35 +41,29 @@ static inline int gpio_shared_add_proxy_lookup(struct device *consumer,
struct gpio_shared_desc {
struct gpio_desc *desc;
- bool can_sleep;
unsigned long cfg;
unsigned int usecnt;
unsigned int highcnt;
- union {
- struct mutex mutex;
- spinlock_t spinlock;
- };
+ struct mutex mutex; /* serializes all proxy operations on this descriptor */
};
struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev);
+/*
+ * Under this lock the proxy may call gpiod_set_config()/gpiod_direction_*(),
+ * which can reach pinctrl paths that take a mutex (e.g. gpiod_set_config() ->
+ * gpiochip_generic_config() -> pinctrl_gpio_set_config()), independent of the
+ * underlying chip's can_sleep. A spinlock would run that sleeping call from
+ * atomic context, so the descriptor lock must be a mutex and the proxy
+ * gpiochip is therefore sleeping (can_sleep=true).
+ */
DEFINE_LOCK_GUARD_1(gpio_shared_desc_lock, struct gpio_shared_desc,
- if (_T->lock->can_sleep)
- mutex_lock(&_T->lock->mutex);
- else
- spin_lock_irqsave(&_T->lock->spinlock, _T->flags),
- if (_T->lock->can_sleep)
- mutex_unlock(&_T->lock->mutex);
- else
- spin_unlock_irqrestore(&_T->lock->spinlock, _T->flags),
- unsigned long flags)
+ mutex_lock(&_T->lock->mutex),
+ mutex_unlock(&_T->lock->mutex))
static inline void gpio_shared_lockdep_assert(struct gpio_shared_desc *shared_desc)
{
- if (shared_desc->can_sleep)
- lockdep_assert_held(&shared_desc->mutex);
- else
- lockdep_assert_held(&shared_desc->spinlock);
+ lockdep_assert_held(&shared_desc->mutex);
}
#endif /* __LINUX_GPIO_SHARED_H */
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH 2/2] pinctrl: meson: restore non-sleeping GPIO access
2026-06-10 15:32 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Viacheslav Bocharov
2026-06-10 15:32 ` [PATCH 1/2] gpio: shared-proxy: always serialize with a sleeping mutex Viacheslav Bocharov
@ 2026-06-10 15:32 ` Viacheslav Bocharov
2026-06-11 8:26 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Marek Szyprowski
2026-06-11 9:53 ` Bartosz Golaszewski
3 siblings, 0 replies; 8+ messages in thread
From: Viacheslav Bocharov @ 2026-06-10 15:32 UTC (permalink / raw)
To: Linus Walleij, Bartosz Golaszewski
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Marek Szyprowski, Robin Murphy, Diederik de Haas, linux-gpio,
linux-arm-kernel, linux-amlogic, linux-kernel
Commit 28f240683871 ("pinctrl: meson: mark the GPIO controller as
sleeping") set gpio_chip.can_sleep = true to work around
gpio-shared-proxy holding a spinlock across a sleeping pinctrl config
path. That locking bug is now fixed in the shared-proxy itself ("gpio:
shared-proxy: always serialize with a sleeping mutex"), so the
controller-wide workaround is no longer needed; the meson GPIO
controller does not sleep.
meson_gpio_get/set/direction_* access MMIO through regmap. The
regmap_mmio bus uses fast I/O (spinlock) locking, so these value
callbacks do not contain sleeping operations. Since gpio_chip.can_sleep
describes the get/set value path, restore can_sleep = false.
Marking the controller sleeping also broke atomic value consumers such
as w1-gpio (1-Wire bitbang): w1_io.c runs its read time slot under
local_irq_save() and uses the non-cansleep gpiod_set_value() /
gpiod_get_value(), which with can_sleep=true trigger WARN_ON(can_sleep)
in gpiolib on every transferred bit (from w1_gpio_write_bit() /
w1_gpio_read_bit() via w1_reset_bus() and w1_search()). The printk and
stack dump inside the IRQs-off, microsecond-scale time slot destroy the
bit timing, so reset/presence detection and ROM search fail: the bus
master registers but w1_master_slave_count stays at 0 and no devices
are found. Verified on an Amlogic A113X board (DS18B20 on GPIOA_14):
with can_sleep restored to false the warnings are gone and the sensor
is detected and read again.
This must not be applied or backported without the shared-proxy locking
fix above; otherwise the original Khadas VIM3 splat returns on boards
that genuinely share a meson GPIO.
Fixes: 28f240683871 ("pinctrl: meson: mark the GPIO controller as sleeping")
Link: https://lore.kernel.org/all/20260105150509.56537-1-bartosz.golaszewski@oss.qualcomm.com/
Signed-off-by: Viacheslav Bocharov <v@baodeep.com>
---
drivers/pinctrl/meson/pinctrl-meson.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/pinctrl/meson/pinctrl-meson.c b/drivers/pinctrl/meson/pinctrl-meson.c
index 4507dc8b5563..18295b15ecd9 100644
--- a/drivers/pinctrl/meson/pinctrl-meson.c
+++ b/drivers/pinctrl/meson/pinctrl-meson.c
@@ -619,7 +619,7 @@ static int meson_gpiolib_register(struct meson_pinctrl *pc)
pc->chip.set = meson_gpio_set;
pc->chip.base = -1;
pc->chip.ngpio = pc->data->num_pins;
- pc->chip.can_sleep = true;
+ pc->chip.can_sleep = false;
ret = gpiochip_add_data(&pc->chip, pc);
if (ret) {
--
2.54.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping
2026-06-10 15:32 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Viacheslav Bocharov
2026-06-10 15:32 ` [PATCH 1/2] gpio: shared-proxy: always serialize with a sleeping mutex Viacheslav Bocharov
2026-06-10 15:32 ` [PATCH 2/2] pinctrl: meson: restore non-sleeping GPIO access Viacheslav Bocharov
@ 2026-06-11 8:26 ` Marek Szyprowski
2026-06-11 9:27 ` Viacheslav
2026-06-23 15:16 ` Robin Murphy
2026-06-11 9:53 ` Bartosz Golaszewski
3 siblings, 2 replies; 8+ messages in thread
From: Marek Szyprowski @ 2026-06-11 8:26 UTC (permalink / raw)
To: Viacheslav Bocharov, Linus Walleij, Bartosz Golaszewski
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Robin Murphy, Diederik de Haas, linux-gpio, linux-arm-kernel,
linux-amlogic, linux-kernel, linux-rockchip, Heiko Stuebner
Hi Viachesla,
On 10.06.2026 17:32, Viacheslav Bocharov wrote:
> gpio-shared-proxy chooses its descriptor lock (mutex vs spinlock) from
> the underlying chip's can_sleep, but under that lock it calls config and
> direction ops that reach sleeping pinctrl paths. On a controller with
> non-sleeping MMIO value ops the lock is a spinlock, so a sleeping call
> runs from atomic context:
>
> BUG: sleeping function called from invalid context
> ... pinctrl_gpio_set_config <- gpiochip_generic_config
> <- gpio_shared_proxy_set_config (voting spinlock held)
> <- ... <- mmc_pwrseq_simple_probe
>
> This was reported on Khadas VIM3 and worked around for Amlogic by
> commit 28f240683871 ("pinctrl: meson: mark the GPIO controller as
> sleeping"), which marked the whole meson controller sleeping. That
> workaround broke atomic value-path consumers: w1-gpio (1-Wire bitbang)
> no longer detects devices, because its IRQ-disabled read slot calls the
> non-cansleep gpiod_*_value() and now hits WARN_ON(can_sleep) per bit.
>
> Patch 1 fixes the proxy locking generically (always a sleeping mutex).
> Patch 2 then restores meson can_sleep=false, fixing 1-Wire.
>
> Patch 1 has a trade-off: a proxied GPIO becomes sleeping, so consumers
> gating on gpiod_cansleep() change behaviour. No current device needs
> atomic (non-cansleep) value access on a shared GPIO -- every report
> (Khadas VIM3, ODROID-M1, my test on JetHub D1+) is a shared reset line
> (eMMC/SDIO pwrseq or PCIe reset) driven through the cansleep accessors,
> which is what the proxy exists to vote on. An alternative that keeps
> atomic value access (split locking) is possible but adds a second lock
> and new race windows. I went with the simpler, verified approach and
> would appreciate guidance on whether the atomic value path must be
> preserved.
>
> The two are a unit: patch 2 must not be applied without patch 1,
> otherwise the original VIM3 splat returns on boards that share a meson
> GPIO -- please keep the order. I have not Cc'd stable; I will request
> stable backports separately once both patches have landed.
>
> Viacheslav Bocharov (2):
> gpio: shared-proxy: always serialize with a sleeping mutex
> pinctrl: meson: restore non-sleeping GPIO access
Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
This probably also affects the similar changes in Rockchip GPIO driver done
by the following commits:
20cf2aed89ac ("gpio: rockchip: mark the GPIO controller as sleeping")
7ca497be0016 ("gpio: rockchip: Stop calling pinctrl for set_direction")
I've checked this patchset with these two reverted and no warning was reported.
Best regards
--
Marek Szyprowski, PhD
Samsung R&D Institute Poland
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping
2026-06-11 8:26 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Marek Szyprowski
@ 2026-06-11 9:27 ` Viacheslav
2026-06-23 15:16 ` Robin Murphy
1 sibling, 0 replies; 8+ messages in thread
From: Viacheslav @ 2026-06-11 9:27 UTC (permalink / raw)
To: Marek Szyprowski, Linus Walleij, Bartosz Golaszewski
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Robin Murphy, Diederik de Haas, linux-gpio, linux-arm-kernel,
linux-amlogic, linux-kernel, linux-rockchip, Heiko Stuebner
Hi, Marek!
11.06.2026 11:26, Marek Szyprowski wrote:
> Hi Viachesla,
>
> On 10.06.2026 17:32, Viacheslav Bocharov wrote:
>
> Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
>
Thanks for testing.
> This probably also affects the similar changes in Rockchip GPIO driver done
> by the following commits:
> 20cf2aed89ac ("gpio: rockchip: mark the GPIO controller as sleeping")
> 7ca497be0016 ("gpio: rockchip: Stop calling pinctrl for set_direction")
>
Yeah, we also need to revert can_sleep=true on rockchip and amlogic-a4
(d6df4abe95a4).
Best regards
--
Viacheslav Bocharov
Shenzhen JetHome Technology Co., Ltd.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping
2026-06-10 15:32 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Viacheslav Bocharov
` (2 preceding siblings ...)
2026-06-11 8:26 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Marek Szyprowski
@ 2026-06-11 9:53 ` Bartosz Golaszewski
3 siblings, 0 replies; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-06-11 9:53 UTC (permalink / raw)
To: Viacheslav Bocharov
Cc: Linus Walleij, Neil Armstrong, Kevin Hilman, Jerome Brunet,
Martin Blumenstingl, Marek Szyprowski, Robin Murphy,
Diederik de Haas, linux-gpio, linux-arm-kernel, linux-amlogic,
linux-kernel
On Wed, Jun 10, 2026 at 5:41 PM Viacheslav Bocharov <v@baodeep.com> wrote:
>
> gpio-shared-proxy chooses its descriptor lock (mutex vs spinlock) from
> the underlying chip's can_sleep, but under that lock it calls config and
> direction ops that reach sleeping pinctrl paths. On a controller with
> non-sleeping MMIO value ops the lock is a spinlock, so a sleeping call
> runs from atomic context:
>
Why am I seeing three versions of this in my inbox? Which one should I review?
Bartosz
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 1/2] gpio: shared-proxy: always serialize with a sleeping mutex
2026-06-10 15:32 ` [PATCH 1/2] gpio: shared-proxy: always serialize with a sleeping mutex Viacheslav Bocharov
@ 2026-06-16 12:53 ` Bartosz Golaszewski
0 siblings, 0 replies; 8+ messages in thread
From: Bartosz Golaszewski @ 2026-06-16 12:53 UTC (permalink / raw)
To: Viacheslav Bocharov
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Marek Szyprowski, Robin Murphy, Diederik de Haas, linux-gpio,
linux-arm-kernel, linux-amlogic, linux-kernel, Linus Walleij,
Bartosz Golaszewski
On Wed, 10 Jun 2026 17:32:10 +0200, Viacheslav Bocharov <v@baodeep.com> said:
> The shared GPIO descriptor used either a mutex or a spinlock, chosen at
> runtime from the underlying chip's can_sleep:
>
> shared_desc->can_sleep = gpiod_cansleep(shared_desc->desc);
> ... if (can_sleep) mutex_lock(); else spin_lock_irqsave();
>
> can_sleep describes only the value path (->get/->set). Under the same
> lock, however, the proxy may call gpiod_set_config() and
> gpiod_direction_*(), which can reach pinctrl paths that take a mutex
> (e.g. gpiod_set_config() -> gpiochip_generic_config() ->
> pinctrl_gpio_set_config()), independent of can_sleep. On a controller
> with non-sleeping MMIO value ops the descriptor lock was a spinlock, so
> the sleeping pinctrl call ran from atomic context. Reproduced on an
> Amlogic A113X board with the workaround from commit 28f240683871
> ("pinctrl: meson: mark the GPIO controller as sleeping") reverted; the
> original Khadas VIM3 report hit the same path:
>
> BUG: sleeping function called from invalid context
> __mutex_lock
> pinctrl_get_device_gpio_range
> pinctrl_gpio_set_config
> gpiochip_generic_config
> gpiod_set_config
> gpio_shared_proxy_set_config <- voting spinlock held
> ...
> mmc_pwrseq_simple_probe
>
> The spinlock existed to take the value vote from atomic context, but the
> vote and the (possibly sleeping) control operations share the same state
> and lock, so this scheme cannot serialize config under a mutex and still
> offer atomic value access. Always serialize the shared descriptor with a
> mutex instead and mark the proxy a sleeping gpiochip, driving the
> underlying GPIO through the cansleep value accessors: those are valid
> for both sleeping and non-sleeping chips, so value access keeps working
> on fast controllers, at the cost of no longer being atomic.
>
> This is observable: consumers gating on gpiod_cansleep() take their
> sleeping branch on a proxied GPIO (mmc-pwrseq-emmc skips its
> emergency-restart reset handler; its normal reset is unaffected), and
> consumers that reject sleeping GPIOs (pwm-gpio, ps2-gpio, ...) would
> fail to probe. Such atomic users do not share a pin through the proxy,
> whose purpose is voting on shared reset/enable lines. The same narrowing
> already applies on Amlogic since that workaround, and rockchip
> addressed the identical splat per-driver in commit 7ca497be0016 ("gpio:
> rockchip: Stop calling pinctrl for set_direction"); fixing the proxy
> addresses the locking error once, for every controller.
>
> The lock type was added by commit a060b8c511ab ("gpiolib: implement
> low-level, shared GPIO support"); the sleeping call under it arrived with
> the proxy driver.
>
> Fixes: e992d54c6f97 ("gpio: shared-proxy: implement the shared GPIO proxy driver")
> Reported-by: Marek Szyprowski <m.szyprowski@samsung.com>
> Closes: https://lore.kernel.org/all/00107523-7737-4b92-a785-14ce4e93b8cb@samsung.com/
> Signed-off-by: Viacheslav Bocharov <v@baodeep.com>
> ---
Sigh... Ok let's try this. This limits users of shared pins to using them from
process context exclusively but maybe there is in fact no need to do it from
atomic context. After all: bit-banging can't work if we're using the voting
mechanism.
>
> drivers/gpio/gpio-shared-proxy.c | 43 +++++++-------------------------
> drivers/gpio/gpiolib-shared.c | 9 ++-----
> drivers/gpio/gpiolib-shared.h | 31 +++++++++--------------
> 3 files changed, 23 insertions(+), 60 deletions(-)
>
> diff --git a/drivers/gpio/gpio-shared-proxy.c b/drivers/gpio/gpio-shared-proxy.c
> index 6941e4be6cf1..856e5b9d6163 100644
> --- a/drivers/gpio/gpio-shared-proxy.c
> +++ b/drivers/gpio/gpio-shared-proxy.c
> @@ -109,7 +109,7 @@ static void gpio_shared_proxy_free(struct gpio_chip *gc, unsigned int offset)
>
> if (proxy->voted_high) {
> ret = gpio_shared_proxy_set_unlocked(proxy,
> - shared_desc->can_sleep ? gpiod_set_value_cansleep : gpiod_set_value, 0);
> + gpiod_set_value_cansleep, 0);
> if (ret)
> dev_err(proxy->dev,
> "Failed to unset the shared GPIO value on release: %d\n", ret);
> @@ -222,13 +222,6 @@ static int gpio_shared_proxy_direction_output(struct gpio_chip *gc,
> return gpio_shared_proxy_set_unlocked(proxy, gpiod_direction_output, value);
> }
>
> -static int gpio_shared_proxy_get(struct gpio_chip *gc, unsigned int offset)
> -{
> - struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
> -
> - return gpiod_get_value(proxy->shared_desc->desc);
> -}
> -
> static int gpio_shared_proxy_get_cansleep(struct gpio_chip *gc,
> unsigned int offset)
> {
> @@ -237,29 +230,15 @@ static int gpio_shared_proxy_get_cansleep(struct gpio_chip *gc,
> return gpiod_get_value_cansleep(proxy->shared_desc->desc);
> }
>
> -static int gpio_shared_proxy_do_set(struct gpio_shared_proxy_data *proxy,
> - int (*set_func)(struct gpio_desc *desc, int value),
> - int value)
> -{
> - guard(gpio_shared_desc_lock)(proxy->shared_desc);
> -
> - return gpio_shared_proxy_set_unlocked(proxy, set_func, value);
> -}
> -
> -static int gpio_shared_proxy_set(struct gpio_chip *gc, unsigned int offset,
> - int value)
> -{
> - struct gpio_shared_proxy_data *proxy = gpiochip_get_data(gc);
> -
> - return gpio_shared_proxy_do_set(proxy, gpiod_set_value, value);
> -}
> -
> 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);
>
> - return gpio_shared_proxy_do_set(proxy, gpiod_set_value_cansleep, value);
> + guard(gpio_shared_desc_lock)(proxy->shared_desc);
> +
> + return gpio_shared_proxy_set_unlocked(proxy, gpiod_set_value_cansleep,
> + value);
> }
>
> static int gpio_shared_proxy_get_direction(struct gpio_chip *gc,
> @@ -302,20 +281,16 @@ static int gpio_shared_proxy_probe(struct auxiliary_device *adev,
> gc->label = dev_name(dev);
> gc->parent = dev;
> gc->owner = THIS_MODULE;
> - gc->can_sleep = shared_desc->can_sleep;
> + /* Always a sleeping gpiochip: see the lock comment in gpiolib-shared.h. */
> + gc->can_sleep = true;
>
> gc->request = gpio_shared_proxy_request;
> gc->free = gpio_shared_proxy_free;
> gc->set_config = gpio_shared_proxy_set_config;
> gc->direction_input = gpio_shared_proxy_direction_input;
> gc->direction_output = gpio_shared_proxy_direction_output;
> - if (gc->can_sleep) {
> - gc->set = gpio_shared_proxy_set_cansleep;
> - gc->get = gpio_shared_proxy_get_cansleep;
> - } else {
> - gc->set = gpio_shared_proxy_set;
> - gc->get = gpio_shared_proxy_get;
> - }
> + gc->set = gpio_shared_proxy_set_cansleep;
> + gc->get = gpio_shared_proxy_get_cansleep;
> gc->get_direction = gpio_shared_proxy_get_direction;
> gc->to_irq = gpio_shared_proxy_to_irq;
>
> diff --git a/drivers/gpio/gpiolib-shared.c b/drivers/gpio/gpiolib-shared.c
> index de72776fb154..495bd3d0ddf0 100644
> --- a/drivers/gpio/gpiolib-shared.c
> +++ b/drivers/gpio/gpiolib-shared.c
> @@ -627,8 +627,7 @@ static void gpio_shared_release(struct kref *kref)
>
> shared_desc = entry->shared_desc;
> gpio_device_put(shared_desc->desc->gdev);
> - if (shared_desc->can_sleep)
> - mutex_destroy(&shared_desc->mutex);
> + mutex_destroy(&shared_desc->mutex);
> kfree(shared_desc);
> entry->shared_desc = NULL;
> }
> @@ -659,11 +658,7 @@ gpiod_shared_desc_create(struct gpio_shared_entry *entry)
> }
>
> shared_desc->desc = &gdev->descs[entry->offset];
> - shared_desc->can_sleep = gpiod_cansleep(shared_desc->desc);
> - if (shared_desc->can_sleep)
> - mutex_init(&shared_desc->mutex);
> - else
> - spin_lock_init(&shared_desc->spinlock);
> + mutex_init(&shared_desc->mutex);
>
> return shared_desc;
> }
> diff --git a/drivers/gpio/gpiolib-shared.h b/drivers/gpio/gpiolib-shared.h
> index 15e72a8dcdb1..5c725118b1af 100644
> --- a/drivers/gpio/gpiolib-shared.h
> +++ b/drivers/gpio/gpiolib-shared.h
> @@ -6,7 +6,6 @@
> #include <linux/cleanup.h>
> #include <linux/lockdep.h>
> #include <linux/mutex.h>
> -#include <linux/spinlock.h>
>
> struct gpio_device;
> struct gpio_desc;
> @@ -42,35 +41,29 @@ static inline int gpio_shared_add_proxy_lookup(struct device *consumer,
>
> struct gpio_shared_desc {
> struct gpio_desc *desc;
> - bool can_sleep;
> unsigned long cfg;
> unsigned int usecnt;
> unsigned int highcnt;
> - union {
> - struct mutex mutex;
> - spinlock_t spinlock;
> - };
> + struct mutex mutex; /* serializes all proxy operations on this descriptor */
> };
>
> struct gpio_shared_desc *devm_gpiod_shared_get(struct device *dev);
>
> +/*
> + * Under this lock the proxy may call gpiod_set_config()/gpiod_direction_*(),
> + * which can reach pinctrl paths that take a mutex (e.g. gpiod_set_config() ->
> + * gpiochip_generic_config() -> pinctrl_gpio_set_config()), independent of the
> + * underlying chip's can_sleep. A spinlock would run that sleeping call from
> + * atomic context, so the descriptor lock must be a mutex and the proxy
> + * gpiochip is therefore sleeping (can_sleep=true).
> + */
> DEFINE_LOCK_GUARD_1(gpio_shared_desc_lock, struct gpio_shared_desc,
> - if (_T->lock->can_sleep)
> - mutex_lock(&_T->lock->mutex);
> - else
> - spin_lock_irqsave(&_T->lock->spinlock, _T->flags),
> - if (_T->lock->can_sleep)
> - mutex_unlock(&_T->lock->mutex);
> - else
> - spin_unlock_irqrestore(&_T->lock->spinlock, _T->flags),
> - unsigned long flags)
> + mutex_lock(&_T->lock->mutex),
> + mutex_unlock(&_T->lock->mutex))
Just drop these wrappers altogether then, let's open-code mutex locking in
the proxy.
>
> static inline void gpio_shared_lockdep_assert(struct gpio_shared_desc *shared_desc)
> {
> - if (shared_desc->can_sleep)
> - lockdep_assert_held(&shared_desc->mutex);
> - else
> - lockdep_assert_held(&shared_desc->spinlock);
> + lockdep_assert_held(&shared_desc->mutex);
Same here, move it to the proxy.
> }
>
> #endif /* __LINUX_GPIO_SHARED_H */
> --
> 2.54.0
>
>
>
Bart
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping
2026-06-11 8:26 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Marek Szyprowski
2026-06-11 9:27 ` Viacheslav
@ 2026-06-23 15:16 ` Robin Murphy
1 sibling, 0 replies; 8+ messages in thread
From: Robin Murphy @ 2026-06-23 15:16 UTC (permalink / raw)
To: Marek Szyprowski, Viacheslav Bocharov, Linus Walleij,
Bartosz Golaszewski
Cc: Neil Armstrong, Kevin Hilman, Jerome Brunet, Martin Blumenstingl,
Diederik de Haas, linux-gpio, linux-arm-kernel, linux-amlogic,
linux-kernel, linux-rockchip, Heiko Stuebner
On 11/06/2026 9:26 am, Marek Szyprowski wrote:
> Hi Viachesla,
>
> On 10.06.2026 17:32, Viacheslav Bocharov wrote:
>> gpio-shared-proxy chooses its descriptor lock (mutex vs spinlock) from
>> the underlying chip's can_sleep, but under that lock it calls config and
>> direction ops that reach sleeping pinctrl paths. On a controller with
>> non-sleeping MMIO value ops the lock is a spinlock, so a sleeping call
>> runs from atomic context:
>>
>> BUG: sleeping function called from invalid context
>> ... pinctrl_gpio_set_config <- gpiochip_generic_config
>> <- gpio_shared_proxy_set_config (voting spinlock held)
>> <- ... <- mmc_pwrseq_simple_probe
>>
>> This was reported on Khadas VIM3 and worked around for Amlogic by
>> commit 28f240683871 ("pinctrl: meson: mark the GPIO controller as
>> sleeping"), which marked the whole meson controller sleeping. That
>> workaround broke atomic value-path consumers: w1-gpio (1-Wire bitbang)
>> no longer detects devices, because its IRQ-disabled read slot calls the
>> non-cansleep gpiod_*_value() and now hits WARN_ON(can_sleep) per bit.
>>
>> Patch 1 fixes the proxy locking generically (always a sleeping mutex).
>> Patch 2 then restores meson can_sleep=false, fixing 1-Wire.
>>
>> Patch 1 has a trade-off: a proxied GPIO becomes sleeping, so consumers
>> gating on gpiod_cansleep() change behaviour. No current device needs
>> atomic (non-cansleep) value access on a shared GPIO -- every report
>> (Khadas VIM3, ODROID-M1, my test on JetHub D1+) is a shared reset line
>> (eMMC/SDIO pwrseq or PCIe reset) driven through the cansleep accessors,
>> which is what the proxy exists to vote on. An alternative that keeps
>> atomic value access (split locking) is possible but adds a second lock
>> and new race windows. I went with the simpler, verified approach and
>> would appreciate guidance on whether the atomic value path must be
>> preserved.
>>
>> The two are a unit: patch 2 must not be applied without patch 1,
>> otherwise the original VIM3 splat returns on boards that share a meson
>> GPIO -- please keep the order. I have not Cc'd stable; I will request
>> stable backports separately once both patches have landed.
>>
>> Viacheslav Bocharov (2):
>> gpio: shared-proxy: always serialize with a sleeping mutex
>> pinctrl: meson: restore non-sleeping GPIO access
>
> Tested-by: Marek Szyprowski <m.szyprowski@samsung.com>
>
> This probably also affects the similar changes in Rockchip GPIO driver done
> by the following commits:
> 20cf2aed89ac ("gpio: rockchip: mark the GPIO controller as sleeping")
> 7ca497be0016 ("gpio: rockchip: Stop calling pinctrl for set_direction")
>
> I've checked this patchset with these two reverted and no warning was reported.
If it hadn't already been fixed, then indeed I guess this might make
20cf2aed89ac redundant. However, 7ca497be0016 is still an objective
improvement either way, since that driver never needed to call pinctrl
at all (it was seemingly just an artefact of how the GPIO code was
originally implemented within the pinctrl driver itself).
Thanks,
Robin.
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-06-23 15:17 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <CGME20260610153425eucas1p29d20a835114a28b15cb12ea00534e074@eucas1p2.samsung.com>
2026-06-10 15:32 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Viacheslav Bocharov
2026-06-10 15:32 ` [PATCH 1/2] gpio: shared-proxy: always serialize with a sleeping mutex Viacheslav Bocharov
2026-06-16 12:53 ` Bartosz Golaszewski
2026-06-10 15:32 ` [PATCH 2/2] pinctrl: meson: restore non-sleeping GPIO access Viacheslav Bocharov
2026-06-11 8:26 ` [PATCH 0/2] gpio: fix sleeping-in-atomic in shared-proxy; restore meson non-sleeping Marek Szyprowski
2026-06-11 9:27 ` Viacheslav
2026-06-23 15:16 ` Robin Murphy
2026-06-11 9:53 ` Bartosz Golaszewski
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox