lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <200804281239.51729.david-b@pacbell.net>
Date:	Mon, 28 Apr 2008 12:39:51 -0700
From:	David Brownell <david-b@...bell.net>
To:	lkml <linux-kernel@...r.kernel.org>
Cc:	Trent Piepho <tpiepho@...escale.com>,
	hartleys <hartleys@...ionengravers.com>,
	Ben Nizette <bn@...sdigital.com>,
	Mike Frysinger <vapier.adi@...il.com>,
	Bryan Wu <cooloney@...nel.org>
Subject: [patch/rfc 2.6.25-git] gpio: sysfs interface

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@...escale.com>, and comments
from various folk including Hartley Sweeten.

Signed-off-by: David Brownell <dbrownell@...rs.sourceforge.net>
---
 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@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ