[PATCH 1/2] external interrupts: abstraction layer

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]
  Powered by Linux