Real-time processes often require the ability to react to an external event. External interrupts are a way for a real-time process to receive a real-world external signal.
An external interrupt is generated via a signal applied to the external interrupt socket on systems supporting such a hardware feature, such as the SGI PCIE-RT cards, which have a 1/8-inch stereo-style jack into which a 0-5V signal can be fed. An exterior piece of hardware can assert this line, causing the card to generate an interrupt.
This chapter discusses the following:
Various external interrupt hardware might implement the external interrupt feature in very different ways. The external interrupt abstraction layer provides the ability to determine when an interrupt occurs, to count the number of interrupts, and to select the source of those interrupts without depending upon specifics of the device being used.
This section discusses the following:
The external interrupt abstraction layer provides a character device and sysfs attribute files to control operation.
Assuming the usual /sys mount-point for sysfs, the attribute files are located in the following directory:
/sys/class/extint/extint#/ |
The extint# component of the path is determined by the extint driver itself. The # character is replaced by a number (possibly multidigit), one per external interrupt device, beginning at 0. For example, if there were three devices, there would be three directories:
/sys/class/extint/extint0/ /sys/class/extint/extint1/ /sys/class/extint/extint2/ |
The sysfs attribute files are as follows. For more information, see “External Interrupt Output for the PCIE-RT Card”.
| File | Description | ||
| dev | Contains the major and minor number of the abstracted external interrupt device. If 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. See “The /dev/extint# Device”. | ||
| mode | Contains the shape of the output signal for interrupt generation. For example, the PCIE-RT card can set the output to one of the following: low, high, pulse, oneshot, toggle, or delay. | ||
| modelist | Contains the list of available valid output modes, one per line. These strings are the legal valid values that can be written to the mode attribute.
| ||
| period | Contains the repetition interval for periodic output signals (such as repeated strobes, automatic toggling). This period is specified in nanoseconds, and is written as a string. | ||
| provider | Contains an indication of which low-level hardware driver and device instance are attached to the external interrupt interface. This string is free-form and is determined by the low-level driver. | ||
| quantum | Contains the interval to which any writes of the period attribute will be rounded. Because 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 that is not a multiple of the quantum is written to the period attribute) is determined by the specific low-level external interrupt drive. However, generally the low-level driver should round to the nearest available quantum multiple. For example, suppose the quantum value is 7800. If a value of 75000 was written into the period attribute, this would represent 9.6 quantums. The actual period will be rounded to 10 quantums, or 78000 nanoseconds. The actual period will be returned upon subsequent reads from the period attribute. | ||
| source | Contains the hardware source of interrupts. For example, the SGI PCIE-RT card can trigger either from the external input or from the card's internal timer section, or both. | ||
| sourcelist | Contains the list of available interrupt sources, one per line. These strings are the legal values that can be written to the source attribute file. |
This section discusses the operations that an application can perform with the read-only external interrupt device file /dev/extint #:
A process may use mmap(2) to memory-map a single memory page from the external interrupt device file into the process' address space. At the beginning of this page, a counter of an unsigned long type is maintained. This counter is incremented with each external interrupt received by the device.
Alternatively, the read(2) system call returns a string representation of the counter's current value.
The poll(2) and select(2) system calls allow a process to wait for an interrupt to trigger:
poll() indicates whether an interrupt has occurred since the last open(2) or read() of the file
select() blocks until the next interrupt is received
The flock(2) system call with the options LOCK_EX|LOCK_MAND ensures exclusive write access to the device attribute files (for example, /sys/class/extint/extint #/mode).
| Note: You must define the _GNU_SOURCE macro before including the header files in order to use the LOCK_MAND flag on the call to flock(2). |
When this lock is obtained, only a process that has access to the corresponding file descriptor will be able to write to the attribute files for that device. Any other process that attempts a write(2) system call on one of these attribute files will fail with errno set to EAGAIN.
The flock() system call will block until there are no other processes that have the device file open and until no other flock() is active on the device. However, if LOCK_NB is passed to flock(), the call will fail and errno will be set to EWOULDBLOCK.
While a lock is in place, any attempt to call open(2) on the device will block. However, if O_NONBLOCK is passed to open(), the call will fail and errno will be set to EWOULDBLOCK.
To release the lock, call flock() with the LOCK_UN argument. The lock will also be automatically dropped when the last user of the corresponding file descriptor closes the file, including via a process exit. The lock will persist if the file descriptor is inherited across fork(2) or exec(2) system calls.
| Note: You must not pass the LOCK_MAND flag along with the LOCK_UN flag. The flock() system call behavior is unspecified in this case. |
Example 3-1 illustrates a method of searching for an unused external interrupt device that can be used exclusively by that program.
Example 3-1. Searching for an Unused External Interrupt Device
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <errno.h>
#include <string.h>
int main(void) {
char devfile[PATH_MAX];
int i = 0;
int fd;
int found = 0;
int status;
try_again:
/* Search for free /dev/extint# device */
while (i <= 255) {
sprintf(devfile, "/dev/extint%d", i);
i++;
fd = open(devfile, O_RDONLY|O_NONBLOCK);
if (fd >= 0) {
/* Found a unlocked device. */
found = 1;
break;
}
/* An error occurred. Check why. */
if (EWOULDBLOCK == errno) {
/* Found a locked device. */
printf("Tried %s, but it is locked.\n", devfile);
}
/* Some other type of error, just try next device.
* But don't complain about non-existent devices.
*/
if (ENOENT != errno)
printf("Unexpected error opening %s: %s\n",
devfile, strerror(errno));
}
if (!found) {
printf("Could not find unlocked extint device to use.\n");
return 1;
}
/* Try locking this device to gain exclusive access. */
status = flock(fd, LOCK_EX|LOCK_MAND|LOCK_NB);
if (status != 0) {
if (EWOULDBLOCK == errno) {
/* The device was available, but another process
* has locked it between the time we opened it
* and made the flock() call.
*/
printf("Opened %s, but someone else locked it.\n",
devfile);
} else {
/* Some other error occurred. */
printf("Unexpected error locking %s: %s\n",
devfile, strerror(errno));
}
/* Try the next device. */
found = 0;
close(fd);
goto try_again;
}
/* Successfully gained exclusive use of device */
printf("Exclusive use of %s established.\n", devfile);
/* Application code begins... */
/* ... application code ends. */
/* Unlock and close external interrupt device */
flock(fd, LOCK_UN);
close(fd);
/* Successful run */
return 0;
} |
The external interrupt abstraction layer as implemented by the extint device driver is used by the SGI pcie-rt driver to present a uniform interface to external interrupt users. It is possible for third-party or end-user device drivers to interface to the extint driver as well, as described below.
The extint_properties and extint_device structures provide the low-level driver interface to the abstraction layer driver. The /usr/local/include/extint.h file defines the structures and function prototypes.
This section discusses the following:
To register the low-level driver with the abstraction layer, use the following call:
struct extint_device*
extint_device_register(struct extint_properties *ep,
void *devdata); |
The ep argument is a pointer to an extint_properties structure that specifies the particular low-level driver functions that the abstraction layer should call when reading/writing the attributes described in “sysfs Attribute Files”.
The devdata argument is an opaque pointer that is stored by the extint code. To retrieve or modify this value, use the following calls:
void* extint_get_devdata(const struct extint_device *ed); void extint_set_devdata(struct extint_device *ed, void* devdata); |
The low-level driver may use the devdata value in any manner desired, because the extint driver does not interpret its contents.
The return value is one of the following:
A pointer to a struct extint_device (which should be saved for later interrupt notification and driver deregistration).
A negative error value (in case of registration failure). The driver should be prepared to deal with such failures.
The struct extint_properties call table is as follows:
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);
/* Arm/disarm timer */
int64_t (*arm_timer)(struct extint_device * ed, int64_t ns, int when);
void (*disarm_timer)(struct extint_device * ed);
}; |
| Note: Additional fields not of interest to the low-level external interrupt driver may be present. You should include /usr/local/include/extint.h to acquire these structure definitions. |
The owner value should be set to the module that 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, you can retrieve the value passed as the devdata argument to the registration function by using the following call:
extint_get_devdata(ed); |
You can update the value by using the following call:
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.
| Field | Description |
| owner | Specifies the module that contains the functions pointed to by the remaining structure members. |
| get_mode | Writes the current mode attribute of the abstraction layer into the single-page-sized buffer passed as the second argument and returns the length of the written string. |
| set_mode | Reads the mode attribute of the abstraction layer as specified in the buffer (passed as the second argument and as sized by the third) and returns the number of characters consumed (or a negative error number in event of failure). It also causes the output mode to be set as requested. |
| get_modelist | Writes strings representing the available interrupt output generation modes into the single-page-sized buffer passed as the second argument, one mode per line. It returns the number of bytes written into this buffer. This implements the modelist attribute of the abstraction layer. |
| get_period | Returns an unsigned long that represents the current repetition period, in nanoseconds. This implements the period attribute of the abstraction layer. |
| set_period | Accepts 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 that can be exactly set into the underlying hardware, the driver is free to adjust the value as it sees fit, although typically it should round the value to the nearest available value. This implements the period attribute of the abstraction layer. |
| get_provider | Writes a human-readable string that identifies the low-level driver and a particular instance of a 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. This implements the provider attribute of the abstraction layer. |
| get_quantum | Returns an unsigned long that represents the granularity to which the interrupt output repetition period can be set, in nanoseconds. This implements the quantum attribute of the abstraction layer. |
| get_source | Writes the current interrupt input source into the single-page-sized buffer passed as the second argument and returns the length of the written string. This implements the source attribute of the abstraction layer. |
| set_source | Reads the source specified in the buffer (passed as the second argument and as sized by the third) and returns the number of characters consumed or a negative error number in event of failure. It also causes the input source to be selected as requested. This implements the source attribute of the abstraction layer. |
| get_sourcelist | Writes strings representing the available interrupt input sources into the single-page-sized buffer passed as the second argument, one source per line. It returns the number of bytes written into this buffer. This implements the sourcelist attribute of the abstraction layer. |
| arm_timer | Sets up the external interrupt device to generate an interrupt at a specified time. The time is specified in nanoseconds via the second argument. The third parameter may be set to the values EXTINT_TIMER_RELATIVE or EXTINT_TIMER_ABSOLUTE. The third parameter controls whether the time is relative to the moment the function is called or is absolute system time, (as returned by the getnstimeofday() system call). Interrupt notifications occur through the standard external interrupt callout mechanism described in “Interrupt Notification Interface”. This field may be set to NULL if the low-level driver does not support timer functionality. |
| disarm_timer | Cancels a pending interrupt, if any, scheduled to be delivered due to a prior call to the arm_timer() function. If the previously scheduled interrupt has already occurred, it is not necessary to call disarm_timer(), and calling disarm_timer() when no interrupt is pending should be harmless. This field may be set to NULL if the low-level driver does not support timer functionality. |
When an external interrupt signal triggers an interrupt that is handled by the low-level driver, the driver should make the following call:
void extint_interrupt(struct extint_device *ed); |
This allows the abstraction layer to perform any appropriate abstracted actions, such as update the interrupt count or trigger poll/ select actions. The sole argument is the struct extint_device that was returned from the registration call.
When the driver desires to deregister a particular device previously registered with the abstraction layer, it should make the following call:
void extint_device_unregister(struct extint_device *ed); |
The sole argument is the struct extint_device that was returned from the registration call. There is no error return from this call, but if invalid data is passed to it, the likelihood of a kernel panic is very high.
If your hardware device supports capabilities that are not provided for in the abstraction layer, you can do one of the following:
Add a new attribute to the abstraction layer by modifying struct extint_properties to add appropriate interface routines and update any existing drivers as necessary.
Have the low-level driver create its own device class and corresponding attributes and/or character special devices. This method is preferred and is required if the capability is dependent on the hardware in a method that cannot be abstracted.
For example, the SGI PCIE-RT card has the ability to map the interrupt output control register directly into a user application in order 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 control register's format, something that cannot be abstracted away by the kernel and is very specific to this particular hardware implementation. This capability is provided by the pcie_rt driver, which supplies its own character special device along with a pcie_rt device class.
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.
This section discusses the following:
For systems (not just applications) that 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 have unexpected harmful effects (such as tying up a CPU spinning on a value) or may not provide appropriate response times.
A callout mechanism lets you write your own kernel module in order to gain minimal-latency notification of events and react accordingly. It also provides an extension capability that might be of interest in certain situations. For example, there could be an application that requires an interrupt counter page similar to the one maintained by the abstraction layer, but that starts counting at 0 when the device special file is opened. Or, there could be an application that requires 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 separate module rather than cluttering the main external interrupt abstraction code.
To register a callout to be invoked upon interrupt ingest, allocate a struct extint_callout , fill it in, and pass it to the following call:
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 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. You should include /usr/local/include/extint.h to acquire these structure definitions. |
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 that is to be invoked whenever an interrupt is ingested by the abstraction layer for the device of interest. The data field is the only argument passed to it; it is used opaquely and is provided solely for use by the caller. That is, the abstraction layer will invoke the following upon each interrupt of the specified device:
ec->function(data); |
You can register multiple callouts 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.
To remove a callout, call the following with the same arguments as provided during callout registration:
extern void
extint_callout_unregister(struct extint_device *ed,
struct extint_callout *ec); |
You can remove both active and orphaned callouts 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.
You can use the pcie_rt.c file as a template for a low-level driver. The file is shipped as part of the extint source RPM.
| Note: In addition to providing the abstraction interface, this low-level driver creates a character special device and a device class that are both specific to PCIE-RT. |
This section describes the following for the SGI PCIE-RT real--time interrupt card:
The PCIE-RT real-time interrupt card provides an interface to external circuitry. It can be used to ingest and generate a simple signal for the following uses:
On the output side, one of the jacks can provide a small selection of output modes that create a 0-5V electrical output
On the input side, one of the jacks will cause the PCIE-RT card to generate an interrupt on the transition edge of an electrical signal
The PCIE-RT card can also be used to generate interrupts within the host system itself without interfacing to external circuitry.
The pcie_rt driver registers itself with the extint abstracted external interrupt driver and lets it take care of the user-facing details.
The output section provides several modes of output. The mode is configurable by the abstraction layer device's mode attribute. The abstraction layer device's modelist attribute contains available modes. The modes are as follows:
The period can be set to a range of values determined by the reference clock of the PCIE-RT hardware. For pulse and toggle modes, this period determines how often the pulse or toggle occurs. The period can be set only to a multiple of this length (rounding will occur automatically in the driver). The period should be configurable by the abstraction-layer device's period attribute, and the tick length can be found from the abstraction-layers device's quantum attribute. For certain modes, there may be minimum or maximum period values enforced by the driver so that the PCIE-RT logic or output sections function correctly.
One device file is provided, which can be memory mapped. This file provides direct access to the PCIE-RT hardware registers that control output and input. Directly manipulation of the register, both for reading and writing, may be performed in order to avoid the kernel overhead that would be necessary if using the abstracted interfaces.
Assuming the typical sysfs mount point, the device number files for these devices can be found at:
/sys/class/pcie_rt/pcie_rt#/dev |
This capability is not abstracted into the external interrupt abstraction layer because it is critical for an application to know that this is PCIE-RT device in order to determine the format of the mapped registers. Table 3-1 shows the register format. The value in the Attribute column of the table describes the register access semantics of the corresponding field, as follows:
| RO | Read-only |
| RW | Read-write |
| RW-V | Read-write, volatile value |
| RW1C | Read-write; write 1 to clear |
| RW1C-V | Read-write, volatile value; write 1 to clear |
|
Table 3-1. Register Format for the SGI PCIE-RT Card
The ingest section provides control over the source of interrupt signals. The external source is a circuit connected to the external jack provided on PCIE-RT cards. The timer source is the output of the external interrupt output timer logic, with the loopback source being the same as the timer, but provided for compatibility with existing software written for IOC4. Options of both and none are also available. The source is configurable by the abstraction layer's source attribute. You can find available sources in the abstraction layer device's sourcelist attribute.
For example, to set up a 100-ms (10-Hz) repeating timer, you would issue the following commands:
[root@linux root]# echo timer > /sys/class/extint/extint0/source [root@linux root]# echo 100000000 > /sys/class/extint/extint0/period [root@linux root]# echo pulse > /sys/class/extint/extint0/mode |
Use a two-conductor shielded cable to connect external interrupt output and input, with the two cable conductors wired to the +5V and interrupt conductors and the sleeves connected to the cable shield at both ends to maintain EMI integrity.
The PCIE-RT card implementation uses female 3.5mm audio jacks. The wiring for the jack is as follows:
Tip: +5V input
Ring: Interrupt input (optoisolated)
Sleeve: Chassis ground/cable shield
The input signal passes through an optoisolator that has a damping effect. The input signal must be of sufficient duration to drive the output of the optoisolator low in order for the interrupt to be recognized by the receiving system. The exact timing constraints may depend on cable quality and drive strength, and experimentation may be necessary to determine a safe value.
Figure 3-1 shows the internal driver circuit for the output connector and the internal receiver circuit for the input connector.