Other Exception Handlers
12.4 Linux exception program flow
For the ARM architecture, all exceptions are handled in (privileged) SVC mode, regardless of
which exception type occurred. A branch instruction is placed in each vector table entry, to call the first part of the exception handler – the so-called exception stub. This is written in assembly code and performs stack maintenance operations, return address calculation and the switch into
SVC mode. The stub is provided as a macro, with different argument values used to specify the
exception type.
The stub then calls the code which does the actual exception specific operation, again written in assembler code and then the corresponding exception handler function (written in C code). After the exception is handled, the processor can return (through the exception stub) back to the originally executing code.
The Linux exception related code is found (mostly) in the arch/arm/kernel/ directory. The most
interesting files are as follows: • entry-armv.S
• entry-common.S
• entry-header.S
We’ll start by looking at the exception stubs and then move onto the actual exception handlers, which can be re-configured dynamically and are rather more complex.
12.4.1 Exception stubs
The file entry-armv.S contains the exception vector table and the stub code for each exception.
It has the following symbols:
.__vectors_start .__vectors_end
The first thing to consider is how Linux fills the ARM processor exception vector table, that is, the space between __vectors_start and __vectors_end? The following example demonstrates
one possibility:
Example 12-2 ARM Linux exception vector table
.globl __vectors_start __vectors_start
ARM( swi SYS_ERROR0 )
THUMB( svc #0 ) THUMB( nop ) W(b) vector_und + stubs_offset W(ldr) pc, .LCvswi + stubs_offset W(b) vector_pabt + stubs_offset W(b) vector_dabt + stubs_offset W(b) vector_addrexcptn + stubs_offset W(b) vector_irq + stubs_offset W(b) vector_fiq + stubs_offset .globl __vectors_end __vectors_end:
Other Exception Handlers
At system boot, the code shown in Example 12-2 on page 12-6 is copied into memory at the location of the exception vector table of the ARM. So, when an exception occurs, the appropriate stub within the kernel will be run.
Note
The addrexcptn entry above is a legacy detail and is not used on any ARM processor that
supports 32-bit addressing.
You might have noticed that we have a SWI (SVC) as the first entry in the table. SWI is the former
name for the SVC instruction. When the system has booted, we do not expect the reset exception
to happen. The assembly instruction swi SYS_ERROR0 causes a kernel dump, should this
unexpected situation arise. The most likely cause of execution from PC = 0 is unexpected use of a null pointer.
Now let’s look at the stub definitions. The kernel defines a macro, which generates the common stub entry routines used for all exceptions except FIQ and SVC.
Example 12-3 Exception vector stub code
macro vector_stub, name, mode, correction=0 .align 5 vector_\name: .if \correction sub lr, lr, #\correction .endif @
@ Save r0, lr_<exception> (parent PC) and spsr_<exception> @ (parent CPSR) @ stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, [sp, #8] @ save spsr @
@ Prepare for SVC32 mode. IRQs remain disabled. @
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE) msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code @
and lr, lr, #0x0f mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode .endm
The macro code in Example 12-3 is straightforward. It makes a mode dependent correction to the value in the link register, to calculate the correct return address. It saves R0 and the link register to the stack, followed by the SPSR. It has to save R0, as we need to use a register for SPSR modification. Recall that IRQ interrupts are automatically disabled upon exception entry and therefore this code can all be treated as atomic. It copies the CPSR into the SPSR, modifying the mode bits so that they select Supervisor (SVC) mode. Finally, we use the mode bits of the
The MOVSPC, LR instruction is a special one – it copies the link register to the PC, but also the
SPSR to the CPSR. This means it both does the branch to the handler and changes the mode to supervisor in one shot.
Linux code runs either in User mode, or in Supervisor mode. Therefore, only two of the entries in this jump table should be valid. Example 12-4 shows the jump table for IRQ. Each entry corresponds to a value of SPSR bits [3:0] – the first being User mode, the second FIQ, the third IRQ and so forth.
Example 12-4 Linux IRQ jump table
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32) .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) .long __irq_svc @ 3 (SVC_26 / SVC_32) .long __irq_invalid @ 4
.long __irq_invalid @ 5
etc.
So, __irq_usr is the function which handles IRQ exceptions that happened in User mode and __irq_svc looks after IRQ exceptions which happened in Supervisor mode.
12.4.2 Boot process
During the boot process, the kernel will allocate a 4KB page as the vector page. It maps this to the location of the exception vectors, virtual address 0xFFFF0000 and/or 0x00000000. This is done
by devicemaps_init() in the file arch/arm/mm/mmu.c. This is invoked very early in the ARM
system boot. After this, trap_init (in arch/arm/kernel/traps.c), copies the exception vector
table, exception stubs and kuser helpers into the vector page. The exception vector table obviously has to be copied to the start of the vector page, the exception stubs being copied to address 0x200 (and kuser helpers copied to the top of the page, at 0x1000- kuser_sz), using a
series of memcpy() operations, as shown in Example 12-5.
Example 12-5 Copying exception vectors during Linux boot
unsigned long vectors = CONFIG_VECTORS_BASE;
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
When the copying is complete, the kernel exception handler is in its runtime dynamic status, ready to handle exceptions
12.4.3 Interrupt dispatch
There are two different handlers, __irq_usr and __irq_svc. These save all of the processor
registers and use a macro get_irqnr_and_base which indicates if there is an interrupt pending.
The handlers loop around this code until no interrupts remain. If there is an interrupt, the code will branch to do_IRQ which exists in arch/arm/kernel/irq.c.
Other Exception Handlers
At this point, the code is the same in all architectures and we call an appropriate handler written in C.
There is however, a further point to consider. When the interrupt is completed, we would normally need to check whether or not the handler did something which needs the kernel scheduler to be called. If the scheduler decides to go to a different thread, the one that was originally interrupted stays dormant until it is selected to run again.
Boot Code
In this chapter, we will look at the work which needs to be undertaken within the boot code running in an ARM processor based system. We will focus on two distinct areas:
• Code to be run immediately after the processor comes out of reset, on a so-called bare-metal system, that is, one in which code is run without the use of an operating system. This is a situation which is often encountered when first bringing-up a chip or system.
• The operation of a Linux bootloader.
If you are writing an application to run on an existing OS port, it is unlikely that you will need to make use of the contents of this chapter. On the other hand, if you are porting from one ARM processor based platform to another, it is highly likely that you might have to work on such code.
Boot Code