1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
Whenever some illegal operation (attempt to execute undefined instruction, attempt to access memory with insufficient permission, etc.) happens or some peripheral device "messages" the ARM core, that something important happened, an exception occurs. Exception is something, that pauses normal execution and passes control to the (specific part of) operating system.
Upon an exception, several things happen:
1. Processor mode <link to processor-modes-explained.txt> changes.
2. CPSR gets saved into new mode's SPSR <link to PSRs-explained>.
3. pc (incremented by some value) is saved into new mode's lr.
4. Execution jumps to an entry in the exception vectors table specific to the exception.
Each exception type is taken to it's specific mode. Types and their modes are:
1. Reset and supervisor mode.
2. Undefined instruction and undefined mode.
3. Supervisor call and supervisor mode.
4. Prefetch abort and abort mode.
5. Data abort and abort mode.
6. Hypervisor trap and hypervisor mode (not used normally, only with extensions).
7. IRQ and IRQ mode.
8. FIQ and FIQ mode.
The new value of the pc (the address, to which the exception "jumps") is the address of nth instruction from exceptiom base address, which, undes simplest settings, is 0x0 (bottom of virtual address space <link to MMU-explained.txt>). N depends on the exception type. It is:
· 1 for reset
· 2 for undefined instruction
· 3 for supervisor call
· 4 for prefetch abort
· 5 for data abort
· 6 for hypervisor trap (not used here)
· 7 for IRQ
· 8 for FIQ
Those 8 instructions constitute the exception vectors table. As the instruction follow one another, each of them should be a branch to some exception-handling routine. In fact, on other architectures often the exception vector table holds raw addresses of where to jump instead of actual instructions, as here.
Bottom of virtual address space can be changed to some other value by manipulating the contents of SCTLR and VBAR coprocessor registers.
On exception entry, the registers r0-r12 contain values used by the code that was executing before. In order for the exception handler to perform some action and return to that code, those registered can be preserved in memory. Some compilers can automatically generate appropriate prologue and epilogue for handler-functions, that will preserve the right registers (see <link to some gcc doc about it>; we're not using this feature in our project).
Having old CPSR in SPSR and old pc in lr is helpful, when after handling the exception, the handler needs to return to the code that was executing before. There are 2 special instructions, subs and ldm ^ (load multiple with a dash ^), that, when used to change the pc (and therefore perform a jump) cause the SPSR to be copied into CPSR. As bits of CPSR determine the current execution mode, this causes the mode to be change to that from before the exception. In short, subs and ldm ^ are the instructions to use to return from exceptions.
As noted eariler, upon exception entry an incremented value of pc is stored in lr. By how much it is incremented, depends on exception type and execution state. For example, entering undefined instruction exception for thumb state places in undef's lr the problematic instruction's address + 2, while taking this exception from ARM state places in undef's lr that instruction's address + 4 (see full table in paragraph B1.8.3 of armv7-ar_arm <link here>).
It's worth noting, that while our implementation of exception handlers (<path to assembly source>) also sets the stack pointer (sp) upon each exception entry, a kernel could be written, where this wouldn't be done, as each mode enterable by exception has it's own sp.
|