Assembly code for creating interrupts vector on LP

2019-05-23 16:18发布

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

3条回答
唯我独甜
2楼-- · 2019-05-23 16:33

Trying to guess:

    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  

ldr seems to load register pc with pointers to the ISR, so it's a sort of jump to those routines.

#if 0
#else

these smells like conditional compilation instructions, just like the C's preprocessor. Shortly, this

    ldr   pc,_swi                   // SWI - _swi

calls the routine stored at _swi, which is defined as

_swi:   .word __swi                     // SWI

(because #if 0 will never be true, so only the #else part matters), and __swi is defined a few lines below:

__swi:  b     .                         // SWI

Keeping on guessing that dot could mean you can specify the address of your interrupt routine, therefore using that one.

查看更多
Bombasti
3楼-- · 2019-05-23 16:56

I'm going to remove the conditional code, since it just complicates things:

// 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

_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

        .size _boot, . - _boot
        .endfunc

Lets look first at the reset/start vector:

b   _start

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):

ldr pc, _undf

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 the ldr pc, _undf instruction loads pc with __undf's address - jumping to __undf:.

And at __undf we see:

__undf: b     .  

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:

ldr   pc,[pc,#-0xFF0]           // IRQ - read the VIC

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 the pc 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 the PC register value for addressing, the memory location that will be read by this instruction will be 0xFFFFF030, which is the address of the memory-mapped VICVectAddr register in the 'Vectored Interrupt Controller` (VIC):

 'apparent'      Pipeline      offset in      effective
  PC value        effect       the opcode      address
-------------   ------------   -----------    ----------
  0x00000018  +  0x00000008  -  0x0000ff0  == 0xfffff030

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 the pc register with the address of the interrupt handler routine for the appropriate device (basically, jumping to the right interrupt handler).

查看更多
Animai°情兽
4楼-- · 2019-05-23 17:00

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.

查看更多
登录 后发表回答