linux-gpio.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] pinctrl: stm32: use a raw spinlock regmap to program the EXTI mux
@ 2026-08-03  6:17 Ju Nan
  2026-08-04  3:22 ` [PATCH v2] pinctrl: stm32: program the EXTI mux from .alloc instead of .activate Ju Nan
  2026-08-04 14:14 ` [PATCH v3] " Ju Nan
  0 siblings, 2 replies; 5+ messages in thread
From: Ju Nan @ 2026-08-03  6:17 UTC (permalink / raw)
  To: Antonio Borneo, Linus Walleij
  Cc: Maxime Coquelin, Alexandre Torgue, Uwe Kleine-König,
	Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
	Lee Jones, Arnd Bergmann, linux-gpio, linux-stm32,
	linux-arm-kernel, linux-rt-devel, mfd, linux-kernel

stm32_gpio_domain_activate() programs the EXTI interrupt multiplexer
with regmap_field_write(), on a regmap the driver obtains from the generic
syscon driver via syscon_regmap_lookup_by_phandle(np, "st,syscfg").

An irq_domain .activate callback is by contract called from raw atomic
context: __setup_irq() takes the raw desc->lock and calls
irq_activate() while holding it.

That regmap is created by syscon with regmap_init_mmio(). The regmap-mmio
bus sets .fast_io = true, and syscon does not set use_raw_spinlock, so
__regmap_init() protects the regmap with a spinlock_t.

On !RT a spinlock_t only ever spins and nothing bad happens at runtime,
which is why this has gone unnoticed. On PREEMPT_RT spinlock_t is a
sleeping lock, and taking it under the raw desc->lock is a sleep in atomic
context. lockdep's wait-context checker catches this ahead of time — with
CONFIG_PROVE_RAW_LOCK_NESTING=y the driver splats on boot as soon as
anything requests a GPIO interrupt (here: an sii902x HDMI bridge):

BUG: Invalid wait context
...
(&syscon_config)->lock){....}-{3:3}, at: regmap_lock_spinlock
other info that might help us debug this:
... 6 locks held by kworker/u8:0/12:
 #5: (&irq_desc_lock_class){-...}-{2:2}, at: __setup_irq

i.e. a wait type 3 (LD_WAIT_CONFIG, sleeping-on-RT) lock is acquired while
the raw desc->lock has already limited the context to wait type 2
(LD_WAIT_SPIN).

Note the driver is already aware that it runs in atomic context here: it uses
the _in_atomic() hwspinlock primitives around this very same register
access. The syscon lock is the one lock in that section it does not control.

The fix:

Register a regmap with use_raw_spinlock = true for the node through
of_syscon_register_regmap() before looking it up, so the syscon layer hands
out that one instead of instantiating its default. It has to go through the
syscon layer rather than staying private to the driver, because both pinctrl
instances of an STM32MP1 (pinctrl and pinctrl_z) reference the same node —
private regmaps would give them one lock each and no mutual exclusion on the
mux registers. Same pattern as drivers/soc/samsung/exynos-pmu.c.

Reported-by: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>
Closes: https://lore.kernel.org/all/20220202174430.pf37tt6lua2op3gc@pengutronix.de/
Signed-off-by: Ju Nan <junan76@163.com>
---
 drivers/pinctrl/stm32/pinctrl-stm32.c | 64 +++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/drivers/pinctrl/stm32/pinctrl-stm32.c b/drivers/pinctrl/stm32/pinctrl-stm32.c
index 6a99708a5a23..39df117489d1 100644
--- a/drivers/pinctrl/stm32/pinctrl-stm32.c
+++ b/drivers/pinctrl/stm32/pinctrl-stm32.c
@@ -1757,6 +1757,68 @@ static struct irq_domain *stm32_pctrl_get_irq_domain(struct platform_device *pde
 	return domain;
 }
 
+/*
+ * The interrupt mux registers are written from stm32_gpio_domain_activate(),
+ * which the irq core calls with the raw desc->lock held. The regmap the
+ * generic syscon driver hands out is protected by a spinlock_t, which may
+ * sleep on PREEMPT_RT and therefore must not be taken from there.
+ *
+ * Publish a raw spinlock regmap for the node before looking it up, so that
+ * all of its users keep sharing one regmap, and one lock.
+ */
+static const struct regmap_config stm32_pctrl_syscfg_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+	.use_raw_spinlock = true,
+};
+
+static void stm32_pctrl_publish_syscfg_regmap(struct device_node *np)
+{
+	struct regmap_config config = stm32_pctrl_syscfg_regmap_config;
+	struct device_node *syscfg_np;
+	struct regmap *regmap;
+	void __iomem *base;
+	struct resource res;
+
+	syscfg_np = of_parse_phandle(np, "st,syscfg", 0);
+	if (!syscfg_np)
+		return;
+
+	if (of_address_to_resource(syscfg_np, 0, &res) ||
+	    resource_size(&res) < config.reg_stride)
+		goto out_put;
+
+	config.max_register = resource_size(&res) - config.reg_stride;
+
+	base = of_iomap(syscfg_np, 0);
+	if (!base)
+		goto out_put;
+
+	/*
+	 * The regmap is handed over to the syscon layer, which never releases
+	 * it, so it must outlive this driver: no device managed allocation
+	 * here, and no device to attach it to either.
+	 */
+	regmap = regmap_init_mmio(NULL, base, &config);
+	if (IS_ERR(regmap)) {
+		iounmap(base);
+		goto out_put;
+	}
+
+	/*
+	 * A regmap is already registered for that node, most likely by the
+	 * other pinctrl instance sharing it. Drop ours and use that one.
+	 */
+	if (of_syscon_register_regmap(syscfg_np, regmap)) {
+		regmap_exit(regmap);
+		iounmap(base);
+	}
+
+out_put:
+	of_node_put(syscfg_np);
+}
+
 static int stm32_pctrl_dt_setup_irq(struct platform_device *pdev,
 			   struct stm32_pinctrl *pctl)
 {
@@ -1766,6 +1828,8 @@ static int stm32_pctrl_dt_setup_irq(struct platform_device *pdev,
 	int offset, ret, i;
 	int mask, mask_width;
 
+	stm32_pctrl_publish_syscfg_regmap(np);
+
 	pctl->regmap = syscon_regmap_lookup_by_phandle(np, "st,syscfg");
 	if (IS_ERR(pctl->regmap))
 		return PTR_ERR(pctl->regmap);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2] pinctrl: stm32: program the EXTI mux from .alloc instead of .activate
  2026-08-03  6:17 [PATCH] pinctrl: stm32: use a raw spinlock regmap to program the EXTI mux Ju Nan
@ 2026-08-04  3:22 ` Ju Nan
  2026-08-04 14:14 ` [PATCH v3] " Ju Nan
  1 sibling, 0 replies; 5+ messages in thread
From: Ju Nan @ 2026-08-04  3:22 UTC (permalink / raw)
  To: junan76
  Cc: alexandre.torgue, antonio.borneo, arnd, bigeasy, clrkwllms, lee,
	linusw, linux-arm-kernel, linux-gpio, linux-kernel,
	linux-rt-devel, linux-stm32, mcoquelin.stm32, mfd, rostedt,
	u.kleine-koenig

stm32_gpio_domain_activate() writes the EXTI interrupt multiplexer
through a regmap obtained from the generic syscon driver. The irq core
calls .activate from __setup_irq() with the raw desc->lock held, so this
happens in a raw atomic section. regmap-mmio sets fast_io, and syscon
does not ask for a raw spinlock, so the regmap is protected by a
spinlock_t. On PREEMPT_RT that is a sleeping lock, which must not be
taken there. lockdep reports it as soon as a GPIO interrupt is
requested:

  BUG: Invalid wait context
  6.17.0 #4 Not tainted
  -----------------------------
  kworker/u8:0/12 is trying to lock:
  (&syscon_config)->lock){....}-{3:3}, at: regmap_lock_spinlock
  other info that might help us debug this:
  6 locks held by kworker/u8:0/12:
   #5: (&irq_desc_lock_class){-...}-{2:2}, at: __setup_irq
  stack backtrace:
   regmap_lock_spinlock
   regmap_field_update_bits_base
   stm32_gpio_domain_activate
   irq_domain_activate_irq
   __setup_irq
   request_threaded_irq

The driver was already aware of running in atomic context there: it uses
the _in_atomic() hwspinlock primitives around the very same access. The
syscon lock is the one lock in that section it does not control.

Program the mux from .alloc instead, which runs in a sleepable context.
That callback already owns this resource: it reserves the mux line in
pctl->irqmux_map under irqmux_lock, so writing the value it just claimed
is a natural fit, and the value only depends on the bank.

Nothing requires the mux to be reprogrammed at interrupt startup time:
the domain has no .deactivate, .free() only releases the irqmux_map bit
without touching the registers, and the resume path reprograms the mux
itself in stm32_pinctrl_restore_gpio_regs().

While moving the code, release the mux reservation when the hwspinlock
cannot be taken, which the .activate callback had no way of doing.

Reported-by: "Uwe Kleine-König" <u.kleine-koenig@pengutronix.de>
Closes: https://lore.kernel.org/all/20220202174430.pf37tt6lua2op3gc@pengutronix.de/
Signed-off-by: Ju Nan <junan76@163.com>
---
Changes in v2:

- remove stm32_gpio_domain_activate callback
- program mux register in stm32_gpio_domain_alloc instead

v1: https://lore.kernel.org/all/20260803061718.43210-1-junan76@163.com/
---
 drivers/pinctrl/stm32/pinctrl-stm32.c | 53 ++++++++++++++-------------
 1 file changed, 28 insertions(+), 25 deletions(-)

diff --git a/drivers/pinctrl/stm32/pinctrl-stm32.c b/drivers/pinctrl/stm32/pinctrl-stm32.c
index 6a99708a5a23..4332ac93a6ff 100644
--- a/drivers/pinctrl/stm32/pinctrl-stm32.c
+++ b/drivers/pinctrl/stm32/pinctrl-stm32.c
@@ -600,30 +600,6 @@ static int stm32_gpio_domain_translate(struct irq_domain *d,
 	return 0;
 }
 
-static int stm32_gpio_domain_activate(struct irq_domain *d,
-				      struct irq_data *irq_data, bool reserve)
-{
-	struct stm32_gpio_bank *bank = d->host_data;
-	struct stm32_pinctrl *pctl = dev_get_drvdata(bank->gpio_chip.parent);
-	int ret = 0;
-
-	if (pctl->hwlock) {
-		ret = hwspin_lock_timeout_in_atomic(pctl->hwlock,
-						    HWSPNLCK_TIMEOUT);
-		if (ret) {
-			dev_err(pctl->dev, "Can't get hwspinlock\n");
-			return ret;
-		}
-	}
-
-	regmap_field_write(pctl->irqmux[irq_data->hwirq], bank->bank_ioport_nr);
-
-	if (pctl->hwlock)
-		hwspin_unlock_in_atomic(pctl->hwlock);
-
-	return ret;
-}
-
 static int stm32_gpio_domain_alloc(struct irq_domain *d,
 				   unsigned int virq,
 				   unsigned int nr_irqs, void *data)
@@ -653,6 +629,27 @@ static int stm32_gpio_domain_alloc(struct irq_domain *d,
 	if (ret)
 		return ret;
 
+	/*
+	 * Now that the line is reserved, point its mux at this bank. Doing it
+	 * here rather than from .activate() keeps the access out of the raw
+	 * atomic section the irq core runs .activate() in; nothing needs it to
+	 * be reprogrammed at startup time, and the resume path rewrites it on
+	 * its own.
+	 */
+	if (pctl->hwlock) {
+		ret = hwspin_lock_timeout_in_atomic(pctl->hwlock,
+						    HWSPNLCK_TIMEOUT);
+		if (ret) {
+			dev_err(pctl->dev, "Can't get hwspinlock\n");
+			goto err_free_mux;
+		}
+	}
+
+	regmap_field_write(pctl->irqmux[hwirq], bank->bank_ioport_nr);
+
+	if (pctl->hwlock)
+		hwspin_unlock_in_atomic(pctl->hwlock);
+
 	parent_fwspec.fwnode = d->parent->fwnode;
 	parent_fwspec.param_count = 2;
 	parent_fwspec.param[0] = fwspec->param[0];
@@ -662,6 +659,13 @@ static int stm32_gpio_domain_alloc(struct irq_domain *d,
 				      bank);
 
 	return irq_domain_alloc_irqs_parent(d, virq, nr_irqs, &parent_fwspec);
+
+err_free_mux:
+	spin_lock_irqsave(&pctl->irqmux_lock, flags);
+	pctl->irqmux_map &= ~BIT(hwirq);
+	spin_unlock_irqrestore(&pctl->irqmux_lock, flags);
+
+	return ret;
 }
 
 static void stm32_gpio_domain_free(struct irq_domain *d, unsigned int virq,
@@ -683,7 +687,6 @@ static const struct irq_domain_ops stm32_gpio_domain_ops = {
 	.translate	= stm32_gpio_domain_translate,
 	.alloc		= stm32_gpio_domain_alloc,
 	.free		= stm32_gpio_domain_free,
-	.activate	= stm32_gpio_domain_activate,
 };
 
 /* Pinctrl functions */
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3] pinctrl: stm32: program the EXTI mux from .alloc instead of .activate
  2026-08-03  6:17 [PATCH] pinctrl: stm32: use a raw spinlock regmap to program the EXTI mux Ju Nan
  2026-08-04  3:22 ` [PATCH v2] pinctrl: stm32: program the EXTI mux from .alloc instead of .activate Ju Nan
@ 2026-08-04 14:14 ` Ju Nan
  2026-08-27 15:10   ` Sebastian Andrzej Siewior
  2026-08-28 11:17   ` Antonio Borneo
  1 sibling, 2 replies; 5+ messages in thread
From: Ju Nan @ 2026-08-04 14:14 UTC (permalink / raw)
  To: junan76, antonio.borneo, linusw
  Cc: alexandre.torgue, arnd, bigeasy, clrkwllms, lee, linux-arm-kernel,
	linux-gpio, linux-kernel, linux-rt-devel, linux-stm32,
	mcoquelin.stm32, mfd, rostedt, Uwe Kleine-König

stm32_gpio_domain_activate() writes the EXTI interrupt multiplexer
through a regmap obtained from the generic syscon driver. The irq core
calls .activate from __setup_irq() with the raw desc->lock held, so this
happens in a raw atomic section. regmap-mmio sets fast_io, and syscon
does not ask for a raw spinlock, so the regmap is protected by a
spinlock_t. On PREEMPT_RT that is a sleeping lock, which must not be
taken there. lockdep reports it as soon as a GPIO interrupt is
requested:

  BUG: Invalid wait context
  6.17.0 #4 Not tainted
  -----------------------------
  kworker/u8:0/12 is trying to lock:
  (&syscon_config)->lock){....}-{3:3}, at: regmap_lock_spinlock
  other info that might help us debug this:
  6 locks held by kworker/u8:0/12:
   #5: (&irq_desc_lock_class){-...}-{2:2}, at: __setup_irq
  stack backtrace:
   regmap_lock_spinlock
   regmap_field_update_bits_base
   stm32_gpio_domain_activate
   irq_domain_activate_irq
   __setup_irq
   request_threaded_irq

The driver was already aware of running in atomic context there: it uses
the _in_atomic() hwspinlock primitives around the very same access. The
syscon lock is the one lock in that section it does not control.

Program the mux from .alloc instead, which runs in a sleepable context.
That callback already owns this resource: it reserves the mux line in
pctl->irqmux_map under irqmux_lock, so writing the value it just claimed
is a natural fit, and the value only depends on the bank.

Nothing requires the mux to be reprogrammed at interrupt startup time:
the domain has no .deactivate, .free() only releases the irqmux_map bit
without touching the registers, and the resume path reprograms the mux
itself in stm32_pinctrl_restore_gpio_regs().

Keep the access inside the existing irqmux_lock section rather than
after it. hwspin_lock_timeout_in_atomic() deliberately skips the local
lock that the other hwspinlock modes take, so its caller is responsible
for disabling preemption and for excluding other local contexts; every
other hwspinlock user in this driver relies on bank->lock for that.
irqmux_lock plays the same role here, and it also turns the mux
reservation and the mux write into a single critical section. Nesting
the regmap spinlock_t inside irqmux_lock, itself a spinlock_t, is what
the wait-context checker expects.

Note that this bounds the semaphore hold time on !PREEMPT_RT only. On
PREEMPT_RT spinlock_t disables neither interrupts nor preemption, so the
hardware semaphore can be held across a preemptible section and a
coprocessor contending for it may poll for an unbounded time. That is
not introduced here: bank->lock is a spinlock_t too, so every existing
hwspinlock user in this driver behaves the same way on PREEMPT_RT.
Removing that exposure requires the EXTI mux regmap to be raw-spinlock
based, which the syscon core does not currently offer, and is left for a
separate change.

While moving the code, release the mux reservation when the hwspinlock
cannot be taken, which the .activate callback had no way of doing.

Reported-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
Closes: https://lore.kernel.org/all/20220202174430.pf37tt6lua2op3gc@pengutronix.de/
Signed-off-by: Ju Nan <junan76@163.com>
---
Changes in v3:

- Keep the mux write inside the existing irqmux_lock critical section
rather than after it.

v2: https://lore.kernel.org/all/20260804032227.35017-3-junan76@163.com/
v1: https://lore.kernel.org/all/20260803061718.43210-1-junan76@163.com/
---
 drivers/pinctrl/stm32/pinctrl-stm32.c | 55 +++++++++++++--------------
 1 file changed, 26 insertions(+), 29 deletions(-)

diff --git a/drivers/pinctrl/stm32/pinctrl-stm32.c b/drivers/pinctrl/stm32/pinctrl-stm32.c
index 6a99708a5a23..d97057ec28af 100644
--- a/drivers/pinctrl/stm32/pinctrl-stm32.c
+++ b/drivers/pinctrl/stm32/pinctrl-stm32.c
@@ -600,30 +600,6 @@ static int stm32_gpio_domain_translate(struct irq_domain *d,
 	return 0;
 }
 
-static int stm32_gpio_domain_activate(struct irq_domain *d,
-				      struct irq_data *irq_data, bool reserve)
-{
-	struct stm32_gpio_bank *bank = d->host_data;
-	struct stm32_pinctrl *pctl = dev_get_drvdata(bank->gpio_chip.parent);
-	int ret = 0;
-
-	if (pctl->hwlock) {
-		ret = hwspin_lock_timeout_in_atomic(pctl->hwlock,
-						    HWSPNLCK_TIMEOUT);
-		if (ret) {
-			dev_err(pctl->dev, "Can't get hwspinlock\n");
-			return ret;
-		}
-	}
-
-	regmap_field_write(pctl->irqmux[irq_data->hwirq], bank->bank_ioport_nr);
-
-	if (pctl->hwlock)
-		hwspin_unlock_in_atomic(pctl->hwlock);
-
-	return ret;
-}
-
 static int stm32_gpio_domain_alloc(struct irq_domain *d,
 				   unsigned int virq,
 				   unsigned int nr_irqs, void *data)
@@ -637,18 +613,40 @@ static int stm32_gpio_domain_alloc(struct irq_domain *d,
 	int ret = 0;
 
 	/*
-	 * Check first that the IRQ MUX of that line is free.
-	 * gpio irq mux is shared between several banks, protect with a lock
+	 * Check first that the IRQ MUX of that line is free, then point it at
+	 * this bank. The mux is shared between several banks, and the register
+	 * is shared with a coprocessor, so hold both irqmux_lock and the
+	 * hwspinlock across the update. irqmux_lock also provides the local
+	 * exclusion and the preemption disabling that the _in_atomic()
+	 * hwspinlock primitives leave to their caller, as it does for the other
+	 * hwspinlock users in this driver.
 	 */
 	spin_lock_irqsave(&pctl->irqmux_lock, flags);
 
 	if (pctl->irqmux_map & BIT(hwirq)) {
 		dev_err(pctl->dev, "irq line %ld already requested.\n", hwirq);
 		ret = -EBUSY;
-	} else {
-		pctl->irqmux_map |= BIT(hwirq);
+		goto unlock;
+	}
+
+	pctl->irqmux_map |= BIT(hwirq);
+
+	if (pctl->hwlock) {
+		ret = hwspin_lock_timeout_in_atomic(pctl->hwlock,
+						    HWSPNLCK_TIMEOUT);
+		if (ret) {
+			dev_err(pctl->dev, "Can't get hwspinlock\n");
+			pctl->irqmux_map &= ~BIT(hwirq);
+			goto unlock;
+		}
 	}
 
+	regmap_field_write(pctl->irqmux[hwirq], bank->bank_ioport_nr);
+
+	if (pctl->hwlock)
+		hwspin_unlock_in_atomic(pctl->hwlock);
+
+unlock:
 	spin_unlock_irqrestore(&pctl->irqmux_lock, flags);
 	if (ret)
 		return ret;
@@ -683,7 +681,6 @@ static const struct irq_domain_ops stm32_gpio_domain_ops = {
 	.translate	= stm32_gpio_domain_translate,
 	.alloc		= stm32_gpio_domain_alloc,
 	.free		= stm32_gpio_domain_free,
-	.activate	= stm32_gpio_domain_activate,
 };
 
 /* Pinctrl functions */
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3] pinctrl: stm32: program the EXTI mux from .alloc instead of .activate
  2026-08-04 14:14 ` [PATCH v3] " Ju Nan
@ 2026-08-27 15:10   ` Sebastian Andrzej Siewior
  2026-08-28 11:17   ` Antonio Borneo
  1 sibling, 0 replies; 5+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-08-27 15:10 UTC (permalink / raw)
  To: Ju Nan
  Cc: antonio.borneo, linusw, alexandre.torgue, arnd, clrkwllms, lee,
	linux-arm-kernel, linux-gpio, linux-kernel, linux-rt-devel,
	linux-stm32, mcoquelin.stm32, mfd, rostedt, Uwe Kleine-König

On 2026-08-04 22:14:03 [+0800], Ju Nan wrote:
…
> Nothing requires the mux to be reprogrammed at interrupt startup time:
> the domain has no .deactivate, .free() only releases the irqmux_map bit
> without touching the registers, and the resume path reprograms the mux
> itself in stm32_pinctrl_restore_gpio_regs().

The deactivate was last removed in commit db0f032512443 ("pinctrl:
stm32: check for IRQ MUX validity during alloc()")

…
> While moving the code, release the mux reservation when the hwspinlock
> cannot be taken, which the .activate callback had no way of doing.

It makes sense.
Acked-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>

> Reported-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> Closes: https://lore.kernel.org/all/20220202174430.pf37tt6lua2op3gc@pengutronix.de/
> Signed-off-by: Ju Nan <junan76@163.com>

Sebastian

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3] pinctrl: stm32: program the EXTI mux from .alloc instead of .activate
  2026-08-04 14:14 ` [PATCH v3] " Ju Nan
  2026-08-27 15:10   ` Sebastian Andrzej Siewior
@ 2026-08-28 11:17   ` Antonio Borneo
  1 sibling, 0 replies; 5+ messages in thread
From: Antonio Borneo @ 2026-08-28 11:17 UTC (permalink / raw)
  To: Ju Nan, linusw
  Cc: alexandre.torgue, arnd, bigeasy, clrkwllms, lee, linux-arm-kernel,
	linux-gpio, linux-kernel, linux-rt-devel, linux-stm32,
	mcoquelin.stm32, mfd, rostedt, Uwe Kleine-König

On Tue, 2026-08-04 at 22:14 +0800, Ju Nan wrote:
> stm32_gpio_domain_activate() writes the EXTI interrupt multiplexer
> through a regmap obtained from the generic syscon driver. The irq core
> calls .activate from __setup_irq() with the raw desc->lock held, so this
> happens in a raw atomic section. regmap-mmio sets fast_io, and syscon
> does not ask for a raw spinlock, so the regmap is protected by a
> spinlock_t. On PREEMPT_RT that is a sleeping lock, which must not be
> taken there. lockdep reports it as soon as a GPIO interrupt is
> requested:
> 
>   BUG: Invalid wait context
>   6.17.0 #4 Not tainted
>   -----------------------------
>   kworker/u8:0/12 is trying to lock:
>   (&syscon_config)->lock){....}-{3:3}, at: regmap_lock_spinlock
>   other info that might help us debug this:
>   6 locks held by kworker/u8:0/12:
>    #5: (&irq_desc_lock_class){-...}-{2:2}, at: __setup_irq
>   stack backtrace:
>    regmap_lock_spinlock
>    regmap_field_update_bits_base
>    stm32_gpio_domain_activate
>    irq_domain_activate_irq
>    __setup_irq
>    request_threaded_irq
> 
> The driver was already aware of running in atomic context there: it uses
> the _in_atomic() hwspinlock primitives around the very same access. The
> syscon lock is the one lock in that section it does not control.
> 
> Program the mux from .alloc instead, which runs in a sleepable context.
> That callback already owns this resource: it reserves the mux line in
> pctl->irqmux_map under irqmux_lock, so writing the value it just claimed
> is a natural fit, and the value only depends on the bank.
> 
> Nothing requires the mux to be reprogrammed at interrupt startup time:
> the domain has no .deactivate, .free() only releases the irqmux_map bit
> without touching the registers, and the resume path reprograms the mux
> itself in stm32_pinctrl_restore_gpio_regs().
> 
> Keep the access inside the existing irqmux_lock section rather than
> after it. hwspin_lock_timeout_in_atomic() deliberately skips the local
> lock that the other hwspinlock modes take, so its caller is responsible
> for disabling preemption and for excluding other local contexts; every
> other hwspinlock user in this driver relies on bank->lock for that.
> irqmux_lock plays the same role here, and it also turns the mux
> reservation and the mux write into a single critical section. Nesting
> the regmap spinlock_t inside irqmux_lock, itself a spinlock_t, is what
> the wait-context checker expects.
> 
> Note that this bounds the semaphore hold time on !PREEMPT_RT only. On
> PREEMPT_RT spinlock_t disables neither interrupts nor preemption, so the
> hardware semaphore can be held across a preemptible section and a
> coprocessor contending for it may poll for an unbounded time. That is
> not introduced here: bank->lock is a spinlock_t too, so every existing
> hwspinlock user in this driver behaves the same way on PREEMPT_RT.
> Removing that exposure requires the EXTI mux regmap to be raw-spinlock
> based, which the syscon core does not currently offer, and is left for a
> separate change.
> 
> While moving the code, release the mux reservation when the hwspinlock
> cannot be taken, which the .activate callback had no way of doing.
> 
> Reported-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
> Closes: https://lore.kernel.org/all/20220202174430.pf37tt6lua2op3gc@pengutronix.de/
> Signed-off-by: Ju Nan <junan76@163.com>
> ---
> Changes in v3:
> 
> - Keep the mux write inside the existing irqmux_lock critical section
> rather than after it.
> 
> v2: https://lore.kernel.org/all/20260804032227.35017-3-junan76@163.com/
> v1: https://lore.kernel.org/all/20260803061718.43210-1-junan76@163.com/
> ---
>  drivers/pinctrl/stm32/pinctrl-stm32.c | 55 +++++++++++++--------------
>  1 file changed, 26 insertions(+), 29 deletions(-)
> 
> diff --git a/drivers/pinctrl/stm32/pinctrl-stm32.c b/drivers/pinctrl/stm32/pinctrl-stm32.c
> index 6a99708a5a23..d97057ec28af 100644
> --- a/drivers/pinctrl/stm32/pinctrl-stm32.c
> +++ b/drivers/pinctrl/stm32/pinctrl-stm32.c
> @@ -600,30 +600,6 @@ static int stm32_gpio_domain_translate(struct irq_domain *d,
>         return 0;
>  }
>  
> -static int stm32_gpio_domain_activate(struct irq_domain *d,
> -                                     struct irq_data *irq_data, bool reserve)
> -{
> -       struct stm32_gpio_bank *bank = d->host_data;
> -       struct stm32_pinctrl *pctl = dev_get_drvdata(bank->gpio_chip.parent);
> -       int ret = 0;
> -
> -       if (pctl->hwlock) {
> -               ret = hwspin_lock_timeout_in_atomic(pctl->hwlock,
> -                                                   HWSPNLCK_TIMEOUT);
> -               if (ret) {
> -                       dev_err(pctl->dev, "Can't get hwspinlock\n");
> -                       return ret;
> -               }
> -       }
> -
> -       regmap_field_write(pctl->irqmux[irq_data->hwirq], bank->bank_ioport_nr);
> -
> -       if (pctl->hwlock)
> -               hwspin_unlock_in_atomic(pctl->hwlock);
> -
> -       return ret;
> -}
> -
>  static int stm32_gpio_domain_alloc(struct irq_domain *d,
>                                    unsigned int virq,
>                                    unsigned int nr_irqs, void *data)
> @@ -637,18 +613,40 @@ static int stm32_gpio_domain_alloc(struct irq_domain *d,
>         int ret = 0;
>  
>         /*
> -        * Check first that the IRQ MUX of that line is free.
> -        * gpio irq mux is shared between several banks, protect with a lock
> +        * Check first that the IRQ MUX of that line is free, then point it at
> +        * this bank. The mux is shared between several banks, and the register
> +        * is shared with a coprocessor, so hold both irqmux_lock and the
> +        * hwspinlock across the update. irqmux_lock also provides the local
> +        * exclusion and the preemption disabling that the _in_atomic()
> +        * hwspinlock primitives leave to their caller, as it does for the other
> +        * hwspinlock users in this driver.
>          */
>         spin_lock_irqsave(&pctl->irqmux_lock, flags);
>  
>         if (pctl->irqmux_map & BIT(hwirq)) {
>                 dev_err(pctl->dev, "irq line %ld already requested.\n", hwirq);
>                 ret = -EBUSY;
> -       } else {
> -               pctl->irqmux_map |= BIT(hwirq);
> +               goto unlock;
> +       }
> +
> +       pctl->irqmux_map |= BIT(hwirq);
> +
> +       if (pctl->hwlock) {
> +               ret = hwspin_lock_timeout_in_atomic(pctl->hwlock,
> +                                                   HWSPNLCK_TIMEOUT);

Your other patch
https://lore.kernel.org/lkml/20260827141127.25789-2-junan76@163.com/
that renames s/HWSPNLCK_TIMEOUT/HWSPNLCK_TIMEOUT_MS/ depends on this code move to
.alloc(), but the dependency is not explicitly reported.
It's probably better to keep the two patches in a series so the dependency is clear.

> +               if (ret) {
> +                       dev_err(pctl->dev, "Can't get hwspinlock\n");
> +                       pctl->irqmux_map &= ~BIT(hwirq);
> +                       goto unlock;
> +               }
>         }
>  
> +       regmap_field_write(pctl->irqmux[hwirq], bank->bank_ioport_nr);

I think there is an issue here, which is probably the initial reason for having the
regmap write in the separate .activate().

You can have two GPIO from separate banks but on the same bank's line (let's say PA5
and PB5, on different bank A vs B but on same line 5) and both require to be IRQ.
Clearly only one GPIO will "win".
The MUX does not have a busy state to know it's already taken. The .alloc() here
relies on the common IRQ parent behind the MUX for that line to detect that the
line (5 in this example) is already taken.
In fact, a few lines below, not show in this diff, .alloc() terminates with:
  return irq_domain_alloc_irqs_parent(d, virq, nr_irqs, &parent_fwspec);

The first GPIO that enters here (e.g. PA5) will set the MUX with regmap, then allocate
the parent's IRQ and return ok.
The second GPIO (e.g. PB5) will also set the MUX (breaking the previous settings for
PA5), try to allocate the same parent's IRQ and fail.
Result, the first GPIO setting is now broken!

While moving the .activate() code in .alloc(), please take care of this issue.

Regards,
Antonio

> +
> +       if (pctl->hwlock)
> +               hwspin_unlock_in_atomic(pctl->hwlock);
> +
> +unlock:
>         spin_unlock_irqrestore(&pctl->irqmux_lock, flags);
>         if (ret)
>                 return ret;
> @@ -683,7 +681,6 @@ static const struct irq_domain_ops stm32_gpio_domain_ops = {
>         .translate      = stm32_gpio_domain_translate,
>         .alloc          = stm32_gpio_domain_alloc,
>         .free           = stm32_gpio_domain_free,
> -       .activate       = stm32_gpio_domain_activate,
>  };
>  
>  /* Pinctrl functions */


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-08-28 11:17 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03  6:17 [PATCH] pinctrl: stm32: use a raw spinlock regmap to program the EXTI mux Ju Nan
2026-08-04  3:22 ` [PATCH v2] pinctrl: stm32: program the EXTI mux from .alloc instead of .activate Ju Nan
2026-08-04 14:14 ` [PATCH v3] " Ju Nan
2026-08-27 15:10   ` Sebastian Andrzej Siewior
2026-08-28 11:17   ` Antonio Borneo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).