[PATCH] gpio: pca953x: Handle direction register read errors in IRQ paths

From: Sahaj Chaudhari

Date: Sun Oct 18 2026 - 12:49:43 EST


Three IRQ paths ignore the result of reading the direction registers
and then use the destination bitmap. pca953x_read_regs() does not populate
that bitmap when regmap_bulk_read() fails, leaving the callers to use
uninitialized stack data.

This can select incorrect GPIOs for direction changes during IRQ bus
synchronization, corrupt the saved input state during IRQ setup, or
produce incorrect pending interrupt decisions.

Check the read result at all three sites. Release irq_lock without
changing GPIO directions on failure, leave the IRQ history untouched
when pending detection fails, and propagate the error from IRQ setup.
The read helper already logs the error.

Fixes: 0f25fda840a9 ("gpio: pca953x: Zap ad-hoc reg_direction cache")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Sahaj Chaudhari <sahaj123.sc@xxxxxxxxx>
---
Testing:
- Built an ARM64 kernel with CONFIG_GPIO_PCA953X_IRQ=y,
CONFIG_PM_SLEEP=y and CONFIG_SUSPEND=y, containing this fix alone.
- Booted it on AM642 and confirmed both expected expanders bound to
pca953x, with an untainted kernel.
- Ran the isolated exact-function harness on an AM642 CPU: 22 checks,
three failures before and none after. The same isolated checks passed
host ASan and UBSan. This harness simulates kernel and hardware APIs.

The hardware test covers boot and probe, not the IRQ error paths.
Neither expander has IRQs configured in this board's device tree, so
direction-register read failures were exercised only in the harness.

drivers/gpio/gpio-pca953x.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/drivers/gpio/gpio-pca953x.c b/drivers/gpio/gpio-pca953x.c
index aac9ea7520b6..b6f0849d3bdf 100644
--- a/drivers/gpio/gpio-pca953x.c
+++ b/drivers/gpio/gpio-pca953x.c
@@ -862,6 +862,7 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d)
DECLARE_BITMAP(irq_mask, MAX_LINE);
DECLARE_BITMAP(reg_direction, MAX_LINE);
int level;
+ int ret;

guard(mutex)(&chip->i2c_lock);

@@ -880,7 +881,9 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d)
}

/* Switch direction to input if needed */
- pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+ ret = pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+ if (ret)
+ goto out;

bitmap_or(irq_mask, chip->irq_trig_fall, chip->irq_trig_raise, gc->ngpio);
bitmap_or(irq_mask, irq_mask, chip->irq_trig_level_high, gc->ngpio);
@@ -890,6 +893,7 @@ static void pca953x_irq_bus_sync_unlock(struct irq_data *d)
for_each_andnot_bit(level, irq_mask, reg_direction, gc->ngpio)
pca953x_gpio_direction_input_unlocked(&chip->gpio_chip, level);

+out:
mutex_unlock(&chip->irq_lock);
}

@@ -984,7 +988,9 @@ static bool pca953x_irq_pending(struct pca953x_chip *chip, unsigned long *pendin
}

/* Remove output pins from the equation */
- pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+ ret = pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+ if (ret)
+ return false;

bitmap_copy(old_stat, chip->irq_stat, gc->ngpio);

@@ -1084,7 +1090,9 @@ static int pca953x_irq_setup(struct pca953x_chip *chip, int irq_base)
* interrupt. We have to rely on the previous read for
* this purpose.
*/
- pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+ ret = pca953x_read_regs(chip, chip->regs->direction, reg_direction);
+ if (ret)
+ return ret;
bitmap_and(chip->irq_stat, irq_stat, reg_direction, gc->ngpio);
mutex_init(&chip->irq_lock);


base-commit: ac46dac1613a7c971e28e16d3a7a6ad4b1ae6c5a
--
2.43.0