You are on page 1of 7

Interrupt handlers

The routi ne that processes an interrupt (called an interrupt handler )


needs to do a number of things.
First, since the interr upt may have occurred anywhere in the main
code, including the middle of a calculation, any of the general -
pur pose registers may have impor tant, unsaved values. Since you
can' t know ahead of time which registers contain important values,
all general - purpose registers changed by the interrupt handler
must be saved on entry and restored on exit. (Don' t try to use
registers that the main program isn' t using: the main program
might be using all of them, and your routine has to be general
enough to work in all cases.)
The interrupt handler may have been invoked for a number of
reasons. (On MIPS, the same routine is called not only for
interrupt s, but also for synchronous event s such as arithmet ic
overflow and unaligned loads / st or es. Collectively, these are called
exceptions , and so a better term for the handler would be
"exception handler".) The exception handler thus needs to
deter mi ne the cause of the exception, and jump to the relevant
subrouti ne that will handle it.
Now, the interr upt needs to be serviced. For instance, if it was a
receiver (keyboar d) interr upt, then the next keypress is read from
the Receiver Data Register.
When the interrupt handler is due to return, the interrupt ed
program needs to have its state (i.e., registers) restored, and then
be resumed at the point it was stopped. The address of this
instruction was saved by the machi ne when the interr upt occurred,
so it is simply a matter of getting this address, and jumpi ng back to
it.
MIPS
To make interr upt s work, you'll need to know a few more features of the
MIPS architect ure. Section 2.1 of the SPIM manual has a few paragraphs
on Coprocessor 0 and interrupt handling, but you may find the following
sections more up- to- date and informative.
Coprocessor 0
MIPS comput er s contain not only the main processor (CPU), but also at
least one coprocessor (see Figure 2 in the SPIM manual ). Interrupt s and
exceptions are managed by Coprocessor 0, which also handles the
memory subsystem. (Coprocessor 1, if present, does floating- point
comput ati ons.) Normal user - level code doesn' t access Coprocessor 0, but
interrupt - aware code has to use it.
Coprocessor 0 has several registers which control interrupt s and
exceptions.
Register 12, the Status Register , is a read- write register which
cont rols whether interrupt s are allowed to happen, and if so, which
ones.
Register 13, the Cause Register , is a mostly read- only register
whose value is set by the system when an interrupt or exception
occurs. It specifies what kind of interr upt or exception just
happened.
Register 14 is Exception Program Counter (EPC). When an interrupt
or exception occurs, the address of the currently running
instruction is copied from the Program Counter to EPC. This is the
address that your interrupt handler jumps back to when it finishes
Registers 9 and 11, Timer Count and Timer Compare. In SPIM, the
timer is simulat ed with two more coprocessor registers: Count ($9),
whose value is continuously incremented by the hardwar e, and
Compare ($11), whose value can be set. When Count and Compare
are equal, an interr upt is raised, at Cause register bit 15. To
schedul e a timer interrupt, the exception handler has to load
Count , add a fixed amount called the time slice (quantum) , and
store this value into Compare . The smaller the time slice, the
greater the frequency of timer interr upt s.
To access these registers, you'll need to know two new MIPS
instructions.
mfc0 (Move From Coprocessor 0) moves a value from a coprocessor
0 register to a general - pur pose register:
mfc0 $t5, $13 # Copy Cause register value to $t5.
mtc0 (Move To Coprocessor 0) moves a value from a general -
pur pose register to a coprocessor 0 register:
mtc0 $v0, $12 # Copy $v0's value to Status register.
If you want to modify a value in a coprocessor 0 register, you need to
move the register' s value to a general - purpose register with mfc0, modify
the value there, and move the changed value back with mtc0.
Enabling interrupts
Enabling interr upt s requires doing three things:
1. Turn on the Interrupt Enable bit at Bit 0 of the Status Register. This
bit is the global on/ off contr ol for interrupt s.
2. Also in the Status Register, turn on the Interrupt Mask bits
correspondi ng to the Receiver (bit 11), Transmit t er (bit 10) and
Timer (bit 15). These bits per mi t the CPU to respond to interr upt s
generated by the keyboard, display and timer.
3. Turn on the Interrupt Enable bit in the Receiver Control Register
(0xFFFF0000). This bit tells the receiver hardware (keyboar d) that it
should generate interr upt s when keypresses occur.
Now an interrupt will be generated when the user presses a key, and the
exception handler will automatically be jumped to.
Later, when your program is ready to write to the termi nal, you' ll want to
turn off the Receiver Control Register Interrupt Enable bit and turn on the
correspondi ng bit in the Transmit t er Control Register (at 0xFFFF0008).
Writing an interrupt handler
When an interr upt occurs, the following things are done automatically by
the hardware:
1. The Exception Level bit (bit 1) in the Status Register is turned on.
While this bit is 1, no further interrupt s can occur. This is essential,
because we don' t want the interr upt handler to be itself
interrupt ed.
2. The Cause register is set to indicate the cause of the interrupt (see
Dispatching an interr upt below).
3. The EPC register is set to the current value in the Program Counter.
This is the address in the main code where you will be resumi ng
after handling the interrupt.
4. The Program Count er is set to 0x80000180.
This address (0x80000180) is where you need to put your exception
handler. Do this with the .ktext (kernel text) and .kdata (kernel data)
directives.
.kdata
# Put any data structures required by the interrupt handler
here.
.ktext 0x80000180
# Exception handler begins here.
Dispatching an interrupt
When the exception handler begins, it needs to first find out what caused
the exception. This can be achieved by examining the Cause Register from
coprocessor 0. The Exception Code (bits 6- 2) describes what caused the
trap; if the Exception Code value is zero, then an interrupt has occurred.
But which interrupt? The Interrupt Pending bits in the Cause Register
indicate which devices need servicing; bit 11 will be on if the receiver has
data to be read, bit 10 will be on if the transmi t ter is ready for another
character and bit 15 will be on if the timer has raise an interrupt.
Registers
Your interrupt handler must save any general - purpose registers that it is
going to use (to be restored at return). But to do so requires you to
modify at least one register first (try it and see; remember that somet hi ng
like sw $t0, saved_t0 expands to two machine instructions using $at).
This situation is resolved by forbiddi ng user programs from using two
general - purpose register s, $k0 and $k1 (The k stands for kernel, which an
exception handler is part of). Your interr upt handler is allowed to use $k0
and $k1 without having to save or restore their values. This allows you
just enough leeway to start saving registers, as well as making returning
from the interrupt handler possible.
Note that your exception handler (and main program) is probably also
using the $at register, which is used silently by the assembler in the
expansion of certain pseudoi nst r uct ions; for example:
# Expansion of "blt $t3, $t4, foo".
slt $at, $t3, $t4
bne $at, $zero, foo
To save $at, you will need to stage its value to a tempor ary location
before saving it to memory, since the act of executing a sw instruction
destr oys $at as a side- effect. Any mention of $at must be bracketed by .
set noat and .set at or the compiler will complain:
# Allow direct references to $at
# (and forbid its use in pseudoinstructions).
.set noat
# Copy $at to a temporarily safe place.
move $k1, $at
# Reserve $at for pseudoinstruction expansions again.
.set at
# Save value to memory (side effect: this changes $at!).
sw $k1, saved_at
# Now save the other registers used by the interrupt handler.
Simply do this in reverse to restore $at at the end of the interr upt
handler.
Spend a bit of time on your register - saving code when you write your
interrupt handler, because it is tricky to get right, and getting it wrong
can lead to subtle inter mit tent failures. For instance, since $at is so
delicate, it makes sense to save it first and restore it last.
Returning from an interrupt
When your interr upt handler is ready to retur n, it must restore the
interrupt ed program' s state first. Most of this is done when your handler
restores saved general - purpose registers. The final steps are to restore
the Status register and to jump back to the user program. These are done
with the eret instruction:
# eret effectively does this, all at once:
# mfc0 $k0, $12 # Get status register.
# li $k1, 0xFFFFFFFD
# and $k0, $k0, $k1 # Clear Exception Level bit (bit 1)
# mtc0 $k0, $12 # to allow interrupts again.
# mfc0 $k0, $14 # Get address to jump back to (EPC).
# jr $k0 # Jump back to that address.
eret

You might also like