diff options
author | Linus Walleij <linus.walleij@linaro.org> | 2016-02-09 12:58:19 +0100 |
---|---|---|
committer | Linus Walleij <linus.walleij@linaro.org> | 2016-02-09 12:58:19 +0100 |
commit | 60afe31d1f6245c3156d540b0c7e5381ea8069d1 (patch) | |
tree | fe2923f401b627a07b7a00b06ae0a39bb42854b0 | |
parent | 42178e2a1e42b480ada954750f248b53d3fb5940 (diff) | |
parent | fe95046e960b4b76e73dc1486955d93f47276134 (diff) | |
download | op-kernel-dev-60afe31d1f6245c3156d540b0c7e5381ea8069d1.zip op-kernel-dev-60afe31d1f6245c3156d540b0c7e5381ea8069d1.tar.gz |
Merge branch 'chardev' into devel
-rw-r--r-- | Documentation/ABI/obsolete/sysfs-gpio (renamed from Documentation/ABI/testing/sysfs-gpio) | 4 | ||||
-rw-r--r-- | Documentation/ABI/testing/gpio-cdev | 26 | ||||
-rw-r--r-- | MAINTAINERS | 4 | ||||
-rw-r--r-- | drivers/gpio/gpiolib-sysfs.c | 12 | ||||
-rw-r--r-- | drivers/gpio/gpiolib.c | 361 | ||||
-rw-r--r-- | drivers/gpio/gpiolib.h | 41 | ||||
-rw-r--r-- | include/linux/gpio/driver.h | 7 | ||||
-rw-r--r-- | include/uapi/linux/Kbuild | 1 | ||||
-rw-r--r-- | include/uapi/linux/gpio.h | 28 | ||||
-rw-r--r-- | tools/Makefile | 8 | ||||
-rw-r--r-- | tools/gpio/Makefile | 12 | ||||
-rw-r--r-- | tools/gpio/gpio-utils.c | 11 | ||||
-rw-r--r-- | tools/gpio/gpio-utils.h | 25 | ||||
-rw-r--r-- | tools/gpio/lsgpio.c | 128 |
14 files changed, 578 insertions, 90 deletions
diff --git a/Documentation/ABI/testing/sysfs-gpio b/Documentation/ABI/obsolete/sysfs-gpio index 55ffa2d..867c1fa 100644 --- a/Documentation/ABI/testing/sysfs-gpio +++ b/Documentation/ABI/obsolete/sysfs-gpio @@ -1,7 +1,7 @@ What: /sys/class/gpio/ Date: July 2008 KernelVersion: 2.6.27 -Contact: David Brownell <dbrownell@users.sourceforge.net> +Contact: Linus Walleij <linusw@kernel.org> Description: As a Kconfig option, individual GPIO signals may be accessed from @@ -26,3 +26,5 @@ Description: /label ... (r/o) descriptive, not necessarily unique /ngpio ... (r/o) number of GPIOs; numbered N to N + (ngpio - 1) + This ABI is deprecated and will be removed after 2020. It is + replaced with the GPIO character device. diff --git a/Documentation/ABI/testing/gpio-cdev b/Documentation/ABI/testing/gpio-cdev new file mode 100644 index 0000000..7b265fbb --- /dev/null +++ b/Documentation/ABI/testing/gpio-cdev @@ -0,0 +1,26 @@ +What: /dev/gpiochip[0-9]+ +Date: November 2015 +KernelVersion: 4.4 +Contact: linux-gpio@vger.kernel.org +Description: + The character device files /dev/gpiochip* are the interface + between GPIO chips and userspace. + + The ioctl(2)-based ABI is defined and documented in + [include/uapi]<linux/gpio.h>. + + The following file operations are supported: + + open(2) + Currently the only useful flags are O_RDWR. + + ioctl(2) + Initiate various actions. + See the inline documentation in [include/uapi]<linux/gpio.h> + for descriptions of all ioctls. + + close(2) + Stops and free up the I/O contexts that was associated + with the file descriptor. + +Users: TBD diff --git a/MAINTAINERS b/MAINTAINERS index f043e95..7b8c058 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4821,10 +4821,14 @@ L: linux-gpio@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-gpio.git S: Maintained F: Documentation/gpio/ +F: Documentation/ABI/testing/gpio-cdev +F: Documentation/ABI/obsolete/sysfs-gpio F: drivers/gpio/ F: include/linux/gpio/ F: include/linux/gpio.h F: include/asm-generic/gpio.h +F: include/uapi/linux/gpio.h +F: tools/gpio/ GRE DEMULTIPLEXER DRIVER M: Dmitry Kozlov <xeb@mail.ru> diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 405dfca..28d3bf2 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -547,6 +547,7 @@ static struct class gpio_class = { int gpiod_export(struct gpio_desc *desc, bool direction_may_change) { struct gpio_chip *chip; + struct gpio_device *gdev; struct gpiod_data *data; unsigned long flags; int status; @@ -566,6 +567,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change) } chip = desc->chip; + gdev = chip->gpiodev; mutex_lock(&sysfs_lock); @@ -605,7 +607,7 @@ int gpiod_export(struct gpio_desc *desc, bool direction_may_change) if (chip->names && chip->names[offset]) ioname = chip->names[offset]; - dev = device_create_with_groups(&gpio_class, chip->parent, + dev = device_create_with_groups(&gpio_class, &gdev->dev, MKDEV(0, 0), data, gpio_groups, ioname ? ioname : "gpio%u", desc_to_gpio(desc)); @@ -771,7 +773,7 @@ static int __init gpiolib_sysfs_init(void) { int status; unsigned long flags; - struct gpio_chip *chip; + struct gpio_device *gdev; status = class_register(&gpio_class); if (status < 0) @@ -784,8 +786,8 @@ static int __init gpiolib_sysfs_init(void) * registered, and so arch_initcall() can always gpio_export(). */ spin_lock_irqsave(&gpio_lock, flags); - list_for_each_entry(chip, &gpio_chips, list) { - if (chip->cdev) + list_for_each_entry(gdev, &gpio_devices, list) { + if (gdev->chip->cdev) continue; /* @@ -798,7 +800,7 @@ static int __init gpiolib_sysfs_init(void) * gpio_lock prevents us from doing this. */ spin_unlock_irqrestore(&gpio_lock, flags); - status = gpiochip_sysfs_register(chip); + status = gpiochip_sysfs_register(gdev->chip); spin_lock_irqsave(&gpio_lock, flags); } spin_unlock_irqrestore(&gpio_lock, flags); diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 5c1ba87..70e0fff 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -16,6 +16,11 @@ #include <linux/gpio/driver.h> #include <linux/gpio/machine.h> #include <linux/pinctrl/consumer.h> +#include <linux/idr.h> +#include <linux/cdev.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <uapi/linux/gpio.h> #include "gpiolib.h" @@ -42,6 +47,14 @@ #define extra_checks 0 #endif +/* Device and char device-related information */ +static DEFINE_IDA(gpio_ida); +static dev_t gpio_devt; +#define GPIO_DEV_MAX 256 /* 256 GPIO chip devices supported */ +static struct bus_type gpio_bus_type = { + .name = "gpio", +}; + /* gpio_lock prevents conflicts during gpio_desc[] table updates. * While any GPIO is requested, its gpio_chip is not removable; * each GPIO's "requested" flag serves as a lock and refcount. @@ -50,8 +63,7 @@ DEFINE_SPINLOCK(gpio_lock); static DEFINE_MUTEX(gpio_lookup_lock); static LIST_HEAD(gpio_lookup_list); -LIST_HEAD(gpio_chips); - +LIST_HEAD(gpio_devices); static void gpiochip_free_hogs(struct gpio_chip *chip); static void gpiochip_irqchip_remove(struct gpio_chip *gpiochip); @@ -67,15 +79,16 @@ static inline void desc_set_label(struct gpio_desc *d, const char *label) */ struct gpio_desc *gpio_to_desc(unsigned gpio) { - struct gpio_chip *chip; + struct gpio_device *gdev; unsigned long flags; spin_lock_irqsave(&gpio_lock, flags); - list_for_each_entry(chip, &gpio_chips, list) { - if (chip->base <= gpio && chip->base + chip->ngpio > gpio) { + list_for_each_entry(gdev, &gpio_devices, list) { + if (gdev->chip->base <= gpio && + gdev->chip->base + gdev->chip->ngpio > gpio) { spin_unlock_irqrestore(&gpio_lock, flags); - return &chip->desc[gpio - chip->base]; + return &gdev->chip->desc[gpio - gdev->chip->base]; } } @@ -125,16 +138,16 @@ EXPORT_SYMBOL_GPL(gpiod_to_chip); /* dynamic allocation of GPIOs, e.g. on a hotplugged device */ static int gpiochip_find_base(int ngpio) { - struct gpio_chip *chip; + struct gpio_device *gdev; int base = ARCH_NR_GPIOS - ngpio; - list_for_each_entry_reverse(chip, &gpio_chips, list) { + list_for_each_entry_reverse(gdev, &gpio_devices, list) { /* found a free space? */ - if (chip->base + chip->ngpio <= base) + if (gdev->chip->base + gdev->chip->ngpio <= base) break; else /* nope, check the space right before the chip */ - base = chip->base - ngpio; + base = gdev->chip->base - ngpio; } if (gpio_is_valid(base)) { @@ -187,18 +200,28 @@ EXPORT_SYMBOL_GPL(gpiod_get_direction); * Return -EBUSY if the new chip overlaps with some other chip's integer * space. */ -static int gpiochip_add_to_list(struct gpio_chip *chip) +static int gpiodev_add_to_list(struct gpio_device *gdev) { - struct gpio_chip *iterator; - struct gpio_chip *previous = NULL; + struct gpio_device *iterator; + struct gpio_device *previous = NULL; - if (list_empty(&gpio_chips)) { - list_add_tail(&chip->list, &gpio_chips); + if (!gdev->chip) + return -EINVAL; + + if (list_empty(&gpio_devices)) { + list_add_tail(&gdev->list, &gpio_devices); return 0; } - list_for_each_entry(iterator, &gpio_chips, list) { - if (iterator->base >= chip->base + chip->ngpio) { + list_for_each_entry(iterator, &gpio_devices, list) { + /* + * The list may contain dangling GPIO devices with no + * live chip assigned. + */ + if (!iterator->chip) + continue; + if (iterator->chip->base >= + gdev->chip->base + gdev->chip->ngpio) { /* * Iterator is the first GPIO chip so there is no * previous one @@ -211,8 +234,8 @@ static int gpiochip_add_to_list(struct gpio_chip *chip) * [base, base + ngpio - 1]) between previous * and iterator chip. */ - if (previous->base + previous->ngpio - <= chip->base) + if (previous->chip->base + previous->chip->ngpio + <= gdev->chip->base) goto found; } } @@ -225,18 +248,18 @@ static int gpiochip_add_to_list(struct gpio_chip *chip) * Let iterator point to the last chip in the list. */ - iterator = list_last_entry(&gpio_chips, struct gpio_chip, list); - if (iterator->base + iterator->ngpio <= chip->base) { - list_add(&chip->list, &iterator->list); + iterator = list_last_entry(&gpio_devices, struct gpio_device, list); + if (iterator->chip->base + iterator->chip->ngpio <= gdev->chip->base) { + list_add(&gdev->list, &iterator->list); return 0; } - dev_err(chip->parent, + dev_err(&gdev->dev, "GPIO integer space overlap, cannot add chip\n"); return -EBUSY; found: - list_add_tail(&chip->list, &iterator->list); + list_add_tail(&gdev->list, &iterator->list); return 0; } @@ -245,16 +268,16 @@ found: */ static struct gpio_desc *gpio_name_to_desc(const char * const name) { - struct gpio_chip *chip; + struct gpio_device *gdev; unsigned long flags; spin_lock_irqsave(&gpio_lock, flags); - list_for_each_entry(chip, &gpio_chips, list) { + list_for_each_entry(gdev, &gpio_devices, list) { int i; - for (i = 0; i != chip->ngpio; ++i) { - struct gpio_desc *gpio = &chip->desc[i]; + for (i = 0; i != gdev->chip->ngpio; ++i) { + struct gpio_desc *gpio = &gdev->chip->desc[i]; if (!gpio->name || !name) continue; @@ -290,8 +313,8 @@ static int gpiochip_set_desc_names(struct gpio_chip *gc) gpio = gpio_name_to_desc(gc->names[i]); if (gpio) - dev_warn(gc->parent, "Detected name collision for " - "GPIO name '%s'\n", + dev_warn(&gc->gpiodev->dev, + "Detected name collision for GPIO name '%s'\n", gc->names[i]); } @@ -303,6 +326,88 @@ static int gpiochip_set_desc_names(struct gpio_chip *gc) } /** + * gpio_ioctl() - ioctl handler for the GPIO chardev + */ +static long gpio_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct gpio_device *gdev = filp->private_data; + struct gpio_chip *chip = gdev->chip; + int __user *ip = (int __user *)arg; + struct gpiochip_info chipinfo; + + /* We fail any subsequent ioctl():s when the chip is gone */ + if (!chip) + return -ENODEV; + + if (cmd == GPIO_GET_CHIPINFO_IOCTL) { + /* Fill in the struct and pass to userspace */ + strncpy(chipinfo.name, dev_name(&gdev->dev), + sizeof(chipinfo.name)); + chipinfo.name[sizeof(chipinfo.name)-1] = '\0'; + chipinfo.lines = chip->ngpio; + if (copy_to_user(ip, &chipinfo, sizeof(chipinfo))) + return -EFAULT; + return 0; + } + return -EINVAL; +} + +/** + * gpio_chrdev_open() - open the chardev for ioctl operations + * @inode: inode for this chardev + * @filp: file struct for storing private data + * Returns 0 on success + */ +static int gpio_chrdev_open(struct inode *inode, struct file *filp) +{ + struct gpio_device *gdev = container_of(inode->i_cdev, + struct gpio_device, chrdev); + + /* Fail on open if the backing gpiochip is gone */ + if (!gdev || !gdev->chip) + return -ENODEV; + get_device(&gdev->dev); + filp->private_data = gdev; + return 0; +} + +/** + * gpio_chrdev_release() - close chardev after ioctl operations + * @inode: inode for this chardev + * @filp: file struct for storing private data + * Returns 0 on success + */ +static int gpio_chrdev_release(struct inode *inode, struct file *filp) +{ + struct gpio_device *gdev = container_of(inode->i_cdev, + struct gpio_device, chrdev); + + if (!gdev) + return -ENODEV; + put_device(&gdev->dev); + return 0; +} + + +static const struct file_operations gpio_fileops = { + .release = gpio_chrdev_release, + .open = gpio_chrdev_open, + .owner = THIS_MODULE, + .llseek = noop_llseek, + .unlocked_ioctl = gpio_ioctl, + .compat_ioctl = gpio_ioctl, +}; + +static void gpiodevice_release(struct device *dev) +{ + struct gpio_device *gdev = dev_get_drvdata(dev); + + cdev_del(&gdev->chrdev); + list_del(&gdev->list); + ida_simple_remove(&gpio_ida, gdev->id); +} + +/** * gpiochip_add_data() - register a gpio_chip * @chip: the chip to register, with chip->base initialized * Context: potentially before irqs will work @@ -323,19 +428,61 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data) { unsigned long flags; int status = 0; - unsigned id; + unsigned i; int base = chip->base; struct gpio_desc *descs; + struct gpio_device *gdev; - descs = kcalloc(chip->ngpio, sizeof(descs[0]), GFP_KERNEL); - if (!descs) + /* + * First: allocate and populate the internal stat container, and + * set up the struct device. + */ + gdev = kmalloc(sizeof(*gdev), GFP_KERNEL); + if (!gdev) return -ENOMEM; + gdev->dev.bus = &gpio_bus_type; + gdev->chip = chip; + chip->gpiodev = gdev; + if (chip->parent) { + gdev->dev.parent = chip->parent; + gdev->dev.of_node = chip->parent->of_node; + } else { +#ifdef CONFIG_OF_GPIO + /* If the gpiochip has an assigned OF node this takes precedence */ + if (chip->of_node) + gdev->dev.of_node = chip->of_node; +#endif + } + gdev->id = ida_simple_get(&gpio_ida, 0, 0, GFP_KERNEL); + if (gdev->id < 0) { + status = gdev->id; + goto err_free_gdev; + } + dev_set_name(&gdev->dev, "gpiochip%d", gdev->id); + device_initialize(&gdev->dev); + dev_set_drvdata(&gdev->dev, gdev); + if (chip->parent && chip->parent->driver) + gdev->owner = chip->parent->driver->owner; + else if (chip->owner) + /* TODO: remove chip->owner */ + gdev->owner = chip->owner; + else + gdev->owner = THIS_MODULE; + + /* FIXME: devm_kcalloc() these and move to gpio_device */ + descs = kcalloc(chip->ngpio, sizeof(descs[0]), GFP_KERNEL); + if (!descs) { + status = -ENOMEM; + goto err_free_gdev; + } + /* FIXME: move driver data into gpio_device dev_set_drvdata() */ chip->data = data; if (chip->ngpio == 0) { chip_err(chip, "tried to insert a GPIO chip with zero lines\n"); - return -EINVAL; + status = -EINVAL; + goto err_free_descs; } spin_lock_irqsave(&gpio_lock, flags); @@ -350,15 +497,16 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data) chip->base = base; } - status = gpiochip_add_to_list(chip); + status = gpiodev_add_to_list(gdev); if (status) { spin_unlock_irqrestore(&gpio_lock, flags); goto err_free_descs; } - for (id = 0; id < chip->ngpio; id++) { - struct gpio_desc *desc = &descs[id]; + for (i = 0; i < chip->ngpio; i++) { + struct gpio_desc *desc = &descs[i]; + /* REVISIT: maybe a pointer to gpio_device is better */ desc->chip = chip; /* REVISIT: most hardware initializes GPIOs as inputs (often @@ -369,18 +517,15 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data) */ desc->flags = !chip->direction_input ? (1 << FLAG_IS_OUT) : 0; } - chip->desc = descs; spin_unlock_irqrestore(&gpio_lock, flags); #ifdef CONFIG_PINCTRL + /* FIXME: move pin ranges to gpio_device */ INIT_LIST_HEAD(&chip->pin_ranges); #endif - if (!chip->owner && chip->parent && chip->parent->driver) - chip->owner = chip->parent->driver->owner; - status = gpiochip_set_desc_names(chip); if (status) goto err_remove_from_list; @@ -391,28 +536,58 @@ int gpiochip_add_data(struct gpio_chip *chip, void *data) acpi_gpiochip_add(chip); + /* + * By first adding the chardev, and then adding the device, + * we get a device node entry in sysfs under + * /sys/bus/gpio/devices/gpiochipN/dev that can be used for + * coldplug of device nodes and other udev business. + */ + cdev_init(&gdev->chrdev, &gpio_fileops); + gdev->chrdev.owner = THIS_MODULE; + gdev->chrdev.kobj.parent = &gdev->dev.kobj; + gdev->dev.devt = MKDEV(MAJOR(gpio_devt), gdev->id); + status = cdev_add(&gdev->chrdev, gdev->dev.devt, 1); + if (status < 0) + chip_warn(chip, "failed to add char device %d:%d\n", + MAJOR(gpio_devt), gdev->id); + else + chip_dbg(chip, "added GPIO chardev (%d:%d)\n", + MAJOR(gpio_devt), gdev->id); + status = device_add(&gdev->dev); + if (status) + goto err_remove_chardev; + status = gpiochip_sysfs_register(chip); if (status) - goto err_remove_chip; + goto err_remove_device; + /* From this point, the .release() function cleans up gpio_device */ + gdev->dev.release = gpiodevice_release; + get_device(&gdev->dev); pr_debug("%s: registered GPIOs %d to %d on device: %s\n", __func__, chip->base, chip->base + chip->ngpio - 1, chip->label ? : "generic"); return 0; +err_remove_device: + device_del(&gdev->dev); +err_remove_chardev: + cdev_del(&gdev->chrdev); err_remove_chip: acpi_gpiochip_remove(chip); gpiochip_free_hogs(chip); of_gpiochip_remove(chip); err_remove_from_list: spin_lock_irqsave(&gpio_lock, flags); - list_del(&chip->list); + list_del(&gdev->list); spin_unlock_irqrestore(&gpio_lock, flags); chip->desc = NULL; err_free_descs: kfree(descs); - +err_free_gdev: + ida_simple_remove(&gpio_ida, gdev->id); + kfree(gdev); /* failures here can mean systems won't boot... */ pr_err("%s: GPIOs %d..%d (%s) failed to register\n", __func__, chip->base, chip->base + chip->ngpio - 1, @@ -429,15 +604,18 @@ EXPORT_SYMBOL_GPL(gpiochip_add_data); */ void gpiochip_remove(struct gpio_chip *chip) { + struct gpio_device *gdev = chip->gpiodev; struct gpio_desc *desc; unsigned long flags; unsigned id; bool requested = false; - gpiochip_sysfs_unregister(chip); + /* Numb the device, cancelling all outstanding operations */ + gdev->chip = NULL; + /* FIXME: should the legacy sysfs handling be moved to gpio_device? */ + gpiochip_sysfs_unregister(chip); gpiochip_irqchip_remove(chip); - acpi_gpiochip_remove(chip); gpiochip_remove_pin_ranges(chip); gpiochip_free_hogs(chip); @@ -450,15 +628,23 @@ void gpiochip_remove(struct gpio_chip *chip) if (test_bit(FLAG_REQUESTED, &desc->flags)) requested = true; } - list_del(&chip->list); spin_unlock_irqrestore(&gpio_lock, flags); if (requested) - dev_crit(chip->parent, + dev_crit(&chip->gpiodev->dev, "REMOVING GPIOCHIP WITH GPIOS STILL REQUESTED\n"); + /* FIXME: need to be moved to gpio_device and held there */ kfree(chip->desc); chip->desc = NULL; + + /* + * The gpiochip side puts its use of the device to rest here: + * if there are no userspace clients, the chardev and device will + * be removed, else it will be dangling until the last user is + * gone. + */ + put_device(&gdev->dev); } EXPORT_SYMBOL_GPL(gpiochip_remove); @@ -477,17 +663,21 @@ struct gpio_chip *gpiochip_find(void *data, int (*match)(struct gpio_chip *chip, void *data)) { + struct gpio_device *gdev; struct gpio_chip *chip; unsigned long flags; spin_lock_irqsave(&gpio_lock, flags); - list_for_each_entry(chip, &gpio_chips, list) - if (match(chip, data)) + list_for_each_entry(gdev, &gpio_devices, list) + if (match(gdev->chip, data)) break; /* No match? */ - if (&chip->list == &gpio_chips) + if (&gdev->list == &gpio_devices) chip = NULL; + else + chip = gdev->chip; + spin_unlock_irqrestore(&gpio_lock, flags); return chip; @@ -617,14 +807,14 @@ static int gpiochip_irq_reqres(struct irq_data *d) { struct gpio_chip *chip = irq_data_get_irq_chip_data(d); - if (!try_module_get(chip->owner)) + if (!try_module_get(chip->gpiodev->owner)) return -ENODEV; if (gpiochip_lock_as_irq(chip, d->hwirq)) { chip_err(chip, "unable to lock HW IRQ %lu for IRQ\n", d->hwirq); - module_put(chip->owner); + module_put(chip->gpiodev->owner); return -EINVAL; } return 0; @@ -635,7 +825,7 @@ static void gpiochip_irq_relres(struct irq_data *d) struct gpio_chip *chip = irq_data_get_irq_chip_data(d); gpiochip_unlock_as_irq(chip, d->hwirq); - module_put(chip->owner); + module_put(chip->gpiodev->owner); } static int gpiochip_to_irq(struct gpio_chip *chip, unsigned offset) @@ -985,10 +1175,10 @@ int gpiod_request(struct gpio_desc *desc, const char *label) if (!chip) goto done; - if (try_module_get(chip->owner)) { + if (try_module_get(chip->gpiodev->owner)) { status = __gpiod_request(desc, label); if (status < 0) - module_put(chip->owner); + module_put(chip->gpiodev->owner); } done: @@ -1034,7 +1224,7 @@ static bool __gpiod_free(struct gpio_desc *desc) void gpiod_free(struct gpio_desc *desc) { if (desc && __gpiod_free(desc)) - module_put(desc->chip->owner); + module_put(desc->chip->gpiodev->owner); else WARN_ON(extra_checks); } @@ -2456,6 +2646,26 @@ void gpiod_put_array(struct gpio_descs *descs) } EXPORT_SYMBOL_GPL(gpiod_put_array); +static int __init gpiolib_dev_init(void) +{ + int ret; + + /* Register GPIO sysfs bus */ + ret = bus_register(&gpio_bus_type); + if (ret < 0) { + pr_err("gpiolib: could not register GPIO bus type\n"); + return ret; + } + + ret = alloc_chrdev_region(&gpio_devt, 0, GPIO_DEV_MAX, "gpiochip"); + if (ret < 0) { + pr_err("gpiolib: failed to allocate char dev region\n"); + bus_unregister(&gpio_bus_type); + } + return ret; +} +core_initcall(gpiolib_dev_init); + #ifdef CONFIG_DEBUG_FS static void gpiolib_dbg_show(struct seq_file *s, struct gpio_chip *chip) @@ -2492,16 +2702,16 @@ static void gpiolib_dbg_show(struct seq_file *s, struct gpio_chip *chip) static void *gpiolib_seq_start(struct seq_file *s, loff_t *pos) { unsigned long flags; - struct gpio_chip *chip = NULL; + struct gpio_device *gdev = NULL; loff_t index = *pos; s->private = ""; spin_lock_irqsave(&gpio_lock, flags); - list_for_each_entry(chip, &gpio_chips, list) + list_for_each_entry(gdev, &gpio_devices, list) if (index-- == 0) { spin_unlock_irqrestore(&gpio_lock, flags); - return chip; + return gdev; } spin_unlock_irqrestore(&gpio_lock, flags); @@ -2511,14 +2721,14 @@ static void *gpiolib_seq_start(struct seq_file *s, loff_t *pos) static void *gpiolib_seq_next(struct seq_file *s, void *v, loff_t *pos) { unsigned long flags; - struct gpio_chip *chip = v; + struct gpio_device *gdev = v; void *ret = NULL; spin_lock_irqsave(&gpio_lock, flags); - if (list_is_last(&chip->list, &gpio_chips)) + if (list_is_last(&gdev->list, &gpio_devices)) ret = NULL; else - ret = list_entry(chip->list.next, struct gpio_chip, list); + ret = list_entry(gdev->list.next, struct gpio_device, list); spin_unlock_irqrestore(&gpio_lock, flags); s->private = "\n"; @@ -2533,15 +2743,24 @@ static void gpiolib_seq_stop(struct seq_file *s, void *v) static int gpiolib_seq_show(struct seq_file *s, void *v) { - struct gpio_chip *chip = v; - struct device *dev; + struct gpio_device *gdev = v; + struct gpio_chip *chip = gdev->chip; + struct device *parent; + + if (!chip) { + seq_printf(s, "%s%s: (dangling chip)", (char *)s->private, + dev_name(&gdev->dev)); + return 0; + } - seq_printf(s, "%sGPIOs %d-%d", (char *)s->private, - chip->base, chip->base + chip->ngpio - 1); - dev = chip->parent; - if (dev) - seq_printf(s, ", %s/%s", dev->bus ? dev->bus->name : "no-bus", - dev_name(dev)); + seq_printf(s, "%s%s: GPIOs %d-%d", (char *)s->private, + dev_name(&gdev->dev), + chip->base, chip->base + chip->ngpio - 1); + parent = chip->parent; + if (parent) + seq_printf(s, ", parent: %s/%s", + parent->bus ? parent->bus->name : "no-bus", + dev_name(parent)); if (chip->label) seq_printf(s, ", %s", chip->label); if (chip->can_sleep) diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 99ed3b0..1524ba0 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -12,14 +12,41 @@ #ifndef GPIOLIB_H #define GPIOLIB_H +#include <linux/gpio/driver.h> #include <linux/err.h> #include <linux/device.h> +#include <linux/module.h> +#include <linux/cdev.h> enum of_gpio_flags; enum gpiod_flags; struct acpi_device; /** + * struct gpio_device - internal state container for GPIO devices + * @id: numerical ID number for the GPIO chip + * @dev: the GPIO device struct + * @chrdev: character device for the GPIO device + * @owner: helps prevent removal of modules exporting active GPIOs + * @chip: pointer to the corresponding gpiochip, holding static + * data for this device + * @list: links gpio_device:s together for traversal + * + * This state container holds most of the runtime variable data + * for a GPIO device and can hold references and live on after the + * GPIO chip has been removed, if it is still being used from + * userspace. + */ +struct gpio_device { + int id; + struct device dev; + struct cdev chrdev; + struct module *owner; + struct gpio_chip *chip; + struct list_head list; +}; + +/** * struct acpi_gpio_info - ACPI GPIO specific information * @gpioint: if %true this GPIO is of type GpioInt otherwise type is GpioIo * @active_low: in case of @gpioint, the pin is active low @@ -90,7 +117,7 @@ struct gpio_desc *of_get_named_gpiod_flags(struct device_node *np, struct gpio_desc *gpiochip_get_desc(struct gpio_chip *chip, u16 hwnum); extern struct spinlock gpio_lock; -extern struct list_head gpio_chips; +extern struct list_head gpio_devices; struct gpio_desc { struct gpio_chip *chip; @@ -149,17 +176,17 @@ static int __maybe_unused gpio_chip_hwgpio(const struct gpio_desc *desc) /* With chip prefix */ #define chip_emerg(chip, fmt, ...) \ - pr_emerg("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__) + dev_emerg(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__) #define chip_crit(chip, fmt, ...) \ - pr_crit("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__) + dev_crit(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__) #define chip_err(chip, fmt, ...) \ - pr_err("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__) + dev_err(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__) #define chip_warn(chip, fmt, ...) \ - pr_warn("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__) + dev_warn(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__) #define chip_info(chip, fmt, ...) \ - pr_info("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__) + dev_info(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__) #define chip_dbg(chip, fmt, ...) \ - pr_debug("GPIO chip %s: " fmt, chip->label, ##__VA_ARGS__) + dev_dbg(&chip->gpiodev->dev, "(%s): " fmt, chip->label, ##__VA_ARGS__) #ifdef CONFIG_GPIO_SYSFS diff --git a/include/linux/gpio/driver.h b/include/linux/gpio/driver.h index 82fda48..f3f1dbd 100644 --- a/include/linux/gpio/driver.h +++ b/include/linux/gpio/driver.h @@ -1,6 +1,7 @@ #ifndef __LINUX_GPIO_DRIVER_H #define __LINUX_GPIO_DRIVER_H +#include <linux/device.h> #include <linux/types.h> #include <linux/module.h> #include <linux/irq.h> @@ -10,22 +11,22 @@ #include <linux/pinctrl/pinctrl.h> #include <linux/kconfig.h> -struct device; struct gpio_desc; struct of_phandle_args; struct device_node; struct seq_file; +struct gpio_device; #ifdef CONFIG_GPIOLIB /** * struct gpio_chip - abstract a GPIO controller * @label: for diagnostics + * @gpiodev: the internal state holder, opaque struct * @parent: optional parent device providing the GPIOs * @cdev: class device used by sysfs interface (may be NULL) * @owner: helps prevent removal of modules exporting active GPIOs * @data: per-instance data assigned by the driver - * @list: links gpio_chips together for traversal * @request: optional hook for chip-specific activation, such as * enabling module power and clock; may sleep * @free: optional hook for chip-specific deactivation, such as @@ -107,11 +108,11 @@ struct seq_file; */ struct gpio_chip { const char *label; + struct gpio_device *gpiodev; struct device *parent; struct device *cdev; struct module *owner; void *data; - struct list_head list; int (*request)(struct gpio_chip *chip, unsigned offset); diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index ebd10e6..5c9ae6a 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -138,6 +138,7 @@ header-y += genetlink.h header-y += gen_stats.h header-y += gfs2_ondisk.h header-y += gigaset_dev.h +header-y += gpio.h header-y += gsmmux.h header-y += hdlcdrv.h header-y += hdlc.h diff --git a/include/uapi/linux/gpio.h b/include/uapi/linux/gpio.h new file mode 100644 index 0000000..3188a87 --- /dev/null +++ b/include/uapi/linux/gpio.h @@ -0,0 +1,28 @@ +/* + * <linux/gpio.h> - userspace ABI for the GPIO character devices + * + * Copyright (C) 2015 Linus Walleij + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _UAPI_GPIO_H_ +#define _UAPI_GPIO_H_ + +#include <linux/ioctl.h> +#include <linux/types.h> + +/** + * struct gpiochip_info - Information about a certain GPIO chip + * @name: the name of this GPIO chip + * @lines: number of GPIO lines on this chip + */ +struct gpiochip_info { + char name[32]; + __u32 lines; +}; + +#define GPIO_GET_CHIPINFO_IOCTL _IOR('o', 0x01, struct gpiochip_info) + +#endif /* _UAPI_GPIO_H_ */ diff --git a/tools/Makefile b/tools/Makefile index 6339f6a..f41e7c6 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -13,6 +13,7 @@ help: @echo ' cpupower - a tool for all things x86 CPU power' @echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer' @echo ' freefall - laptop accelerometer program for disk protection' + @echo ' gpio - GPIO tools' @echo ' hv - tools used when in Hyper-V clients' @echo ' iio - IIO tools' @echo ' lguest - a minimal 32-bit x86 hypervisor' @@ -53,7 +54,7 @@ acpi: FORCE cpupower: FORCE $(call descend,power/$@) -cgroup firewire hv guest spi usb virtio vm net iio: FORCE +cgroup firewire hv guest spi usb virtio vm net iio gpio: FORCE $(call descend,$@) liblockdep: FORCE @@ -119,7 +120,7 @@ acpi_clean: cpupower_clean: $(call descend,power/cpupower,clean) -cgroup_clean hv_clean firewire_clean lguest_clean spi_clean usb_clean virtio_clean vm_clean net_clean iio_clean: +cgroup_clean hv_clean firewire_clean lguest_clean spi_clean usb_clean virtio_clean vm_clean net_clean iio_clean gpio_clean: $(call descend,$(@:_clean=),clean) liblockdep_clean: @@ -155,6 +156,7 @@ build_clean: clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean lguest_clean \ perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \ vm_clean net_clean iio_clean x86_energy_perf_policy_clean tmon_clean \ - freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean + freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \ + gpio_clean .PHONY: FORCE diff --git a/tools/gpio/Makefile b/tools/gpio/Makefile new file mode 100644 index 0000000..4d198d5 --- /dev/null +++ b/tools/gpio/Makefile @@ -0,0 +1,12 @@ +CC = $(CROSS_COMPILE)gcc +CFLAGS += -Wall -g -D_GNU_SOURCE + +all: lsgpio + +lsgpio: lsgpio.o gpio-utils.o + +%.o: %.c gpio-utils.h + +.PHONY: clean +clean: + rm -f *.o lsgpio diff --git a/tools/gpio/gpio-utils.c b/tools/gpio/gpio-utils.c new file mode 100644 index 0000000..8208718 --- /dev/null +++ b/tools/gpio/gpio-utils.c @@ -0,0 +1,11 @@ +/* + * GPIO tools - helpers library for the GPIO tools + * + * Copyright (C) 2015 Linus Walleij + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ + +#include "gpio-utils.h" diff --git a/tools/gpio/gpio-utils.h b/tools/gpio/gpio-utils.h new file mode 100644 index 0000000..b18209a --- /dev/null +++ b/tools/gpio/gpio-utils.h @@ -0,0 +1,25 @@ +/* + * GPIO tools - utility helpers library for the GPIO tools + * + * Copyright (C) 2015 Linus Walleij + * + * Portions copied from iio_utils and lssio: + * Copyright (c) 2010 Manuel Stahl <manuel.stahl@iis.fraunhofer.de> + * Copyright (c) 2008 Jonathan Cameron + * * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + */ +#ifndef _GPIO_UTILS_H_ +#define _GPIO_UTILS_H_ + +#include <string.h> + +static inline int check_prefix(const char *str, const char *prefix) +{ + return strlen(str) > strlen(prefix) && + strncmp(str, prefix, strlen(prefix)) == 0; +} + +#endif /* _GPIO_UTILS_H_ */ diff --git a/tools/gpio/lsgpio.c b/tools/gpio/lsgpio.c new file mode 100644 index 0000000..4cfe29d --- /dev/null +++ b/tools/gpio/lsgpio.c @@ -0,0 +1,128 @@ +/* + * lsgpio - example on how to list the GPIO lines on a system + * + * Copyright (C) 2015 Linus Walleij + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * Usage: + * lsgpio <-n device-name> + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdbool.h> +#include <stdio.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <poll.h> +#include <fcntl.h> +#include <getopt.h> +#include <sys/ioctl.h> +#include <linux/gpio.h> + +#include "gpio-utils.h" + +int list_device(const char *device_name) +{ + struct gpiochip_info cinfo; + char *chrdev_name; + int fd; + int ret; + + ret = asprintf(&chrdev_name, "/dev/%s", device_name); + if (ret < 0) + return -ENOMEM; + + fd = open(chrdev_name, 0); + if (fd == -1) { + ret = -errno; + fprintf(stderr, "Failed to open %s\n", chrdev_name); + goto free_chrdev_name; + } + + /* Inspect this GPIO chip */ + ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &cinfo); + if (ret == -1) { + ret = -errno; + fprintf(stderr, "Failed to retrieve GPIO fd\n"); + if (close(fd) == -1) + perror("Failed to close GPIO character device file"); + + goto free_chrdev_name; + } + fprintf(stdout, "GPIO chip: %s, %u GPIO lines\n", + cinfo.name, cinfo.lines); + + if (close(fd) == -1) { + ret = -errno; + goto free_chrdev_name; + } + +free_chrdev_name: + free(chrdev_name); + + return ret; + +} + +void print_usage(void) +{ + fprintf(stderr, "Usage: lsgpio [options]...\n" + "List GPIO chips, lines and states\n" + " -n <name> List GPIOs on a named device\n" + " -? This helptext\n" + ); +} + +int main(int argc, char **argv) +{ + const char *device_name; + int ret; + int c; + + while ((c = getopt(argc, argv, "n:")) != -1) { + switch (c) { + case 'n': + device_name = optarg; + break; + case '?': + print_usage(); + return -1; + } + } + + if (device_name) + ret = list_device(device_name); + else { + const struct dirent *ent; + DIR *dp; + + /* List all GPIO devices one at a time */ + dp = opendir("/dev"); + if (!dp) { + ret = -errno; + goto error_out; + } + + ret = -ENOENT; + while (ent = readdir(dp), ent) { + if (check_prefix(ent->d_name, "gpiochip")) { + ret = list_device(ent->d_name); + if (ret) + break; + } + } + + ret = 0; + if (closedir(dp) == -1) { + perror("scanning devices: Failed to close directory"); + ret = -errno; + } + } +error_out: + return ret; +} |