[PATCH v2 4/6] gpiolib: cdev: don't sleep in the SRCU read section in read()

From: Adrian Ambrozewicz

Date: Fri Oct 16 2026 - 05:25:11 EST


linereq_read(), lineevent_read() and lineinfo_watch_read() enter the
gdev->srcu read section to check that the chip is still there and stay
in it while they sleep waiting for an event. gpiochip_remove() clears
the chip pointer and then calls synchronize_srcu() on gdev->srcu, which
cannot return while a reader sleeps there. The wake-up meant for that
reader is sent by the device notifier, which runs later in
gpiochip_remove() and would not end the wait anyway, as the wait
condition only looks at the event FIFO. Unless an event arrives, a
process blocked in read() on a line request, a v1 line event or a chip
file with watched lines holds up the removal of the chip until it is
signalled:

task:init state:D stack:0 pid:1 tgid:1 ppid:0
Call Trace:
__synchronize_srcu
gpiochip_remove
release_nodes
devres_release_all
device_unbind_cleanup
device_release_driver_internal
bus_remove_device
device_del
platform_device_unregister
gpio_sim_device_deactivate
gpio_sim_device_config_live_store
configfs_write_iter
vfs_write

and since the device lock is held meanwhile, so does device_shutdown()
on power-off.

Check the chip pointer in a scoped SRCU section only. Nothing after the
check dereferences the chip, the request and its FIFO belong to the
file. Also end the wait once the chip is gone, which the notifiers'
wake-ups now see, and return -ENODEV if there is no event to report.

This dates back to the RW semaphore that the SRCU section replaced,
which these functions also held while sleeping and gpiochip_remove()
took for writing.

Reproduced with gpio-sim: one thread blocked in read() on a line
request while another sets the chip's configfs "live" attribute to 0.
The write does not return until the reader is killed; with this change
it returns at once and read() fails with -ENODEV.

Fixes: bdbbae241a04 ("gpiolib: protect the GPIO device against being dropped while in use by user-space")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM
Signed-off-by: Adrian Ambrozewicz <aambrozewicz@xxxxxxxxxx>
---
Changes in v2:
- The three chip checks factored into gpiodev_check_chip() (Andy).
- Assisted-by tag.

drivers/gpio/gpiolib-cdev.c | 50 ++++++++++++++++++++++++++-----------
1 file changed, 35 insertions(+), 15 deletions(-)

diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
index 3792ccee4da0..6f84aaa95033 100644
--- a/drivers/gpio/gpiolib-cdev.c
+++ b/drivers/gpio/gpiolib-cdev.c
@@ -1512,6 +1512,20 @@ static __poll_t linereq_poll(struct file *file,
return events;
}

+/*
+ * For the read() paths, which must not stay in the SRCU read section while
+ * sleeping: that would stall gpiochip_remove() until an event arrives.
+ */
+static int gpiodev_check_chip(struct gpio_device *gdev)
+{
+ guard(srcu)(&gdev->srcu);
+
+ if (!rcu_access_pointer(gdev->chip))
+ return -ENODEV;
+
+ return 0;
+}
+
static ssize_t linereq_read(struct file *file, char __user *buf,
size_t count, loff_t *f_ps)
{
@@ -1520,10 +1534,9 @@ static ssize_t linereq_read(struct file *file, char __user *buf,
ssize_t bytes_read = 0;
int ret;

- guard(srcu)(&lr->gdev->srcu);
-
- if (!rcu_access_pointer(lr->gdev->chip))
- return -ENODEV;
+ ret = gpiodev_check_chip(lr->gdev);
+ if (ret)
+ return ret;

if (count < sizeof(le))
return -EINVAL;
@@ -1538,9 +1551,12 @@ static ssize_t linereq_read(struct file *file, char __user *buf,
return -EAGAIN;

ret = wait_event_interruptible_locked(lr->wait,
- !kfifo_is_empty(&lr->events));
+ !kfifo_is_empty(&lr->events) ||
+ !rcu_access_pointer(lr->gdev->chip));
if (ret)
return ret;
+ if (kfifo_is_empty(&lr->events))
+ return -ENODEV;
}

if (kfifo_out(&lr->events, &le, 1) != 1) {
@@ -1835,10 +1851,9 @@ static ssize_t lineevent_read(struct file *file, char __user *buf,
ssize_t ge_size;
int ret;

- guard(srcu)(&le->gdev->srcu);
-
- if (!rcu_access_pointer(le->gdev->chip))
- return -ENODEV;
+ ret = gpiodev_check_chip(le->gdev);
+ if (ret)
+ return ret;

/*
* When compatible system call is being used the struct gpioevent_data,
@@ -1866,9 +1881,12 @@ static ssize_t lineevent_read(struct file *file, char __user *buf,
return -EAGAIN;

ret = wait_event_interruptible_locked(le->wait,
- !kfifo_is_empty(&le->events));
+ !kfifo_is_empty(&le->events) ||
+ !rcu_access_pointer(le->gdev->chip));
if (ret)
return ret;
+ if (kfifo_is_empty(&le->events))
+ return -ENODEV;
}

if (kfifo_out(&le->events, &ge, 1) != 1) {
@@ -2612,10 +2630,9 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
int ret;
size_t event_size;

- guard(srcu)(&cdev->gdev->srcu);
-
- if (!rcu_access_pointer(cdev->gdev->chip))
- return -ENODEV;
+ ret = gpiodev_check_chip(cdev->gdev);
+ if (ret)
+ return ret;

#ifndef CONFIG_GPIO_CDEV_V1
event_size = sizeof(struct gpio_v2_line_info_changed);
@@ -2633,9 +2650,12 @@ static ssize_t lineinfo_watch_read(struct file *file, char __user *buf,
return -EAGAIN;

ret = wait_event_interruptible_locked(cdev->wait,
- !kfifo_is_empty(&cdev->events));
+ !kfifo_is_empty(&cdev->events) ||
+ !rcu_access_pointer(cdev->gdev->chip));
if (ret)
return ret;
+ if (kfifo_is_empty(&cdev->events))
+ return -ENODEV;
}
#ifdef CONFIG_GPIO_CDEV_V1
/* must be after kfifo check so watch_abi_version is set */
--
2.43.0