Structure of an Interrupt Handler

I/O devices and their controllers fall into three major classes.

        1.     Program Controlled

        2.     Interrupt Driven

        3.     Direct Memory Access

Of these classes, only the latter two can generate interrupts.  Here we focus on how the CPU processes the interrupts associated with such devices.

This lecture focuses on what I call the “Interrupt Controller Hub”.

This hub processes interrupts from multiple devices and sends a single INT signal to the
CPU when an interrupt is recognized.

The CPU sends a single ACK signal back to the hub, which sends it to the appropriate device.


Handling I/O Interrupts

An interrupt is a signal external to the CPU program that causes the normal program
execution to be interrupted and another program (the interrupt handler) to be run.

There are two basic types of interrupts: I/O interrupts and page fault interrupts.  The
latter type are used in virtual memory systems and can be quite complex to handle.

Interrupts from I/O devices are more easily handled in that they can be easily fit into
the standard fetch/execute cycle of the CPU.

In this cycle, each instruction is fetched from the Instruction Memory and then
executed.  The control unit selects the instruction to be executed next.

For I/O interrupts, the design change to accommodate interrupts is minor.

Before each instruction in a program is executed, the control unit checks the status
of the INT signal.  There are two possibilities:

    1. INT = 0.  In this case the next instruction is executed.

    2. INT = 1.  In this case, the address of this next instruction is saved for later use,
        and a high–level interrupt handler is run to identify the source of the interrupt.


Interrupt Priority and CPU Priority

We follow the design of the PDP–11, developed by the Digital Equipment Corporation
(now defunct) in discussing an interrupt structure.

We begin with the idea of a CPU execution priority.  This is specified by a 3–bit number
in the program status register (PSR).  Here is the structure that we shall use.

Bits

15 – 8

7

6

5

4

3

2

1

0

Use

Other Uses

N

Z

V

C

I

CPU Priority

We postulate a 16–bit program status register with the following bits:

                N, Z, V, & C           Status of the previous arithmetic operation
                                                (Always included in the PSR, so we use these too)

                I                               Interrupts enabled.
                                                When I = 0, the CPU ignores any interrupt.

                Priority                    A 3–bit unsigned integer representing the CPU
                                                execution priority.

 


The I Bit and CPU Execution Priority

These four bits are used in processing interrupts.

Disabling Interrupts (I = 0)

This should be done very seldom.  Only the Operating System can set the I bit.

There are certain times in processing an interrupt during which another interrupt
cannot be processed.  During these short times, the CPU sets I = 0.

CPU Priority

Normal programming practice allows for multiple interrupt priorities and nested
interrupts.  A high priority device, such as a disk, can take precedence over the
processing of an interrupt for a low priority device, such as a keyboard.

To manage devices at various priorities, each interrupt is processed as follows.
        1.     A device interrupts with priority K.
        2.     The CPU sets I = 0 and saves various registers.
        3.     The CPU sets its priority to K (the same number), sets I = 1,
                and then begins execution of the interrupt handler.

Devices with higher priorities can now interrupt and have their interrupts handled.


More on CPU Priority

We follow the PDP–11 convention.

Priority = 0                     All user programs execute at this level.

Priority = 1, 2, 3             Various Operating System Utilities operate at these levels.
                                        We generally ignore these levels.

Priority = 4, 5, 6, 7         Interrupt handlers operate at these levels.

The CPU will acknowledge and process an interrupt only if the priority of the
interrupting device is higher than the CPU execution priority.

For this reason, almost all interrupt handlers are written to execute with a CPU
priority exactly equal to the device priority.

The convention is that all hardware devices are assigned one of four interrupt levels:
4, 5, 6, and 7.

        Priority 4         is the lowest hardware priority, reserved for the keyboard, etc.

        Priority 7         is the highest hardware priority, reserved for disks, etc.


Vectored Interrupts

How does the CPU identify the device that asserted the interrupt and begin
execution of its interrupt handler?  More on this later, but for now:

1.     The device sends its “vector”, which is an address of a data structure.
        This is most often an address in low memory, say addresses 0 – 1023.

2.     The data structure at the specified address contains the following.
        a)     The address of the interrupt handler associated with the device.
        b)     The CPU execution priority for the interrupt handler.

The interrupt handling sequence can be elaborated.

1.     Clear the Interrupt Enabled bit (set I = 0) to block other interrupts.

2.     Store the essential registers, so that the user program can be restarted later.

3.     Load the PSR with the execution priority and load the PC with the address.

4.     Set I = 1 to allow nested interrupts and start execution of the handler.


More on Vectors as Used in I/O

In this context a vector is just an address into the computer main memory.
It is most commonly a low memory address.

Here is the scenario in which an I/O vector is appropriately used.

1.   The CPU has received the INT (Interrupt) signal from the I/O hub,
      indicating that there is a valid interrupt pending.

2.   The CPU has sent the ACK (Acknowledge) signal to the I/O hub,
      commanding the I/O device to identify itself so that the data can be
      transferred properly.

In vectored I/O handling, the device that raised the interrupt will identify itself
by indicating the program to manage the data transfer.

How is this program to be identified?  The best way is to indicate its address in memory,
so that the handler program can be called quickly.

This must be based on an address that is part of a published standard, so that it is
known ahead of time and can be used by all manufacturers of the device.


What About Fixed Addresses for the Device Driver?

The software to manage I/O for an I/O device is often called the “device driver”.

One way to manage this is to place the device driver at a fixed address in the computer
memory.  The driver for the NIC (Network Interface Card, used to connect the computer
to the Internet) might be placed at address 0x01F0 8000.

This solution would have been sound in the early 1970’s, but would present severe
problems for the design of a modern operating system.

Modern operating systems, based on sharing the computer among several programs,
assume that no program is placed at a fixed address.

Each time the computer is booted up, the operating system selects an address for each
program to be run, and places that program in its slot.

The memory slot for a given program is selected by the operating system in response to
a number of factors.  The operating system does this to optimize overall performance.


The Use of a Vector

The idea of a vector is a location in which to store the address of the device driver.

As an example, suppose the following.

    1.   The vector to be associated with the NIC is 0x100.

    2.   At boot–up time, the operating system selects the address 0x01F0 8000 as the
          start address for the device handler for the NIC.

After loading the device driver into the address 0x01F0 8000, the operating system loads
that address into location 0x100.  What we have is as follows:

Processing the NIC Interrupt

Here, I assume that the NIC vector is 0x100.

At the start of the scenario, the CPU must first identify the device raising the interrupt.

    1.   The INT is asserted and the CPU begins processing the interrupt.
          As a first step, the CPU saves the current value of the PC (Program Counter)
          so that the executing program can be restarted.

    2.   The CPU asserts the ACK signal.

    3.   The NIC places the vector 0x100 onto the I/O data lines.

    4.   The CPU recognizes 0x100 as a vector.
          It goes to address 0x100 and retrieves the contents: 0x01F0 8000.

    5.   The CPU loads the address 0x01F0 8000 into the PC (Program Counter) and
          begins execution of the program at that address.

At some time later, the program that was interrupted is resumed by restoring the
saved value of the PC.


Interrupt Lines and Assertion Levels

The structure of the interrupt lines on our computer is as follows:

We have four interrupt lines, one for each of the four priority levels.
Each is paired with an acknowledge line for the same priority.

Interrupts are asserted low; that is, the signal goes to 0 when the device interrupts.

Acknowledgements are asserted high; that is, the signal goes to logic 1 to acknowledge.

In order to understand the process of low assertions, we must explain the idea of
a pull–down resistor.  We begin with some basic electronics.


Voltage Across Resistors

We begin by studying an electronic circuit in which a voltage is placed across two
resistors in series.  Standard algebra gives the current through these two resistors
as a function of the voltage applied and the individual resistors.

It is a basic result that the current through the two resistors is the same.

The goal of this analysis is to find the voltage at the point between the resistors.
I call it V2.


The Voltage V2

Here we apply some basic electronics to get the voltage at this point between
the two resistors.  Here is the figure with the basic equation.

Apply a few algebra tricks to get this into a form that is useable.

We assume that R1 > 0 (that it is never zero) and look at two cases.
        1.     R2 = 0, and
        2.     R2 = 1000
·R1.


Suppose R2 = 0

Suppose that R2 = 0.  Then, we use the first simple equation to note that V2 = 0.

This can be derived with the first form of the second equation.


Suppose R2 = 1000·R1

If R2 = 1000·R1, then R1/R2 = 0.001.  We use the second variant of the equation.

If the source voltage is 5.00 volts, this gives V2 = 4.995 volts, which cannot be
distinguished from 5.00 volts.

If R2 = 1000·R1, then for all practical purposes V2 = V.


Mechanism for Asserting an Interrupt

Each interrupt line is attached to a “pull down” resistor.

When the device asserts an interrupt, it sets its Interrupt Flip–Flop.  Thus Q = 1.
This enables the tri–state, which becomes a closed switch with very low resistance.

With the tri–state enabled, all the voltage drop is across the resistor so that the voltage
on the Interrupt Line becomes 0.  The interrupt is asserted.

With the tri–state disabled, it becomes an open switch, a line with very high resistance.
All the voltage drop is across the tri–state, so the Interrupt Line stays at voltage.

 


Multiple Devices on One Line

By design, Interrupts are active low in order to facilitate attaching multiple
interrupting devices on a single interrupt line.

Here we see four devices attached to a single line.

If no device is interrupting, we have Int = Logic 1

If any one device is interrupting, we have Int = Logic 0.  The interrupt is asserted.

If more than one device is interrupting, we still have Int = Logic 0.
The devices cannot interfere with each other.


The Big Picture

Here is the entire circuit for the controller hub.

Daisy Chaining

In daisy chaining, the ACK is passed from device to device until it hits a device
that has asserted an interrupt.

If the I/O device has not asserted an interrupt, it passes the ACK to the
next “downstream” I/O device.

Priority rank is by physical proximity to the CPU.


Daisy Chaining (Part 2)

The ACK is passed down the line to the first I/O device attached.

If that device has not raised an interrupt, it passes the ACK to the next device.
When a device that asserted an interrupt gets the ACK, it captures it and does not pass it.