gpio: free irqs that are still requested when the chip is being removed
authorBartosz Golaszewski <bartosz.golaszewski@linaro.org>
Thu, 19 Sep 2024 13:51:04 +0000 (15:51 +0200)
committerBartosz Golaszewski <bartosz.golaszewski@linaro.org>
Mon, 30 Sep 2024 08:10:45 +0000 (10:10 +0200)
If we remove a GPIO chip that is also an interrupt controller with users
not having freed some interrupts, we'll end up leaking resources as
indicated by the following warning:

  remove_proc_entry: removing non-empty directory 'irq/30', leaking at least 'gpio'

As there's no way of notifying interrupt users about the irqchip going
away and the interrupt subsystem is not plugged into the driver model and
so not all cases can be handled by devlinks, we need to make sure to free
all interrupts before the complete the removal of the provider.

Reviewed-by: Herve Codina <herve.codina@bootlin.com>
Tested-by: Herve Codina <herve.codina@bootlin.com>
Link: https://lore.kernel.org/r/20240919135104.3583-1-brgl@bgdev.pl
Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@linaro.org>
drivers/gpio/gpiolib.c

index c6afbf434366bc5f7a5a09aedeb2ccf201c1ce58..16c16414f7218e40f03912e98acba75c9b66dffd 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/idr.h>
 #include <linux/interrupt.h>
 #include <linux/irq.h>
+#include <linux/irqdesc.h>
 #include <linux/kernel.h>
 #include <linux/list.h>
 #include <linux/lockdep.h>
@@ -713,6 +714,45 @@ 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)
+{
+       int irq = gpiod_to_irq(desc);
+       struct irq_desc *irqd = irq_to_desc(irq);
+       void *cookie;
+
+       for (;;) {
+               /*
+                * Make sure the action doesn't go away while we're
+                * dereferencing it. Retrieve and store the cookie value.
+                * If the irq is freed after we release the lock, that's
+                * alright - the underlying maple tree lookup will return NULL
+                * and nothing will happen in free_irq().
+                */
+               scoped_guard(mutex, &irqd->request_mutex) {
+                       if (!irq_desc_has_action(irqd))
+                               return;
+
+                       cookie = irqd->action->dev_id;
+               }
+
+               free_irq(irq, cookie);
+       }
+}
+
+/*
+ * The chip is going away but there may be users who had requested interrupts
+ * on its GPIO lines who have no idea about its removal and have no way of
+ * being notified about it. We need to free any interrupts still in use here or
+ * we'll leak memory and resources (like procfs files).
+ */
+static void gpiochip_free_remaining_irqs(struct gpio_chip *gc)
+{
+       struct gpio_desc *desc;
+
+       for_each_gpio_desc_with_flag(gc, desc, FLAG_USED_AS_IRQ)
+               gpiod_free_irqs(desc);
+}
+
 static void gpiodev_release(struct device *dev)
 {
        struct gpio_device *gdev = to_gpio_device(dev);
@@ -1125,6 +1165,7 @@ void gpiochip_remove(struct gpio_chip *gc)
        /* FIXME: should the legacy sysfs handling be moved to gpio_device? */
        gpiochip_sysfs_unregister(gdev);
        gpiochip_free_hogs(gc);
+       gpiochip_free_remaining_irqs(gc);
 
        scoped_guard(mutex, &gpio_devices_lock)
                list_del_rcu(&gdev->list);