[patch/rfc 2.6.25-git] gpio: sysfs interface

From: David Brownell
Date: Mon Apr 28 2008 - 15:40:33 EST


Simple sysfs interface for GPIOs.

/sys/class/gpio
/gpio-N ... for each exported GPIO #N
/value ... always readable, writes fail except for output GPIOs
/direction ... writable as: in, out (default low), high, low
/control ... to request a GPIO be exported or unexported

GPIOs may be exported by kernel code using gpio_export(), which should
be most useful for driver debugging. Userspace may also ask that they
be exported by writing to the sysfs control file, helping to cope with
incomplete board support:

echo "export 23" > /sys/class/gpio/control
... will gpio_request(23, "sysfs") and gpio_export(23); use
/sys/class/gpio/gpio-23/direction to configure it.
echo "unexport 23" > /sys/class/gpio/control
... will gpio_free(23)

The D-space footprint is negligible, except for the sysfs resources
associated with each exported GPIO. The additional I-space footprint
is about half of the current size of gpiolib. No /dev node creation
involved, and no "udev" support is needed.

This adds a device pointer to "struct gpio_chip". When GPIO providers
initialize that, sysfs gpio class devices become children of that device
instead of being "virtual" devices. The (few) gpio_chip providers which
have such a device node have been updated. (Some also needed to update
their module "owner" field ... for which missing kerneldoc was added.)

Based on a patch from Trent Piepho <tpiepho@xxxxxxxxxxxxx>, and comments
from various folk including Hartley Sweeten.

Signed-off-by: David Brownell <dbrownell@xxxxxxxxxxxxxxxxxxxxx>
---
arch/avr32/mach-at32ap/pio.c | 2
drivers/gpio/Kconfig | 16 ++
drivers/gpio/gpiolib.c | 281 +++++++++++++++++++++++++++++++++++++++++++
drivers/gpio/mcp23s08.c | 1
drivers/gpio/pca953x.c | 1
drivers/gpio/pcf857x.c | 1
drivers/i2c/chips/tps65010.c | 2
drivers/mfd/htc-egpio.c | 2
include/asm-generic/gpio.h | 28 ++++
9 files changed, 333 insertions(+), 1 deletion(-)

--- g26.orig/arch/avr32/mach-at32ap/pio.c 2008-04-28 11:11:07.000000000 -0700
+++ g26/arch/avr32/mach-at32ap/pio.c 2008-04-28 11:31:21.000000000 -0700
@@ -358,6 +358,8 @@ static int __init pio_probe(struct platf
pio->chip.label = pio->name;
pio->chip.base = pdev->id * 32;
pio->chip.ngpio = 32;
+ pio->chip.dev = &pdev->dev;
+ pio->chip.owner = THIS_MODULE;

pio->chip.direction_input = direction_input;
pio->chip.get = gpio_get;
--- g26.orig/drivers/gpio/Kconfig 2008-04-28 11:11:08.000000000 -0700
+++ g26/drivers/gpio/Kconfig 2008-04-28 11:46:57.000000000 -0700
@@ -23,6 +23,22 @@ config DEBUG_GPIO
slower. The diagnostics help catch the type of setup errors
that are most common when setting up new platforms or boards.

+config GPIO_SYSFS
+ bool "/sys/class/gpio/... (EXPERIMENTAL sysfs interface)"
+ depends on SYSFS && EXPERIMENTAL
+ help
+ Say Y here to add a sysfs interface for GPIOs.
+
+ This is mostly useful to work around omissions in a system's
+ kernel support. Those are common in custom and semicustom
+ hardware assembled using standard kernels with a minimum of
+ custom patches. In those cases, userspace code may ask that
+ a given GPIO be exported, if no kernel driver requested it.
+
+ Kernel drivers may also request that a particular GPIO be
+ made available to userspace as well as that driver. This
+ can be useful when debugging.
+
# put expanders in the right section, in alphabetical order

comment "I2C GPIO expanders:"
--- g26.orig/drivers/gpio/gpiolib.c 2008-04-28 11:17:42.000000000 -0700
+++ g26/drivers/gpio/gpiolib.c 2008-04-28 11:40:36.000000000 -0700
@@ -44,6 +44,8 @@ struct gpio_desc {
#define FLAG_REQUESTED 0
#define FLAG_IS_OUT 1
#define FLAG_RESERVED 2
+#define FLAG_EXPORT 3
+#define FLAG_SYSFS 4

#ifdef CONFIG_DEBUG_FS
const char *label;
@@ -286,6 +288,283 @@ done:
}
EXPORT_SYMBOL_GPL(gpio_request);

+
+#ifdef CONFIG_GPIO_SYSFS
+
+/*
+ * /sys/class/gpio/gpio-N/... only for GPIOs that are exported
+ * - direction
+ * * is read/write as in/out
+ * * may also be written as high/low, initializing output
+ * value as specified (plain "out" implies "low")
+ * - value
+ * * always readable, subject to hardware behavior
+ * * may be writable, as zero/nonzero
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+
+static ssize_t gpio_direction_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct gpio_desc *desc = dev_get_drvdata(dev);
+
+ /* handle GPIOs being removed from underneath us... */
+ if (!test_bit(FLAG_EXPORT, &desc->flags))
+ return -EIO;
+
+ return sprintf(buf, "%s\n",
+ test_bit(FLAG_IS_OUT, &desc->flags) ? "out" : "in");
+}
+
+static ssize_t gpio_direction_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ const struct gpio_desc *desc = dev_get_drvdata(dev);
+ unsigned gpio = desc - gpio_desc;
+ unsigned len = size;
+ ssize_t status = -EINVAL;
+
+ /* handle GPIOs being removed from underneath us... */
+ if (!test_bit(FLAG_EXPORT, &desc->flags))
+ return -EIO;
+
+ if (buf[len - 1] == '\n')
+ len--;
+
+ if (len == 4 && strncmp(buf, "high", 4) == 0)
+ status = gpio_direction_output(gpio, 1);
+
+ else if (len == 3 && (strncmp(buf, "out", 3) == 0
+ || strncmp(buf, "low", 3) == 0))
+ status = gpio_direction_output(gpio, 0);
+
+ else if (len == 2 && strncmp(buf, "in", 2) == 0)
+ status = gpio_direction_input(gpio);
+
+ return (status < 0) ? status : size;
+}
+
+static const DEVICE_ATTR(direction, 0644, gpio_direction_show, gpio_direction_store);
+
+static ssize_t gpio_value_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ const struct gpio_desc *desc = dev_get_drvdata(dev);
+ unsigned gpio = desc - gpio_desc;
+
+ /* handle GPIOs being removed from underneath us... */
+ if (!test_bit(FLAG_EXPORT, &desc->flags))
+ return -EIO;
+
+ return sprintf(buf, "%d\n", gpio_get_value_cansleep(gpio));
+}
+
+static ssize_t gpio_value_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ const struct gpio_desc *desc = dev_get_drvdata(dev);
+ unsigned gpio = desc - gpio_desc;
+ long value;
+ int ret;
+
+ /* handle GPIOs being removed from underneath us... */
+ if (!test_bit(FLAG_EXPORT, &desc->flags))
+ return -EIO;
+
+ if (!test_bit(FLAG_IS_OUT, &desc->flags))
+ return -EINVAL;
+
+ ret = strict_strtol(buf, 0, &value);
+ if (ret < 0)
+ return ret;
+ gpio_set_value_cansleep(gpio, value != 0);
+ return size;
+}
+
+static const DEVICE_ATTR(value, 0644, gpio_value_show, gpio_value_store);
+
+static const struct attribute *gpio_attrs[] = {
+ &dev_attr_direction.attr,
+ &dev_attr_value.attr,
+ NULL,
+};
+
+static const struct attribute_group gpio_attr_group = {
+ .attrs = (struct attribute **) gpio_attrs,
+};
+
+static struct class *gpio_class;
+
+/**
+ * gpio_export - export a GPIO through sysfs
+ * @gpio: gpio to make available, already requested
+ *
+ * When drivers want to make a GPIO accessible to userspace after they
+ * have requested it -- perhaps while debugging, or as part of their
+ * public interface -- they may use this routine.
+ *
+ * Returns zero on success, else an error.
+ */
+int gpio_export(unsigned gpio)
+{
+ unsigned long flags;
+ struct gpio_desc *desc;
+ int status = -EINVAL;
+
+ if (!gpio_class)
+ return -ENOSYS;
+
+ if (!gpio_is_valid(gpio))
+ return -EINVAL;
+
+ /* REVISIT mode param to say if it direction may be changed */
+
+ spin_lock_irqsave(&gpio_lock, flags);
+ desc = &gpio_desc[gpio];
+ if (test_bit(FLAG_REQUESTED, &desc->flags)
+ && !test_bit(FLAG_EXPORT, &desc->flags))
+ status = 0;
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ if (status)
+ pr_debug("%s: gpio-%d status %d\n", __func__, gpio, status);
+ else {
+ struct device *dev;
+
+ dev = device_create(gpio_class, desc->chip->dev, 0,
+ "gpio-%d", gpio);
+ if (dev) {
+ dev_set_drvdata(dev, desc);
+ status = sysfs_create_group(&dev->kobj,
+ &gpio_attr_group);
+ }
+ if (status == 0)
+ set_bit(FLAG_EXPORT, &desc->flags);
+ }
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(gpio_export);
+
+static int match_export(struct device *dev, void *data)
+{
+ return dev_get_drvdata(dev) == data;
+}
+
+/**
+ * gpio_unexport - reverse effect of gpio_export()
+ * @gpio: gpio to make unavailable
+ *
+ * This is implicit on gpio_free().
+ */
+void gpio_unexport(unsigned gpio)
+{
+ unsigned long flags;
+ struct gpio_desc *desc;
+ int status = -EINVAL;
+
+ if (!gpio_is_valid(gpio))
+ return;
+
+ spin_lock_irqsave(&gpio_lock, flags);
+ desc = &gpio_desc[gpio];
+ if (test_bit(FLAG_EXPORT, &desc->flags))
+ status = 0;
+ spin_unlock_irqrestore(&gpio_lock, flags);
+
+ if (status == 0) {
+ struct device *dev = NULL;
+
+ dev = class_find_device(gpio_class, desc, match_export);
+ if (dev) {
+ clear_bit(FLAG_EXPORT, &desc->flags);
+ put_device(dev);
+ device_unregister(dev);
+ }
+ }
+
+}
+EXPORT_SYMBOL_GPL(gpio_unexport);
+
+/*
+ * /sys/class/gpio/control ... write-only
+ * export N
+ * unexport N
+ */
+static ssize_t control_store(struct class *class, const char *buf, size_t len)
+{
+ char *scratch = (char *)buf;
+ char *cmd, *tmp;
+ int status;
+ unsigned long gpio;
+
+ /* export/unexport */
+ cmd = strsep(&scratch, " \t\n");
+ if (!cmd)
+ goto fail;
+
+ /* N */
+ tmp = strsep(&scratch, " \t\n");
+ if (!tmp)
+ goto fail;
+ status = strict_strtoul(tmp, 0, &gpio);
+ if (status < 0)
+ goto done;
+
+ /* reject commands with garbage at end */
+ tmp = strsep(&scratch, " \t\n");
+ if ((tmp && *tmp) || scratch)
+ goto fail;
+
+ if (strcmp(cmd, "export") == 0) {
+ status = gpio_request(gpio, "sysfs");
+ if (status < 0)
+ goto done;
+
+ status = gpio_export(gpio);
+ if (status < 0)
+ gpio_free(gpio);
+ else
+ set_bit(FLAG_SYSFS, &gpio_desc[gpio].flags);
+
+ } else if (strcmp(cmd, "unexport") == 0) {
+ /* reject bogus commands (gpio_unexport ignores them) */
+ if (!gpio_is_valid(gpio))
+ goto fail;
+ if (!test_and_clear_bit(FLAG_SYSFS, &gpio_desc[gpio].flags))
+ goto fail;
+
+ gpio_free(gpio);
+ }
+done:
+ return (status < 0) ? status : len;
+fail:
+ return -EINVAL;
+}
+
+static CLASS_ATTR(control, 0200, NULL, control_store);
+
+static int __init gpiolib_sysfs_init(void)
+{
+ int status;
+
+ gpio_class = class_create(THIS_MODULE, "gpio");
+ if (IS_ERR(gpio_class))
+ return PTR_ERR(gpio_class);
+
+ status = class_create_file(gpio_class, &class_attr_control);
+ if (status < 0) {
+ class_destroy(gpio_class);
+ gpio_class = NULL;
+ }
+ return status;
+}
+postcore_initcall(gpiolib_sysfs_init);
+
+#endif /* CONFIG_GPIO_SYSFS */
+
void gpio_free(unsigned gpio)
{
unsigned long flags;
@@ -296,6 +575,8 @@ void gpio_free(unsigned gpio)
return;
}

+ gpio_unexport(gpio);
+
spin_lock_irqsave(&gpio_lock, flags);

desc = &gpio_desc[gpio];
--- g26.orig/drivers/gpio/mcp23s08.c 2008-04-28 11:17:42.000000000 -0700
+++ g26/drivers/gpio/mcp23s08.c 2008-04-28 11:31:21.000000000 -0700
@@ -239,6 +239,7 @@ static int mcp23s08_probe(struct spi_dev
mcp->chip.base = pdata->base;
mcp->chip.ngpio = 8;
mcp->chip.can_sleep = 1;
+ mcp->chip.dev = &spi->dev;
mcp->chip.owner = THIS_MODULE;

spi_set_drvdata(spi, mcp);
--- g26.orig/drivers/gpio/pca953x.c 2008-04-28 11:17:42.000000000 -0700
+++ g26/drivers/gpio/pca953x.c 2008-04-28 11:31:21.000000000 -0700
@@ -189,6 +189,7 @@ static void pca953x_setup_gpio(struct pc
gc->base = chip->gpio_start;
gc->ngpio = gpios;
gc->label = chip->client->name;
+ gc->dev = &chip->client->dev;
gc->owner = THIS_MODULE;
}

--- g26.orig/drivers/gpio/pcf857x.c 2008-04-28 11:17:42.000000000 -0700
+++ g26/drivers/gpio/pcf857x.c 2008-04-28 11:31:21.000000000 -0700
@@ -159,6 +159,7 @@ static int pcf857x_probe(struct i2c_clie

gpio->chip.base = pdata->gpio_base;
gpio->chip.can_sleep = 1;
+ gpio->chip.dev = &client->dev;
gpio->chip.owner = THIS_MODULE;

/* NOTE: the OnSemi jlc1562b is also largely compatible with
--- g26.orig/drivers/i2c/chips/tps65010.c 2008-04-28 11:11:07.000000000 -0700
+++ g26/drivers/i2c/chips/tps65010.c 2008-04-28 11:31:21.000000000 -0700
@@ -650,6 +650,8 @@ static int tps65010_probe(struct i2c_cli
tps->outmask = board->outmask;

tps->chip.label = client->name;
+ tps->chip.dev = &client->dev;
+ tps->chip.owner = THIS_MODULE;

tps->chip.set = tps65010_gpio_set;
tps->chip.direction_output = tps65010_output;
--- g26.orig/drivers/mfd/htc-egpio.c 2008-04-28 11:11:07.000000000 -0700
+++ g26/drivers/mfd/htc-egpio.c 2008-04-28 11:31:21.000000000 -0700
@@ -318,6 +318,8 @@ static int __init egpio_probe(struct pla
ei->chip[i].dev = &(pdev->dev);
chip = &(ei->chip[i].chip);
chip->label = "htc-egpio";
+ chip->dev = &pdev->dev;
+ chip->owner = THIS_MODULE;
chip->get = egpio_get;
chip->set = egpio_set;
chip->direction_input = egpio_direction_input;
--- g26.orig/include/asm-generic/gpio.h 2008-04-28 11:17:44.000000000 -0700
+++ g26/include/asm-generic/gpio.h 2008-04-28 11:49:49.000000000 -0700
@@ -28,6 +28,8 @@ struct module;
/**
* struct gpio_chip - abstract a GPIO controller
* @label: for diagnostics
+ * @dev: optional device providing the GPIOs
+ * @owner: helps prevent removal of modules exporting active GPIOs
* @direction_input: configures signal "offset" as input, or returns error
* @get: returns value for signal "offset"; for output signals this
* returns either the value actually sensed, or zero
@@ -55,6 +57,7 @@ struct module;
*/
struct gpio_chip {
char *label;
+ struct device *dev;
struct module *owner;

int (*direction_input)(struct gpio_chip *chip,
@@ -104,7 +107,19 @@ extern void __gpio_set_value(unsigned gp
extern int __gpio_cansleep(unsigned gpio);


-#else
+#ifdef CONFIG_GPIO_SYSFS
+#define HAVE_GPIO_SYSFS
+
+/*
+ * A sysfs interface can be exported by individual drivers if they want,
+ * but more typically is configured entirely from userspace.
+ */
+extern int gpio_export(unsigned gpio);
+extern void gpio_unexport(unsigned gpio);
+
+#endif /* CONFIG_GPIO_SYSFS */
+
+#else /* !CONFIG_HAVE_GPIO_LIB */

static inline int gpio_is_valid(int number)
{
@@ -135,4 +150,15 @@ static inline void gpio_set_value_cansle

#endif

+#ifndef HAVE_GPIO_SYSFS
+static inline int gpio_export(unsigned gpio)
+{
+ return -ENOSYS;
+}
+
+static inline void gpio_unexport(unsigned gpio)
+{
+}
+#endif /* HAVE_GPIO_SYSFS */
+
#endif /* _ASM_GENERIC_GPIO_H */
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/