From: Kent Gibson <warthog618@gmail.com>
To: Adrian Ambrozewicz <aambrozewicz@nvidia.com>
Cc: Bartosz Golaszewski <brgl@kernel.org>,
Linus Walleij <linusw@kernel.org>,
Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>,
"Paul E. McKenney" <paulmck@kernel.org>,
Herve Codina <herve.codina@bootlin.com>,
Jia Jia <physicalmtea@gmail.com>,
Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
linux-gpio@vger.kernel.org, linux-kernel@vger.kernel.org,
stable@vger.kernel.org
Subject: Re: [PATCH v2 6/6] gpiolib: let the character device free its IRQs first on chip removal
Date: Fri, 16 Oct 2026 21:40:03 +0800 [thread overview]
Message-ID: <20261016134003.GA155960@rigel> (raw)
In-Reply-To: <20261016-gpiolib-chip-removal-v2-6-aambrozewicz@nvidia.com>
On Fri, Oct 16, 2026 at 12:23:38PM +0300, Adrian Ambrozewicz wrote:
> gpiochip_remove() force-frees every IRQ still requested on the chip's
> lines with gpiochip_free_remaining_irqs() before anything else, then
> clears the chip pointer, removes the irqchip, disposing of the IRQ
> mappings, and only at the very end, from gpiolib_cdev_unregister(),
> calls the device notifier on which line requests and v1 line events
> stop edge detection and free their own IRQs. That leaves two windows in
> which the character device frees an IRQ it no longer owns:
>
> - The notifier runs after the mappings are gone. If an IRQ number freed
> by gpiochip_irqchip_remove() is allocated to another device in the
> meantime, irq_has_action() finds that device's action and free_irq()
> warns "Trying to free already-free IRQ". IRQs requested through the
> character device after gpiochip_free_remaining_irqs() but before the
> chip pointer is cleared are still installed when their mappings are
> disposed of.
>
> - Releasing a request concurrently with gpiochip_remove() races
> gpiod_free_irqs(), which can free the action between the release
> path's irq_has_action() check and its free_irq(), and then
> gpiochip_irqchip_remove(), which can dispose of the descriptor that
> free_irq() is still using.
>
> Call the device notifier from gpiochip_remove() right after the chip
> pointer is cleared and the SRCU grace period has elapsed, when no ioctl
> can request new IRQs any more, and only free the remaining IRQs after
> it. Have the release paths free their IRQs before unregistering from the
> notifier, under the lock that the notifier callback also takes, so that
> the notifier does not return while a release is still freeing an IRQ.
> For v1 line events that needs a new mutex; line requests use their
> config_mutex, which now has to be initialized before the first error
> path. As the chip pointer is NULL by the time gpiod_free_irqs() runs,
> have it call gc->to_irq() directly rather than gpiod_to_irq().
>
> Every IRQ requested through the character device is thus freed exactly
> once, by the notifier or by the release path, and before
> gpiod_free_irqs() and the removal of the irqchip.
>
> Moving the character device's part of the removal earlier was proposed
> by Herve Codina before gpiochip_free_remaining_irqs() existed; with that
> in place, only the order of the two is left to fix.
>
> Reproduced with a test-only GPIO chip that uses the gpiolib hierarchical
> irqchip, unbound from its driver with lines requested through the
> character device. For the first window, the chip hands the IRQ number of
> line 0 to a dummy handler while gpiochip_irqchip_remove() disposes of
> the mappings, and the requests are closed after the unbind. For the
> second, it sleeps in its irq_bus_lock() callback once removal has
> started, and the request is closed while gpiochip_remove() is in
> free_irq(). Each of 30 cycles warns "Trying to free already-free IRQ" in
> both cases, for v2 line requests and v1 line events alike; with this
> change none does.
>
> Link: https://lore.kernel.org/all/20240227113426.253232-1-herve.codina@bootlin.com/
> Fixes: ec8b6f55b981 ("gpio: free irqs that are still requested when the chip is being removed")
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Adrian Ambrozewicz <aambrozewicz@nvidia.com>
> ---
> Changes in v2:
> - mutex_destroy() for config_mutex and irq_lock (Andy).
> - The missing ->to_irq() case folded into the irq <= 0 check (Andy).
> - Assisted-by tag.
>
> drivers/gpio/gpiolib-cdev.c | 36 ++++++++++++++++++++++++++++--------
> drivers/gpio/gpiolib.c | 29 ++++++++++++++++++++++++-----
> 2 files changed, 52 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/gpio/gpiolib-cdev.c b/drivers/gpio/gpiolib-cdev.c
> index 788a36475e5d..b4b3c9ac54c2 100644
> --- a/drivers/gpio/gpiolib-cdev.c
> +++ b/drivers/gpio/gpiolib-cdev.c
> @@ -1588,19 +1588,29 @@ static void linereq_free(struct linereq *lr)
> {
> unsigned int i;
>
> + /*
> + * Stop edge detection while still registered for the notification, so
> + * that gpiochip_remove() does not get past it until the IRQs are freed.
> + */
> + scoped_guard(mutex, &lr->config_mutex) {
> + for (i = 0; i < lr->num_lines; i++) {
> + if (lr->lines[i].desc)
> + edge_detector_stop(&lr->lines[i]);
> + }
> + }
> +
> if (lr->device_unregistered_nb.notifier_call)
> blocking_notifier_chain_unregister(&lr->gdev->device_notifier,
> &lr->device_unregistered_nb);
>
> for (i = 0; i < lr->num_lines; i++) {
> - if (lr->lines[i].desc) {
> - edge_detector_stop(&lr->lines[i]);
> + if (lr->lines[i].desc)
> gpiod_free(lr->lines[i].desc);
> - }
> }
> kfifo_free(&lr->events);
> kfree(lr->label);
> gpio_device_put(lr->gdev);
> + mutex_destroy(&lr->config_mutex);
> kvfree(lr);
> }
>
> @@ -1671,6 +1681,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
> if (!lr)
> return -ENOMEM;
> lr->num_lines = ulr.num_lines;
> + mutex_init(&lr->config_mutex);
>
> lr->gdev = gpio_device_get(gdev);
>
> @@ -1688,7 +1699,6 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
> return -ENOMEM;
> }
>
> - mutex_init(&lr->config_mutex);
> init_waitqueue_head(&lr->wait);
> INIT_KFIFO(lr->events);
> lr->event_buffer_size = ulr.event_buffer_size;
> @@ -1788,6 +1798,7 @@ static int linereq_create(struct gpio_device *gdev, void __user *ip)
> * @eflags: the event flags this line was requested with
> * @irq: the interrupt that trigger in response to events on this GPIO
> * @irq_label: the name @irq was requested with
> + * @irq_lock: serializes freeing @irq on release and on chip removal
> * @wait: wait queue that handles blocking reads of events
> * @device_unregistered_nb: notifier block for receiving gdev unregister events
> * @events: KFIFO for the GPIO events
> @@ -1802,6 +1813,8 @@ struct lineevent_state {
> u32 eflags;
> int irq;
> char *irq_label;
> + /* release vs. chip removal notifier */
> + struct mutex irq_lock;
> wait_queue_head_t wait;
> struct notifier_block device_unregistered_nb;
> DECLARE_KFIFO(events, struct gpioevent_data, 16);
> @@ -1837,8 +1850,10 @@ static int lineevent_unregistered_notify(struct notifier_block *nb,
> struct lineevent_state *le = container_of(nb, struct lineevent_state,
> device_unregistered_nb);
>
> - free_line_irq(le->irq, le, &le->irq_label);
> - le->irq = 0;
> + scoped_guard(mutex, &le->irq_lock) {
> + free_line_irq(le->irq, le, &le->irq_label);
> + le->irq = 0;
> + }
>
> wake_up_poll(&le->wait, EPOLLIN | EPOLLERR);
>
> @@ -1918,14 +1933,19 @@ static ssize_t lineevent_read(struct file *file, char __user *buf,
>
> static void lineevent_free(struct lineevent_state *le)
> {
> + /* As in linereq_free(), free the IRQ before unregistering. */
First part of the comment can rapidly become stale, and the last part
doesn't add anything the code doesn't already say.
> + scoped_guard(mutex, &le->irq_lock) {
> + free_line_irq(le->irq, le, &le->irq_label);
> + le->irq = 0;
> + }
> if (le->device_unregistered_nb.notifier_call)
> blocking_notifier_chain_unregister(&le->gdev->device_notifier,
> &le->device_unregistered_nb);
> - free_line_irq(le->irq, le, &le->irq_label);
> if (le->desc)
> gpiod_free(le->desc);
> kfree(le->label);
> gpio_device_put(le->gdev);
> + mutex_destroy(&le->irq_lock);
> kfree(le);
> }
>
> @@ -2097,6 +2117,7 @@ static int lineevent_create(struct gpio_device *gdev, void __user *ip)
> le = kzalloc_obj(*le);
> if (!le)
> return -ENOMEM;
> + mutex_init(&le->irq_lock);
> le->gdev = gpio_device_get(gdev);
>
> if (eventreq.consumer_label[0] != '\0') {
> @@ -2832,5 +2853,4 @@ void gpiolib_cdev_unregister(struct gpio_device *gdev)
> {
> destroy_workqueue(gdev->line_state_wq);
> cdev_device_del(&gdev->chrdev, &gdev->dev);
> - blocking_notifier_call_chain(&gdev->device_notifier, 0, NULL);
> }
> diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
> index 31ccdd373765..7dd730228b05 100644
> --- a/drivers/gpio/gpiolib.c
> +++ b/drivers/gpio/gpiolib.c
> @@ -851,11 +851,23 @@ bool gpiochip_line_is_valid(const struct gpio_chip *gc,
> }
> EXPORT_SYMBOL_GPL(gpiochip_line_is_valid);
>
> -static void gpiod_free_irqs(struct gpio_desc *desc)
> +static void gpiod_free_irqs(struct gpio_chip *gc, struct gpio_desc *desc)
> {
> - int irq = gpiod_to_irq(desc);
> - struct irq_desc *irqd = irq_to_desc(irq);
> + struct irq_desc *irqd;
> void *cookie;
> + int irq;
> +
> + /* Called with gdev->chip already NULL, so gpiod_to_irq() can't be used. */
> + if (gc->to_irq)
> + irq = gc->to_irq(gc, gpiod_hwgpio(desc));
> + else
> + irq = 0;
> + if (irq <= 0)
> + return;
Perhaps I'm channelling Andy here, but how about:
if (!gc->to_irq)
return;
irq = gc->to_irq(gc, gpiod_hwgpio(desc));
if (irq <= 0)
return;
> +
> + irqd = irq_to_desc(irq);
> + if (!irqd)
> + return;
>
> for (;;) {
> /*
> @@ -887,7 +899,7 @@ static void gpiochip_free_remaining_irqs(struct gpio_chip *gc)
> struct gpio_desc *desc;
>
> for_each_gpio_desc_with_flag(gc, desc, GPIOD_FLAG_USED_AS_IRQ)
> - gpiod_free_irqs(desc);
> + gpiod_free_irqs(gc, desc);
> }
>
> static void gpiodev_release(struct device *dev)
> @@ -1422,7 +1434,6 @@ void gpiochip_remove(struct gpio_chip *gc)
> /* FIXME: should the legacy sysfs handling be moved to gpio_device? */
> gpiochip_sysfs_unregister(gc);
> gpiochip_free_hogs(gc);
> - gpiochip_free_remaining_irqs(gc);
>
> scoped_guard(mutex, &gpio_devices_lock)
> list_del_rcu(&gdev->list);
> @@ -1431,6 +1442,14 @@ void gpiochip_remove(struct gpio_chip *gc)
> /* Numb the device, cancelling all outstanding operations */
> rcu_assign_pointer(gdev->chip, NULL);
> synchronize_srcu(&gdev->srcu);
> +
> + /*
> + * No new interrupts can be requested through the character device now.
> + * Have it free the ones it holds before forcibly freeing the rest, and
> + * both before the irqchip and its mappings go away.
> + */
> + blocking_notifier_call_chain(&gdev->device_notifier, 0, NULL);
> + gpiochip_free_remaining_irqs(gc);
> gpio_device_teardown_shared(gdev);
> gpiochip_irqchip_remove(gc);
> acpi_gpiochip_remove(gc);
> --
> 2.43.0
>
That's all I've got from a first pass of the whole series - looks great.
Cheers,
Kent.
prev parent reply other threads:[~2026-10-16 13:40 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-16 9:23 [PATCH v2 0/6] gpiolib: fix chip removal with lines still requested from user space Adrian Ambrozewicz
2026-10-16 9:23 ` [PATCH v2 1/6] gpiolib: wait for pending label frees before cleaning up desc_srcu Adrian Ambrozewicz
2026-10-16 9:23 ` [PATCH v2 2/6] gpiolib: free the label of lines released after their chip was removed Adrian Ambrozewicz
2026-10-16 13:38 ` Andy Shevchenko
2026-10-16 9:23 ` [PATCH v2 3/6] gpiolib: cdev: do not free line IRQs again after the chip is removed Adrian Ambrozewicz
2026-10-16 9:23 ` [PATCH v2 4/6] gpiolib: cdev: don't sleep in the SRCU read section in read() Adrian Ambrozewicz
2026-10-16 9:23 ` [PATCH v2 5/6] gpiolib: cdev: don't leak the IRQ name when the chip is removed Adrian Ambrozewicz
2026-10-16 13:41 ` Andy Shevchenko
2026-10-16 9:23 ` [PATCH v2 6/6] gpiolib: let the character device free its IRQs first on chip removal Adrian Ambrozewicz
2026-10-16 9:45 ` sashiko-bot
2026-10-16 10:01 ` Adrian Ambrozewicz
2026-10-16 13:40 ` Kent Gibson [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261016134003.GA155960@rigel \
--to=warthog618@gmail.com \
--cc=aambrozewicz@nvidia.com \
--cc=andriy.shevchenko@linux.intel.com \
--cc=bartosz.golaszewski@oss.qualcomm.com \
--cc=brgl@kernel.org \
--cc=herve.codina@bootlin.com \
--cc=linusw@kernel.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=paulmck@kernel.org \
--cc=physicalmtea@gmail.com \
--cc=stable@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.