diff options
Diffstat (limited to 'drivers/gpio/gpiolib-sysfs.c')
-rw-r--r-- | drivers/gpio/gpiolib-sysfs.c | 676 |
1 files changed, 482 insertions, 194 deletions
diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 4a3aa09dad9d..b64106f1cb7b 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -3,7 +3,6 @@ #include <linux/bitops.h> #include <linux/cleanup.h> #include <linux/device.h> -#include <linux/idr.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/kdev_t.h> @@ -12,7 +11,6 @@ #include <linux/mutex.h> #include <linux/printk.h> #include <linux/slab.h> -#include <linux/spinlock.h> #include <linux/string.h> #include <linux/srcu.h> #include <linux/sysfs.h> @@ -26,6 +24,8 @@ #include "gpiolib.h" #include "gpiolib-sysfs.h" +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + struct kernfs_node; #define GPIO_IRQF_TRIGGER_NONE 0 @@ -34,15 +34,64 @@ struct kernfs_node; #define GPIO_IRQF_TRIGGER_BOTH (GPIO_IRQF_TRIGGER_FALLING | \ GPIO_IRQF_TRIGGER_RISING) +enum { + GPIO_SYSFS_LINE_CLASS_ATTR_DIRECTION = 0, + GPIO_SYSFS_LINE_CLASS_ATTR_VALUE, + GPIO_SYSFS_LINE_CLASS_ATTR_EDGE, + GPIO_SYSFS_LINE_CLASS_ATTR_ACTIVE_LOW, + GPIO_SYSFS_LINE_CLASS_ATTR_SENTINEL, + GPIO_SYSFS_LINE_CLASS_ATTR_SIZE, +}; + +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ + +enum { + GPIO_SYSFS_LINE_CHIP_ATTR_DIRECTION = 0, + GPIO_SYSFS_LINE_CHIP_ATTR_VALUE, + GPIO_SYSFS_LINE_CHIP_ATTR_SENTINEL, + GPIO_SYSFS_LINE_CHIP_ATTR_SIZE, +}; + struct gpiod_data { + struct list_head list; + struct gpio_desc *desc; + struct device *dev; struct mutex mutex; +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) struct kernfs_node *value_kn; int irq; unsigned char irq_flags; +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ bool direction_can_change; + + struct kobject *parent; + struct device_attribute dir_attr; + struct device_attribute val_attr; + +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + struct device_attribute edge_attr; + struct device_attribute active_low_attr; + + struct attribute *class_attrs[GPIO_SYSFS_LINE_CLASS_ATTR_SIZE]; + struct attribute_group class_attr_group; + const struct attribute_group *class_attr_groups[2]; +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ + + struct attribute *chip_attrs[GPIO_SYSFS_LINE_CHIP_ATTR_SIZE]; + struct attribute_group chip_attr_group; + const struct attribute_group *chip_attr_groups[2]; +}; + +struct gpiodev_data { + struct list_head exported_lines; + struct gpio_device *gdev; + struct device *cdev_id; /* Class device by GPIO device ID */ +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + struct device *cdev_base; /* Class device by GPIO base */ +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ }; /* @@ -73,9 +122,10 @@ static DEFINE_MUTEX(sysfs_lock); */ static ssize_t direction_show(struct device *dev, - struct device_attribute *attr, char *buf) + struct device_attribute *attr, char *buf) { - struct gpiod_data *data = dev_get_drvdata(dev); + struct gpiod_data *data = container_of(attr, struct gpiod_data, + dir_attr); struct gpio_desc *desc = data->desc; int value; @@ -88,11 +138,13 @@ static ssize_t direction_show(struct device *dev, } static ssize_t direction_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) + struct device_attribute *attr, const char *buf, + size_t size) { - struct gpiod_data *data = dev_get_drvdata(dev); + struct gpiod_data *data = container_of(attr, struct gpiod_data, + dir_attr); struct gpio_desc *desc = data->desc; - ssize_t status; + ssize_t status; guard(mutex)(&data->mutex); @@ -107,14 +159,14 @@ static ssize_t direction_store(struct device *dev, return status ? : size; } -static DEVICE_ATTR_RW(direction); -static ssize_t value_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t value_show(struct device *dev, struct device_attribute *attr, + char *buf) { - struct gpiod_data *data = dev_get_drvdata(dev); + struct gpiod_data *data = container_of(attr, struct gpiod_data, + val_attr); struct gpio_desc *desc = data->desc; - ssize_t status; + ssize_t status; scoped_guard(mutex, &data->mutex) status = gpiod_get_value_cansleep(desc); @@ -125,10 +177,11 @@ static ssize_t value_show(struct device *dev, return sysfs_emit(buf, "%zd\n", status); } -static ssize_t value_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) +static ssize_t value_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) { - struct gpiod_data *data = dev_get_drvdata(dev); + struct gpiod_data *data = container_of(attr, struct gpiod_data, + val_attr); struct gpio_desc *desc = data->desc; ssize_t status; long value; @@ -145,8 +198,8 @@ static ssize_t value_store(struct device *dev, return size; } -static DEVICE_ATTR_PREALLOC(value, S_IWUSR | S_IRUGO, value_show, value_store); +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) static irqreturn_t gpio_sysfs_irq(int irq, void *priv) { struct gpiod_data *data = priv; @@ -157,9 +210,8 @@ static irqreturn_t gpio_sysfs_irq(int irq, void *priv) } /* Caller holds gpiod-data mutex. */ -static int gpio_sysfs_request_irq(struct device *dev, unsigned char flags) +static int gpio_sysfs_request_irq(struct gpiod_data *data, unsigned char flags) { - struct gpiod_data *data = dev_get_drvdata(dev); struct gpio_desc *desc = data->desc; unsigned long irq_flags; int ret; @@ -172,33 +224,29 @@ static int gpio_sysfs_request_irq(struct device *dev, unsigned char flags) if (data->irq < 0) return -EIO; - data->value_kn = sysfs_get_dirent(dev->kobj.sd, "value"); - if (!data->value_kn) - return -ENODEV; - irq_flags = IRQF_SHARED; if (flags & GPIO_IRQF_TRIGGER_FALLING) { irq_flags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ? - IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING; + IRQF_TRIGGER_RISING : IRQF_TRIGGER_FALLING; set_bit(FLAG_EDGE_FALLING, &desc->flags); } if (flags & GPIO_IRQF_TRIGGER_RISING) { irq_flags |= test_bit(FLAG_ACTIVE_LOW, &desc->flags) ? - IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; + IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; set_bit(FLAG_EDGE_RISING, &desc->flags); } /* * FIXME: This should be done in the irq_request_resources callback - * when the irq is requested, but a few drivers currently fail - * to do so. + * when the irq is requested, but a few drivers currently fail to do + * so. * - * Remove this redundant call (along with the corresponding - * unlock) when those drivers have been fixed. + * Remove this redundant call (along with the corresponding unlock) + * when those drivers have been fixed. */ ret = gpiochip_lock_as_irq(guard.gc, gpio_chip_hwgpio(desc)); if (ret < 0) - goto err_put_kn; + goto err_clr_bits; ret = request_any_context_irq(data->irq, gpio_sysfs_irq, irq_flags, "gpiolib", data); @@ -211,10 +259,9 @@ static int gpio_sysfs_request_irq(struct device *dev, unsigned char flags) err_unlock: gpiochip_unlock_as_irq(guard.gc, gpio_chip_hwgpio(desc)); -err_put_kn: +err_clr_bits: clear_bit(FLAG_EDGE_RISING, &desc->flags); clear_bit(FLAG_EDGE_FALLING, &desc->flags); - sysfs_put(data->value_kn); return ret; } @@ -223,9 +270,8 @@ err_put_kn: * Caller holds gpiod-data mutex (unless called after class-device * deregistration). */ -static void gpio_sysfs_free_irq(struct device *dev) +static void gpio_sysfs_free_irq(struct gpiod_data *data) { - struct gpiod_data *data = dev_get_drvdata(dev); struct gpio_desc *desc = data->desc; CLASS(gpio_chip_guard, guard)(desc); @@ -237,20 +283,20 @@ static void gpio_sysfs_free_irq(struct device *dev) gpiochip_unlock_as_irq(guard.gc, gpio_chip_hwgpio(desc)); clear_bit(FLAG_EDGE_RISING, &desc->flags); clear_bit(FLAG_EDGE_FALLING, &desc->flags); - sysfs_put(data->value_kn); } -static const char * const trigger_names[] = { +static const char *const trigger_names[] = { [GPIO_IRQF_TRIGGER_NONE] = "none", [GPIO_IRQF_TRIGGER_FALLING] = "falling", [GPIO_IRQF_TRIGGER_RISING] = "rising", [GPIO_IRQF_TRIGGER_BOTH] = "both", }; -static ssize_t edge_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t edge_show(struct device *dev, struct device_attribute *attr, + char *buf) { - struct gpiod_data *data = dev_get_drvdata(dev); + struct gpiod_data *data = container_of(attr, struct gpiod_data, + edge_attr); int flags; scoped_guard(mutex, &data->mutex) @@ -262,10 +308,11 @@ static ssize_t edge_show(struct device *dev, return sysfs_emit(buf, "%s\n", trigger_names[flags]); } -static ssize_t edge_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) +static ssize_t edge_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) { - struct gpiod_data *data = dev_get_drvdata(dev); + struct gpiod_data *data = container_of(attr, struct gpiod_data, + edge_attr); ssize_t status = size; int flags; @@ -279,12 +326,12 @@ static ssize_t edge_store(struct device *dev, return size; if (data->irq_flags) - gpio_sysfs_free_irq(dev); + gpio_sysfs_free_irq(data); if (!flags) return size; - status = gpio_sysfs_request_irq(dev, flags); + status = gpio_sysfs_request_irq(data, flags); if (status) return status; @@ -292,17 +339,14 @@ static ssize_t edge_store(struct device *dev, return size; } -static DEVICE_ATTR_RW(edge); /* Caller holds gpiod-data mutex. */ -static int gpio_sysfs_set_active_low(struct device *dev, int value) +static int gpio_sysfs_set_active_low(struct gpiod_data *data, int value) { - struct gpiod_data *data = dev_get_drvdata(dev); unsigned int flags = data->irq_flags; struct gpio_desc *desc = data->desc; int status = 0; - if (!!test_bit(FLAG_ACTIVE_LOW, &desc->flags) == !!value) return 0; @@ -310,9 +354,9 @@ static int gpio_sysfs_set_active_low(struct device *dev, int value) /* reconfigure poll(2) support if enabled on one edge only */ if (flags == GPIO_IRQF_TRIGGER_FALLING || - flags == GPIO_IRQF_TRIGGER_RISING) { - gpio_sysfs_free_irq(dev); - status = gpio_sysfs_request_irq(dev, flags); + flags == GPIO_IRQF_TRIGGER_RISING) { + gpio_sysfs_free_irq(data); + status = gpio_sysfs_request_irq(data, flags); } gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_CONFIG); @@ -321,9 +365,10 @@ static int gpio_sysfs_set_active_low(struct device *dev, int value) } static ssize_t active_low_show(struct device *dev, - struct device_attribute *attr, char *buf) + struct device_attribute *attr, char *buf) { - struct gpiod_data *data = dev_get_drvdata(dev); + struct gpiod_data *data = container_of(attr, struct gpiod_data, + active_low_attr); struct gpio_desc *desc = data->desc; int value; @@ -334,9 +379,11 @@ static ssize_t active_low_show(struct device *dev, } static ssize_t active_low_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) + struct device_attribute *attr, + const char *buf, size_t size) { - struct gpiod_data *data = dev_get_drvdata(dev); + struct gpiod_data *data = container_of(attr, struct gpiod_data, + active_low_attr); ssize_t status; long value; @@ -346,84 +393,189 @@ static ssize_t active_low_store(struct device *dev, guard(mutex)(&data->mutex); - return gpio_sysfs_set_active_low(dev, value) ?: size; + return gpio_sysfs_set_active_low(data, value) ?: size; } -static DEVICE_ATTR_RW(active_low); +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ static umode_t gpio_is_visible(struct kobject *kobj, struct attribute *attr, int n) { - struct device *dev = kobj_to_dev(kobj); - struct gpiod_data *data = dev_get_drvdata(dev); - struct gpio_desc *desc = data->desc; + struct device_attribute *dev_attr = container_of(attr, + struct device_attribute, attr); umode_t mode = attr->mode; - bool show_direction = data->direction_can_change; + struct gpiod_data *data; + + if (strcmp(attr->name, "direction") == 0) { + data = container_of(dev_attr, struct gpiod_data, dir_attr); - if (attr == &dev_attr_direction.attr) { - if (!show_direction) + if (!data->direction_can_change) mode = 0; - } else if (attr == &dev_attr_edge.attr) { - if (gpiod_to_irq(desc) < 0) +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + } else if (strcmp(attr->name, "edge") == 0) { + data = container_of(dev_attr, struct gpiod_data, edge_attr); + + if (gpiod_to_irq(data->desc) < 0) mode = 0; - if (!show_direction && test_bit(FLAG_IS_OUT, &desc->flags)) + + if (!data->direction_can_change && + test_bit(FLAG_IS_OUT, &data->desc->flags)) mode = 0; +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ } return mode; } -static struct attribute *gpio_attrs[] = { - &dev_attr_direction.attr, - &dev_attr_edge.attr, - &dev_attr_value.attr, - &dev_attr_active_low.attr, - NULL, -}; - -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/ * /base ... matching gpio_chip.base (N) * /label ... matching gpio_chip.label * /ngpio ... matching gpio_chip.ngpio + * + * AND + * + * /sys/class/gpio/chipX/ + * /export ... export GPIO at given offset + * /unexport ... unexport GPIO at given offset + * /label ... matching gpio_chip.label + * /ngpio ... matching gpio_chip.ngpio */ -static ssize_t base_show(struct device *dev, - struct device_attribute *attr, char *buf) +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) +static ssize_t base_show(struct device *dev, struct device_attribute *attr, + char *buf) { - const struct gpio_device *gdev = dev_get_drvdata(dev); + const struct gpiodev_data *data = dev_get_drvdata(dev); - return sysfs_emit(buf, "%u\n", gdev->base); + return sysfs_emit(buf, "%u\n", data->gdev->base); } static DEVICE_ATTR_RO(base); +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ -static ssize_t label_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t label_show(struct device *dev, struct device_attribute *attr, + char *buf) { - const struct gpio_device *gdev = dev_get_drvdata(dev); + const struct gpiodev_data *data = dev_get_drvdata(dev); - return sysfs_emit(buf, "%s\n", gdev->label); + return sysfs_emit(buf, "%s\n", data->gdev->label); } static DEVICE_ATTR_RO(label); -static ssize_t ngpio_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t ngpio_show(struct device *dev, struct device_attribute *attr, + char *buf) { - const struct gpio_device *gdev = dev_get_drvdata(dev); + const struct gpiodev_data *data = dev_get_drvdata(dev); - return sysfs_emit(buf, "%u\n", gdev->ngpio); + return sysfs_emit(buf, "%u\n", data->gdev->ngpio); } static DEVICE_ATTR_RO(ngpio); +static int export_gpio_desc(struct gpio_desc *desc) +{ + int offset, ret; + + CLASS(gpio_chip_guard, guard)(desc); + if (!guard.gc) + return -ENODEV; + + offset = gpio_chip_hwgpio(desc); + if (!gpiochip_line_is_valid(guard.gc, offset)) { + pr_debug_ratelimited("%s: GPIO %d masked\n", __func__, + gpio_chip_hwgpio(desc)); + return -EINVAL; + } + + /* + * No extra locking here; FLAG_SYSFS just signifies that the + * request and export were done by on behalf of userspace, so + * they may be undone on its behalf too. + */ + + ret = gpiod_request_user(desc, "sysfs"); + if (ret) + return ret; + + ret = gpiod_set_transitory(desc, false); + if (ret) { + gpiod_free(desc); + return ret; + } + + ret = gpiod_export(desc, true); + if (ret < 0) { + gpiod_free(desc); + } else { + set_bit(FLAG_SYSFS, &desc->flags); + gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED); + } + + return ret; +} + +static int unexport_gpio_desc(struct gpio_desc *desc) +{ + /* + * No extra locking here; FLAG_SYSFS just signifies that the + * request and export were done by on behalf of userspace, so + * they may be undone on its behalf too. + */ + if (!test_and_clear_bit(FLAG_SYSFS, &desc->flags)) + return -EINVAL; + + gpiod_unexport(desc); + gpiod_free(desc); + + return 0; +} + +static ssize_t do_chip_export_store(struct device *dev, + struct device_attribute *attr, + const char *buf, ssize_t size, + int (*handler)(struct gpio_desc *desc)) +{ + struct gpiodev_data *data = dev_get_drvdata(dev); + struct gpio_device *gdev = data->gdev; + struct gpio_desc *desc; + unsigned int gpio; + int ret; + + ret = kstrtouint(buf, 0, &gpio); + if (ret) + return ret; + + desc = gpio_device_get_desc(gdev, gpio); + if (IS_ERR(desc)) + return PTR_ERR(desc); + + ret = handler(desc); + if (ret) + return ret; + + return size; +} + +static ssize_t chip_export_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return do_chip_export_store(dev, attr, buf, size, export_gpio_desc); +} + +static struct device_attribute dev_attr_export = __ATTR(export, 0200, NULL, + chip_export_store); + +static ssize_t chip_unexport_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + return do_chip_export_store(dev, attr, buf, size, unexport_gpio_desc); +} + +static struct device_attribute dev_attr_unexport = __ATTR(unexport, 0200, + NULL, + chip_unexport_store); + +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) static struct attribute *gpiochip_attrs[] = { &dev_attr_base.attr, &dev_attr_label.attr, @@ -431,7 +583,18 @@ static struct attribute *gpiochip_attrs[] = { NULL, }; ATTRIBUTE_GROUPS(gpiochip); +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ +static struct attribute *gpiochip_ext_attrs[] = { + &dev_attr_label.attr, + &dev_attr_ngpio.attr, + &dev_attr_export.attr, + &dev_attr_unexport.attr, + NULL +}; +ATTRIBUTE_GROUPS(gpiochip_ext); + +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) /* * /sys/class/gpio/export ... write-only * integer N ... number of GPIO to export (full access) @@ -439,11 +602,11 @@ ATTRIBUTE_GROUPS(gpiochip); * integer N ... number of GPIO to unexport */ static ssize_t export_store(const struct class *class, - const struct class_attribute *attr, - const char *buf, size_t len) + const struct class_attribute *attr, + const char *buf, size_t len) { struct gpio_desc *desc; - int status, offset; + int status; long gpio; status = kstrtol(buf, 0, &gpio); @@ -457,40 +620,7 @@ static ssize_t export_store(const struct class *class, return -EINVAL; } - CLASS(gpio_chip_guard, guard)(desc); - if (!guard.gc) - return -ENODEV; - - offset = gpio_chip_hwgpio(desc); - if (!gpiochip_line_is_valid(guard.gc, offset)) { - pr_debug_ratelimited("%s: GPIO %ld masked\n", __func__, gpio); - return -EINVAL; - } - - /* No extra locking here; FLAG_SYSFS just signifies that the - * request and export were done by on behalf of userspace, so - * they may be undone on its behalf too. - */ - - status = gpiod_request_user(desc, "sysfs"); - if (status) - goto done; - - status = gpiod_set_transitory(desc, false); - if (status) { - gpiod_free(desc); - goto done; - } - - status = gpiod_export(desc, true); - if (status < 0) { - gpiod_free(desc); - } else { - set_bit(FLAG_SYSFS, &desc->flags); - gpiod_line_state_notify(desc, GPIO_V2_LINE_CHANGED_REQUESTED); - } - -done: + status = export_gpio_desc(desc); if (status) pr_debug("%s: status %d\n", __func__, status); return status ? : len; @@ -498,8 +628,8 @@ done: static CLASS_ATTR_WO(export); static ssize_t unexport_store(const struct class *class, - const struct class_attribute *attr, - const char *buf, size_t len) + const struct class_attribute *attr, + const char *buf, size_t len) { struct gpio_desc *desc; int status; @@ -507,7 +637,7 @@ static ssize_t unexport_store(const struct class *class, status = kstrtol(buf, 0, &gpio); if (status < 0) - goto done; + return status; desc = gpio_to_desc(gpio); /* reject bogus commands (gpiod_unexport() ignores them) */ @@ -516,18 +646,7 @@ static ssize_t unexport_store(const struct class *class, return -EINVAL; } - status = -EINVAL; - - /* No extra locking here; FLAG_SYSFS just signifies that the - * request and export were done by on behalf of userspace, so - * they may be undone on its behalf too. - */ - if (test_and_clear_bit(FLAG_SYSFS, &desc->flags)) { - gpiod_unexport(desc); - gpiod_free(desc); - status = 0; - } -done: + status = unexport_gpio_desc(desc); if (status) pr_debug("%s: status %d\n", __func__, status); return status ? : len; @@ -540,12 +659,55 @@ static struct attribute *gpio_class_attrs[] = { NULL, }; ATTRIBUTE_GROUPS(gpio_class); +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ static const struct class gpio_class = { .name = "gpio", +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) .class_groups = gpio_class_groups, +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ +}; + +static int match_gdev(struct device *dev, const void *desc) +{ + struct gpiodev_data *data = dev_get_drvdata(dev); + const struct gpio_device *gdev = desc; + + return data && data->gdev == gdev; +} + +static struct gpiodev_data * +gdev_get_data(struct gpio_device *gdev) __must_hold(&sysfs_lock) +{ + /* + * Find the first device in GPIO class that matches. Whether that's + * the one indexed by GPIO base or device ID doesn't matter, it has + * the same address set as driver data. + */ + struct device *cdev __free(put_device) = class_find_device(&gpio_class, + NULL, gdev, + match_gdev); + if (!cdev) + return NULL; + + return dev_get_drvdata(cdev); }; +static void gpiod_attr_init(struct device_attribute *dev_attr, const char *name, + ssize_t (*show)(struct device *dev, + struct device_attribute *attr, + char *buf), + ssize_t (*store)(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count)) +{ + sysfs_attr_init(&dev_attr->attr); + dev_attr->attr.name = name; + dev_attr->attr.mode = 0644; + dev_attr->show = show; + dev_attr->store = store; +} + /** * gpiod_export - export a GPIO through sysfs * @desc: GPIO to make available, already requested @@ -564,9 +726,11 @@ static const struct class gpio_class = { */ int gpiod_export(struct gpio_desc *desc, bool direction_may_change) { + char *path __free(kfree) = NULL; + struct gpiodev_data *gdev_data; + struct gpiod_data *desc_data; struct gpio_device *gdev; - struct gpiod_data *data; - struct device *dev; + struct attribute **attrs; int status; /* can't export until sysfs is available ... */ @@ -591,43 +755,116 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change) guard(mutex)(&sysfs_lock); - /* check if chip is being removed */ - if (!gdev->mockdev) { - status = -ENODEV; - goto err_clear_bit; - } - if (!test_bit(FLAG_REQUESTED, &desc->flags)) { gpiod_dbg(desc, "%s: unavailable (not requested)\n", __func__); status = -EPERM; goto err_clear_bit; } - data = kzalloc(sizeof(*data), GFP_KERNEL); - if (!data) { + desc_data = kzalloc(sizeof(*desc_data), GFP_KERNEL); + if (!desc_data) { status = -ENOMEM; goto err_clear_bit; } - data->desc = desc; - mutex_init(&data->mutex); + desc_data->desc = desc; + mutex_init(&desc_data->mutex); if (guard.gc->direction_input && guard.gc->direction_output) - data->direction_can_change = direction_may_change; + desc_data->direction_can_change = direction_may_change; else - data->direction_can_change = false; + desc_data->direction_can_change = false; + + gpiod_attr_init(&desc_data->dir_attr, "direction", + direction_show, direction_store); + gpiod_attr_init(&desc_data->val_attr, "value", value_show, value_store); - dev = device_create_with_groups(&gpio_class, &gdev->dev, - MKDEV(0, 0), data, gpio_groups, - "gpio%u", desc_to_gpio(desc)); - if (IS_ERR(dev)) { - status = PTR_ERR(dev); +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + gpiod_attr_init(&desc_data->edge_attr, "edge", edge_show, edge_store); + gpiod_attr_init(&desc_data->active_low_attr, "active_low", + active_low_show, active_low_store); + + attrs = desc_data->class_attrs; + desc_data->class_attr_group.is_visible = gpio_is_visible; + attrs[GPIO_SYSFS_LINE_CLASS_ATTR_DIRECTION] = &desc_data->dir_attr.attr; + attrs[GPIO_SYSFS_LINE_CLASS_ATTR_VALUE] = &desc_data->val_attr.attr; + attrs[GPIO_SYSFS_LINE_CLASS_ATTR_EDGE] = &desc_data->edge_attr.attr; + attrs[GPIO_SYSFS_LINE_CLASS_ATTR_ACTIVE_LOW] = &desc_data->active_low_attr.attr; + + desc_data->class_attr_group.attrs = desc_data->class_attrs; + desc_data->class_attr_groups[0] = &desc_data->class_attr_group; + + /* + * Note: we need to continue passing desc_data here as there's still + * at least one known user of gpiod_export_link() in the tree. This + * function still uses class_find_device() internally. + */ + desc_data->dev = device_create_with_groups(&gpio_class, &gdev->dev, + MKDEV(0, 0), desc_data, + desc_data->class_attr_groups, + "gpio%u", + desc_to_gpio(desc)); + if (IS_ERR(desc_data->dev)) { + status = PTR_ERR(desc_data->dev); goto err_free_data; } + desc_data->value_kn = sysfs_get_dirent(desc_data->dev->kobj.sd, + "value"); + if (!desc_data->value_kn) { + status = -ENODEV; + goto err_unregister_device; + } +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ + + gdev_data = gdev_get_data(gdev); + if (!gdev_data) { + status = -ENODEV; + goto err_put_dirent; + } + + desc_data->chip_attr_group.name = kasprintf(GFP_KERNEL, "gpio%u", + gpio_chip_hwgpio(desc)); + if (!desc_data->chip_attr_group.name) { + status = -ENOMEM; + goto err_put_dirent; + } + + attrs = desc_data->chip_attrs; + desc_data->chip_attr_group.is_visible = gpio_is_visible; + attrs[GPIO_SYSFS_LINE_CHIP_ATTR_DIRECTION] = &desc_data->dir_attr.attr; + attrs[GPIO_SYSFS_LINE_CHIP_ATTR_VALUE] = &desc_data->val_attr.attr; + + desc_data->chip_attr_group.attrs = attrs; + desc_data->chip_attr_groups[0] = &desc_data->chip_attr_group; + + desc_data->parent = &gdev_data->cdev_id->kobj; + status = sysfs_create_groups(desc_data->parent, + desc_data->chip_attr_groups); + if (status) + goto err_free_name; + + path = kasprintf(GFP_KERNEL, "gpio%u/value", gpio_chip_hwgpio(desc)); + if (!path) { + status = -ENOMEM; + goto err_remove_groups; + } + + list_add(&desc_data->list, &gdev_data->exported_lines); + return 0; +err_remove_groups: + sysfs_remove_groups(desc_data->parent, desc_data->chip_attr_groups); +err_free_name: + kfree(desc_data->chip_attr_group.name); +err_put_dirent: +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + sysfs_put(desc_data->value_kn); +err_unregister_device: + device_unregister(desc_data->dev); err_free_data: - kfree(data); +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ + kfree(desc_data); err_clear_bit: clear_bit(FLAG_EXPORT, &desc->flags); gpiod_dbg(desc, "%s: status %d\n", __func__, status); @@ -635,12 +872,14 @@ err_clear_bit: } EXPORT_SYMBOL_GPL(gpiod_export); +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) static int match_export(struct device *dev, const void *desc) { struct gpiod_data *data = dev_get_drvdata(dev); - return data->desc == desc; + return gpiod_is_equal(data->desc, desc); } +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ /** * gpiod_export_link - create a sysfs link to an exported GPIO node @@ -657,6 +896,7 @@ static int match_export(struct device *dev, const void *desc) int gpiod_export_link(struct device *dev, const char *name, struct gpio_desc *desc) { +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) struct device *cdev; int ret; @@ -673,6 +913,9 @@ int gpiod_export_link(struct device *dev, const char *name, put_device(cdev); return ret; +#else + return -EOPNOTSUPP; +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ } EXPORT_SYMBOL_GPL(gpiod_export_link); @@ -684,8 +927,9 @@ EXPORT_SYMBOL_GPL(gpiod_export_link); */ void gpiod_unexport(struct gpio_desc *desc) { - struct gpiod_data *data; - struct device *dev; + struct gpiod_data *tmp, *desc_data = NULL; + struct gpiodev_data *gdev_data; + struct gpio_device *gdev; if (!desc) { pr_warn("%s: invalid GPIO\n", __func__); @@ -696,32 +940,50 @@ void gpiod_unexport(struct gpio_desc *desc) if (!test_bit(FLAG_EXPORT, &desc->flags)) return; - dev = class_find_device(&gpio_class, NULL, desc, match_export); - if (!dev) + gdev = gpiod_to_gpio_device(desc); + gdev_data = gdev_get_data(gdev); + if (!gdev_data) + return; + + list_for_each_entry(tmp, &gdev_data->exported_lines, list) { + if (gpiod_is_equal(desc, tmp->desc)) { + desc_data = tmp; + break; + } + } + + if (!desc_data) return; - data = dev_get_drvdata(dev); + list_del(&desc_data->list); clear_bit(FLAG_EXPORT, &desc->flags); - device_unregister(dev); +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + sysfs_put(desc_data->value_kn); + device_unregister(desc_data->dev); /* * Release irq after deregistration to prevent race with * edge_store. */ - if (data->irq_flags) - gpio_sysfs_free_irq(dev); + if (desc_data->irq_flags) + gpio_sysfs_free_irq(desc_data); +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ + + sysfs_remove_groups(desc_data->parent, + desc_data->chip_attr_groups); } - put_device(dev); - kfree(data); + mutex_destroy(&desc_data->mutex); + kfree(desc_data); } EXPORT_SYMBOL_GPL(gpiod_unexport); int gpiochip_sysfs_register(struct gpio_device *gdev) { + struct gpiodev_data *data; struct gpio_chip *chip; struct device *parent; - struct device *dev; + int err; /* * Many systems add gpio chips for SOC support very early, @@ -747,32 +1009,61 @@ int gpiochip_sysfs_register(struct gpio_device *gdev) else parent = &gdev->dev; - /* use chip->base for the ID; it's already known to be unique */ - dev = device_create_with_groups(&gpio_class, parent, MKDEV(0, 0), gdev, - gpiochip_groups, GPIOCHIP_NAME "%d", - chip->base); - if (IS_ERR(dev)) - return PTR_ERR(dev); + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->gdev = gdev; + INIT_LIST_HEAD(&data->exported_lines); guard(mutex)(&sysfs_lock); - gdev->mockdev = dev; + +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + /* use chip->base for the ID; it's already known to be unique */ + data->cdev_base = device_create_with_groups(&gpio_class, parent, + MKDEV(0, 0), data, + gpiochip_groups, + GPIOCHIP_NAME "%d", + chip->base); + if (IS_ERR(data->cdev_base)) { + err = PTR_ERR(data->cdev_base); + kfree(data); + return err; + } +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ + + data->cdev_id = device_create_with_groups(&gpio_class, parent, + MKDEV(0, 0), data, + gpiochip_ext_groups, + "chip%d", gdev->id); + if (IS_ERR(data->cdev_id)) { +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + device_unregister(data->cdev_base); +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ + err = PTR_ERR(data->cdev_id); + kfree(data); + return err; + } return 0; } void gpiochip_sysfs_unregister(struct gpio_device *gdev) { + struct gpiodev_data *data; struct gpio_desc *desc; struct gpio_chip *chip; scoped_guard(mutex, &sysfs_lock) { - if (!gdev->mockdev) + data = gdev_get_data(gdev); + if (!data) return; - device_unregister(gdev->mockdev); - - /* prevent further gpiod exports */ - gdev->mockdev = NULL; +#if IS_ENABLED(CONFIG_GPIO_SYSFS_LEGACY) + device_unregister(data->cdev_base); +#endif /* CONFIG_GPIO_SYSFS_LEGACY */ + device_unregister(data->cdev_id); + kfree(data); } guard(srcu)(&gdev->srcu); @@ -798,9 +1089,6 @@ static int gpiofind_sysfs_register(struct gpio_chip *gc, const void *data) struct gpio_device *gdev = gc->gpiodev; int ret; - if (gdev->mockdev) - return 0; - ret = gpiochip_sysfs_register(gdev); if (ret) chip_err(gc, "failed to register the sysfs entry: %d\n", ret); |