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>] [day] [month] [year] [list]
Date:	Wed, 25 Jun 2008 16:28:34 +1000
From:	Ben Nizette <bn@...sdigital.com>
To:	David Brownell <david-b@...bell.net>
Cc:	linux-kernel <linux-kernel@...r.kernel.org>,
	Guennadi Liakhovetski <g.liakhovetski@....de>
Subject: [PATCH][RFC] gpiolib: async notification of gpio events


This patch adds poll/select functionality to the gpio sysfs interface's
"value" attribute.  It applies on top of the latest sysfs interface
patch along with the locking bugfix.

This patch hits .text size for 1.5k but .bss for an annoying 13k on
AVR32.  Owch.  This is mostly due to the per-descriptor workqueues.  We
can get around this by
	- Using one big delayed workqueue which scans each descriptor and acts
on it appropriately.  I don't love this approach, scanning each
descriptor (or keeping a list of descriptors needing attention) is
awkward.
	- Only creating the struct delayed_work when needed.  This isn't
completely trivial as that struct can no longer take a reference back to
private data and therefore must be container_of() friendly.

Also before final inclusion I'll do a docco update.

I've left this a little late (uni exams took more study than I'd
hoped :-) ) and I'll be on leave for 2 weeks from Monday.  I doubt
there's enough of a rush for this to be a problem though, eh?

Signed-off-by: Ben Nizette <bn@...sdigital.com>

---
diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c
index 02c1f44..4d75cbe 100644
--- a/drivers/gpio/gpiolib.c
+++ b/drivers/gpio/gpiolib.c
@@ -1,6 +1,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/irq.h>
+#include <linux/interrupt.h>
 #include <linux/spinlock.h>
 #include <linux/device.h>
 #include <linux/err.h>
@@ -49,10 +50,25 @@ struct gpio_desc {
 #define FLAG_RESERVED	2
 #define FLAG_EXPORT	3	/* protected by sysfs_lock */
 #define FLAG_SYSFS	4	/* exported via /sys/class/gpio/control */
+#define ASYNC_MODE_IRQ	5	/* using interrupts for async notification */
+#define ASYNC_MODE_POLL	6	/* using polling for async notification */
+#define ASYNC_RISING	7	/* will notify on rising edges */
+#define ASYNC_FALLING	8	/* will notify on falling edges */
 
 #ifdef CONFIG_DEBUG_FS
 	const char		*label;
 #endif
+
+#ifdef CONFIG_GPIO_SYSFS
+	struct device		*dev;
+
+	/* Poll interval in jiffies */
+	long			timeout;
+	struct delayed_work	work;
+
+	/* Last known value */
+	int			val;
+#endif
 };
 static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
 
@@ -173,12 +189,60 @@ static DEFINE_MUTEX(sysfs_lock);
  *   /value
  *      * always readable, subject to hardware behavior
  *      * may be writable, as zero/nonzero
- *
- * REVISIT there will likely be an attribute for configuring async
- * notifications, e.g. to specify polling interval or IRQ trigger type
- * that would for example trigger a poll() on the "value".
+ *   /notify
+ *      * read/write as "irq", "poll" or "none"
+ *   /notify_filter
+ *      * read/write as "rising", "falling" or "both"
+ *   /notify_interval
+ *      * write the preferred interval (in ms) between polls of
+ *        the pin.
+ *      * read the actual interval between polls, accounting
+ *        for timer resolution etc.
  */
 
+struct gpio_desc *work_to_desc(struct work_struct *ws)
+{
+	return container_of((struct delayed_work *)ws, struct gpio_desc, work);
+}
+
+void gpio_poll_work(struct work_struct *ws)
+{
+	struct gpio_desc	*desc = work_to_desc(ws);
+	int			gpio = desc - gpio_desc;
+
+	int new = gpio_get_value_cansleep(gpio);
+	int old = desc->val;
+
+	if ((new && !old && test_bit(ASYNC_RISING, &desc->flags)) ||
+	    (!new && old && test_bit(ASYNC_FALLING, &desc->flags)))
+		sysfs_notify(&desc->dev->kobj, NULL, "value");
+
+	desc->val = new;
+
+	schedule_delayed_work(&desc->work, desc->timeout);
+}
+
+static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
+{
+	struct gpio_desc *desc = dev_id;
+	int gpio = desc - gpio_desc;
+	int new, old;
+
+	if (!gpio_is_valid(gpio))
+		return IRQ_NONE;
+
+	new = gpio_get_value(gpio);
+	old = desc->val;
+
+	if ((new && !old && test_bit(ASYNC_RISING, &desc->flags)) ||
+	    (!new && old && test_bit(ASYNC_FALLING, &desc->flags)))
+		sysfs_notify(&desc->dev->kobj, NULL, "value");
+
+	desc->val = new;
+
+	return IRQ_HANDLED;
+}
+
 static ssize_t gpio_direction_show(struct device *dev,
 		struct device_attribute *attr, char *buf)
 {
@@ -273,9 +337,185 @@ static ssize_t gpio_value_store(struct device *dev,
 static /*const*/ DEVICE_ATTR(value, 0644,
 		gpio_value_show, gpio_value_store);
 
+static ssize_t gpio_notify_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	const struct gpio_desc	*desc = dev_get_drvdata(dev);
+	ssize_t ret;
+
+	mutex_lock(&sysfs_lock);
+
+	if (test_bit(ASYNC_MODE_IRQ, &desc->flags))
+		ret = sprintf(buf, "irq\n");
+	else if (test_bit(ASYNC_MODE_POLL, &desc->flags))
+		ret = sprintf(buf, "poll\n");
+	else
+		ret = sprintf(buf, "none\n");
+
+	mutex_unlock(&sysfs_lock);
+	return ret;
+}
+
+static ssize_t gpio_notify_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct gpio_desc	*desc = dev_get_drvdata(dev);
+	unsigned		gpio = desc - gpio_desc;
+	ssize_t			status = 0;
+	int			new;
+
+	mutex_lock(&sysfs_lock);
+
+	if (sysfs_streq(buf, "irq"))
+		new = ASYNC_MODE_IRQ;
+	else if (sysfs_streq(buf, "poll"))
+		new = ASYNC_MODE_POLL;
+	else if (sysfs_streq(buf, "none"))
+		new = -1;
+	else {
+		status = -EINVAL;
+		goto out;
+	}
+
+	if (test_and_clear_bit(ASYNC_MODE_IRQ, &desc->flags))
+		free_irq(gpio_to_irq(gpio), desc);
+	else if (test_and_clear_bit(ASYNC_MODE_POLL, &desc->flags))
+		cancel_delayed_work(&desc->work);
+
+	if (new == ASYNC_MODE_IRQ) {
+		if (desc->chip->can_sleep) {
+			/* -EINVAL probably isn't appropriate here,
+			 * what's code for "not supported by
+			 * underlying hardware"? */
+			status = -EINVAL;
+			goto out;
+		}
+
+		desc->val = gpio_get_value(gpio);
+
+		/* REVISIT: We always request both edges then filter in the
+		 * irq handler.  How many devices don't support this..?? */
+		status = request_irq(gpio_to_irq(gpio), gpio_irq_handler,
+				     IRQF_SHARED | IRQF_TRIGGER_RISING |
+						IRQF_TRIGGER_FALLING,
+				     "gpiolib", desc);
+	} else if (new == ASYNC_MODE_POLL) {
+		desc->val = gpio_get_value_cansleep(gpio);
+		INIT_DELAYED_WORK(&desc->work, gpio_poll_work);
+		schedule_delayed_work(&desc->work, desc->timeout);
+	}
+
+	if (status) {
+		dev_err(dev, "Can't set gpio notification mode to %s\n", buf);
+		goto out;
+	}
+
+	if (new >= 0)
+		set_bit(new, &desc->flags);
+
+out:
+	mutex_unlock(&sysfs_lock);
+	return status ? : size;
+}
+
+static const DEVICE_ATTR(notify, 0644,
+		gpio_notify_show, gpio_notify_store);
+
+static ssize_t gpio_notify_interval_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	const struct gpio_desc	*desc = dev_get_drvdata(dev);
+
+	long ms = desc->timeout * 1000 / HZ;
+
+	return sprintf(buf, "%ld\n", ms);
+}
+
+static ssize_t gpio_notify_interval_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct gpio_desc	*desc = dev_get_drvdata(dev);
+	long			value, rnd;
+	ssize_t			status;
+
+	mutex_lock(&sysfs_lock);
+
+	status = strict_strtol(buf, 0, &value);
+
+	if (status)
+		goto out;
+
+	/* 10ms arbitrary limit on poll interval to stop DoS */
+	if (value < 10) {
+		status = -EINVAL;
+		goto out;
+	}
+
+	/* value will be in ms, convert to jiffies, minimum 1. */
+	desc->timeout = DIV_ROUND_UP(value * HZ, 1000);
+	rnd = desc->timeout * 1000 / HZ;
+
+out:
+	mutex_unlock(&sysfs_lock);
+	return status ? : size;
+}
+
+static const DEVICE_ATTR(notify_interval, 0644,
+		gpio_notify_interval_show, gpio_notify_interval_store);
+
+static ssize_t gpio_notify_filter_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct gpio_desc	*desc = dev_get_drvdata(dev);
+	ssize_t			status;
+
+	mutex_lock(&sysfs_lock);
+
+	if (test_bit(ASYNC_FALLING, &desc->flags) &&
+			test_bit(ASYNC_RISING, &desc->flags))
+		status = sprintf(buf, "both\n");
+	else if (test_bit(ASYNC_RISING, &desc->flags))
+		status = sprintf(buf, "rising\n");
+	else
+		status = sprintf(buf, "falling\n");
+
+	mutex_unlock(&sysfs_lock);
+	return status;
+}
+
+static ssize_t gpio_notify_filter_store(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t size)
+{
+	struct gpio_desc	*desc = dev_get_drvdata(dev);
+	ssize_t			status = 0;
+
+	mutex_lock(&sysfs_lock);
+
+	if (sysfs_streq(buf, "rising")) {
+		set_bit(ASYNC_RISING, &desc->flags);
+		clear_bit(ASYNC_FALLING, &desc->flags);
+	} else if (sysfs_streq(buf, "falling")) {
+		clear_bit(ASYNC_RISING, &desc->flags);
+		set_bit(ASYNC_FALLING, &desc->flags);
+	} else if (sysfs_streq(buf, "both")) {
+		set_bit(ASYNC_RISING, &desc->flags);
+		set_bit(ASYNC_FALLING, &desc->flags);
+	} else
+		status = -EINVAL;
+
+	mutex_unlock(&sysfs_lock);
+	return status ? : size;
+}
+
+static const DEVICE_ATTR(notify_filter, 0644,
+		gpio_notify_filter_show, gpio_notify_filter_store);
+
 static const struct attribute *gpio_attrs[] = {
 	&dev_attr_direction.attr,
 	&dev_attr_value.attr,
+	&dev_attr_notify.attr,
+	&dev_attr_notify_filter.attr,
+	&dev_attr_notify_interval.attr,
 	NULL,
 };
 
@@ -469,6 +709,14 @@ int gpio_export(unsigned gpio, bool direction_may_change)
 			status = -ENODEV;
 		if (status == 0)
 			set_bit(FLAG_EXPORT, &desc->flags);
+
+#ifdef CONFIG_GPIO_SYSFS
+		desc->dev = dev;
+		/* 100ms default poll interval */
+		desc->timeout = HZ / 10;
+		set_bit(ASYNC_RISING, &desc->flags);
+		set_bit(ASYNC_FALLING, &desc->flags);
+#endif
 	}
 
 	mutex_unlock(&sysfs_lock);


--
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