| From 483d821108791092798f5d230686868112927044 Mon Sep 17 00:00:00 2001 |
| From: Johan Hovold <johan@kernel.org> |
| Date: Tue, 21 Apr 2015 17:42:09 +0200 |
| Subject: gpio: sysfs: fix memory leaks and device hotplug |
| |
| From: Johan Hovold <johan@kernel.org> |
| |
| commit 483d821108791092798f5d230686868112927044 upstream. |
| |
| Unregister GPIOs requested through sysfs at chip remove to avoid leaking |
| the associated memory and sysfs entries. |
| |
| The stale sysfs entries prevented the gpio numbers from being exported |
| when the gpio range was later reused (e.g. at device reconnect). |
| |
| This also fixes the related module-reference leak. |
| |
| Note that kernfs makes sure that any on-going sysfs operations finish |
| before the class devices are unregistered and that further accesses |
| fail. |
| |
| The chip exported flag is used to prevent gpiod exports during removal. |
| This also makes it harder to trigger, but does not fix, the related race |
| between gpiochip_remove and export_store, which is really a race with |
| gpiod_request that needs to be addressed separately. |
| |
| Also note that this would prevent the crashes (e.g. NULL-dereferences) |
| at reconnect that affects pre-3.18 kernels, as well as use-after-free on |
| operations on open attribute files on pre-3.14 kernels (prior to |
| kernfs). |
| |
| Fixes: d8f388d8dc8d ("gpio: sysfs interface") |
| Signed-off-by: Johan Hovold <johan@kernel.org> |
| Signed-off-by: Linus Walleij <linus.walleij@linaro.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/gpio/gpiolib.c | 19 +++++++++++++++++++ |
| 1 file changed, 19 insertions(+) |
| |
| --- a/drivers/gpio/gpiolib.c |
| +++ b/drivers/gpio/gpiolib.c |
| @@ -752,6 +752,7 @@ static struct class gpio_class = { |
| */ |
| static int gpiod_export(struct gpio_desc *desc, bool direction_may_change) |
| { |
| + struct gpio_chip *chip; |
| unsigned long flags; |
| int status; |
| const char *ioname = NULL; |
| @@ -769,8 +770,16 @@ static int gpiod_export(struct gpio_desc |
| return -EINVAL; |
| } |
| |
| + chip = desc->chip; |
| + |
| mutex_lock(&sysfs_lock); |
| |
| + /* check if chip is being removed */ |
| + if (!chip || !chip->exported) { |
| + status = -ENODEV; |
| + goto fail_unlock; |
| + } |
| + |
| spin_lock_irqsave(&gpio_lock, flags); |
| if (!test_bit(FLAG_REQUESTED, &desc->flags) || |
| test_bit(FLAG_EXPORT, &desc->flags)) { |
| @@ -1040,6 +1049,8 @@ static void gpiochip_unexport(struct gpi |
| { |
| int status; |
| struct device *dev; |
| + struct gpio_desc *desc; |
| + unsigned int i; |
| |
| mutex_lock(&sysfs_lock); |
| dev = class_find_device(&gpio_class, NULL, chip, match_export); |
| @@ -1047,6 +1058,7 @@ static void gpiochip_unexport(struct gpi |
| sysfs_remove_group(&dev->kobj, &gpiochip_attr_group); |
| put_device(dev); |
| device_unregister(dev); |
| + /* prevent further gpiod exports */ |
| chip->exported = 0; |
| status = 0; |
| } else |
| @@ -1056,6 +1068,13 @@ static void gpiochip_unexport(struct gpi |
| if (status) |
| pr_debug("%s: chip %s status %d\n", __func__, |
| chip->label, status); |
| + |
| + /* unregister gpiod class devices owned by sysfs */ |
| + for (i = 0; i < chip->ngpio; i++) { |
| + desc = &chip->desc[i]; |
| + if (test_and_clear_bit(FLAG_SYSFS, &desc->flags)) |
| + gpiod_free(desc); |
| + } |
| } |
| |
| static int __init gpiolib_sysfs_init(void) |