Interrupt handlers The routi ne that processes an interru p t (called an interrupt handler ) needs to do a num be r of thing s. • First, since the interr up t may have occurred anywhere in the main code, including the mid dle of a calculation, any of the general pur pos e registers may have impor ta n t, unsaved values. Since you can't know ahead of time which registers contain importa nt values, all general - purp ose registers changed by the interrup t 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 enoug h to work in all cases.) • The interru p t handler may have been invoked for a number of reason s. (On MIPS, the sam e routine is called not only for interru p t s, but also for synchrono us event s such as arithm et ic overflow and unaligned loads / s t or e s. 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 up t needs to be serviced. For instance, if it was a receiver (keyboar d) interr up t, then the next keypress is read from the Receiver Data Register. • When the interru pt handler is due to return, the interru pt e d program needs to have its state (i.e., registers) restore d, and then be resu m e d at the point it was stoppe d. The addres s of this instruction was saved by the machi ne when the interr up t occurre d, so it is sim ply a matter of getting this address, and jumpi ng back to it. MIPS To make interr up t s work, you'll need to know a few more features of the MIPS architect u re. Section 2.1 of the SPIM manual has a few paragrap h s on Coprocess or 0 and interrup t handling, but you may find the following sections more up - to- date and inform ative. Coprocess or 0 MIPS com put er s contain not only the main process or (CPU), but also at least one coprocessor (see Figure 2 in the SPIM man ual ). Interrup t s and exceptions are managed by Coprocessor 0 , which also handles the mem ory subsyste m. (Coprocess or 1, if present, does floating - point com put ati ons.) Normal user - level code doesn't access Coprocessor 0, but interru pt - aware code has to use it. Coprocessor 0 has several registers which control interrup t s and exceptions. • Register 12, the Status Register , is a read - write register which cont rols whether interrup t 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 interru pt or exception occurs. It specifies what kind of interr upt or exceptio n just happene d. • Register 14 is Exception Progra m Counter (EPC). When an interru p t or exception occurs, the address of the curren tly running instruction is copied from the Program Counter to EPC. This is the addres s that your interru pt handler jumps back to when it finishes • Registers 9 and 11, Timer Count and Timer Compare. In SPIM, the timer is sim ulat ed with two more coprocess or registers: Count ($9), whose value is continuo u sly incremen te d 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 interru p t, the exception handler has to load Count , add a fixed amoun t called the time slice (quantu m) , 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 instruction s. • mfc0 (Move From Coprocessor 0) moves a value from a coprocessor 0 register to a general - pur pos e register: mfc0 $t5, $13 # Copy Cause register value to $t5. • mtc0 (Move To Coprocessor 0) moves a value from a general - pur pos e 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 u p t s requires doing three things: 1. Turn on the Interru pt Enable bit at Bit 0 of the Status Register. This bit is the global on/ off contr ol for interru pt s. 2. Also in the Status Register, turn on the Interru pt Mask bits corres po n di ng to the Receiver (bit 11), Trans mit t er (bit 10) and Timer (bit 15). These bits per mi t the CPU to respon d to interr upt s generate d by the keyboard, display and timer. 3. Turn on the Interru pt Enable bit in the Receiver Control Register (0xFFFF0000). This bit tells the receiver hardware (keyboar d) that it should generate interr up t s when keypresses occur. Now an interru p t will be generated when the user presses a key, and the exception han dler will autom atically 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 Interru pt Enable bit and turn on the corres po n di ng bit in the Trans mit t er Control Register (at 0xFFFF0008). Writing an interrupt handler When an interr up t occurs, the following things are done autom atically by the hardware: 1. The Exception Level bit (bit 1) in the Status Register is turned on. While this bit is 1, no further interru pt s can occur. This is essential, because we don't want the interr up t handler to be itself interru p t e d. 2. The Cause register is set to indicate the cause of the interru pt (see Dispatching an interr u p t below). 3. The EPC register is set to the current value in the Program Counter. This is the add res s in the main code where you will be resumi ng after han dling the interrup t. 4. The Program Count er is set to 0x8000018 0. This addres s (0x80000 1 80) is where you need to put your exception han dler. 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 excep tion handler begins, it needs to first find out what caused the exceptio n. 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 interrup t has occurred. But which interru p t? The Interrup t 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 trans mi t te r is ready for another character and bit 15 will be on if the timer has raise an interrup t. Registers Your interru p t 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; reme m ber that somet hi ng like sw $t0, saved_t0 expands to two machin e instructions using $at). This situation is resolved by forbiddi ng user program s from using two general - purpo se register s, $k0 and $k1 (The k stands for kernel, which an exception han dler is part of). Your interr upt han dler is allowed to use $k0 and $k1 withou t having to save or restore their values. This allows you just enough leeway to start saving registers, as well as making returning from the interru pt han dler possible. Note that your exception handler (and main program) is probably also using the $at register, which is used silently by the assem bler in the expansio n of certain pseudoi ns t r uct io ns; for exam ple: # 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 mem ory, 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 com piler 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 interru pt handler, because it is tricky to get right, and getting it wrong can lead to subtle inter mit te n t 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 u p t handler is ready to retur n, it must restore the interru pt e d progra m' 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 jum p back to the user program. These are done with the eret instruction: # # # # # # # eret effectively does mfc0 $k0, $12 li $k1, 0xFFFFFFFD and $k0, $k0, $k1 mtc0 $k0, $12 mfc0 $k0, $14 jr $k0 this, all at once: # Get status register. # # # # Clear Exception Level bit (bit 1) to allow interrupts again. Get address to jump back to (EPC). Jump back to that address. eret