I have just recently started to work with LPC2148 ARM processor.
I am trying to understand some assembly code regarding the creation of the interrupts vector.
here is the code:
// Runtime Interrupt Vectors
// -------------------------
Vectors:
b _start // reset - _start
ldr pc,_undf // undefined - _undf
ldr pc,_swi // SWI - _swi
ldr pc,_pabt // program abort - _pabt
ldr pc,_dabt // data abort - _dabt
nop // reserved
ldr pc,[pc,#-0xFF0] // IRQ - read the VIC
ldr pc,_fiq // FIQ - _fiq
#if 0
// Use this group for production
_undf: .word _reset // undefined - _reset
_swi: .word _reset // SWI - _reset
_pabt: .word _reset // program abort - _reset
_dabt: .word _reset // data abort - _reset
_irq: .word _reset // IRQ - _reset
_fiq: .word _reset // FIQ - _reset
#else
// Use this group for development
_undf: .word __undf // undefined
_swi: .word __swi // SWI
_pabt: .word __pabt // program abort
_dabt: .word __dabt // data abort
_irq: .word __irq // IRQ
_fiq: .word __fiq // FIQ
__undf: b . // undefined
__swi: b . // SWI
__pabt: b . // program abort
__dabt: b . // data abort
__irq: b . // IRQ
__fiq: b . // FIQ
#endif
.size _boot, . - _boot
.endfunc
I dont understand what is going on here at all. If someone could explain to me the process, especially how the ldr instruction is used here, i would appreciate it.
Tal
Trying to guess:
ldr seems to load register pc with pointers to the ISR, so it's a sort of jump to those routines.
these smells like conditional compilation instructions, just like the C's preprocessor. Shortly, this
calls the routine stored at _swi, which is defined as
(because
#if 0
will never be true, so only the#else
part matters), and __swi is defined a few lines below:Keeping on guessing that dot could mean you can specify the address of your interrupt routine, therefore using that one.
I'm going to remove the conditional code, since it just complicates things:
Lets look first at the reset/start vector:
That instruction is an unconditional branch (a jump) to the the code labeled "_start" which I don't see in your snippet. Basically it'll be the assembly that initializes the stack and processor registers, maybe some memory areas then probably jumps to a C routine that performs the bulk of the initialization.
Next is the "undefined" vector that the ARM will go to when an invalid instruction is executed (if my memory is right):
That instruction loads the
pc
register (the 'program counter' or instruction pointer) with the address at the "_undf" symbol. If we look at the_undf
variable, it contains the address represented by symbol__undf
. So theldr pc, _undf
instruction loadspc
with__undf
's address - jumping to__undf:
.And at
__undf
we see:That's just a branch to the same address - an infinite loop (the
.
symbol means 'here' or 'this location').So for most of these vectors (which follow the same technique as the undefined vector), they'll just jump to little infinite loops. You could replace the infinite loops at those labels with code that's appropriate for the vector in question, though many projects don't because those vectors firing would represent some sort of serious error.
Finally, the vector slot that's most interesting is the IRQ vector:
This looks like the handler for an NXP device.
It loads the
pc
register with the value read from a memory location that's relative to thepc
register. Since on the ARM architecture the IRQ vector is always at address 0x00000018 (I'm going to ignore implementations that can map the vectors elsewhere or ARMs like the Cortex-M3 that use a different vector model) and because of the instruction pipeline's effect on using thePC
register value for addressing, the memory location that will be read by this instruction will be0xFFFFF030
, which is the address of the memory-mappedVICVectAddr
register in the 'Vectored Interrupt Controller` (VIC):This device register will contain the address of the interrupt handler for the interrupt that just occurred (of course the VIC needs to be properly initialized so it knows that address).
So when the
ldr pc,[pc,#-0xFF0]
instruction executes, it'll load thepc
register with the address of the interrupt handler routine for the appropriate device (basically, jumping to the right interrupt handler).Many/most processors implementation of an interrupt vector table is to have a list of addresses to interrupt handlers. The reset vector contains the address in memory where the first instruction of the reset handler lives. the interrupt vector is an address where the first instruction of the interrupt handler code lives, etc.
Traditionally ARM does not do it this way. Most ARM cores actually execute at that address, so the table contains executable instructions. Because there is an entry point every 4 bytes in that table you basically want to have some flavor of branch instruction. You can use more than one instruction, but as you can see that second or third instruction becomes an entry point for other exceptions. If you are sure you will never have one of those other exceptions, you can get away with it, but it is usually unwise. If you limit yourself to one instruction your choices are a pc relative branch or a load pc with some nearby address. So the ldr pc,something is loading an address of the first instruction of some code into the program counter so that it essentially branches to that code. b . is an assembler (gnu only?) syntax short cut meaning branch to self, or branch in an infinite loop. If the destination is close enough to exception table you can simply b __irq. There are various solutions and various reasons why you would choose one way or another which I wont get into.
The relatively newer Cortex-M3 and other thumb2 only ARM processors (thumb is a reduced form of the ARM instruction set, and thumb2 is an extension to the thumb instruction set). You can run thumb only or thumb2 on a Cortex-M3, but you cannot run any ARM instructions, so the traditional (ARM) model of a table of instructions either has to change to a table of 16 bit, 2 byte instructions or it has to change all together. What they did was go for a model very similar to most other processors, they went with a table of addresses. The exception to that is at address 0 there is a 32 bit value in the table which is loaded into the stack pointer, which is a nice feature for embedded. Post reset, the core will read 32 bits at address zero, put that value in the stack pointer, read 32 bits at address 0x4 and put that value in the program counter, essentially branching to that address. Note the list of exceptions in a cortex-m3 is different than the list in a traditional ARM core. There are many interrupt handler vectors in the cortex-m3 table for example.