All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] gpio: pch: use raw_spinlock_t for the register lock
@ 2026-07-23  1:41 Junjie Cao
  2026-07-23  2:02 ` sashiko-bot
  0 siblings, 1 reply; 2+ messages in thread
From: Junjie Cao @ 2026-07-23  1:41 UTC (permalink / raw)
  To: Bartosz Golaszewski, Linus Walleij, Andy Shevchenko
  Cc: Sebastian Andrzej Siewior, Clark Williams, Steven Rostedt,
	linux-gpio, linux-kernel, linux-rt-devel, stable

pch_irq_type() is registered as the irq_chip .irq_set_type callback and
takes chip->spinlock with spin_lock_irqsave().  This callback is reached
from __setup_irq() -> __irq_set_trigger() -> chip->irq_set_type() while
the caller holds desc->lock, a raw_spinlock_t, with hardirqs disabled.
That context is not sleepable, but on PREEMPT_RT a regular spinlock_t is
an rtmutex-backed sleeping lock, so acquiring it there is invalid.

This was confirmed on a PREEMPT_RT kernel with lockdep
(PROVE_RAW_LOCK_NESTING and DEBUG_ATOMIC_SLEEP).  A grounded PoC mirrored
pch_irq_type()'s locking and drove it through the real genirq carrier
irq_set_irq_type() -> __irq_set_trigger() -> chip->irq_set_type(), i.e.
the same __irq_set_trigger() edge that __setup_irq() takes for a
requested IRQ.  With the original spin_lock_irqsave() edge lockdep
reported an invalid wait context, immediately followed by:

  BUG: sleeping function called from invalid context at kernel/locking/spinlock_rt.c:48
  in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 95, name: insmod
  hardirqs last disabled at (3784): _raw_spin_lock_irqsave+0x4f/0x60
   rt_spin_lock+0x3a/0x1c0
   repro_irq_set_type+0x64/0xa0 [pch_repro]
   __irq_set_trigger+0x69/0x140
   irq_set_irq_type+0x78/0xd0

Switching the mirrored lock to raw_spinlock_t made both splats go away.

Convert the register lock to raw_spinlock_t.  The same lock also
serializes the GPIO direction/value callbacks and the suspend/resume
register save/restore, but all of those critical sections only perform
MMIO register accesses (ioread32()/iowrite32()) and
irq_set_handler_locked(); none of them contain sleepable operations.
Keeping this register lock non-sleeping is therefore appropriate for the
irqchip callbacks and does not change the GPIO-side locking contract.

This is the same class of issue and fix as recently addressed for other
GPIO controllers, e.g. commit 286533cb14a3 ("gpio: sch: use raw_spinlock_t
in the irq startup path") and commit 90f0109019e6 ("gpio: eic-sprd: use
raw_spinlock_t in the irq startup path").

Fixes: 38eb18a6f92d ("gpio-pch: Support interrupt function")
Cc: stable@vger.kernel.org
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
 drivers/gpio/gpio-pch.c | 28 ++++++++++++++--------------
 1 file changed, 14 insertions(+), 14 deletions(-)

diff --git a/drivers/gpio/gpio-pch.c b/drivers/gpio/gpio-pch.c
index 4ffa0955a9e3..07a5617b314b 100644
--- a/drivers/gpio/gpio-pch.c
+++ b/drivers/gpio/gpio-pch.c
@@ -96,7 +96,7 @@ struct pch_gpio {
 	struct pch_gpio_reg_data pch_gpio_reg;
 	int irq_base;
 	enum pch_type_t ioh;
-	spinlock_t spinlock;
+	raw_spinlock_t spinlock;
 };
 
 static int pch_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val)
@@ -105,7 +105,7 @@ static int pch_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val)
 	struct pch_gpio *chip =	gpiochip_get_data(gpio);
 	unsigned long flags;
 
-	spin_lock_irqsave(&chip->spinlock, flags);
+	raw_spin_lock_irqsave(&chip->spinlock, flags);
 	reg_val = ioread32(&chip->reg->po);
 	if (val)
 		reg_val |= BIT(nr);
@@ -113,7 +113,7 @@ static int pch_gpio_set(struct gpio_chip *gpio, unsigned int nr, int val)
 		reg_val &= ~BIT(nr);
 
 	iowrite32(reg_val, &chip->reg->po);
-	spin_unlock_irqrestore(&chip->spinlock, flags);
+	raw_spin_unlock_irqrestore(&chip->spinlock, flags);
 
 	return 0;
 }
@@ -133,7 +133,7 @@ static int pch_gpio_direction_output(struct gpio_chip *gpio, unsigned int nr,
 	u32 reg_val;
 	unsigned long flags;
 
-	spin_lock_irqsave(&chip->spinlock, flags);
+	raw_spin_lock_irqsave(&chip->spinlock, flags);
 
 	reg_val = ioread32(&chip->reg->po);
 	if (val)
@@ -147,7 +147,7 @@ static int pch_gpio_direction_output(struct gpio_chip *gpio, unsigned int nr,
 	pm |= BIT(nr);
 	iowrite32(pm, &chip->reg->pm);
 
-	spin_unlock_irqrestore(&chip->spinlock, flags);
+	raw_spin_unlock_irqrestore(&chip->spinlock, flags);
 
 	return 0;
 }
@@ -158,12 +158,12 @@ static int pch_gpio_direction_input(struct gpio_chip *gpio, unsigned int nr)
 	u32 pm;
 	unsigned long flags;
 
-	spin_lock_irqsave(&chip->spinlock, flags);
+	raw_spin_lock_irqsave(&chip->spinlock, flags);
 	pm = ioread32(&chip->reg->pm);
 	pm &= BIT(gpio_pins[chip->ioh]) - 1;
 	pm &= ~BIT(nr);
 	iowrite32(pm, &chip->reg->pm);
-	spin_unlock_irqrestore(&chip->spinlock, flags);
+	raw_spin_unlock_irqrestore(&chip->spinlock, flags);
 
 	return 0;
 }
@@ -265,7 +265,7 @@ static int pch_irq_type(struct irq_data *d, unsigned int type)
 		return 0;
 	}
 
-	spin_lock_irqsave(&chip->spinlock, flags);
+	raw_spin_lock_irqsave(&chip->spinlock, flags);
 
 	/* Set interrupt mode */
 	im = ioread32(im_reg) & ~(PCH_IM_MASK << (im_pos * 4));
@@ -277,7 +277,7 @@ static int pch_irq_type(struct irq_data *d, unsigned int type)
 	else if (type & IRQ_TYPE_EDGE_BOTH)
 		irq_set_handler_locked(d, handle_edge_irq);
 
-	spin_unlock_irqrestore(&chip->spinlock, flags);
+	raw_spin_unlock_irqrestore(&chip->spinlock, flags);
 	return 0;
 }
 
@@ -374,7 +374,7 @@ static int pch_gpio_probe(struct pci_dev *pdev,
 	chip->ioh = id->driver_data;
 	chip->reg = chip->base;
 	pci_set_drvdata(pdev, chip);
-	spin_lock_init(&chip->spinlock);
+	raw_spin_lock_init(&chip->spinlock);
 	pch_gpio_setup(chip);
 
 	ret = devm_gpiochip_add_data(dev, &chip->gpio, chip);
@@ -407,9 +407,9 @@ static int pch_gpio_suspend(struct device *dev)
 	struct pch_gpio *chip = dev_get_drvdata(dev);
 	unsigned long flags;
 
-	spin_lock_irqsave(&chip->spinlock, flags);
+	raw_spin_lock_irqsave(&chip->spinlock, flags);
 	pch_gpio_save_reg_conf(chip);
-	spin_unlock_irqrestore(&chip->spinlock, flags);
+	raw_spin_unlock_irqrestore(&chip->spinlock, flags);
 
 	return 0;
 }
@@ -419,11 +419,11 @@ static int pch_gpio_resume(struct device *dev)
 	struct pch_gpio *chip = dev_get_drvdata(dev);
 	unsigned long flags;
 
-	spin_lock_irqsave(&chip->spinlock, flags);
+	raw_spin_lock_irqsave(&chip->spinlock, flags);
 	iowrite32(0x01, &chip->reg->reset);
 	iowrite32(0x00, &chip->reg->reset);
 	pch_gpio_restore_reg_conf(chip);
-	spin_unlock_irqrestore(&chip->spinlock, flags);
+	raw_spin_unlock_irqrestore(&chip->spinlock, flags);
 
 	return 0;
 }
-- 
2.43.0


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

* Re: [PATCH] gpio: pch: use raw_spinlock_t for the register lock
  2026-07-23  1:41 [PATCH] gpio: pch: use raw_spinlock_t for the register lock Junjie Cao
@ 2026-07-23  2:02 ` sashiko-bot
  0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-07-23  2:02 UTC (permalink / raw)
  To: Junjie Cao; +Cc: linux-rt-devel, linux-kernel

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [High] The GPIO chip is registered via devm_gpiochip_add_data() before its irq_base is initialized, exposing incorrect IRQ mappings to consumers.
- [High] The shared interrupt handler is requested before the generic irqchip is fully initialized, which could lead to uninitialized descriptor accesses.
- [High] pch_gpio_to_irq() returns valid-looking bogus IRQ numbers when IRQ allocation fails during probe.
--

--- Patch [1]: [PATCH] gpio: pch: use raw_spinlock_t for the register lock ---
commit 90a7cbda6861f749a9ed833233971bb8a60d95dc
Author: Junjie Cao <junjie.cao@intel.com>

gpio: pch: use raw_spinlock_t for the register lock

This patch converts the spinlock_t register lock in the PCH GPIO driver to a
raw_spinlock_t. This prevents invalid wait context and scheduling while atomic
issues on PREEMPT_RT kernels, as the lock is acquired within the non-sleepable
irq_set_type callback.

> diff --git a/drivers/gpio/gpio-pch.c b/drivers/gpio/gpio-pch.c
> index 4ffa0955a9e3d..07a5617b314b3 100644
> --- a/drivers/gpio/gpio-pch.c
> +++ b/drivers/gpio/gpio-pch.c
[ ... ]
> @@ -374,7 +374,7 @@ static int pch_gpio_probe(struct pci_dev *pdev,
>  	chip->ioh = id->driver_data;
>  	chip->reg = chip->base;
>  	pci_set_drvdata(pdev, chip);
> -	spin_lock_init(&chip->spinlock);
> +	raw_spin_lock_init(&chip->spinlock);
>  	pch_gpio_setup(chip);
>  
>  	ret = devm_gpiochip_add_data(dev, &chip->gpio, chip);

[Severity: High]
This isn't a bug introduced by this patch, but in
drivers/gpio/gpio-pch.c:pch_gpio_probe(), does registering the GPIO chip before
initializing its IRQ base expose incorrect IRQ mappings to consumers?

If a consumer acquires the GPIO and calls gpiod_to_irq() in the race window
after devm_gpiochip_add_data() but before devm_irq_alloc_descs() sets
chip->irq_base, pch_gpio_to_irq() will return 0 + offset since the chip
structure is initially zeroed out.

[Severity: High]
This is a pre-existing issue, but in drivers/gpio/gpio-pch.c:pch_gpio_probe(),
if devm_irq_alloc_descs() fails, probe succeeds but leaves chip->irq_base
as -1:

	irq_base = devm_irq_alloc_descs(dev, -1, 0,
					gpio_pins[chip->ioh], NUMA_NO_NODE);
	if (irq_base < 0) {
		dev_warn(dev, "PCH gpio: Failed to get IRQ base num\n");
		chip->irq_base = -1;
		return 0;
	}

When a consumer later calls gpiod_to_irq(), pch_gpio_to_irq() calculates
chip->irq_base + offset:

static int pch_gpio_to_irq(struct gpio_chip *gpio, unsigned int offset)
{
	struct pch_gpio *chip = gpiochip_get_data(gpio);

	return chip->irq_base + offset;
}

For offsets > 0, does this compute a non-negative value (e.g. 0, 1, 2),
breaking the error-reporting contract of to_irq and potentially causing
consumers to steal essential system IRQs?

[Severity: High]
This is also a pre-existing issue, but later in
drivers/gpio/gpio-pch.c:pch_gpio_probe(), the shared interrupt handler is
requested before the generic irqchip is allocated:

	ret = devm_request_irq(dev, pdev->irq, pch_gpio_handler,
			       IRQF_SHARED, KBUILD_MODNAME, chip);
	if (ret)
		return dev_err_probe(dev, ret, "Failed to request IRQ\n");

	return pch_gpio_alloc_generic_chip(chip, irq_base, gpio_pins[chip->ioh]);

If an interrupt fires immediately on the shared line, could pch_gpio_handler()
try to dispatch the interrupt using uninitialized descriptors, leading to a
handle_bad_irq warning and potentially disabling the shared interrupt line?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260723014129.1129730-1-junjie.cao@intel.com?part=1

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

end of thread, other threads:[~2026-07-23  2:02 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23  1:41 [PATCH] gpio: pch: use raw_spinlock_t for the register lock Junjie Cao
2026-07-23  2:02 ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.