4.2.1.1 Example 1: A Single Interrupt Source

This example shows a simple scheme for handling a single interrupt source. A timer is programmed to generate interrupts two times per second. The count value in the timer is decremented at some predefined rate. When it reaches zero, an interrupt request is asserted on one of the input pins (INT0-INT5). The association of interrupt requests and particular input pins is defined in the macro CLK1_INT in machine.h. The function handler uses the interrupt to increment the memory location ticks, and main prints the value of ticks whenever it is updated.

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).