The preprocessor symbol USER_INTS specifies which interrupts will be enabled, in this case CLK1_INT.
4 #define USER_INTS (CLK1_INT)
After the RAM has been initialized and the .bss cleared, init calls
copyHandler, which copies the exception-handler code to the general exeption
vector address 0x8000.0080 in RAM (BEV=0).
35 la a0,handler
36 la a1,ehandler
37 li a2,0x80000080 # general vector
38 jal copyHandler
85 copyHandler:
89 or t0,a0,K1BASE
90 or t1,a1,K1BASE
91 1: lw v0,(t0)
92 sw v0,(a2)
93 addu t0,4
94 addu a2,4
95 blt t0,t1,1b
96 j ra
Notice that the entire procedure handler has been copied to the vector
area. You should check to ensure that your handler will fit between the start of
the vector area and the beginning of any allocated memory. In this example,
128 bytes are provided for the handler by specifying a .data start address of
0x80000100 to the linker. Notice also that copyHandler forces the address of the first two arguments to kseg1, so this function can be used before the D-Cache has been flushed.
After flushing the caches, copying the data to RAM, and initializing sp, gp, and the I/O devices, the macro CLK1_INIT is used to initialize clock 1. CLK1_INIT specifies the frequency of interrupts, where the parameter in the macro call specifies the number of milliseconds between interrupts, in this case 500.
62 CLK1_INIT(500)
Before enabling interrupts, the program checks to make sure the Sw bits in the Cause Register are cleared, to avoid another exception being generated immediately.
66 mtc0 zero,C0_CAUSE
Next the Status Register is written with a word that has the user-defined Interrupt Mask (IM) bits set and the Interrupt Enable bit set. This operation will also clear BEV, causing the vectors to be accessed with kseg0 addresses.
68 # clear BEV and set IM and IEC bits
69 li t0,(USER_INTS|SR_IEC)
70 .set noreorder
71 mtc0 t0,C0_SR
72 .set reorder
Then control is transferred to main using a jump and link indirect, which switches execution to kseg0 as previously explained.
75 la t0,main
76 jal t0
When an interrupt occurs, control is passed to the procedure handler. handler first acknowledges the interrupt, using the macro CLK1_ACK (in machine.h). Note that the interrupt must be acknowledged before control can be returned to the interrupted program, because the return restores the Status Register, which reenables interrupts. Note that because we have only a single interrupt source, we do not have to check the IP or IM bits to identify the exception.
105 CLK1_ACK
Then the memory location ticks is incremented by loading the word at the address, adding one to it, and storing it back to memory.
107 la k0,ticks
108 lw k1,(k0)
109 addu k1,1
110 sw k1,(k0)
To return to the interrupted C program, the value in the EPC Register is read into k0, and the required nop is inserted (the mfc0 instruction requires two cycles), followed by a jump indirect on k0. The rfe in the delay slot restores the Status Register.
113 mfc0 k0,C0_EPC
114 nop
115 j k0
116 rfe
Note that in this very simple example, the interrupt handler uses only two registers for temporary variables. The MIPS register-usage conventions reserve k0 and k1 for use by interrupt service routines. These registers can be used by an interrupt service routine without first being saved, so long as interrupts remain disabled. Thus in this example, no additional registers need to be saved before transferring control to handler.
Note also that because the interrupt handler is very short, interrupts can be left disabled without having any negative impact on interrupt latency. As shown in Section 4.4, as long as the interrupt handler is less than 39 cycles, it will never degrade the maximum interrupt latency (39 cycles is the worst-case number of cycles for which a divide instruction can disable interrupts).