Registering Interrupts on Zynq

A look at how Zynq interrupt vectors are configured


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.

Cortex A9 Vector Table
Cortex A9 Vector Table

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