9.5.3. 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 9.14 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 9.14. 

	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 ?
	SUBLES		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 9.15 is similar to Example 9.14, except that there are two channels being handled (which may be the input and output side of the same channel). 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 and 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 9.15. 

	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 9.16 dispatches up to 32 interrupt sources to their appropriate handler routines. Because it is designed for use with the normal interrupt vector (IRQ), it should be 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 (ARM DAI 0030) describes multiple source prioritization of interrupts using software, as opposed to using hardware as described here.

Example 9.16. 

	; 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 9.17 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 9.3 shows the layout of the PCBs that the example expects.

Figure 9.3. 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 9.17. 

	STMIA		r13, {r0 - r14}^						; Dump user registers above r13.
	MSR		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.
	MRSNE		SPSR_csxf, r0						; Restore the status.
	LDMNEIA		r13, {r0 - r14}^						; Get the rest of the registers
	SUBNES		pc, r14						; and return and restore CPSR.
; Insert "no next process code" here.
Copyright © 1997, 1998 ARM Limited. All rights reserved.ARM DUI 0040D
Non-Confidential