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 <[EMAIL PROTECTED]>

 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 <[EMAIL PROTECTED]>
+
+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 <[EMAIL PROTECTED]> 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. <[EMAIL PROTECTED]>");
+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,
[EMAIL PROTECTED]                        how then could you be brave?
Silicon Graphics, Inc.                    -- Queen Dama, Source Wars
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to