gpio: sysfs: fix gpio attribute-creation race
authorJohan Hovold <johan@kernel.org>
Tue, 13 Jan 2015 12:00:06 +0000 (13:00 +0100)
committerBen Hutchings <ben@decadent.org.uk>
Fri, 20 Feb 2015 00:49:38 +0000 (00:49 +0000)
commit ebbeba120ab2ec6ac5f3afc1425ec6ff0b77ad6f upstream.

Fix attribute-creation race with userspace by using the default group
to create also the contingent gpio device attributes.

Fixes: d8f388d8dc8d ("gpio: sysfs interface")
Signed-off-by: Johan Hovold <johan@kernel.org>
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
[bwh: Backported to 3.2:
 - Adjust filenames, context
 - Use gpio_to_desc(), not gpiod_to_desc(), in gpio_is_visible()
 - gpio_is_visible() must return mode_t]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
drivers/gpio/gpiolib.c

index abfb878..51e5ee8 100644 (file)
@@ -58,6 +58,7 @@ struct gpio_desc {
 #define FLAG_TRIG_FALL 5       /* trigger on falling edge */
 #define FLAG_TRIG_RISE 6       /* trigger on rising edge */
 #define FLAG_ACTIVE_LOW        7       /* sysfs value has active low */
 #define FLAG_TRIG_FALL 5       /* trigger on falling edge */
 #define FLAG_TRIG_RISE 6       /* trigger on rising edge */
 #define FLAG_ACTIVE_LOW        7       /* sysfs value has active low */
+#define FLAG_SYSFS_DIR 10      /* show sysfs direction attribute */
 
 #define ID_SHIFT       16      /* add new flags before this one */
 
 
 #define ID_SHIFT       16      /* add new flags before this one */
 
@@ -543,12 +544,45 @@ static ssize_t gpio_active_low_store(struct device *dev,
 static DEVICE_ATTR(active_low, 0644,
                gpio_active_low_show, gpio_active_low_store);
 
 static DEVICE_ATTR(active_low, 0644,
                gpio_active_low_show, gpio_active_low_store);
 
+static mode_t gpio_is_visible(struct kobject *kobj, struct attribute *attr,
+                              int n)
+{
+       struct device *dev = container_of(kobj, struct device, kobj);
+       struct gpio_desc *desc = dev_get_drvdata(dev);
+       unsigned gpio = desc - gpio_desc;
+       mode_t mode = attr->mode;
+       bool show_direction = test_bit(FLAG_SYSFS_DIR, &desc->flags);
+
+       if (attr == &dev_attr_direction.attr) {
+               if (!show_direction)
+                       mode = 0;
+       } else if (attr == &dev_attr_edge.attr) {
+               if (gpio_to_irq(gpio) < 0)
+                       mode = 0;
+               if (!show_direction && test_bit(FLAG_IS_OUT, &desc->flags))
+                       mode = 0;
+       }
+
+       return mode;
+}
+
 static struct attribute *gpio_attrs[] = {
 static struct attribute *gpio_attrs[] = {
+       &dev_attr_direction.attr,
+       &dev_attr_edge.attr,
        &dev_attr_value.attr,
        &dev_attr_active_low.attr,
        NULL,
 };
        &dev_attr_value.attr,
        &dev_attr_active_low.attr,
        NULL,
 };
-ATTRIBUTE_GROUPS(gpio);
+
+static const struct attribute_group gpio_group = {
+       .attrs = gpio_attrs,
+       .is_visible = gpio_is_visible,
+};
+
+static const struct attribute_group *gpio_groups[] = {
+       &gpio_group,
+       NULL
+};
 
 /*
  * /sys/class/gpio/gpiochipN/
 
 /*
  * /sys/class/gpio/gpiochipN/
@@ -723,8 +757,11 @@ int gpio_export(unsigned gpio, bool direction_may_change)
                return -EPERM;
        }
 
                return -EPERM;
        }
 
-       if (!desc->chip->direction_input || !desc->chip->direction_output)
-               direction_may_change = false;
+       if (desc->chip->direction_input && desc->chip->direction_output &&
+                       direction_may_change) {
+               set_bit(FLAG_SYSFS_DIR, &desc->flags);
+       }
+
        spin_unlock_irqrestore(&gpio_lock, flags);
 
        if (desc->chip->names && desc->chip->names[gpio - desc->chip->base])
        spin_unlock_irqrestore(&gpio_lock, flags);
 
        if (desc->chip->names && desc->chip->names[gpio - desc->chip->base])
@@ -738,27 +775,10 @@ int gpio_export(unsigned gpio, bool direction_may_change)
                goto fail_unlock;
        }
 
                goto fail_unlock;
        }
 
-       if (direction_may_change) {
-               status = device_create_file(dev, &dev_attr_direction);
-               if (status)
-                       goto fail_unregister_device;
-       }
-
-       if (gpio_to_irq(gpio) >= 0 && (direction_may_change ||
-                                      !test_bit(FLAG_IS_OUT, &desc->flags))) {
-               status = device_create_file(dev, &dev_attr_edge);
-               if (status)
-                       goto fail_remove_attr_direction;
-       }
-
        set_bit(FLAG_EXPORT, &desc->flags);
        mutex_unlock(&sysfs_lock);
        return 0;
 
        set_bit(FLAG_EXPORT, &desc->flags);
        mutex_unlock(&sysfs_lock);
        return 0;
 
-fail_remove_attr_direction:
-       device_remove_file(dev, &dev_attr_direction);
-fail_unregister_device:
-       device_unregister(dev);
 fail_unlock:
        mutex_unlock(&sysfs_lock);
        pr_debug("%s: gpio%d status %d\n", __func__, gpio, status);
 fail_unlock:
        mutex_unlock(&sysfs_lock);
        pr_debug("%s: gpio%d status %d\n", __func__, gpio, status);
@@ -889,6 +909,7 @@ void gpio_unexport(unsigned gpio)
                dev = class_find_device(&gpio_class, NULL, desc, match_export);
                if (dev) {
                        gpio_setup_irq(desc, dev, 0);
                dev = class_find_device(&gpio_class, NULL, desc, match_export);
                if (dev) {
                        gpio_setup_irq(desc, dev, 0);
+                       clear_bit(FLAG_SYSFS_DIR, &desc->flags);
                        clear_bit(FLAG_EXPORT, &desc->flags);
                } else
                        status = -ENODEV;
                        clear_bit(FLAG_EXPORT, &desc->flags);
                } else
                        status = -ENODEV;
@@ -896,8 +917,6 @@ void gpio_unexport(unsigned gpio)
 
        mutex_unlock(&sysfs_lock);
        if (dev) {
 
        mutex_unlock(&sysfs_lock);
        if (dev) {
-               device_remove_file(dev, &dev_attr_edge);
-               device_remove_file(dev, &dev_attr_direction);
                device_unregister(dev);
                put_device(dev);
        }
                device_unregister(dev);
                put_device(dev);
        }