[previous]
[index]
[next]
Example 5: Interrupt Service Routines
This example demonstrates how to attach a function to an interrupt, so
that it is called whenever the interrupt is triggered. These functions
are commonly known as "interrupt service routines" (ISRs), and are used to
handle interrupts from hardware such as timer chips, printers, disk drives,
Ethernet controllers, etc.
Refer to the commented source code of the example
for the details.
Principle of Operation
In this example the interrupt source is the PC parallel port.
Extensive documentation on programming and using the PC
parallel port for data acquisition and control can be found on the
Web. Briefly,
- The parallel port consists of three bytes in the PC I/O address
space, starting at address 0x378.
- The first byte at 0x378 is for 8 bits of output
- The second byte at 0x379 is for 5 bits of input, 3 bits unused
- The third byte at 0x37A is for 4 bits of output, 4 bits of setup
- The parallel port generates interrupts on the x86's interrupt request (IRQ)
7 when pin 10 is shorted to ground (any of pins 19-25).
- The parallel
port control registers can be programmed to generate an interrupt when
this happens, and to inhibit interrupts when one is being
serviced.
- In this example we don't write outputs or read inputs, although
presumably you would in a real application. Here we just program
the parallel port's control register to generate an interrupt, and set
up RT Linux to invoke
our ISR when an interrupt is generated.
- The ISR increments a cumulative count of the number of interrupts
received, and writes this to a FIFO.
- A Linux application reads the FIFO and prints out any new count
that arrives.
Setting up the Parallel Port
Setting up a hardware device and writing an ISR depends almost
entirely on the device. RT Linux gives the facilities to connect a
particular interrupt to a particular ISR, but that's it.
- The parallel
port is perhaps the simplest of all devices to set up for interrupts:
simply set bit 4 (of 0 through 7) of the control register to enable
interrupts, and
clear it to disable them.
- To set a bit at a particular I/O address, first read in the byte,
then set the bit using the bitwise-or operation '|', then write the byte back out. That way the other
bits are not affected (there is no x86
instruction for individual bit-setting). In our case,
byte = inb(0x37A);
byte = byte | 0x10; /* hex 10 = binary 00010000 */
outb(byte, 0x37A);
Now, shorting pin 10 to ground will generate an interrupt.
- To clear the bit and disable interrupts, follow a similar
procedure using the bitwise-and operation '&':
byte = inb(0x37A);
byte = byte & 0xEF; /* hex EF = binary 11101111 */
outb(byte, 0x37A);
Now, shorting pin 10 to ground will have no effect.
Defining the ISR
- An ISR is simply a function that takes no arguments and returns no
value, and mediates the interaction of a hardware device with the
real-time application (it may be the entire real-time application).
- This simple ISR just increments the cumulative count of interrupts
serviced so far, and write this integer to the fifo.
- The interrupt
source is not periodic, since they are generated by a person touching
two wires together, and they can be quite bursty as the wires are
brought close together.
- We need to disable the physical source of
interrupts while we are manipulating the fifo, otherwise the fifo
code will be re-entered and cause problems. Here's the whole ISR:
static void isr_code(void)
{
static int interrupts = 0; /* cumulative count of interrupts */
outb(inb(0x37A) & 0xEF, 0x37A); /* disable interrupt */
interrupts++;
rtf_put(0, &interrupts, sizeof(interrupts));
outb(inb(0x37A) | 0x10, 0x37A); /* enable interrupt */
}
Associating the ISR to the Interrupt
- Associating the ISR to the interrupt is simple, using a few RTAI
functions. In our case, we would do this in 'init_module()':
rt_free_global_irq(7); /* disconnect any other ISR */
rt_request_global_irq(7, isr_code); /* connect ours */
rt_startup_irq(7); /* tell RTAI to honor it */
outb(inb(0x37A) | 0x10, 0x37A); /* tell the hardware to issue it */
- In 'cleanup_module()', we should disconnect the ISR:
outb(inb(0x37A) & 0xEF, 0x37A); /* disable interrupt */
rt_shutdown_irq(7); /* tell RTAI not to honor it */
rt_free_global_irq(7); /* disconnect the ISR */
A Note on Re-Entrancy
- Suppose an interrupt happens when we are in the ISR. The ISR itself
would be interrupted, and called again.
- This is "re-entrancy," similar
to the programming concept of "recursion," in which a function calls
itself.
- This is often useful, but care needs to be taken in order to
code re-entrant functions. The only safe area to write is the
"stack," the arguments and variables that appear at the top of your
function, without any 'static' qualifiers that would make them
persist. A new stack is created for each invocation of an ISR.
- In particular, avoid writing to resources that can't be written
atomically, for instance
- global data structures
- hardware registers
- other non-re-entrant functions
- There are two problems that make our ISR non-re-entrant: the
persistent 'interrupts' cumulative count, and the 'rtf_put()' call.
- Incrementing 'interrupts' means reading its value, adding 1 to it,
and writing it back. This sequence could be split, and we'd lose a
count (perhaps not critical).
- We can surmise that 'rtf_put()' manipulates some global data
structure associated with the FIFO. If the sequence of writing data
and updating any counts or pointers is interrupted, the FIFO will be
corrupted.
- The solution is to disable interrupts as soon as the ISR
runs. Hardware typically expects this to be done and will throttle
back accordingly.
- Suppose the instructions to disable interrupts is interrupted?
- In our case, these instructions are "idempotent," meaning doing
them twice or redoing them in the middle gives the same net effect as
doing them once.
- While disabling an interrupt is typically an idempotent sequence,
it may not be possible on some hardware. In this case, mutual
exclusion techniques such as semaphores can be used.
Running the Demo
To run the demo, change to the 'ex05_isr' subdirectory of the
top-level tutorial directory, and run the 'run' script by typing
./run
Alternatively, change to the top-level tutorial directory and run the
'runall' script there by typing
./runall
and selecting the "Interrupt Service Routine" button.
You'll have 10 seconds to short pin 10 to any of pins 19 through
25. During this time, you'll see messages like "cumulative
interrupts: 123" printing out as you short the pins. Note that
many interrupts are generated each time you short the pins, due to the
noisy nature of the wires touching together.
See the Code
Next: Example 6, Shared Memory Communication
Back: Example 4, FIFOs