Interrupts on other ARM chips can be quite different. The M series ARM chips like the STM32F103 have a very different vector scheme, yet a lot in common.
Interrupts are a specific case of what are called "exceptions". I won't try to generalize the discussion to include these.
.align 5 .globl vectors vectors: b reset ldr pc, _undef ldr pc, _swi ldr pc, _iabt ldr pc, _dabt ldr pc, _unused ldr pc, _irq ldr pc, _fiqDon't ask me why I use the "ldr" instruction instead of just "b".
On the Cortex-A ARM, the vector table looks like this. It only has 8 entries. The entries are actual ARM instructions and each entry thus ought to be 32 bits in size. On reset, the processor expects to find this table at address zero (or whatever address the processor fires up at, perhaps 0xffff0000). In fact, the H3 bootrom looks like this:
ffff0000: ea000008 b 0xffff0028 ; reset ffff0004: ea000006 b 0xffff0024 ; undefined instruction ffff0008: ea000005 b 0xffff0024 ; software interrupt ffff000c: ea000004 b 0xffff0024 ; prefetch abort ffff0010: ea000003 b 0xffff0024 ; data abort ffff0014: ea000002 b 0xffff0024 ; unused ffff0018: ea000011 b 0xffff0064 ; IRQ ffff001c: ea000000 b 0xffff0024 ; FIQAfter your own code gets running, you can specify the location of the vector table by loading the VBAR register with the table location using code like this:
asm volatile ( "mcr p15, 0, %0, c12, c0, 0" : : "r" ( val ) )
This means you either must have initialized this stack pointer when your code set itself up, or you must load the stack pointer for every interrupts.
The ARM has a pair of instructions that make it fairly easy to save and restore all the registers, namely "stm" and "ldm" (store and load multiple). The ARM also has a bunch of interesting (and tricky) addressing modes that can be used to specify how all those registers get shoved onto the stack.
As a quick aside, here is code from the H3 bootrom to handle FIQ:
ffff0064: e24ee004 sub lr, lr, #4 ffff0068: e92d5fff push {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr} ffff006c: eb000723 bl 0xffff1d00 ffff0070: e8fd9fff ldm sp!, {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, pc}^
Now the mystery. How does the processor know that you are returning from an interrupt so that it can clear the "interrupt mode" bits in the PSR? For example the following instruction is a commonly recommended way to return from an interrupt:
subs pc,lr,#4Take a look at the FIQ code from the bootrom up above. Here, the first thing done is to adjust the value in "lr" by subtracting 4 from it. This means that when the "ldm" instruction restores that value into the PC when the above returns, it will resume at the right location.
While we are looking at that bootrom code, why is that "up-arrow" (caret) at the end of that ldm statement? It is crucial. It says that "for an LDM instruction that loads the PC, the CPSR should also be set (copied) from the SPSR.
What about the SUBS instruction. First of all, note that this is not the usual "SUB" instruction. The trailing letter "S" indicates that the instruction updates the CPSR, specifically if the destination register is R15 (the PC) then the CPSR is set (copied) from the SPSR.
push {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, sl, fp, ip, lr}Registers sp and lr are "banked" for an IRQ. This means that the processor switches to an entirely separate pair of registers for the duration of the interrupt. It sets "lr" as we have already discussed to allow return from the interrupt (the other "lr" is left unmolested). The other "sp" is switched to, but no value is set, as previously mentioned.
As a side note, an FIQ would switch to banked registers for R8 and up. This means that you would either need to save fewer registers (only R0 to R7), or if you were really clever and avoided R0-R7 you would not need to save/restore any registers!
If this is true, why is the bootrom code above saving r8 and so forth? Why indeed?
The ARM has 16 general registers. R15 is the PC, and we have no need to push that. If we did and then restored it, we would loop back to this push instruction all over again or do something else equally pointless. R14 is the lr. R13 is the sp, and we have no interest in pushing it. For whatever reason, R10 has the alias sl, R11 has the alias fp, and R12 has the alias ip. That accounts for all the registers that ought to be accounted for.
PUSH {r3} is: str r3, [sp, #-4]! POP {r3} is: ldr r3, [sp], #4Actually ARM allows a full list of registers in each, but these then become aliases for stmdb and ldmia. so:
pop {r0-r3} is: ldm sp!, {r0-r3}
It is bad form to push the pc or pop the lr. Such is either deprecated or perhaps even illegal. Having the base register in the list is also bad form.
STMxx Ry{!}, reglist{^}The exclamation says to modify Ry to reflect the end result.
The "xx" is one of four modes: IA, IB, DA, DB.
Where IA is increment after, IB is increment before.
Where DA is decrement after, DB is decrement before.
So, a push would use a stmdb and a pop would use a ldmia.
So, to get an interrupt you have to allow it at each of these 3 levels. When you handle the interrupt, you need to acknowledge it at two levels. You have to tell the GIC that you are handling it, and you need to tell the device (such as the UART) that you have handled the interrupt. If you fail to do this, and try to return from the interrupt, you will immediately get kicked back in! This is a well known "hard interrupt loop" that will just hang your software.
#define int_DISABLE \ asm volatile ( "mrs r0, cpsr; \ orr r0, r0, #0xc0; \ msr cpsr, r0" ::: "r0" ) #define int_ENABLE \ asm volatile ( "mrs r0, cpsr; \ bic r0, r0, #0xc0; \ msr cpsr, r0" ::: "r0" )
Tom's electronics pages / tom@mmto.org