public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/2] external interrupts: abstraction layer
@ 2005-07-28 21:13 Brent Casavant
  0 siblings, 0 replies; 6+ messages in thread
From: Brent Casavant @ 2005-07-28 21:13 UTC (permalink / raw)
  To: linux-kernel

This patch implements an abstraction layer for external interrupt devices.
It creates a new sysfs class "extint" which provides a number of read-write
and a few read-only attributes which can be used to control a lower-level
hardware-specific external interrupt device driver.

The abstraction layer provides a mechanism to insulate applications from
the details of the capabilities and mechanisms of a particular external
interrupt device.  It also greatly simplifies low-level drivers.

Signed-off-by: Brent Casavant <bcasavan@sgi.com>

 Documentation/extint.txt        |  431 +++++++++++++++++++++++++
 arch/ia64/configs/sn2_defconfig |    1 
 arch/ia64/defconfig             |    1 
 drivers/char/Kconfig            |    7 
 drivers/char/Makefile           |    1 
 drivers/char/extint.c           |  673 ++++++++++++++++++++++++++++++++++++++++
 include/linux/extint.h          |  115 ++++++
 7 files changed, 1229 insertions(+)

diff --git a/Documentation/extint.txt b/Documentation/extint.txt
new file mode 100644
--- /dev/null
+++ b/Documentation/extint.txt
@@ -0,0 +1,431 @@
+		  External Interrupt Abstraction Layer Driver
+		       Brent Casavant <bcasavan@sgi.com>
+
+Things you might ask yourself right away
+========================================
+
+What is an external interrupt?
+------------------------------
+
+Some types of applications, particuarly realtime process-control or
+simulations, need the ability to react to some simple external event.
+One way of doing this is special hardware that generates an interrupt
+whenever some external signal is applied to it.  A good example is
+the IO9 and IO10 cards on SGI Altix machines, which have an 1/8"
+stereo-style jack where a 0-5V signal can be fed into it.  Some
+outside piece of hardware can wiggle this line to cause the IOC4
+chip on these cards to generate an interrupt.
+
+Anything else to it?
+--------------------
+
+Besides being able to receive these interrupts, sometimes you'd like to
+generate similar signals for use by the outside world.  The abstraction
+layer also provides a signal output control mechanism.
+
+Why an abstraction layer?
+-------------------------
+
+Different chips might implement the external interrupt feature in
+very different ways, but in the end you just want to know when an
+interrupt occurs, perhaps have an ongoing count of these interrupts,
+and select the source of those interrupts.  An abstraction layer
+gives us this capability without needing to depend on specifics
+of the device being used.
+
+What an application or user cares about
+=======================================
+
+The external interrupt abstraction layer provides a character device,
+and several sysfs attributes to control operation.  Assuming the
+usual /sys mount-point for sysfs, these files are:
+
+	/sys/class/extint/extint#/dev
+				  mode
+				  modelist
+				  period
+				  provider
+				  quantum
+				  source
+				  sourcelist
+
+The "extint#" component of this path is determined by the extint driver
+itself, with the "#" replaced by a number (possibly multi-digit), one
+per external interrupt device beginning at zero.  There will be one of
+these dirctories per external interrupt device.
+
+The "dev" attribute contains the major and minor number of the abstracted
+external interrupt device.  If Linux's sysfs, hotplug, and udev are
+configured appropriately, udev will automatically create a /dev/extint#
+character special device file with this major and minor number.  If
+you prefer, you may manually invoke mknod(1) to create the character
+special device file.
+
+Once created, this device file provides a counter that can be used by
+applications in a variety of ways.  It can be memory mapped read-only
+as a single page, in which case the first unsigned long word of the
+page contains a counter that is incremented each time an interrupt
+occurs.  You can use poll(2) and select(2) for reading on a read-only
+file descriptor opened on the device, in which case the poll will
+indicate whether an interrupt has occurred since the last read(2)
+or open(2) of the file, and select will return when the next interrupt
+is received.  The file can also be the subject of read(2), in which
+case it returns an string representation of the value of the counter.
+
+The "source" attribute can be written to set the hardware source of
+interrupts (e.g. SGI's IOC4 chip can trigger either from the external
+pin, or an internal loopback from its interrupt output section).  It
+can be read to determine the current setting of the source.
+
+The "sourcelist" attribute can be read to determine the list of available
+interrupt sources, one per line.  These strings are the legal values
+which can be written to the "source" attribute.
+
+The "mode" attribute can be written to set the shape of the output
+signal for interrupt generation.  For example, SGI's IOC4 chip can
+set the output to a logic low, a logic high, strobe from low to high
+to low one time, set up a repeating strobe, or repeatedly toggle between
+high and low.  It can be read to determine the current setting of the
+mode.
+
+The "modelist" attribute can be read to determine the list of available
+output modes, one per line.  These strings are the legal values which
+can be written to the "mode" attribute.  (Note that at least in the
+case of the SGI IOC4 chip, there are other values which may be read
+from the "mode" attribute which don't appear in "modelist"; these
+represent invalid hardware states.  Only the modes present from
+the modelist are valid settings to be written to the mode attribute.)
+
+The "period" attribute can be written to set the repetition interval
+for periodic output signals (e.g. repeated strobes, automatic toggling).
+This period should be specified in nanoseconds, and should be written
+as a string.  It can be read to determine the current period setting.
+
+The "quantum" attribute can be read to determine the interval to which
+any writes of the "period" attribute will be rounded.  External
+interrupt output hardware may not support nanosecond granularity
+for output periods -- this attribute allows you to determine the
+supported granularity.  The behavior of the interrupt output when
+a value which is not a multiple of the quantum is written to the
+"period" attribute is determined by the specific low-level external
+interrupt driver, however generally the low-level driver should round
+to the nearest available quantum multiple.
+
+The "provider" attribute can be read to obtain an indication of which
+low-level hardware driver and device instance is attached to the
+external interrupt interface.  This string is free-form and determined
+by the low-level driver.  For example, the SGI IOC4 low-level driver
+will return a string of the form "ioc4_intout#".
+
+What a low-level external interrupt driver writer cares about
+=============================================================
+
+The interface to the abstraction layer driver is provided through
+the extint_properties and extint_device structures as defined in
+<linux/extint.h>, and the function prototypes contained therein.
+
+Driver registration
+-------------------
+To register the low-level driver with the abstraction layer, a
+call is made to:
+
+	struct extint_device*
+	extint_device_register(struct extint_properties *ep,
+			       void *devdata);
+
+The "ep" argument is a pointer to an extint_properties structure, which
+specifies the particular low-level driver functions the abstraction layer
+should call when reading/writing the attributes described in the previous
+section.  This is described below.
+
+The "devdata" argument is an opaque pointer which is stored by the
+extint code.  This value can be retrieved or modified via
+
+	void* extint_get_devdata(const struct extint_device *ed);
+	void extint_set_devdata(struct extint_device *ed, void* devdata);
+
+respectively.  This value can be used by the low-level driver to
+determine which of multiple devices it is operating upon, or whatever
+purpose may be desired.  This is described below.
+
+The return value is either a pointer to a struct extint_device (which
+should be saved for later interrupt notification and driver deregistration),
+or a negative error value in case of registration failure.  The driver
+should be prepared to deal with such failures.
+
+Implementation functions
+------------------------
+
+The struct extint_properties is as follows:
+
+struct extint_properties {
+	struct module *owner;
+	ssize_t (*get_mode)(struct extint_device *ed, char *buf);
+	ssize_t (*set_mode)(struct extint_device *ed, const char *buf,
+			    size_t count);
+	ssize_t (*get_modelist)(struct extint_device *ed, char *buf);
+	unsigned long (*get_period)(struct extint_device *ed);
+	ssize_t (*set_period)(struct extint_device *ed, unsigned long period);
+	ssize_t (*get_provider)(struct extint_device *ed, char *buf);
+	unsigned long (*get_quantum)(struct extint_device *ed);
+	ssize_t (*get_source)(struct extint_device *ed, char *buf);
+	ssize_t (*set_source)(struct extint_device *ed, const char *buf,
+			      size_t count);
+	ssize_t (*get_sourcelist)(struct extint_device *ed, char *buf);
+};
+
+(Note: Additional fields not of interest to the low-level external interrupt
+driver may be present -- drivers are encouraged to include linux/extint.h
+to acquire this structure definition.)
+
+"owner" should be set to the module which contains the functions
+pointed to by the remaining structure members.
+
+The remaining functions implement low-level aspects of the abstraction
+layer attributes.  They all take a pointer to the struct extint_device
+as was returned from the registration function.  In all of these functions,
+the value passed as the "devdata" argument to the registration function
+can be retrieved via:
+
+	extint_get_devdata(ed);
+
+And can be updated via:
+
+	extint_set_devdata(ed, newvalue);
+
+Typically this value is a pointer to driver-specific data for the
+individual device being operated upon.  It may, for example, contain
+pointers to mapped PCI regions where control registers reside.
+
+"get_mode" and "set_mode" implement the "mode" attribute of the abstraction
+layer.  "get_mode" should write the current mode into the single-page sized
+buffer passed as the second argument, and return the length of the written
+string.  "set_mode" should read the mode specified in the buffer passed as
+the second argument, and as sized by the third, and return the number of
+characters consumed (or a negative error number in event of failure).
+It should of course also cause the output mode to be set as requested.
+
+"get_modelist" implements the "modelist" attribute of the abstraction
+layer.  "get_modelist" should write strings representing the available
+interrupt output generation modes into the single-page sized buffer
+passed as the second argument, one mode per line.  It should return
+the number of bytes written into this buffer.
+
+"get_period" and "set_period" implement the "period" attribute of the
+abstraction layer.  "get_period" should return an unsigned long which
+represents the current repetition period in nanoseconds.  "set_period"
+should accept an unsigned long as the new value for the repetition
+period, specified in nanoseconds, and returning either 0 or a negative
+error number indicating a failure.  If the requested repetition period
+is not a value which can be exactly set into the underlying hardware,
+the driver is free to adjust the value as it sees fit, though tyipically
+it should round the value to the nearest available value.
+
+"get_provider" implements the "provider" attribute of the abstraction
+layer.  "get_provider" should write a human-readable string which
+identifies the low-level driver and a particular instance of a the
+driven hardware device.  For example, if the low-level driver provides
+its own additional device files for extra functionality not present
+in the abstraction layer, this routine might emit the name of the
+driver module and the names (or device numbers) of the low-level
+driver's own character special device files.
+
+"get_quantum" implements the "quantum" attribute of the abstraction
+layer.  "get_quantum" should return an unsigned long which represents
+the granularity to which the interrupt output repetition period can
+be set, in nanoseconds.
+
+"get_source" and "set_source" implement the "source" attribute of the
+abstraction layer.  "get_source" should write the current interrupt
+input source into the single-page sized buffer passed as the second
+argument, and return the length of the written string.  "set_source"
+should read the source specified in the buffer passed as the second
+argument, and as sized by the third, and return the number of characters
+consumed (or a negative error number in event of failure).   It should
+of course also cause the input source to be selected as requested.
+
+"get_sourcelist" implements the "sourcelist" attribute of the abstraction
+layer.  "get_sourcelist" should write strings representing the available
+interrupt input sources into the single-page sized buffer passed as the
+second argument, one source per line.  It should return the number of
+bytes written into this buffer.
+
+When an interrupt occurs
+------------------------
+
+When an external interrupt signal triggers an interrupt that is
+handled by the low-level driver, the driver should call:
+
+	void
+	extint_interrupt(struct extint_device *ed);
+
+This allows the abstraction layer to perform any appropriate
+abstracted actions (i.e. update the interrupt count, trigger
+poll/select actions, etc).  The sole argument is the struct
+extint_device which was returned from the registration call.
+
+Driver deregistration
+---------------------
+
+When the driver desires to unregister a particular device previously
+registered with the abstraction layer, it should call:
+
+	void
+	extint_device_unregister(struct extint_device *ed);
+
+The sole argument is the struct extint_device which was returned
+from the registration call.  There is no error return from this
+call, however if invalid data is passed to it the likelihood of
+a kernel panic is very high indeed.
+
+What a kernel-level external interrupt user cares about
+=======================================================
+
+In addition to the user-visible aspects of the external interrupt
+abstraction layer, there is a kernel-only interface available for
+interrupt notification.  This interface provides the ability for
+other kernel modules to register a callout to be invoked whenever
+an external interrupt is ingested for a particular device.
+
+Callout registration
+--------------------
+
+To register a callout to be invoked upon interrupt ingest, a
+struct extint_callout should be allocated, filled in, and
+passed to:
+
+	int
+	extint_callout_register(struct extint_device *ed,
+				struct extint_callout *ec);
+
+The first argument is the struct extint_device corresponding to
+the particular abstracted external interrupt hardware device of
+interest.  How exactly this structure is found is up to the
+caller, however the "file_to_extint_device" function will convert
+a struct file pointer to a struct extint_device pointer.  This
+function will return -EINVAL if an inappropriate file descriptor
+is passed to it.
+
+The second argument is one of the following structures:
+
+struct extint_callout {
+	struct module* owner;
+	void (*function)(void *);
+	void *data;
+};
+
+(Note: Additional fields not of interest to the external interrupt user
+may be present -- drivers are encouraged to include linux/extint.h
+to acquire this structure definition.)
+
+The "owner" field should be set to the module containing the function
+and data pointed to by the remaining fields.
+
+The "function" pointer is a callout function which is to be invoked
+whenever an interrupt is ingested by the abstraction layer for the
+device of interest.  It will be passed as its sole argument the
+"data" field, which is used opaquely and is provided solely for use
+by the caller.  That is, the abstraction layer will invoke:
+
+	ec->function(data);
+
+upon each interrupt of the specified device.
+
+Multiple callouts can be registered for the same abstracted external
+interrupt device.  They will be invoked in no guaranteed order, but
+will be invoked one at a time.
+
+The interrupt counter will be incremented before the callouts are
+invoked, but before any signal/poll notifications occur.
+
+The module specified by the "owner" field in the callout structure,
+as well as the module corresponding to the low-level external interrupt
+device driver, will have their reference counts increased by one until
+the callout is deregistered.
+
+Callout deregistration
+----------------------
+
+To remove a callout, simply call:
+
+	extern void
+	extint_callout_unregister(struct extint_device *ed,
+				  struct extint_callout *ec);
+
+With the same arguments as provided during callout registration.
+Both active and orphaned callouts can be removed in this manner
+with no distinction between the two.
+
+The callout function must continue to be able to be invoked
+until the call to extint_callout_unregister completes.
+
+Things you might ask yourself at the end
+========================================
+
+What if my hardware device supports a capability not in the abstraction?
+------------------------------------------------------------------------
+
+There are two possibilities.  The first would be to add a new attribute
+to the abstraction, modify struct extint_properties to add appropriate
+interface routines, and update any existing drivers as necessary.
+
+The second, and generally preferable method, is for the the
+low-level driver to create its own device class and corresponding
+attributes and/or character special devices.  This is definitely
+the correct route to take if the capability is dependent on the
+hardware in a method that cannot be abstracted.
+
+A good example is the SGI IOC4's ability to map the interrupt output
+control register directly into a user application to avoid the kernel
+overhead of reading/writing the abstracted attribute files.  Using
+this capability means that the application must have intimate knowledge
+of the format of the control register -- something which both cannot
+be abstracted away by the kernel, and which is very specific to this
+particular IO controller chip.  This capability is provided by the
+ioc4_extint driver supplying its own character special device along
+with an ioc4_intout device class.
+
+Is there an example low-level driver to pattern mine after?
+-----------------------------------------------------------
+
+Sure.  linux/drivers/char/ioc4_extint.c
+
+Note that this low-level driver in addition to providing the
+abstraction interface, creates an IOC4-specific character
+special device and an IOC4-specific device class, as mentioned
+in the answer to the previous question.
+
+Why the callout mechanism?
+--------------------------
+
+For systems (not just applications, we're taking a higher-level view
+here) which are critically interested in responding as quickly as
+possible to an externally triggered event, waiting for a poll/select
+operation, or even busy-waiting on the value of the interrupt counter
+to change may not provide appropriate response times or have other
+deleterious effects (i.e. tieing up a CPU spinning on a value).  This
+gives the system architecht a tool to gain minimal-latency notification
+of events, and react accordingly, by writing their own kernel module.
+
+It also provides an extension capability that might be of interest
+in certain scenarios.  For example, there could be an application
+that wants a interrupt counter page much as maintained by the
+abstraction layer, but which starts counting at zero when the
+device special file is opened.  Or, there could be an application
+which wants a signal to be generated and delivered to the process
+when an interrupt is ingested.  These examples are more esoteric
+than the simple counter page, and are best provided by a seperate
+module rather than cluttering the main external interrupt abstraction
+code.
+
+Why wasn't this made compatible with IRIX's ei(7) driver from an
+application perspective?
+----------------------------------------------------------------
+
+IRIX's driver uses ioctl(2) calls to interact with user space.  This
+is frowned upon in Linux, and generally doesn't perform very well on
+Linux due to kernel locking issues.
+
+One advantage gained by this mechanism is control methods which are
+easily utilized from the command-line, rather than requiring specially
+written and compiled applications to function the device.
diff --git a/arch/ia64/configs/sn2_defconfig b/arch/ia64/configs/sn2_defconfig
--- a/arch/ia64/configs/sn2_defconfig
+++ b/arch/ia64/configs/sn2_defconfig
@@ -630,6 +630,7 @@ CONFIG_MMTIMER=y
 #
 # Misc devices
 #
+CONFIG_EXTINT=m
 
 #
 # Multimedia devices
diff --git a/arch/ia64/defconfig b/arch/ia64/defconfig
--- a/arch/ia64/defconfig
+++ b/arch/ia64/defconfig
@@ -717,6 +717,7 @@ CONFIG_MMTIMER=y
 #
 # Misc devices
 #
+CONFIG_EXTINT=m
 
 #
 # Multimedia devices
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -413,6 +413,13 @@ config SGI_MBCS
          If you have an SGI Altix with an attached SABrick
          say Y or M here, otherwise say N.
 
+config EXTINT
+	tristate "Abstraction layer for external interrupt devices"
+	help
+	  This option provides an abstraction layer for external
+	  interrupt devices (such as SGI IOC3 and IOC4 IO controllers).
+	  If you have such a device, say Y. Otherwise, say N.
+
 source "drivers/serial/Kconfig"
 
 config UNIX98_PTYS
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_NWFLASH) += nwflash.o
 obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
 obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
 obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+obj-$(CONFIG_EXTINT) += extint.o
 
 obj-$(CONFIG_WATCHDOG)	+= watchdog/
 obj-$(CONFIG_MWAVE) += mwave/
diff --git a/drivers/char/extint.c b/drivers/char/extint.c
new file mode 100644
--- /dev/null
+++ b/drivers/char/extint.c
@@ -0,0 +1,673 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2005 Silicon Graphics, Inc.  All Rights Reserved.
+ */
+
+/* This file provides an abstraction for lowlevel external interrupt
+ * operation.
+ *
+ * External interrupts are hardware mechanisms to generate or ingest
+ * a simple interrupt signal.
+ *
+ * Generation typically involves driving an output circuit voltage
+ * level, with a variety of single or recurring waveforms (e.g.
+ * a one-shot pulse, a square wave, etc.)  The repetition period
+ * for recurring waveforms can be set within hardware restrictions.
+ *
+ * Ingest typically involves responding to an input circuit voltage
+ * level or transition.  Multiple input sources may be available.
+ *
+ * 2005.07.27	Brent Casavant <bcasavan@sgi.com> Initial code
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/ctype.h>
+#include <linux/err.h>
+#include <linux/extint.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/kallsyms.h>
+#include <linux/device.h>
+#include <linux/poll.h>
+
+/**********************
+ * Module global data *
+ **********************/
+
+/* Device numbers */
+#define EXTINT_NUMDEVS	255	/* Number of minor devices to reserve */
+static dev_t firstdev;		/* Start of dynamic range */
+static dev_t nextdev;		/* Next number to assign */
+static DEFINE_SPINLOCK(nextdev_lock);
+
+/* Device status.  Controls whether new callouts can be registered. */
+enum extint_state {
+	EXTINT_COMING,
+	EXTINT_ALIVE,
+	EXTINT_GOING,
+	EXTINT_DEAD
+};
+
+/**********************
+ * Abstracted devices *
+ **********************/
+
+static struct page *extint_counter_vma_nopage(struct vm_area_struct *vma,
+					      unsigned long address, int *type)
+{
+	struct extint_device *ed = vma->vm_private_data;
+	struct page *page;
+
+	/* Only a single page is ever mapped */
+	if (address >= vma->vm_start + PAGE_SIZE)
+		return NOPAGE_SIGBUS;
+
+	/* virt_to_page can be expensive, but this is executed
+	 * only once each time the counter page is mapped.
+	 */
+	page = virt_to_page(ed->counter_page);
+	get_page(page);
+
+	if (type)
+		*type = VM_FAULT_MINOR;
+
+	return page;
+}
+
+static struct vm_operations_struct extint_counter_vm_ops = {
+	.nopage = extint_counter_vma_nopage,
+};
+
+static int extint_counter_open(struct inode *inode, struct file *filp)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+
+	/* Counter is always read-only */
+	if (filp->f_mode & FMODE_WRITE)
+		return -EPERM;
+
+	/* Prevent low-level module from unloading while
+	 * corresponding abstracted device is open
+	 */
+	if (!try_module_get(ed->props->owner))
+		return -ENXIO;
+
+	/* Snapshot initial value, for later use by poll */
+	filp->private_data = (void *)*ed->counter_page;
+
+	return 0;
+}
+
+static int extint_counter_release(struct inode *inode, struct file *filp)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+
+	/* Allow low-level module to unload now that the
+	 * corresponding abstracted device is really closed.
+	 */
+	module_put(ed->props->owner);
+
+	return 0;
+}
+
+static ssize_t
+extint_counter_read(struct file *filp, char *buff, size_t count, loff_t * offp)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+	char outbuff[21];	/* 20 chars for value of 2^64, plus \0 */
+
+	/* Snapshot last value read, for later use by poll */
+	memset(outbuff, 0, 21);
+	filp->private_data = (void *)*ed->counter_page;
+	snprintf(outbuff, 21, "%ld", (unsigned long)filp->private_data);
+	outbuff[20] = '\0';
+
+	return simple_read_from_buffer(buff, count, offp, outbuff,
+				       strlen(outbuff));
+}
+
+static int extint_counter_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+
+	if ((PAGE_SIZE != vma->vm_end - vma->vm_start) || (0 != vma->vm_pgoff))
+		return -EINVAL;
+
+	vma->vm_ops = &extint_counter_vm_ops;
+	vma->vm_flags |= VM_RESERVED;
+	vma->vm_flags &= ~(VM_WRITE | VM_MAYWRITE);	/* Read-only */
+	vma->vm_private_data = ed;
+	return 0;
+}
+
+static unsigned int
+extint_counter_poll(struct file *filp, struct poll_table_struct *wait)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+
+	poll_wait(filp, &ed->counter_queue, wait);
+
+	/* Check counter against last value read from it */
+	if (*ed->counter_page != (unsigned long)filp->private_data)
+		return (POLLIN | POLLRDNORM);
+
+	return 0;
+}
+
+static struct file_operations extint_fops = {
+	.owner = THIS_MODULE,
+	.open = extint_counter_open,
+	.release = extint_counter_release,
+	.read = extint_counter_read,
+	.mmap = extint_counter_mmap,
+	.poll = extint_counter_poll,
+};
+
+static int extint_device_create(struct extint_device *ed)
+{
+	int ret;
+
+	/* Allocate counter page */
+	ed->counter_page = (unsigned long *)get_zeroed_page(GFP_KERNEL);
+	if (!ed->counter_page) {
+		printk(KERN_WARNING
+		       "%s: failed to allocate extint counter page.\n",
+		       __FUNCTION__);
+		ret = -ENOMEM;
+		goto out_page;
+	}
+
+	/* Set up device */
+	init_waitqueue_head(&ed->counter_queue);
+	cdev_init(&ed->counter_cdev, &extint_fops);
+	ed->counter_cdev.owner = THIS_MODULE;
+	kobject_set_name(&ed->counter_cdev.kobj, "extint_counter%d",
+			 MINOR(ed->devt));
+	ret = cdev_add(&ed->counter_cdev, ed->devt, 1);
+	if (ret) {
+		printk(KERN_WARNING
+		       "%s: failed to add cdev for extint_counter%d.\n",
+		       __FUNCTION__, MINOR(ed->devt));
+		goto out_cdev;
+	}
+
+	return 0;
+
+out_cdev:
+	kobject_put(&ed->counter_cdev.kobj);
+	free_page((unsigned long)ed->counter_page);
+out_page:
+	return ret;
+}
+
+static void extint_device_destroy(struct extint_device *ed)
+{
+	BUG_ON(waitqueue_active(&ed->counter_queue));
+	cdev_del(&ed->counter_cdev);
+}
+
+/**************************
+ * Misc. class attributes *
+ **************************/
+
+static ssize_t extint_show_dev(struct class_device *cdev, char *buf)
+{
+	struct extint_device *ed = class_get_devdata(cdev);
+
+	return print_dev_t(buf, ed->devt);
+}
+
+/********************************
+ * Abstracted device attributes *
+ ********************************/
+
+#define classdev_to_extint_device(obj)	\
+	container_of(obj, struct extint_device, class_dev)
+
+/* Gets current mode (shape) of interrupt generation */
+static ssize_t extint_show_mode(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_mode))
+		rc = ed->props->get_mode(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Sets the mode (shape) of interrupt generation */
+static ssize_t extint_store_mode(struct class_device *cdev, const char *buf,
+				 size_t count)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->set_mode))
+		rc = ed->props->set_mode(ed, buf, count);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets available modes of interrupt generation */
+static ssize_t extint_show_modelist(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_modelist))
+		rc = ed->props->get_modelist(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets period (nanoseconds) of periodic modes of interrupt generation */
+static ssize_t extint_show_period(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_period))
+		rc = sprintf(buf, "%ld\n", ed->props->get_period(ed));
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+static ssize_t extint_show_provider(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_provider))
+		rc = ed->props->get_provider(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Sets period (nanoseconds) of periodic modes of interrupt generation */
+static ssize_t extint_store_period(struct class_device *cdev, const char *buf,
+				   size_t count)
+{
+	int rc;
+	char *endp;
+	unsigned long period;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	period = simple_strtoul(buf, &endp, 0);
+	if (*endp && !isspace(*endp))
+		return -EINVAL;
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->set_period)) {
+		rc = ed->props->set_period(ed, period);
+		if (!rc)
+			rc = count;	/* Swallow entire input */
+	} else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets rounding increment for interrupt generation periodic modes */
+static ssize_t extint_show_quantum(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props))
+		rc = sprintf(buf, "%ld\n", ed->props->get_quantum(ed));
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets current source of interrupt ingest */
+static ssize_t extint_show_source(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_source))
+		rc = ed->props->get_source(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Sets source of interrupt ingest */
+static ssize_t extint_store_source(struct class_device *cdev, const char *buf,
+				   size_t count)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->set_source))
+		rc = ed->props->set_source(ed, buf, count);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets list of available sources of interrupt ingest */
+static ssize_t extint_show_sourcelist(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_sourcelist))
+		rc = ed->props->get_sourcelist(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Release allocated memory when last reference to a device goes away */
+static void extint_class_release(struct class_device *cdev)
+{
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	BUG_ON(ed->state != EXTINT_DEAD);
+	BUG_ON(!list_empty(&ed->callouts));
+	kfree(ed);
+}
+
+static struct class extint_class = {
+	.name = "extint",
+	.release = extint_class_release,
+};
+
+#define DECLARE_ATTR(_name,_mode,_show,_store)	\
+{					 	\
+	.attr	= { .name = __stringify(_name),	\
+		    .mode = _mode,		\
+		    .owner = THIS_MODULE },	\
+	.show	= _show,			\
+	.store	= _store,			\
+}
+
+static struct class_device_attribute extint_class_device_attributes[] = {
+	DECLARE_ATTR(dev, 0444, extint_show_dev, NULL),
+	DECLARE_ATTR(mode, 0644, extint_show_mode, extint_store_mode),
+	DECLARE_ATTR(modelist, 0444, extint_show_modelist, NULL),
+	DECLARE_ATTR(period, 0644, extint_show_period, extint_store_period),
+	DECLARE_ATTR(provider, 0444, extint_show_provider, NULL),
+	DECLARE_ATTR(quantum, 0444, extint_show_quantum, NULL),
+	DECLARE_ATTR(source, 0644, extint_show_source, extint_store_source),
+	DECLARE_ATTR(sourcelist, 0444, extint_show_sourcelist, NULL),
+};
+
+/*************
+ * Interface *
+ *************/
+
+/* Register a low-level driver with the abstraction layer */
+struct extint_device *extint_device_register(struct extint_properties *ep,
+					     void *devdata)
+{
+	struct extint_device *ed;
+	int rc;
+	int i;
+
+	/* Create new control structure and initialize */
+	ed = kmalloc(sizeof(struct extint_device), GFP_KERNEL);
+	if (unlikely(!ed))
+		return ERR_PTR(-ENOMEM);
+	memset(ed, 0, sizeof(struct extint_device));
+
+	ed->state = EXTINT_COMING;
+	init_MUTEX(&ed->sem);
+	ed->props = ep;
+	INIT_LIST_HEAD(&ed->callouts);
+	spin_lock_init(&ed->callouts_lock);
+	extint_set_devdata(ed, devdata);
+
+	/* Allocate device number */
+	spin_lock(&nextdev_lock);
+	ed->devt = nextdev++;
+	spin_unlock(&nextdev_lock);
+	if (ed->devt > (firstdev + EXTINT_NUMDEVS)) {
+		rc = -ENOSPC;
+		goto out_devnum;
+	}
+
+	/* Add this device to the class */
+	ed->class_dev.class = &extint_class;
+	snprintf(ed->class_dev.class_id, BUS_ID_SIZE, "extint%d",
+		 MINOR(ed->devt));
+	class_set_devdata(&ed->class_dev, ed);
+	rc = class_device_register(&ed->class_dev);
+	if (rc)
+		goto out_class;
+
+	/* Create character device */
+	rc = extint_device_create(ed);
+	if (rc)
+		goto out_device;
+
+	/* Create attributes */
+	for (i = 0; i < ARRAY_SIZE(extint_class_device_attributes); i++) {
+		rc = class_device_create_file(&ed->class_dev,
+					      &extint_class_device_attributes
+					      [i]);
+		if (rc)
+			goto out_attr;
+	}
+
+	ed->state = EXTINT_ALIVE;
+	return ed;
+
+out_class:
+out_devnum:
+	ed->state = EXTINT_DEAD;
+	kfree(ed);
+	return ERR_PTR(rc);
+
+out_attr:
+	while (--i >= 0)
+		class_device_remove_file(&ed->class_dev,
+					 &extint_class_device_attributes[i]);
+out_device:
+	ed->state = EXTINT_DEAD;
+	class_device_unregister(&ed->class_dev);
+	/* extint_class_release frees ed for us */
+	return ERR_PTR(rc);
+}
+
+EXPORT_SYMBOL(extint_device_register);
+
+/* Unregister a previously registered low-level driver */
+void extint_device_unregister(struct extint_device *ed)
+{
+	int i;
+
+	if (!ed)
+		return;
+
+	/* Remove counter device */
+	ed->state = EXTINT_GOING;
+	BUG_ON(!list_empty(&ed->callouts));
+	extint_device_destroy(ed);
+
+	/* Remove all abstracted attributes */
+	for (i = 0; i < ARRAY_SIZE(extint_class_device_attributes); i++)
+		class_device_remove_file(&ed->class_dev,
+					 &extint_class_device_attributes[i]);
+
+	/* Make sure device-specific functions are never invoked again */
+	down(&ed->sem);
+	ed->props = NULL;
+	up(&ed->sem);
+	ed->state = EXTINT_DEAD;
+
+	/* Remove this device from the class */
+	class_device_unregister(&ed->class_dev);
+}
+
+EXPORT_SYMBOL(extint_device_unregister);
+
+/* Obtain extint_device structure from an open file */
+struct extint_device *file_to_extint_device(const struct file *filp)
+{
+	/* Validate that this really is an extint device file */
+	if (filp->f_dentry->d_inode->i_cdev->dev < firstdev ||
+	    filp->f_dentry->d_inode->i_cdev->dev > (firstdev + EXTINT_NUMDEVS))
+		return ERR_PTR(-EINVAL);
+
+	return container_of(filp->f_dentry->d_inode->i_cdev,
+			    struct extint_device, counter_cdev);
+}
+
+EXPORT_SYMBOL(file_to_extint_device);
+
+/* Register a callout function to invoke when an interrupt is ingested */
+int extint_callout_register(struct extint_device *ed, struct extint_callout *ec)
+{
+	int ret;
+	unsigned long flags;
+
+	/* Disallow unload of callout owner */
+	if (!try_module_get(ec->owner))
+		return -ENXIO;
+
+	/* Disallow unload of low-level driver */
+	if (!try_module_get(ed->props->owner)) {
+		module_put(ec->owner);
+		return -ENXIO;
+	}
+
+	spin_lock_irqsave(&ed->callouts_lock, flags);
+	switch (ed->state) {
+	case EXTINT_COMING:
+		ret = -EAGAIN;
+		module_put(ed->props->owner);
+		module_put(ec->owner);
+		break;
+	case EXTINT_ALIVE:
+		list_add(&ec->list, &ed->callouts);
+		ret = 0;
+		break;
+	default:
+		ret = -EBUSY;
+		module_put(ed->props->owner);
+		module_put(ec->owner);
+		break;
+	}
+	spin_unlock_irqrestore(&ed->callouts_lock, flags);
+
+	return ret;
+}
+
+EXPORT_SYMBOL(extint_callout_register);
+
+/* Unregister a previously registered callout function */
+void extint_callout_unregister(struct extint_device *ed,
+			       struct extint_callout *ec)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ed->callouts_lock, flags);
+	list_del(&ec->list);
+	spin_unlock_irqrestore(&ed->callouts_lock, flags);
+
+	/* Allow callout owner to unload */
+	module_put(ec->owner);
+	/* Allow low-level driver to unload */
+	module_put(ed->props->owner);
+}
+
+EXPORT_SYMBOL(extint_callout_unregister);
+
+/* Allows a low-level driver to notify the
+ * abstraction layer of an ingested interrupt.
+ */
+void extint_interrupt(struct extint_device *ed)
+{
+	struct extint_callout *ec;
+
+	/* Bump global counter */
+	(*ed->counter_page)++;
+
+	/* Invoke all registered callouts */
+	spin_lock(&ed->callouts_lock);
+	list_for_each_entry(ec, &ed->callouts, list)
+		ec->function(ec->data);
+	spin_unlock(&ed->callouts_lock);
+
+	/* Wake up poll/select waiters */
+	wake_up_all(&ed->counter_queue);
+}
+
+EXPORT_SYMBOL(extint_interrupt);
+
+/*********************
+ * Module management *
+ *********************/
+
+static int __devinit extint_init(void)
+{
+	int ret;
+
+	/* Reserve a block of device numbers */
+	ret = alloc_chrdev_region(&firstdev, 0, EXTINT_NUMDEVS, "extint");
+	if (ret) {
+		printk(KERN_WARNING
+		       "%s: failed to allocate external interrupt "
+		       "device numbers.\n", __FUNCTION__);
+		return ret;
+	}
+	nextdev = firstdev;
+
+	return class_register(&extint_class);
+}
+
+static void __devexit extint_exit(void)
+{
+	class_unregister(&extint_class);
+
+	unregister_chrdev_region(firstdev, EXTINT_NUMDEVS);
+}
+
+module_init(extint_init);
+module_exit(extint_exit);
+
+MODULE_AUTHOR("Brent Casavant - Silicon Graphics, Inc. <bcasavan@sgi.com>");
+MODULE_DESCRIPTION("External interrupt abstraction class module");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/extint.h b/include/linux/extint.h
new file mode 100644
--- /dev/null
+++ b/include/linux/extint.h
@@ -0,0 +1,115 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2005 Silicon Graphics, Inc.  All Rights Reserved.
+ */
+
+/* External interrupt control abstraction */
+
+#ifndef _LINUX_EXTINT_H
+#define _LINUX_EXTINT_H
+
+#include <linux/device.h>
+
+struct extint_device;
+
+struct extint_properties {
+	/* Owner module */
+	struct module *owner;
+
+	/* Get/set generation mode */
+	ssize_t (*get_mode)(struct extint_device * ed, char *buf);
+	ssize_t (*set_mode)(struct extint_device * ed, const char *buf,
+			     size_t count);
+
+	/* Get generation mode list */
+	ssize_t (*get_modelist)(struct extint_device * ed, char *buf);
+
+	/* Get/set generation period */
+	unsigned long (*get_period)(struct extint_device * ed);
+	ssize_t (*set_period)(struct extint_device * ed, unsigned long period);
+
+	/* Get low-level provider name */
+	ssize_t (*get_provider)(struct extint_device *ed, char *buf);
+
+	/* Generation period quantum */
+	unsigned long (*get_quantum)(struct extint_device * ed);
+
+	/* Get/set ingest source */
+	ssize_t (*get_source)(struct extint_device * ed, char *buf);
+	ssize_t (*set_source)(struct extint_device * ed, const char *buf,
+			      size_t count);
+
+	/* Get ingest source list */
+	ssize_t (*get_sourcelist)(struct extint_device * ed, char *buf);
+};
+
+struct extint_device {
+	/* Current status of device */
+	int state;
+
+	/* Semaphore protects 'props' field.  If 'props' is NULL, the
+	 * driver that registered this device has been unloaded, and
+	 * if class_get_devdata() points to something in the body of
+	 * that driver, it is also invalid.
+	 */
+	struct semaphore sem;
+	struct extint_properties *props;	/* Downcalls */
+
+	/* A list of callouts to invoke whenever this device ingests
+	 * an interrupt.
+	 */
+	struct list_head callouts;
+	spinlock_t callouts_lock;
+
+	/* Mappable counter page support */
+	struct cdev counter_cdev;		/* Character dev */
+	unsigned long *counter_page;		/* Mappable page */
+	wait_queue_head_t counter_queue;	/* Poll/select queue */
+
+	/* The class device structure */
+	struct class_device class_dev;
+
+	/* Device number of abstracted counter */
+	dev_t devt;
+
+	/* Private device data for device-specific drivers */
+	void* devdata;
+};
+
+static inline void* extint_get_devdata(const struct extint_device *ed) {
+	return ed->devdata;
+}
+
+static inline void extint_set_devdata(struct extint_device *ed, void* devdata) {
+	ed->devdata = devdata;
+}
+
+struct extint_callout {
+	struct list_head list;
+	struct module *owner;		/* Callout function and data owner */
+	void (*function) (void *);	/* Callout to invoke */
+	void *data;			/* Passed to callout */
+};
+
+extern struct extint_device *extint_device_register(struct extint_properties
+						    *ep, void *devdata);
+extern void extint_device_unregister(struct extint_device *ed);
+
+/* Used by other kernel modules to request a function be invoked each
+ * time an interrupt is ingested
+ */
+extern struct extint_device *file_to_extint_device(const struct file *filp);
+extern int extint_callout_register(struct extint_device *ed,
+				   struct extint_callout *ec);
+extern void extint_callout_unregister(struct extint_device *ed,
+				      struct extint_callout *ec);
+
+/* Used by external interrupt low-level drivers to trigger invocation
+ * of per-interrupt actions.
+ */
+extern void extint_interrupt(struct extint_device *ed);
+
+#endif

-- 
Brent Casavant                          If you had nothing to fear,
bcasavan@sgi.com                        how then could you be brave?
Silicon Graphics, Inc.                    -- Queen Dama, Source Wars

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/2] external interrupts: abstraction layer
@ 2005-08-19 21:55 Brent Casavant
  2005-08-20  9:46 ` Christoph Hellwig
  0 siblings, 1 reply; 6+ messages in thread
From: Brent Casavant @ 2005-08-19 21:55 UTC (permalink / raw)
  To: linux-kernel; +Cc: Andrew Morton

This patch implements an abstraction layer for external interrupt devices.
It creates a new sysfs class "extint" which provides a number of read-write
and a few read-only attributes which can be used to control a lower-level
hardware-specific external interrupt device driver.

The abstraction layer provides a mechanism to insulate applications from
the details of the capabilities and mechanisms of a particular external
interrupt device.  It also greatly simplifies low-level drivers.

Signed-off-by: Brent Casavant <bcasavan@sgi.com>

 Documentation/extint.txt        |  431 +++++++++++++++++++++++++
 arch/ia64/configs/sn2_defconfig |    1 
 arch/ia64/defconfig             |    1 
 drivers/char/Kconfig            |    7 
 drivers/char/Makefile           |    1 
 drivers/char/extint.c           |  673 ++++++++++++++++++++++++++++++++++++++++
 include/linux/extint.h          |  115 ++++++
 7 files changed, 1229 insertions(+)

diff --git a/Documentation/extint.txt b/Documentation/extint.txt
new file mode 100644
--- /dev/null
+++ b/Documentation/extint.txt
@@ -0,0 +1,431 @@
+		  External Interrupt Abstraction Layer Driver
+		       Brent Casavant <bcasavan@sgi.com>
+
+Things you might ask yourself right away
+========================================
+
+What is an external interrupt?
+------------------------------
+
+Some types of applications, particuarly realtime process-control or
+simulations, need the ability to react to some simple external event.
+One way of doing this is special hardware that generates an interrupt
+whenever some external signal is applied to it.  A good example is
+the IO9 and IO10 cards on SGI Altix machines, which have an 1/8"
+stereo-style jack where a 0-5V signal can be fed into it.  Some
+outside piece of hardware can wiggle this line to cause the IOC4
+chip on these cards to generate an interrupt.
+
+Anything else to it?
+--------------------
+
+Besides being able to receive these interrupts, sometimes you'd like to
+generate similar signals for use by the outside world.  The abstraction
+layer also provides a signal output control mechanism.
+
+Why an abstraction layer?
+-------------------------
+
+Different chips might implement the external interrupt feature in
+very different ways, but in the end you just want to know when an
+interrupt occurs, perhaps have an ongoing count of these interrupts,
+and select the source of those interrupts.  An abstraction layer
+gives us this capability without needing to depend on specifics
+of the device being used.
+
+What an application or user cares about
+=======================================
+
+The external interrupt abstraction layer provides a character device,
+and several sysfs attributes to control operation.  Assuming the
+usual /sys mount-point for sysfs, these files are:
+
+	/sys/class/extint/extint#/dev
+				  mode
+				  modelist
+				  period
+				  provider
+				  quantum
+				  source
+				  sourcelist
+
+The "extint#" component of this path is determined by the extint driver
+itself, with the "#" replaced by a number (possibly multi-digit), one
+per external interrupt device beginning at zero.  There will be one of
+these dirctories per external interrupt device.
+
+The "dev" attribute contains the major and minor number of the abstracted
+external interrupt device.  If Linux's sysfs, hotplug, and udev are
+configured appropriately, udev will automatically create a /dev/extint#
+character special device file with this major and minor number.  If
+you prefer, you may manually invoke mknod(1) to create the character
+special device file.
+
+Once created, this device file provides a counter that can be used by
+applications in a variety of ways.  It can be memory mapped read-only
+as a single page, in which case the first unsigned long word of the
+page contains a counter that is incremented each time an interrupt
+occurs.  You can use poll(2) and select(2) for reading on a read-only
+file descriptor opened on the device, in which case the poll will
+indicate whether an interrupt has occurred since the last read(2)
+or open(2) of the file, and select will return when the next interrupt
+is received.  The file can also be the subject of read(2), in which
+case it returns an string representation of the value of the counter.
+
+The "source" attribute can be written to set the hardware source of
+interrupts (e.g. SGI's IOC4 chip can trigger either from the external
+pin, or an internal loopback from its interrupt output section).  It
+can be read to determine the current setting of the source.
+
+The "sourcelist" attribute can be read to determine the list of available
+interrupt sources, one per line.  These strings are the legal values
+which can be written to the "source" attribute.
+
+The "mode" attribute can be written to set the shape of the output
+signal for interrupt generation.  For example, SGI's IOC4 chip can
+set the output to a logic low, a logic high, strobe from low to high
+to low one time, set up a repeating strobe, or repeatedly toggle between
+high and low.  It can be read to determine the current setting of the
+mode.
+
+The "modelist" attribute can be read to determine the list of available
+output modes, one per line.  These strings are the legal values which
+can be written to the "mode" attribute.  (Note that at least in the
+case of the SGI IOC4 chip, there are other values which may be read
+from the "mode" attribute which don't appear in "modelist"; these
+represent invalid hardware states.  Only the modes present from
+the modelist are valid settings to be written to the mode attribute.)
+
+The "period" attribute can be written to set the repetition interval
+for periodic output signals (e.g. repeated strobes, automatic toggling).
+This period should be specified in nanoseconds, and should be written
+as a string.  It can be read to determine the current period setting.
+
+The "quantum" attribute can be read to determine the interval to which
+any writes of the "period" attribute will be rounded.  External
+interrupt output hardware may not support nanosecond granularity
+for output periods -- this attribute allows you to determine the
+supported granularity.  The behavior of the interrupt output when
+a value which is not a multiple of the quantum is written to the
+"period" attribute is determined by the specific low-level external
+interrupt driver, however generally the low-level driver should round
+to the nearest available quantum multiple.
+
+The "provider" attribute can be read to obtain an indication of which
+low-level hardware driver and device instance is attached to the
+external interrupt interface.  This string is free-form and determined
+by the low-level driver.  For example, the SGI IOC4 low-level driver
+will return a string of the form "ioc4_intout#".
+
+What a low-level external interrupt driver writer cares about
+=============================================================
+
+The interface to the abstraction layer driver is provided through
+the extint_properties and extint_device structures as defined in
+<linux/extint.h>, and the function prototypes contained therein.
+
+Driver registration
+-------------------
+To register the low-level driver with the abstraction layer, a
+call is made to:
+
+	struct extint_device*
+	extint_device_register(struct extint_properties *ep,
+			       void *devdata);
+
+The "ep" argument is a pointer to an extint_properties structure, which
+specifies the particular low-level driver functions the abstraction layer
+should call when reading/writing the attributes described in the previous
+section.  This is described below.
+
+The "devdata" argument is an opaque pointer which is stored by the
+extint code.  This value can be retrieved or modified via
+
+	void* extint_get_devdata(const struct extint_device *ed);
+	void extint_set_devdata(struct extint_device *ed, void* devdata);
+
+respectively.  This value can be used by the low-level driver to
+determine which of multiple devices it is operating upon, or whatever
+purpose may be desired.  This is described below.
+
+The return value is either a pointer to a struct extint_device (which
+should be saved for later interrupt notification and driver deregistration),
+or a negative error value in case of registration failure.  The driver
+should be prepared to deal with such failures.
+
+Implementation functions
+------------------------
+
+The struct extint_properties is as follows:
+
+struct extint_properties {
+	struct module *owner;
+	ssize_t (*get_mode)(struct extint_device *ed, char *buf);
+	ssize_t (*set_mode)(struct extint_device *ed, const char *buf,
+			    size_t count);
+	ssize_t (*get_modelist)(struct extint_device *ed, char *buf);
+	unsigned long (*get_period)(struct extint_device *ed);
+	ssize_t (*set_period)(struct extint_device *ed, unsigned long period);
+	ssize_t (*get_provider)(struct extint_device *ed, char *buf);
+	unsigned long (*get_quantum)(struct extint_device *ed);
+	ssize_t (*get_source)(struct extint_device *ed, char *buf);
+	ssize_t (*set_source)(struct extint_device *ed, const char *buf,
+			      size_t count);
+	ssize_t (*get_sourcelist)(struct extint_device *ed, char *buf);
+};
+
+(Note: Additional fields not of interest to the low-level external interrupt
+driver may be present -- drivers are encouraged to include linux/extint.h
+to acquire this structure definition.)
+
+"owner" should be set to the module which contains the functions
+pointed to by the remaining structure members.
+
+The remaining functions implement low-level aspects of the abstraction
+layer attributes.  They all take a pointer to the struct extint_device
+as was returned from the registration function.  In all of these functions,
+the value passed as the "devdata" argument to the registration function
+can be retrieved via:
+
+	extint_get_devdata(ed);
+
+And can be updated via:
+
+	extint_set_devdata(ed, newvalue);
+
+Typically this value is a pointer to driver-specific data for the
+individual device being operated upon.  It may, for example, contain
+pointers to mapped PCI regions where control registers reside.
+
+"get_mode" and "set_mode" implement the "mode" attribute of the abstraction
+layer.  "get_mode" should write the current mode into the single-page sized
+buffer passed as the second argument, and return the length of the written
+string.  "set_mode" should read the mode specified in the buffer passed as
+the second argument, and as sized by the third, and return the number of
+characters consumed (or a negative error number in event of failure).
+It should of course also cause the output mode to be set as requested.
+
+"get_modelist" implements the "modelist" attribute of the abstraction
+layer.  "get_modelist" should write strings representing the available
+interrupt output generation modes into the single-page sized buffer
+passed as the second argument, one mode per line.  It should return
+the number of bytes written into this buffer.
+
+"get_period" and "set_period" implement the "period" attribute of the
+abstraction layer.  "get_period" should return an unsigned long which
+represents the current repetition period in nanoseconds.  "set_period"
+should accept an unsigned long as the new value for the repetition
+period, specified in nanoseconds, and returning either 0 or a negative
+error number indicating a failure.  If the requested repetition period
+is not a value which can be exactly set into the underlying hardware,
+the driver is free to adjust the value as it sees fit, though tyipically
+it should round the value to the nearest available value.
+
+"get_provider" implements the "provider" attribute of the abstraction
+layer.  "get_provider" should write a human-readable string which
+identifies the low-level driver and a particular instance of a the
+driven hardware device.  For example, if the low-level driver provides
+its own additional device files for extra functionality not present
+in the abstraction layer, this routine might emit the name of the
+driver module and the names (or device numbers) of the low-level
+driver's own character special device files.
+
+"get_quantum" implements the "quantum" attribute of the abstraction
+layer.  "get_quantum" should return an unsigned long which represents
+the granularity to which the interrupt output repetition period can
+be set, in nanoseconds.
+
+"get_source" and "set_source" implement the "source" attribute of the
+abstraction layer.  "get_source" should write the current interrupt
+input source into the single-page sized buffer passed as the second
+argument, and return the length of the written string.  "set_source"
+should read the source specified in the buffer passed as the second
+argument, and as sized by the third, and return the number of characters
+consumed (or a negative error number in event of failure).   It should
+of course also cause the input source to be selected as requested.
+
+"get_sourcelist" implements the "sourcelist" attribute of the abstraction
+layer.  "get_sourcelist" should write strings representing the available
+interrupt input sources into the single-page sized buffer passed as the
+second argument, one source per line.  It should return the number of
+bytes written into this buffer.
+
+When an interrupt occurs
+------------------------
+
+When an external interrupt signal triggers an interrupt that is
+handled by the low-level driver, the driver should call:
+
+	void
+	extint_interrupt(struct extint_device *ed);
+
+This allows the abstraction layer to perform any appropriate
+abstracted actions (i.e. update the interrupt count, trigger
+poll/select actions, etc).  The sole argument is the struct
+extint_device which was returned from the registration call.
+
+Driver deregistration
+---------------------
+
+When the driver desires to unregister a particular device previously
+registered with the abstraction layer, it should call:
+
+	void
+	extint_device_unregister(struct extint_device *ed);
+
+The sole argument is the struct extint_device which was returned
+from the registration call.  There is no error return from this
+call, however if invalid data is passed to it the likelihood of
+a kernel panic is very high indeed.
+
+What a kernel-level external interrupt user cares about
+=======================================================
+
+In addition to the user-visible aspects of the external interrupt
+abstraction layer, there is a kernel-only interface available for
+interrupt notification.  This interface provides the ability for
+other kernel modules to register a callout to be invoked whenever
+an external interrupt is ingested for a particular device.
+
+Callout registration
+--------------------
+
+To register a callout to be invoked upon interrupt ingest, a
+struct extint_callout should be allocated, filled in, and
+passed to:
+
+	int
+	extint_callout_register(struct extint_device *ed,
+				struct extint_callout *ec);
+
+The first argument is the struct extint_device corresponding to
+the particular abstracted external interrupt hardware device of
+interest.  How exactly this structure is found is up to the
+caller, however the "file_to_extint_device" function will convert
+a struct file pointer to a struct extint_device pointer.  This
+function will return -EINVAL if an inappropriate file descriptor
+is passed to it.
+
+The second argument is one of the following structures:
+
+struct extint_callout {
+	struct module* owner;
+	void (*function)(void *);
+	void *data;
+};
+
+(Note: Additional fields not of interest to the external interrupt user
+may be present -- drivers are encouraged to include linux/extint.h
+to acquire this structure definition.)
+
+The "owner" field should be set to the module containing the function
+and data pointed to by the remaining fields.
+
+The "function" pointer is a callout function which is to be invoked
+whenever an interrupt is ingested by the abstraction layer for the
+device of interest.  It will be passed as its sole argument the
+"data" field, which is used opaquely and is provided solely for use
+by the caller.  That is, the abstraction layer will invoke:
+
+	ec->function(data);
+
+upon each interrupt of the specified device.
+
+Multiple callouts can be registered for the same abstracted external
+interrupt device.  They will be invoked in no guaranteed order, but
+will be invoked one at a time.
+
+The interrupt counter will be incremented before the callouts are
+invoked, but before any signal/poll notifications occur.
+
+The module specified by the "owner" field in the callout structure,
+as well as the module corresponding to the low-level external interrupt
+device driver, will have their reference counts increased by one until
+the callout is deregistered.
+
+Callout deregistration
+----------------------
+
+To remove a callout, simply call:
+
+	extern void
+	extint_callout_unregister(struct extint_device *ed,
+				  struct extint_callout *ec);
+
+With the same arguments as provided during callout registration.
+Both active and orphaned callouts can be removed in this manner
+with no distinction between the two.
+
+The callout function must continue to be able to be invoked
+until the call to extint_callout_unregister completes.
+
+Things you might ask yourself at the end
+========================================
+
+What if my hardware device supports a capability not in the abstraction?
+------------------------------------------------------------------------
+
+There are two possibilities.  The first would be to add a new attribute
+to the abstraction, modify struct extint_properties to add appropriate
+interface routines, and update any existing drivers as necessary.
+
+The second, and generally preferable method, is for the the
+low-level driver to create its own device class and corresponding
+attributes and/or character special devices.  This is definitely
+the correct route to take if the capability is dependent on the
+hardware in a method that cannot be abstracted.
+
+A good example is the SGI IOC4's ability to map the interrupt output
+control register directly into a user application to avoid the kernel
+overhead of reading/writing the abstracted attribute files.  Using
+this capability means that the application must have intimate knowledge
+of the format of the control register -- something which both cannot
+be abstracted away by the kernel, and which is very specific to this
+particular IO controller chip.  This capability is provided by the
+ioc4_extint driver supplying its own character special device along
+with an ioc4_intout device class.
+
+Is there an example low-level driver to pattern mine after?
+-----------------------------------------------------------
+
+Sure.  linux/drivers/char/ioc4_extint.c
+
+Note that this low-level driver in addition to providing the
+abstraction interface, creates an IOC4-specific character
+special device and an IOC4-specific device class, as mentioned
+in the answer to the previous question.
+
+Why the callout mechanism?
+--------------------------
+
+For systems (not just applications, we're taking a higher-level view
+here) which are critically interested in responding as quickly as
+possible to an externally triggered event, waiting for a poll/select
+operation, or even busy-waiting on the value of the interrupt counter
+to change may not provide appropriate response times or have other
+deleterious effects (i.e. tieing up a CPU spinning on a value).  This
+gives the system architecht a tool to gain minimal-latency notification
+of events, and react accordingly, by writing their own kernel module.
+
+It also provides an extension capability that might be of interest
+in certain scenarios.  For example, there could be an application
+that wants a interrupt counter page much as maintained by the
+abstraction layer, but which starts counting at zero when the
+device special file is opened.  Or, there could be an application
+which wants a signal to be generated and delivered to the process
+when an interrupt is ingested.  These examples are more esoteric
+than the simple counter page, and are best provided by a seperate
+module rather than cluttering the main external interrupt abstraction
+code.
+
+Why wasn't this made compatible with IRIX's ei(7) driver from an
+application perspective?
+----------------------------------------------------------------
+
+IRIX's driver uses ioctl(2) calls to interact with user space.  This
+is frowned upon in Linux, and generally doesn't perform very well on
+Linux due to kernel locking issues.
+
+One advantage gained by this mechanism is control methods which are
+easily utilized from the command-line, rather than requiring specially
+written and compiled applications to function the device.
diff --git a/arch/ia64/configs/sn2_defconfig b/arch/ia64/configs/sn2_defconfig
--- a/arch/ia64/configs/sn2_defconfig
+++ b/arch/ia64/configs/sn2_defconfig
@@ -667,6 +667,7 @@ CONFIG_MMTIMER=y
 #
 # Misc devices
 #
+CONFIG_EXTINT=m
 
 #
 # Multimedia devices
diff --git a/arch/ia64/defconfig b/arch/ia64/defconfig
--- a/arch/ia64/defconfig
+++ b/arch/ia64/defconfig
@@ -717,6 +717,7 @@ CONFIG_MMTIMER=y
 #
 # Misc devices
 #
+CONFIG_EXTINT=m
 
 #
 # Multimedia devices
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig
--- a/drivers/char/Kconfig
+++ b/drivers/char/Kconfig
@@ -413,6 +413,13 @@ config SGI_MBCS
          If you have an SGI Altix with an attached SABrick
          say Y or M here, otherwise say N.
 
+config EXTINT
+	tristate "Abstraction layer for external interrupt devices"
+	help
+	  This option provides an abstraction layer for external
+	  interrupt devices (such as SGI IOC3 and IOC4 IO controllers).
+	  If you have such a device, say Y. Otherwise, say N.
+
 source "drivers/serial/Kconfig"
 
 config UNIX98_PTYS
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_NWFLASH) += nwflash.o
 obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
 obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
 obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+obj-$(CONFIG_EXTINT) += extint.o
 
 obj-$(CONFIG_WATCHDOG)	+= watchdog/
 obj-$(CONFIG_MWAVE) += mwave/
diff --git a/drivers/char/extint.c b/drivers/char/extint.c
new file mode 100644
--- /dev/null
+++ b/drivers/char/extint.c
@@ -0,0 +1,673 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2005 Silicon Graphics, Inc.  All Rights Reserved.
+ */
+
+/* This file provides an abstraction for lowlevel external interrupt
+ * operation.
+ *
+ * External interrupts are hardware mechanisms to generate or ingest
+ * a simple interrupt signal.
+ *
+ * Generation typically involves driving an output circuit voltage
+ * level, with a variety of single or recurring waveforms (e.g.
+ * a one-shot pulse, a square wave, etc.)  The repetition period
+ * for recurring waveforms can be set within hardware restrictions.
+ *
+ * Ingest typically involves responding to an input circuit voltage
+ * level or transition.  Multiple input sources may be available.
+ *
+ * 2005.07.27	Brent Casavant <bcasavan@sgi.com> Initial code
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/cdev.h>
+#include <linux/ctype.h>
+#include <linux/err.h>
+#include <linux/extint.h>
+#include <linux/gfp.h>
+#include <linux/init.h>
+#include <linux/kallsyms.h>
+#include <linux/device.h>
+#include <linux/poll.h>
+
+/**********************
+ * Module global data *
+ **********************/
+
+/* Device numbers */
+#define EXTINT_NUMDEVS	255	/* Number of minor devices to reserve */
+static dev_t firstdev;		/* Start of dynamic range */
+static dev_t nextdev;		/* Next number to assign */
+static DEFINE_SPINLOCK(nextdev_lock);
+
+/* Device status.  Controls whether new callouts can be registered. */
+enum extint_state {
+	EXTINT_COMING,
+	EXTINT_ALIVE,
+	EXTINT_GOING,
+	EXTINT_DEAD
+};
+
+/**********************
+ * Abstracted devices *
+ **********************/
+
+static struct page *extint_counter_vma_nopage(struct vm_area_struct *vma,
+					      unsigned long address, int *type)
+{
+	struct extint_device *ed = vma->vm_private_data;
+	struct page *page;
+
+	/* Only a single page is ever mapped */
+	if (address >= vma->vm_start + PAGE_SIZE)
+		return NOPAGE_SIGBUS;
+
+	/* virt_to_page can be expensive, but this is executed
+	 * only once each time the counter page is mapped.
+	 */
+	page = virt_to_page(ed->counter_page);
+	get_page(page);
+
+	if (type)
+		*type = VM_FAULT_MINOR;
+
+	return page;
+}
+
+static struct vm_operations_struct extint_counter_vm_ops = {
+	.nopage = extint_counter_vma_nopage,
+};
+
+static int extint_counter_open(struct inode *inode, struct file *filp)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+
+	/* Counter is always read-only */
+	if (filp->f_mode & FMODE_WRITE)
+		return -EPERM;
+
+	/* Prevent low-level module from unloading while
+	 * corresponding abstracted device is open
+	 */
+	if (!try_module_get(ed->props->owner))
+		return -ENXIO;
+
+	/* Snapshot initial value, for later use by poll */
+	filp->private_data = (void *)*ed->counter_page;
+
+	return 0;
+}
+
+static int extint_counter_release(struct inode *inode, struct file *filp)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+
+	/* Allow low-level module to unload now that the
+	 * corresponding abstracted device is really closed.
+	 */
+	module_put(ed->props->owner);
+
+	return 0;
+}
+
+static ssize_t
+extint_counter_read(struct file *filp, char *buff, size_t count, loff_t * offp)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+	char outbuff[21];	/* 20 chars for value of 2^64, plus \0 */
+
+	/* Snapshot last value read, for later use by poll */
+	memset(outbuff, 0, 21);
+	filp->private_data = (void *)*ed->counter_page;
+	snprintf(outbuff, 21, "%ld", (unsigned long)filp->private_data);
+	outbuff[20] = '\0';
+
+	return simple_read_from_buffer(buff, count, offp, outbuff,
+				       strlen(outbuff));
+}
+
+static int extint_counter_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+
+	if ((PAGE_SIZE != vma->vm_end - vma->vm_start) || (0 != vma->vm_pgoff))
+		return -EINVAL;
+
+	vma->vm_ops = &extint_counter_vm_ops;
+	vma->vm_flags |= VM_RESERVED;
+	vma->vm_flags &= ~(VM_WRITE | VM_MAYWRITE);	/* Read-only */
+	vma->vm_private_data = ed;
+	return 0;
+}
+
+static unsigned int
+extint_counter_poll(struct file *filp, struct poll_table_struct *wait)
+{
+	struct extint_device *ed = file_to_extint_device(filp);
+
+	poll_wait(filp, &ed->counter_queue, wait);
+
+	/* Check counter against last value read from it */
+	if (*ed->counter_page != (unsigned long)filp->private_data)
+		return (POLLIN | POLLRDNORM);
+
+	return 0;
+}
+
+static struct file_operations extint_fops = {
+	.owner = THIS_MODULE,
+	.open = extint_counter_open,
+	.release = extint_counter_release,
+	.read = extint_counter_read,
+	.mmap = extint_counter_mmap,
+	.poll = extint_counter_poll,
+};
+
+static int extint_device_create(struct extint_device *ed)
+{
+	int ret;
+
+	/* Allocate counter page */
+	ed->counter_page = (unsigned long *)get_zeroed_page(GFP_KERNEL);
+	if (!ed->counter_page) {
+		printk(KERN_WARNING
+		       "%s: failed to allocate extint counter page.\n",
+		       __FUNCTION__);
+		ret = -ENOMEM;
+		goto out_page;
+	}
+
+	/* Set up device */
+	init_waitqueue_head(&ed->counter_queue);
+	cdev_init(&ed->counter_cdev, &extint_fops);
+	ed->counter_cdev.owner = THIS_MODULE;
+	kobject_set_name(&ed->counter_cdev.kobj, "extint_counter%d",
+			 MINOR(ed->devt));
+	ret = cdev_add(&ed->counter_cdev, ed->devt, 1);
+	if (ret) {
+		printk(KERN_WARNING
+		       "%s: failed to add cdev for extint_counter%d.\n",
+		       __FUNCTION__, MINOR(ed->devt));
+		goto out_cdev;
+	}
+
+	return 0;
+
+out_cdev:
+	kobject_put(&ed->counter_cdev.kobj);
+	free_page((unsigned long)ed->counter_page);
+out_page:
+	return ret;
+}
+
+static void extint_device_destroy(struct extint_device *ed)
+{
+	BUG_ON(waitqueue_active(&ed->counter_queue));
+	cdev_del(&ed->counter_cdev);
+}
+
+/**************************
+ * Misc. class attributes *
+ **************************/
+
+static ssize_t extint_show_dev(struct class_device *cdev, char *buf)
+{
+	struct extint_device *ed = class_get_devdata(cdev);
+
+	return print_dev_t(buf, ed->devt);
+}
+
+/********************************
+ * Abstracted device attributes *
+ ********************************/
+
+#define classdev_to_extint_device(obj)	\
+	container_of(obj, struct extint_device, class_dev)
+
+/* Gets current mode (shape) of interrupt generation */
+static ssize_t extint_show_mode(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_mode))
+		rc = ed->props->get_mode(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Sets the mode (shape) of interrupt generation */
+static ssize_t extint_store_mode(struct class_device *cdev, const char *buf,
+				 size_t count)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->set_mode))
+		rc = ed->props->set_mode(ed, buf, count);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets available modes of interrupt generation */
+static ssize_t extint_show_modelist(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_modelist))
+		rc = ed->props->get_modelist(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets period (nanoseconds) of periodic modes of interrupt generation */
+static ssize_t extint_show_period(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_period))
+		rc = sprintf(buf, "%ld\n", ed->props->get_period(ed));
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+static ssize_t extint_show_provider(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_provider))
+		rc = ed->props->get_provider(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Sets period (nanoseconds) of periodic modes of interrupt generation */
+static ssize_t extint_store_period(struct class_device *cdev, const char *buf,
+				   size_t count)
+{
+	int rc;
+	char *endp;
+	unsigned long period;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	period = simple_strtoul(buf, &endp, 0);
+	if (*endp && !isspace(*endp))
+		return -EINVAL;
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->set_period)) {
+		rc = ed->props->set_period(ed, period);
+		if (!rc)
+			rc = count;	/* Swallow entire input */
+	} else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets rounding increment for interrupt generation periodic modes */
+static ssize_t extint_show_quantum(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props))
+		rc = sprintf(buf, "%ld\n", ed->props->get_quantum(ed));
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets current source of interrupt ingest */
+static ssize_t extint_show_source(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_source))
+		rc = ed->props->get_source(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Sets source of interrupt ingest */
+static ssize_t extint_store_source(struct class_device *cdev, const char *buf,
+				   size_t count)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->set_source))
+		rc = ed->props->set_source(ed, buf, count);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Gets list of available sources of interrupt ingest */
+static ssize_t extint_show_sourcelist(struct class_device *cdev, char *buf)
+{
+	int rc;
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	down(&ed->sem);
+	if (likely(ed->props && ed->props->get_sourcelist))
+		rc = ed->props->get_sourcelist(ed, buf);
+	else
+		rc = -ENXIO;
+	up(&ed->sem);
+
+	return rc;
+}
+
+/* Release allocated memory when last reference to a device goes away */
+static void extint_class_release(struct class_device *cdev)
+{
+	struct extint_device *ed = classdev_to_extint_device(cdev);
+
+	BUG_ON(ed->state != EXTINT_DEAD);
+	BUG_ON(!list_empty(&ed->callouts));
+	kfree(ed);
+}
+
+static struct class extint_class = {
+	.name = "extint",
+	.release = extint_class_release,
+};
+
+#define DECLARE_ATTR(_name,_mode,_show,_store)	\
+{					 	\
+	.attr	= { .name = __stringify(_name),	\
+		    .mode = _mode,		\
+		    .owner = THIS_MODULE },	\
+	.show	= _show,			\
+	.store	= _store,			\
+}
+
+static struct class_device_attribute extint_class_device_attributes[] = {
+	DECLARE_ATTR(dev, 0444, extint_show_dev, NULL),
+	DECLARE_ATTR(mode, 0644, extint_show_mode, extint_store_mode),
+	DECLARE_ATTR(modelist, 0444, extint_show_modelist, NULL),
+	DECLARE_ATTR(period, 0644, extint_show_period, extint_store_period),
+	DECLARE_ATTR(provider, 0444, extint_show_provider, NULL),
+	DECLARE_ATTR(quantum, 0444, extint_show_quantum, NULL),
+	DECLARE_ATTR(source, 0644, extint_show_source, extint_store_source),
+	DECLARE_ATTR(sourcelist, 0444, extint_show_sourcelist, NULL),
+};
+
+/*************
+ * Interface *
+ *************/
+
+/* Register a low-level driver with the abstraction layer */
+struct extint_device *extint_device_register(struct extint_properties *ep,
+					     void *devdata)
+{
+	struct extint_device *ed;
+	int rc;
+	int i;
+
+	/* Create new control structure and initialize */
+	ed = kmalloc(sizeof(struct extint_device), GFP_KERNEL);
+	if (unlikely(!ed))
+		return ERR_PTR(-ENOMEM);
+	memset(ed, 0, sizeof(struct extint_device));
+
+	ed->state = EXTINT_COMING;
+	init_MUTEX(&ed->sem);
+	ed->props = ep;
+	INIT_LIST_HEAD(&ed->callouts);
+	spin_lock_init(&ed->callouts_lock);
+	extint_set_devdata(ed, devdata);
+
+	/* Allocate device number */
+	spin_lock(&nextdev_lock);
+	ed->devt = nextdev++;
+	spin_unlock(&nextdev_lock);
+	if (ed->devt > (firstdev + EXTINT_NUMDEVS)) {
+		rc = -ENOSPC;
+		goto out_devnum;
+	}
+
+	/* Add this device to the class */
+	ed->class_dev.class = &extint_class;
+	snprintf(ed->class_dev.class_id, BUS_ID_SIZE, "extint%d",
+		 MINOR(ed->devt));
+	class_set_devdata(&ed->class_dev, ed);
+	rc = class_device_register(&ed->class_dev);
+	if (rc)
+		goto out_class;
+
+	/* Create character device */
+	rc = extint_device_create(ed);
+	if (rc)
+		goto out_device;
+
+	/* Create attributes */
+	for (i = 0; i < ARRAY_SIZE(extint_class_device_attributes); i++) {
+		rc = class_device_create_file(&ed->class_dev,
+					      &extint_class_device_attributes
+					      [i]);
+		if (rc)
+			goto out_attr;
+	}
+
+	ed->state = EXTINT_ALIVE;
+	return ed;
+
+out_class:
+out_devnum:
+	ed->state = EXTINT_DEAD;
+	kfree(ed);
+	return ERR_PTR(rc);
+
+out_attr:
+	while (--i >= 0)
+		class_device_remove_file(&ed->class_dev,
+					 &extint_class_device_attributes[i]);
+out_device:
+	ed->state = EXTINT_DEAD;
+	class_device_unregister(&ed->class_dev);
+	/* extint_class_release frees ed for us */
+	return ERR_PTR(rc);
+}
+
+EXPORT_SYMBOL(extint_device_register);
+
+/* Unregister a previously registered low-level driver */
+void extint_device_unregister(struct extint_device *ed)
+{
+	int i;
+
+	if (!ed)
+		return;
+
+	/* Remove counter device */
+	ed->state = EXTINT_GOING;
+	BUG_ON(!list_empty(&ed->callouts));
+	extint_device_destroy(ed);
+
+	/* Remove all abstracted attributes */
+	for (i = 0; i < ARRAY_SIZE(extint_class_device_attributes); i++)
+		class_device_remove_file(&ed->class_dev,
+					 &extint_class_device_attributes[i]);
+
+	/* Make sure device-specific functions are never invoked again */
+	down(&ed->sem);
+	ed->props = NULL;
+	up(&ed->sem);
+	ed->state = EXTINT_DEAD;
+
+	/* Remove this device from the class */
+	class_device_unregister(&ed->class_dev);
+}
+
+EXPORT_SYMBOL(extint_device_unregister);
+
+/* Obtain extint_device structure from an open file */
+struct extint_device *file_to_extint_device(const struct file *filp)
+{
+	/* Validate that this really is an extint device file */
+	if (filp->f_dentry->d_inode->i_cdev->dev < firstdev ||
+	    filp->f_dentry->d_inode->i_cdev->dev > (firstdev + EXTINT_NUMDEVS))
+		return ERR_PTR(-EINVAL);
+
+	return container_of(filp->f_dentry->d_inode->i_cdev,
+			    struct extint_device, counter_cdev);
+}
+
+EXPORT_SYMBOL(file_to_extint_device);
+
+/* Register a callout function to invoke when an interrupt is ingested */
+int extint_callout_register(struct extint_device *ed, struct extint_callout *ec)
+{
+	int ret;
+	unsigned long flags;
+
+	/* Disallow unload of callout owner */
+	if (!try_module_get(ec->owner))
+		return -ENXIO;
+
+	/* Disallow unload of low-level driver */
+	if (!try_module_get(ed->props->owner)) {
+		module_put(ec->owner);
+		return -ENXIO;
+	}
+
+	spin_lock_irqsave(&ed->callouts_lock, flags);
+	switch (ed->state) {
+	case EXTINT_COMING:
+		ret = -EAGAIN;
+		module_put(ed->props->owner);
+		module_put(ec->owner);
+		break;
+	case EXTINT_ALIVE:
+		list_add(&ec->list, &ed->callouts);
+		ret = 0;
+		break;
+	default:
+		ret = -EBUSY;
+		module_put(ed->props->owner);
+		module_put(ec->owner);
+		break;
+	}
+	spin_unlock_irqrestore(&ed->callouts_lock, flags);
+
+	return ret;
+}
+
+EXPORT_SYMBOL(extint_callout_register);
+
+/* Unregister a previously registered callout function */
+void extint_callout_unregister(struct extint_device *ed,
+			       struct extint_callout *ec)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ed->callouts_lock, flags);
+	list_del(&ec->list);
+	spin_unlock_irqrestore(&ed->callouts_lock, flags);
+
+	/* Allow callout owner to unload */
+	module_put(ec->owner);
+	/* Allow low-level driver to unload */
+	module_put(ed->props->owner);
+}
+
+EXPORT_SYMBOL(extint_callout_unregister);
+
+/* Allows a low-level driver to notify the
+ * abstraction layer of an ingested interrupt.
+ */
+void extint_interrupt(struct extint_device *ed)
+{
+	struct extint_callout *ec;
+
+	/* Bump global counter */
+	(*ed->counter_page)++;
+
+	/* Invoke all registered callouts */
+	spin_lock(&ed->callouts_lock);
+	list_for_each_entry(ec, &ed->callouts, list)
+		ec->function(ec->data);
+	spin_unlock(&ed->callouts_lock);
+
+	/* Wake up poll/select waiters */
+	wake_up_all(&ed->counter_queue);
+}
+
+EXPORT_SYMBOL(extint_interrupt);
+
+/*********************
+ * Module management *
+ *********************/
+
+static int __devinit extint_init(void)
+{
+	int ret;
+
+	/* Reserve a block of device numbers */
+	ret = alloc_chrdev_region(&firstdev, 0, EXTINT_NUMDEVS, "extint");
+	if (ret) {
+		printk(KERN_WARNING
+		       "%s: failed to allocate external interrupt "
+		       "device numbers.\n", __FUNCTION__);
+		return ret;
+	}
+	nextdev = firstdev;
+
+	return class_register(&extint_class);
+}
+
+static void __devexit extint_exit(void)
+{
+	class_unregister(&extint_class);
+
+	unregister_chrdev_region(firstdev, EXTINT_NUMDEVS);
+}
+
+module_init(extint_init);
+module_exit(extint_exit);
+
+MODULE_AUTHOR("Brent Casavant - Silicon Graphics, Inc. <bcasavan@sgi.com>");
+MODULE_DESCRIPTION("External interrupt abstraction class module");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/extint.h b/include/linux/extint.h
new file mode 100644
--- /dev/null
+++ b/include/linux/extint.h
@@ -0,0 +1,115 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (c) 2005 Silicon Graphics, Inc.  All Rights Reserved.
+ */
+
+/* External interrupt control abstraction */
+
+#ifndef _LINUX_EXTINT_H
+#define _LINUX_EXTINT_H
+
+#include <linux/device.h>
+
+struct extint_device;
+
+struct extint_properties {
+	/* Owner module */
+	struct module *owner;
+
+	/* Get/set generation mode */
+	ssize_t (*get_mode)(struct extint_device * ed, char *buf);
+	ssize_t (*set_mode)(struct extint_device * ed, const char *buf,
+			     size_t count);
+
+	/* Get generation mode list */
+	ssize_t (*get_modelist)(struct extint_device * ed, char *buf);
+
+	/* Get/set generation period */
+	unsigned long (*get_period)(struct extint_device * ed);
+	ssize_t (*set_period)(struct extint_device * ed, unsigned long period);
+
+	/* Get low-level provider name */
+	ssize_t (*get_provider)(struct extint_device *ed, char *buf);
+
+	/* Generation period quantum */
+	unsigned long (*get_quantum)(struct extint_device * ed);
+
+	/* Get/set ingest source */
+	ssize_t (*get_source)(struct extint_device * ed, char *buf);
+	ssize_t (*set_source)(struct extint_device * ed, const char *buf,
+			      size_t count);
+
+	/* Get ingest source list */
+	ssize_t (*get_sourcelist)(struct extint_device * ed, char *buf);
+};
+
+struct extint_device {
+	/* Current status of device */
+	int state;
+
+	/* Semaphore protects 'props' field.  If 'props' is NULL, the
+	 * driver that registered this device has been unloaded, and
+	 * if class_get_devdata() points to something in the body of
+	 * that driver, it is also invalid.
+	 */
+	struct semaphore sem;
+	struct extint_properties *props;	/* Downcalls */
+
+	/* A list of callouts to invoke whenever this device ingests
+	 * an interrupt.
+	 */
+	struct list_head callouts;
+	spinlock_t callouts_lock;
+
+	/* Mappable counter page support */
+	struct cdev counter_cdev;		/* Character dev */
+	unsigned long *counter_page;		/* Mappable page */
+	wait_queue_head_t counter_queue;	/* Poll/select queue */
+
+	/* The class device structure */
+	struct class_device class_dev;
+
+	/* Device number of abstracted counter */
+	dev_t devt;
+
+	/* Private device data for device-specific drivers */
+	void* devdata;
+};
+
+static inline void* extint_get_devdata(const struct extint_device *ed) {
+	return ed->devdata;
+}
+
+static inline void extint_set_devdata(struct extint_device *ed, void* devdata) {
+	ed->devdata = devdata;
+}
+
+struct extint_callout {
+	struct list_head list;
+	struct module *owner;		/* Callout function and data owner */
+	void (*function) (void *);	/* Callout to invoke */
+	void *data;			/* Passed to callout */
+};
+
+extern struct extint_device *extint_device_register(struct extint_properties
+						    *ep, void *devdata);
+extern void extint_device_unregister(struct extint_device *ed);
+
+/* Used by other kernel modules to request a function be invoked each
+ * time an interrupt is ingested
+ */
+extern struct extint_device *file_to_extint_device(const struct file *filp);
+extern int extint_callout_register(struct extint_device *ed,
+				   struct extint_callout *ec);
+extern void extint_callout_unregister(struct extint_device *ed,
+				      struct extint_callout *ec);
+
+/* Used by external interrupt low-level drivers to trigger invocation
+ * of per-interrupt actions.
+ */
+extern void extint_interrupt(struct extint_device *ed);
+
+#endif

-- 
Brent Casavant                          If you had nothing to fear,
bcasavan@sgi.com                        how then could you be brave?
Silicon Graphics, Inc.                    -- Queen Dama, Source Wars

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] external interrupts: abstraction layer
  2005-08-19 21:55 [PATCH 1/2] external interrupts: abstraction layer Brent Casavant
@ 2005-08-20  9:46 ` Christoph Hellwig
  2005-08-23 22:01   ` Brent Casavant
  0 siblings, 1 reply; 6+ messages in thread
From: Christoph Hellwig @ 2005-08-20  9:46 UTC (permalink / raw)
  To: Brent Casavant; +Cc: linux-kernel, Andrew Morton

> diff --git a/drivers/char/extint.c b/drivers/char/extint.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/char/extint.c
> @@ -0,0 +1,673 @@
> +/*
> + * This file is subject to the terms and conditions of the GNU General Public
> + * License.  See the file "COPYING" in the main directory of this archive
> + * for more details.
> + *
> + * Copyright (C) 2005 Silicon Graphics, Inc.  All Rights Reserved.
> + */
> +
> +/* This file provides an abstraction for lowlevel external interrupt
> + * operation.
> + *
> + * External interrupts are hardware mechanisms to generate or ingest
> + * a simple interrupt signal.
> + *
> + * Generation typically involves driving an output circuit voltage
> + * level, with a variety of single or recurring waveforms (e.g.
> + * a one-shot pulse, a square wave, etc.)  The repetition period
> + * for recurring waveforms can be set within hardware restrictions.
> + *
> + * Ingest typically involves responding to an input circuit voltage
> + * level or transition.  Multiple input sources may be available.
> + *
> + * 2005.07.27	Brent Casavant <bcasavan@sgi.com> Initial code
> + */
> +
> +#include <linux/module.h>
> +#include <linux/fs.h>
> +#include <linux/cdev.h>
> +#include <linux/ctype.h>
> +#include <linux/err.h>
> +#include <linux/extint.h>
> +#include <linux/gfp.h>
> +#include <linux/init.h>
> +#include <linux/kallsyms.h>
> +#include <linux/device.h>
> +#include <linux/poll.h>
> +
> +/**********************
> + * Module global data *
> + **********************/
> +
> +/* Device numbers */
> +#define EXTINT_NUMDEVS	255	/* Number of minor devices to reserve */
> +static dev_t firstdev;		/* Start of dynamic range */
> +static dev_t nextdev;		/* Next number to assign */
> +static DEFINE_SPINLOCK(nextdev_lock);
> +
> +/* Device status.  Controls whether new callouts can be registered. */
> +enum extint_state {
> +	EXTINT_COMING,
> +	EXTINT_ALIVE,
> +	EXTINT_GOING,
> +	EXTINT_DEAD
> +};
> +
> +/**********************
> + * Abstracted devices *
> + **********************/
> +
> +static struct page *extint_counter_vma_nopage(struct vm_area_struct *vma,
> +					      unsigned long address, int *type)
> +{
> +	struct extint_device *ed = vma->vm_private_data;
> +	struct page *page;
> +
> +	/* Only a single page is ever mapped */
> +	if (address >= vma->vm_start + PAGE_SIZE)
> +		return NOPAGE_SIGBUS;
> +
> +	/* virt_to_page can be expensive, but this is executed
> +	 * only once each time the counter page is mapped.
> +	 */
> +	page = virt_to_page(ed->counter_page);

I think you should store both the struct page pointer and the virtual
address in struct extint_device.  This avoids doing the virt_to_page
at every pagefauls.  It's also cleaner as you an juse use alloc_page()
and the page_address() to get the kernel virtual address.

> +	get_page(page);
> +
> +	if (type)
> +		*type = VM_FAULT_MINOR;
> +
> +	return page;
> +}
> +
> +static struct vm_operations_struct extint_counter_vm_ops = {
> +	.nopage = extint_counter_vma_nopage,
> +};
> +
> +static int extint_counter_open(struct inode *inode, struct file *filp)
> +{
> +	struct extint_device *ed = file_to_extint_device(filp);

you don't need the file but just the inode (strictly speaking the cdev),
and doing this based on the file is rather confusing to the reader because
the struct file passed to ->open has just been allocated.

> +
> +	/* Counter is always read-only */
> +	if (filp->f_mode & FMODE_WRITE)
> +		return -EPERM;
> +
> +	/* Prevent low-level module from unloading while
> +	 * corresponding abstracted device is open
> +	 */
> +	if (!try_module_get(ed->props->owner))
> +		return -ENXIO;
> +
> +	/* Snapshot initial value, for later use by poll */
> +	filp->private_data = (void *)*ed->counter_page;

What about storing the whole extint_device pointer in file->private_data?
That's get rid of a lot of casting and would make possible future additions
easier.

> +
> +	return 0;
> +}
> +
> +static int extint_counter_release(struct inode *inode, struct file *filp)
> +{
> +	struct extint_device *ed = file_to_extint_device(filp);
> +
> +	/* Allow low-level module to unload now that the
> +	 * corresponding abstracted device is really closed.
> +	 */
> +	module_put(ed->props->owner);
> +
> +	return 0;
> +}
> +
> +static ssize_t
> +extint_counter_read(struct file *filp, char *buff, size_t count, loff_t * offp)
> +{
> +	struct extint_device *ed = file_to_extint_device(filp);
> +	char outbuff[21];	/* 20 chars for value of 2^64, plus \0 */
> +
> +	/* Snapshot last value read, for later use by poll */
> +	memset(outbuff, 0, 21);
> +	filp->private_data = (void *)*ed->counter_page;
> +	snprintf(outbuff, 21, "%ld", (unsigned long)filp->private_data);
> +	outbuff[20] = '\0';
> +
> +	return simple_read_from_buffer(buff, count, offp, outbuff,
> +				       strlen(outbuff));
> +}
> +
> +static int extint_counter_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> +	struct extint_device *ed = file_to_extint_device(filp);
> +
> +	if ((PAGE_SIZE != vma->vm_end - vma->vm_start) || (0 != vma->vm_pgoff))
> +		return -EINVAL;
> +
> +	vma->vm_ops = &extint_counter_vm_ops;
> +	vma->vm_flags |= VM_RESERVED;
> +	vma->vm_flags &= ~(VM_WRITE | VM_MAYWRITE);	/* Read-only */
> +	vma->vm_private_data = ed;
> +	return 0;
> +}
> +
> +static unsigned int
> +extint_counter_poll(struct file *filp, struct poll_table_struct *wait)
> +{
> +	struct extint_device *ed = file_to_extint_device(filp);
> +
> +	poll_wait(filp, &ed->counter_queue, wait);
> +
> +	/* Check counter against last value read from it */
> +	if (*ed->counter_page != (unsigned long)filp->private_data)
> +		return (POLLIN | POLLRDNORM);
> +
> +	return 0;
> +}
> +
> +static struct file_operations extint_fops = {
> +	.owner = THIS_MODULE,
> +	.open = extint_counter_open,
> +	.release = extint_counter_release,
> +	.read = extint_counter_read,
> +	.mmap = extint_counter_mmap,
> +	.poll = extint_counter_poll,
> +};
> +
> +static int extint_device_create(struct extint_device *ed)
> +{
> +	int ret;
> +
> +	/* Allocate counter page */
> +	ed->counter_page = (unsigned long *)get_zeroed_page(GFP_KERNEL);
> +	if (!ed->counter_page) {
> +		printk(KERN_WARNING
> +		       "%s: failed to allocate extint counter page.\n",
> +		       __FUNCTION__);
> +		ret = -ENOMEM;
> +		goto out_page;
> +	}
> +
> +	/* Set up device */
> +	init_waitqueue_head(&ed->counter_queue);
> +	cdev_init(&ed->counter_cdev, &extint_fops);
> +	ed->counter_cdev.owner = THIS_MODULE;
> +	kobject_set_name(&ed->counter_cdev.kobj, "extint_counter%d",
> +			 MINOR(ed->devt));
> +	ret = cdev_add(&ed->counter_cdev, ed->devt, 1);
> +	if (ret) {
> +		printk(KERN_WARNING
> +		       "%s: failed to add cdev for extint_counter%d.\n",
> +		       __FUNCTION__, MINOR(ed->devt));
> +		goto out_cdev;
> +	}
> +
> +	return 0;
> +
> +out_cdev:
> +	kobject_put(&ed->counter_cdev.kobj);
> +	free_page((unsigned long)ed->counter_page);
> +out_page:
> +	return ret;
> +}
> +
> +static void extint_device_destroy(struct extint_device *ed)
> +{
> +	BUG_ON(waitqueue_active(&ed->counter_queue));
> +	cdev_del(&ed->counter_cdev);
> +}
> +
> +/**************************
> + * Misc. class attributes *
> + **************************/
> +
> +static ssize_t extint_show_dev(struct class_device *cdev, char *buf)
> +{
> +	struct extint_device *ed = class_get_devdata(cdev);
> +
> +	return print_dev_t(buf, ed->devt);
> +}
> +
> +/********************************
> + * Abstracted device attributes *
> + ********************************/
> +
> +#define classdev_to_extint_device(obj)	\
> +	container_of(obj, struct extint_device, class_dev)
> +
> +/* Gets current mode (shape) of interrupt generation */
> +static ssize_t extint_show_mode(struct class_device *cdev, char *buf)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->get_mode))
> +		rc = ed->props->get_mode(ed, buf);
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Sets the mode (shape) of interrupt generation */
> +static ssize_t extint_store_mode(struct class_device *cdev, const char *buf,
> +				 size_t count)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->set_mode))
> +		rc = ed->props->set_mode(ed, buf, count);
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Gets available modes of interrupt generation */
> +static ssize_t extint_show_modelist(struct class_device *cdev, char *buf)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->get_modelist))
> +		rc = ed->props->get_modelist(ed, buf);
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Gets period (nanoseconds) of periodic modes of interrupt generation */
> +static ssize_t extint_show_period(struct class_device *cdev, char *buf)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->get_period))
> +		rc = sprintf(buf, "%ld\n", ed->props->get_period(ed));
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +static ssize_t extint_show_provider(struct class_device *cdev, char *buf)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->get_provider))
> +		rc = ed->props->get_provider(ed, buf);
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Sets period (nanoseconds) of periodic modes of interrupt generation */
> +static ssize_t extint_store_period(struct class_device *cdev, const char *buf,
> +				   size_t count)
> +{
> +	int rc;
> +	char *endp;
> +	unsigned long period;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	period = simple_strtoul(buf, &endp, 0);
> +	if (*endp && !isspace(*endp))
> +		return -EINVAL;
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->set_period)) {
> +		rc = ed->props->set_period(ed, period);
> +		if (!rc)
> +			rc = count;	/* Swallow entire input */
> +	} else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Gets rounding increment for interrupt generation periodic modes */
> +static ssize_t extint_show_quantum(struct class_device *cdev, char *buf)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props))
> +		rc = sprintf(buf, "%ld\n", ed->props->get_quantum(ed));
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Gets current source of interrupt ingest */
> +static ssize_t extint_show_source(struct class_device *cdev, char *buf)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->get_source))
> +		rc = ed->props->get_source(ed, buf);
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Sets source of interrupt ingest */
> +static ssize_t extint_store_source(struct class_device *cdev, const char *buf,
> +				   size_t count)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->set_source))
> +		rc = ed->props->set_source(ed, buf, count);
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Gets list of available sources of interrupt ingest */
> +static ssize_t extint_show_sourcelist(struct class_device *cdev, char *buf)
> +{
> +	int rc;
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	down(&ed->sem);
> +	if (likely(ed->props && ed->props->get_sourcelist))
> +		rc = ed->props->get_sourcelist(ed, buf);
> +	else
> +		rc = -ENXIO;
> +	up(&ed->sem);
> +
> +	return rc;
> +}
> +
> +/* Release allocated memory when last reference to a device goes away */
> +static void extint_class_release(struct class_device *cdev)
> +{
> +	struct extint_device *ed = classdev_to_extint_device(cdev);
> +
> +	BUG_ON(ed->state != EXTINT_DEAD);
> +	BUG_ON(!list_empty(&ed->callouts));
> +	kfree(ed);
> +}
> +
> +static struct class extint_class = {
> +	.name = "extint",
> +	.release = extint_class_release,
> +};
> +
> +#define DECLARE_ATTR(_name,_mode,_show,_store)	\
> +{					 	\
> +	.attr	= { .name = __stringify(_name),	\
> +		    .mode = _mode,		\
> +		    .owner = THIS_MODULE },	\
> +	.show	= _show,			\
> +	.store	= _store,			\
> +}
> +
> +static struct class_device_attribute extint_class_device_attributes[] = {
> +	DECLARE_ATTR(dev, 0444, extint_show_dev, NULL),
> +	DECLARE_ATTR(mode, 0644, extint_show_mode, extint_store_mode),
> +	DECLARE_ATTR(modelist, 0444, extint_show_modelist, NULL),
> +	DECLARE_ATTR(period, 0644, extint_show_period, extint_store_period),
> +	DECLARE_ATTR(provider, 0444, extint_show_provider, NULL),
> +	DECLARE_ATTR(quantum, 0444, extint_show_quantum, NULL),
> +	DECLARE_ATTR(source, 0644, extint_show_source, extint_store_source),
> +	DECLARE_ATTR(sourcelist, 0444, extint_show_sourcelist, NULL),
> +};
> +
> +/*************
> + * Interface *
> + *************/
> +
> +/* Register a low-level driver with the abstraction layer */
> +struct extint_device *extint_device_register(struct extint_properties *ep,
> +					     void *devdata)
> +{
> +	struct extint_device *ed;
> +	int rc;
> +	int i;
> +
> +	/* Create new control structure and initialize */
> +	ed = kmalloc(sizeof(struct extint_device), GFP_KERNEL);
> +	if (unlikely(!ed))
> +		return ERR_PTR(-ENOMEM);
> +	memset(ed, 0, sizeof(struct extint_device));
> +
> +	ed->state = EXTINT_COMING;
> +	init_MUTEX(&ed->sem);
> +	ed->props = ep;
> +	INIT_LIST_HEAD(&ed->callouts);
> +	spin_lock_init(&ed->callouts_lock);
> +	extint_set_devdata(ed, devdata);
> +
> +	/* Allocate device number */
> +	spin_lock(&nextdev_lock);
> +	ed->devt = nextdev++;
> +	spin_unlock(&nextdev_lock);
> +	if (ed->devt > (firstdev + EXTINT_NUMDEVS)) {
> +		rc = -ENOSPC;
> +		goto out_devnum;
> +	}
> +
> +	/* Add this device to the class */
> +	ed->class_dev.class = &extint_class;
> +	snprintf(ed->class_dev.class_id, BUS_ID_SIZE, "extint%d",
> +		 MINOR(ed->devt));
> +	class_set_devdata(&ed->class_dev, ed);
> +	rc = class_device_register(&ed->class_dev);
> +	if (rc)
> +		goto out_class;
> +
> +	/* Create character device */
> +	rc = extint_device_create(ed);
> +	if (rc)
> +		goto out_device;
> +
> +	/* Create attributes */
> +	for (i = 0; i < ARRAY_SIZE(extint_class_device_attributes); i++) {
> +		rc = class_device_create_file(&ed->class_dev,
> +					      &extint_class_device_attributes
> +					      [i]);
> +		if (rc)
> +			goto out_attr;
> +	}
> +
> +	ed->state = EXTINT_ALIVE;
> +	return ed;
> +
> +out_class:
> +out_devnum:
> +	ed->state = EXTINT_DEAD;
> +	kfree(ed);
> +	return ERR_PTR(rc);
> +
> +out_attr:
> +	while (--i >= 0)
> +		class_device_remove_file(&ed->class_dev,
> +					 &extint_class_device_attributes[i]);
> +out_device:
> +	ed->state = EXTINT_DEAD;
> +	class_device_unregister(&ed->class_dev);
> +	/* extint_class_release frees ed for us */
> +	return ERR_PTR(rc);
> +}
> +
> +EXPORT_SYMBOL(extint_device_register);
> +
> +/* Unregister a previously registered low-level driver */
> +void extint_device_unregister(struct extint_device *ed)
> +{
> +	int i;
> +
> +	if (!ed)
> +		return;
> +
> +	/* Remove counter device */
> +	ed->state = EXTINT_GOING;
> +	BUG_ON(!list_empty(&ed->callouts));
> +	extint_device_destroy(ed);
> +
> +	/* Remove all abstracted attributes */
> +	for (i = 0; i < ARRAY_SIZE(extint_class_device_attributes); i++)
> +		class_device_remove_file(&ed->class_dev,
> +					 &extint_class_device_attributes[i]);
> +
> +	/* Make sure device-specific functions are never invoked again */
> +	down(&ed->sem);
> +	ed->props = NULL;
> +	up(&ed->sem);
> +	ed->state = EXTINT_DEAD;
> +
> +	/* Remove this device from the class */
> +	class_device_unregister(&ed->class_dev);
> +}
> +
> +EXPORT_SYMBOL(extint_device_unregister);
> +
> +/* Obtain extint_device structure from an open file */
> +struct extint_device *file_to_extint_device(const struct file *filp)
> +{
> +	/* Validate that this really is an extint device file */
> +	if (filp->f_dentry->d_inode->i_cdev->dev < firstdev ||
> +	    filp->f_dentry->d_inode->i_cdev->dev > (firstdev + EXTINT_NUMDEVS))
> +		return ERR_PTR(-EINVAL);
> +
> +	return container_of(filp->f_dentry->d_inode->i_cdev,
> +			    struct extint_device, counter_cdev);
> +}
> +
> +EXPORT_SYMBOL(file_to_extint_device);

Why is this exported?  Also normally having helpers ontop of the file
so you've already read them when looking at their users helps understanding
a little.

> +/* Register a callout function to invoke when an interrupt is ingested */
> +int extint_callout_register(struct extint_device *ed, struct extint_callout *ec)
> +{
> +	int ret;
> +	unsigned long flags;
> +
> +	/* Disallow unload of callout owner */
> +	if (!try_module_get(ec->owner))
> +		return -ENXIO;
> +
> +	/* Disallow unload of low-level driver */
> +	if (!try_module_get(ed->props->owner)) {
> +		module_put(ec->owner);
> +		return -ENXIO;
> +	}
> +
> +	spin_lock_irqsave(&ed->callouts_lock, flags);
> +	switch (ed->state) {
> +	case EXTINT_COMING:
> +		ret = -EAGAIN;
> +		module_put(ed->props->owner);
> +		module_put(ec->owner);
> +		break;
> +	case EXTINT_ALIVE:
> +		list_add(&ec->list, &ed->callouts);
> +		ret = 0;
> +		break;
> +	default:
> +		ret = -EBUSY;
> +		module_put(ed->props->owner);
> +		module_put(ec->owner);
> +		break;
> +	}
> +	spin_unlock_irqrestore(&ed->callouts_lock, flags);
> +
> +	return ret;
> +}
> +
> +EXPORT_SYMBOL(extint_callout_register);

I've not seen any users of the in-kernel extint consumer interface in
your previous posting or this one.  Do you plan to submit a patch to
use them soon?  Else please keep the interface out for now, we can easily
add it when it's nessecary.

Also callout isn't really a name we use a lot in linux, unlike other
unix variants.

> +static inline void* extint_get_devdata(const struct extint_device *ed) {
> +	return ed->devdata;
> +}

minimal style nipick, please put the opening brace on a line of it's own.


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] external interrupts: abstraction layer
  2005-08-20  9:46 ` Christoph Hellwig
@ 2005-08-23 22:01   ` Brent Casavant
  2005-08-24  7:38     ` Christoph Hellwig
  0 siblings, 1 reply; 6+ messages in thread
From: Brent Casavant @ 2005-08-23 22:01 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: linux-kernel, Andrew Morton

On Sat, 20 Aug 2005, Christoph Hellwig wrote:

> > +static struct page *extint_counter_vma_nopage(struct vm_area_struct *vma,
> > +					      unsigned long address, int *type)
> > +{
> > +	struct extint_device *ed = vma->vm_private_data;
> > +	struct page *page;
> > +
> > +	/* Only a single page is ever mapped */
> > +	if (address >= vma->vm_start + PAGE_SIZE)
> > +		return NOPAGE_SIGBUS;
> > +
> > +	/* virt_to_page can be expensive, but this is executed
> > +	 * only once each time the counter page is mapped.
> > +	 */
> > +	page = virt_to_page(ed->counter_page);
> 
> I think you should store both the struct page pointer and the virtual
> address in struct extint_device.  This avoids doing the virt_to_page
> at every pagefauls.  It's also cleaner as you an juse use alloc_page()
> and the page_address() to get the kernel virtual address.

Done.  I'm now storing both in extint_device.  Unfortunately, though,
it isn't any cleaner as we still need the error-handling in case
alloc_page() returns NULL (in extint_device_create()).  The only
thing this really changes is the relative order of getting a virtual
address and the struct page*.

> > +static int extint_counter_open(struct inode *inode, struct file *filp)
> > +{
> > +	struct extint_device *ed = file_to_extint_device(filp);
> 
> you don't need the file but just the inode (strictly speaking the cdev),
> and doing this based on the file is rather confusing to the reader because
> the struct file passed to ->open has just been allocated.

Perhaps I'm not following something here.

This behavior enables using poll/select to detect or wait for the
external interrupt counter (which is per-device) to change.  The
"has changed" status must be on a per-open basis, not a per-device
basis, as seperate processes may read the counter at different times.

Neither does it suffice to wake all waiters in extint_interrupt()
without checking a per-open snapshotted counter, as there could be
a race between the thread performing poll/select and the interrupt
which increments the counter.  i.e., the following sequence:

	Thread			Interrupted CPU
	------			-----------------
				Receive interrupt
				Bump counter
	Read counter
	Poll
				Wake polling threads
	Continue execution

To avoid this race (the thread should have returned to poll_wait()),
we snapshot the value of the counter when it was last read, and
check that the counter value has indeed changed before continuing
execution (by returning 0 from extint_counter_poll).

Storing the snapshot in a per-open structure (i.e. struct file) looks
to be the right thing to avoid this race and take care of independent
processes reading the counter.

Or did I miss your point?

> > +	/* Snapshot initial value, for later use by poll */
> > +	filp->private_data = (void *)*ed->counter_page;
> 
> What about storing the whole extint_device pointer in file->private_data?
> That's get rid of a lot of casting and would make possible future additions
> easier.

It seems kind of messy to manage another pointer, but I agreee this
makes things more clear in most cases.  Considering my comments above
(about needing per-open counter snapshots), filp->private data now
points at one of these:

	struct extint_file {
		struct extint_device	*ed;
		unsigned long		snapshot;
	};

The resulting code changes, along with the ramifications of the next
comment, also meant that we can remove file_to_extint_device().

> > +/* Obtain extint_device structure from an open file */
> > +struct extint_device *file_to_extint_device(const struct file *filp)
> > +{
> > +	/* Validate that this really is an extint device file */
> > +	if (filp->f_dentry->d_inode->i_cdev->dev < firstdev ||
> > +	    filp->f_dentry->d_inode->i_cdev->dev > (firstdev + EXTINT_NUMDEVS))
> > +		return ERR_PTR(-EINVAL);
> > +
> > +	return container_of(filp->f_dentry->d_inode->i_cdev,
> > +			    struct extint_device, counter_cdev);
> > +}
> > +
> > +EXPORT_SYMBOL(file_to_extint_device);
> 
> Why is this exported?  Also normally having helpers ontop of the file
> so you've already read them when looking at their users helps understanding
> a little.

It was exported in order to enable the callout registration mechanism to
locate the correct device to register against.  However, with that
mechanism removed (see below) and the changes from the previous comment,
the whole function went away.

> > +/* Register a callout function to invoke when an interrupt is ingested */
> > +int extint_callout_register(struct extint_device *ed, struct extint_callout *ec)

> I've not seen any users of the in-kernel extint consumer interface in
> your previous posting or this one.  Do you plan to submit a patch to
> use them soon?  Else please keep the interface out for now, we can easily
> add it when it's nessecary.

There are currently no users of this functionality, though I think I
could contrive one that supports a reference atomic clock for NTP
master clock synchronization.  Apparently these things can operate in
a mode of providing periodic pulses to a computer system, something
external interrupts would be perfectly suited for.

Anyway, it's now omitted from the patch.  Though, honestly, not having
it makes the patch far less useful, to the point where I may not continue
to push for its inclusion.

> Also callout isn't really a name we use a lot in linux, unlike other
> unix variants.

Not that it matters anymore, but any ideas for better terminology?

> > +static inline void* extint_get_devdata(const struct extint_device *ed) {
> > +	return ed->devdata;
> > +}
> 
> minimal style nipick, please put the opening brace on a line of it's own.

Just a goof on my part.  Thanks for catching this.

Brent

-- 
Brent Casavant                          If you had nothing to fear,
bcasavan@sgi.com                        how then could you be brave?
Silicon Graphics, Inc.                    -- Queen Dama, Source Wars

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] external interrupts: abstraction layer
  2005-08-23 22:01   ` Brent Casavant
@ 2005-08-24  7:38     ` Christoph Hellwig
  2005-08-24 15:00       ` Brent Casavant
  0 siblings, 1 reply; 6+ messages in thread
From: Christoph Hellwig @ 2005-08-24  7:38 UTC (permalink / raw)
  To: Brent Casavant; +Cc: Christoph Hellwig, linux-kernel, Andrew Morton

> > > +static int extint_counter_open(struct inode *inode, struct file *filp)
> > > +{
> > > +	struct extint_device *ed = file_to_extint_device(filp);
> > 
> > you don't need the file but just the inode (strictly speaking the cdev),
> > and doing this based on the file is rather confusing to the reader because
> > the struct file passed to ->open has just been allocated.
> 
> Perhaps I'm not following something here.
> 
> This behavior enables using poll/select to detect or wait for the
> external interrupt counter (which is per-device) to change.  The
> "has changed" status must be on a per-open basis, not a per-device
> basis, as seperate processes may read the counter at different times.

Sorry, the whole behaviour is complety fine.  I just don't thing the name
and calling convention of file_to_extint_device is optimal.  It should
take an struct inode * and be called just to_extint_device or someting.
The above would become

	struct extint_device *ed = to_extint_device(filp->f_dentry->d_inode);


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 1/2] external interrupts: abstraction layer
  2005-08-24  7:38     ` Christoph Hellwig
@ 2005-08-24 15:00       ` Brent Casavant
  0 siblings, 0 replies; 6+ messages in thread
From: Brent Casavant @ 2005-08-24 15:00 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: linux-kernel, Andrew Morton

On Wed, 24 Aug 2005, Christoph Hellwig wrote:

> Sorry, the whole behaviour is complety fine.  I just don't thing the name
> and calling convention of file_to_extint_device is optimal.  It should
> take an struct inode * and be called just to_extint_device or someting.
> The above would become
> 
> 	struct extint_device *ed = to_extint_device(filp->f_dentry->d_inode);

OK, no problem, since file_to_extint_device() went away anyway.

Brent

-- 
Brent Casavant                          If you had nothing to fear,
bcasavan@sgi.com                        how then could you be brave?
Silicon Graphics, Inc.                    -- Queen Dama, Source Wars

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2005-08-24 15:01 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-08-19 21:55 [PATCH 1/2] external interrupts: abstraction layer Brent Casavant
2005-08-20  9:46 ` Christoph Hellwig
2005-08-23 22:01   ` Brent Casavant
2005-08-24  7:38     ` Christoph Hellwig
2005-08-24 15:00       ` Brent Casavant
  -- strict thread matches above, loose matches on Subject: below --
2005-07-28 21:13 Brent Casavant

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox