6.7.4. Example interrupt handlers in assembly language

Interrupt handlers are often written in assembly language to ensure that they execute quickly. The following sections give some examples:

Single-channel DMA transfer

Example 6.16 shows an interrupt handler that performs interrupt driven I/O to memory transfers (soft DMA). The code is an FIQ handler. It uses the banked FIQ registers to maintain state between interrupts. This code is best situated at location 0x1C.

In the example code:

r8

Points to the base address of the I/O device that data is read from.

IOData

Is the offset from the base address to the 32-bit data register that is read. Reading this register clears the interrupt.

r9

Points to the memory location to where that data is being transferred.

r10

Points to the last address to transfer to.

The entire sequence for handling a normal transfer is four instructions. Code situated after the conditional return is used to signal that the transfer is complete.

Example 6.16. FIQ handler

    LDR     r11, [r8, #IOData]     ; Load port data from the IO device.
    STR     r11, [r9], #4          ; Store it to memory: update the pointer.
    CMP     r9, r10                ; Reached the end ?
    SUBLSS  pc, lr, #4             ; No, so return.
                                   ; Insert transfer complete
                                   ; code here.

Byte transfers can be made by replacing the load instructions with load byte instructions. Transfers from memory to an I/O device are made by swapping the addressing modes between the load instruction and the store instruction.

Dual-channel DMA transfer

Example 6.17 is similar to Example 6.16, except that there are two channels being handled. The code is an FIQ handler. It uses the banked FIQ registers to maintain state between interrupts. It is best situated at location 0x1c.

In the example code:

r8

Points to the base address of the I/O device from which data is read.

IOStat

Is the offset from the base address to a register indicating which of two ports caused the interrupt.

IOPort1Active

Is a bit mask indicating if the first port caused the interrupt (otherwise it is assumed that the second port caused the interrupt).

IOPort1, IOPort2

Are offsets to the two data registers to be read. Reading a data register clears the interrupt for the corresponding port.

r9

Points to the memory location to which data from the first port is being transferred.

r10

Points to the memory location to which data from the second port is being transferred.

r11, r12

Point to the last address to transfer to (r11 for the first port, r12 for the second).

The entire sequence to handle a normal transfer takes nine instructions. Code situated after the conditional return is used to signal that the transfer is complete.

Example 6.17. FIQ handler

    LDR     r13, [r8, #IOStat]      ; Load status register to find which port
                                    ; caused the interrupt.
    TST     r13, #IOPort1Active
    LDREQ   r13, [r8, #IOPort1]     ; Load port 1 data.
    LDRNE   r13, [r8, #IOPort2]     ; Load port 2 data.
    STREQ   r13, [r9], #4           ; Store to buffer 1.
    STRNE   r13, [r10], #4          ; Store to buffer 2.
    CMP     r9, r11                 ; Reached the end?
    CMPLE   r10, r12                ; On either channel?
    SUBNES  pc, lr, #4              ; Return
                            ; Insert transfer complete code here.

Byte transfers can be made by replacing the load instructions with load byte instructions. Transfers from memory to an I/O device are made by swapping the addressing modes between the conditional load instructions and the conditional store instructions.

Interrupt prioritization

Example 6.18 dispatches up to 32 interrupt sources to their appropriate handler routines. Because it is designed for use with the normal interrupt vector (IRQ), it is branched to from location 0x18.

External hardware is used to prioritize the interrupt and present the high-priority active interrupt in an I/O register.

In the example code:

IntBase

Holds the base address of the interrupt controller.

IntLevel

Holds the offset of the register containing the highest-priority active interrupt.

r13

Is assumed to point to a small full descending stack.

Interrupts are enabled after ten instructions, including the branch to this code.

The specific handler for each interrupt is entered after a further two instructions (with all registers preserved on the stack).

In addition, the last three instructions of each handler are executed with interrupts turned off again, so that the SPSR can be safely recovered from the stack.

Note

Application Note 30: Software Prioritization of Interrupts describes multiple-source prioritization of interrupts using software, as opposed to using hardware as described here.

Example 6.18. 

    ; first save the critical state
    SUB     lr, lr, #4              ; Adjust the return address
                                    ; before we save it.
    STMFD   sp!, {lr}               ; Stack return address
    MRS     r14, SPSR               ; get the SPSR ...
    STMFD   sp!, {r12, r14}         ; ... and stack that plus a
                                    ; working register too.
                                    ; Now get the priority level of the
                                    ; highest priority active interrupt.
    MOV     r12, #IntBase           ; Get the interrupt controller's
                                    ; base address.
    LDR     r12, [r12, #IntLevel]   ; Get the interrupt level (0 to 31).

    ; Now read-modify-write the CPSR to enable interrupts.

    MRS     r14, CPSR               ; Read the status register.
    BIC     r14, r14, #0x80         ; Clear the I bit
                                    ; (use 0x40 for the F bit).
    MSR     CPSR_c, r14             ; Write it back to re-enable
                                    ; interrupts and
    LDR     pc, [pc, r12, LSL #2]   ; jump to the correct handler.
                                    ; PC base address points to this
                                    ; instruction + 8
    NOP                             ; pad so the PC indexes this table.

                                    ; Table of handler start addresses
    DCD     Priority0Handler
    DCD     Priority1Handler
    DCD     Priority2Handler
; ...
    Priority0Handler
    STMFD   sp!, {r0 - r11}         ; Save other working registers.
                                    ; Insert handler code here.
; ...
    LDMFD   sp!, {r0 - r11}         ; Restore working registers (not r12).

    ; Now read-modify-write the CPSR to disable interrupts.
    MRS     r12, CPSR               ; Read the status register.
    ORR     r12, r12, #0x80         ; Set the I bit
                                    ; (use 0x40 for the F bit).
    MSR     CPSR_c, r12             ; Write it back to disable interrupts.

    ; Now that interrupt disabled, can safely restore SPSR then return.
    LDMFD   sp!, {r12, r14}         ; Restore r12 and get SPSR.
    MSR     SPSR_csxf, r14          ; Restore status register from r14.
    LDMFD   sp!, {pc}^              ; Return from handler.
Priority1Handler
; ...

Context switch

Example 6.19 performs a context switch on the User mode process. The code is based around a list of pointers to Process Control Blocks (PCBs) of processes that are ready to run.

Figure 6.5 shows the layout of the PCBs that the example expects.

Figure 6.5. PCB layout

The pointer to the PCB of the next process to run is pointed to by r12, and the end of the list has a zero pointer. Register r13 is a pointer to the PCB, and is preserved between time slices, so that on entry it points to the PCB of the currently running process.

Example 6.19. 

    STMIA   r13, {r0 - r14}^        ; Dump user registers above r13.
    MRS     r0, SPSR                ; Pick up the user status
    STMDB   r13, {r0, lr}           ; and dump with return address below.
    LDR     r13, [r12], #4          ; Load next process info pointer.
    CMP     r13, #0                 ; If it is zero, it is invalid
    LDMNEDB r13, {r0, lr}           ; Pick up status and return address.
    MSRNE   SPSR_cxsf, r0           ; Restore the status.
    LDMNEIA r13, {r0 - r14}^        ; Get the rest of the registers
    NOP
    SUBNES  pc, lr, #4              ; and return and restore CPSR.
                    ; Insert "no next process code" here.
Copyright © 2002-2006 ARM Limited. All rights reserved.ARM DUI 0203G
Non-Confidential