The ARM vector table
Recall the ARM Vector table: A region in the program code set aside for instructions that execute for exceptions. The table below shows the vectors for the ARM Cortex A9.
The 7th entry in the table (offset 0x18) is the interrupt service vector. When the processor accepts an interrupt, it executes the instruction at that address in memory.
Shown below is the vector table disassembled from actual ARM A9 code. Each entry is a branch to a defined label.
00100000 <_vector_table>:
100000: ea000049 b 10012c <_boot>
100004: ea000025 b 1000a0 <Undefined>
100008: ea00002b b 1000bc <SVCHandler>
10000c: ea00003b b 100100 <PrefetchAbortHandler>
100010: ea000032 b 1000e0 <DataAbortHandler>
100014: e320f000 nop {0}
100018: ea000000 b 100020 <IRQHandler>
10001c: ea00000f b 100060 <FIQHandler>
Note the address offset at 0x18 (note the vector table base is at 0x00100000) is, as stated above, the IQR Handler vector. The disassembled code at this vector is listed below:
00100020 <IRQHandler>:
100020: e92d500f push {r0, r1, r2, r3, ip, lr}
100024: ed2d0b10 vpush {d0-d7}
100028: ed6d0b20 vpush {d16-d31}
10002c: eef11a10 vmrs r1, fpscr
100030: e52d1004 push {r1} ; (str r1, [sp, #-4]!)
100034: eef81a10 vmrs r1, fpexc
100038: e52d1004 push {r1} ; (str r1, [sp, #-4]!)
10003c: eb0001a2 bl 1006cc <IRQInterrupt>
100040: e49d1004 pop {r1} ; (ldr r1, [sp], #4)
100044: eee81a10 vmsr fpexc, r1
100048: e49d1004 pop {r1} ; (ldr r1, [sp], #4)
10004c: eee11a10 vmsr fpscr, r1
100050: ecfd0b20 vpop {d16-d31}
100054: ecbd0b10 vpop {d0-d7}
100058: e8bd500f pop {r0, r1, r2, r3, ip, lr}
10005c: e25ef004 subs pc, lr, #4
Instructions 0x100020 to 0x100038 are stack operations that preserve the both ARM’s General Purpose and Floating Point registers.
The instruction at 0x10003c is a branch to the label defined as IRQInterrupt
. After the branch returns, the processor restores all the registers it pushed to the stack, and uses subs pc, lr, #4
to return from interrupt.
The code branched to is shown below:
001006cc <IRQInterrupt>:
1006cc: e3003bc4 movw r3, #3012 ; 0xbc4
1006d0: e3403010 movt r3, #16
1006d4: e5932028 ldr r2, [r3, #40] ; 0x28
1006d8: e593002c ldr r0, [r3, #44] ; 0x2c
1006dc: e12fff12 bx r2
This code results in yet another branch. Prior to doing so, it loads register 3 with the address 0x100bc4
, which below is defined in the program’s symbol table
as:
00100bc4 g O .data 00000038 XExc_VectorTable
This is a Symbol defined by the tools as Xilinx’s own software vector table. Which is modified through their C function calls.
Xilinx’s Vector table
Shown below is the C inialization of Xilinx’s own software vector table. This can be found in the file xil_exception.c
XExc_VectorTableEntry XExc_VectorTable[XIL_EXCEPTION_ID_LAST + 1] =
{
{Xil_ExceptionNullHandler, NULL},
{Xil_UndefinedExceptionHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
{Xil_PrefetchAbortHandler, NULL},
{Xil_DataAbortHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
};
In the code above labeled IRQInterrupt
before jumping, register 2 is loaded with the address of the vector table with an offset of 0x28. In addition register 0 is loaded with the same address with the offset 0x2C.
Looking again at Xilinx’s data structure, it is an array with each entry being a pair of 32-bit values, a version is shown below with associated addresses for the pairs.
XExc_VectorTableEntry XExc_VectorTable[XIL_EXCEPTION_ID_LAST + 1] =
{
{Xil_ExceptionNullHandler, NULL}, //offset 0x0, 0x4
{Xil_UndefinedExceptionHandler, NULL}, //offset 0x8, 0xC
{Xil_ExceptionNullHandler, NULL}, //offset 0x10, 0x14
{Xil_PrefetchAbortHandler, NULL}, //offset 0x18, 0x1C
{Xil_DataAbortHandler, NULL}, //offset 0x20, 0x24
{Xil_ExceptionNullHandler, NULL}, //offset 0x28, 0x2C
{Xil_ExceptionNullHandler, NULL}, //offset 0x30, 0x34
};
We can see the location the IRQInterrupt
code branches to (offset 0x28) is a function called Xil_ExceptionNullHandler
. This is a function Xilinx defines for undefined exceptions. The entry is placed by default as the initial value.
So in order for an Interrupt to execute code written to handle interrupts, it must be written into Xilinx’s Vector Table.
Registering an interrupt into Xilinx’s table
In order to facilitate writing to their defined table, Xilinx provides a function: void Xil_exceptionRegisterHandler(u32 Exception_id, Xil_ExceptionHandler Handler, void *Data)
. This function takes 3 arguments: The exception number, the exception vector, and data to accompany the vector.
The function writes the given vector and data to the specified exception ID. The entries in the table go from 0 (offset 0) to 6 (offset 0x2C), so to modify the entry in the table for IRQ
exceptions, use 5
for the first argument. The second argument asks for a pointer to an exception handler; you can just pass the name of your written handler as the argument. The third argument is for data associated with the interrupt. By default this is NULL and you can just write NULL again to the entry.
A code example is given below
//include definitions for xilinx's vector table and functions relevant to it
#include "xil_exception.h"
void interrupt_handler(void); //prototype for handler (code needs to be written)
void register_my_irq_handler(void) //registers the above defined function to xilinx's table
{
//this register's the interrupt handler to the 6th entry in Xilinx's table
Xil_ExceptionRegisterHandler(5, interrupt_handler, NULL);
}