Interrupt Service Routines

Writing an interrupt service routine and registering it to the vector table

3389

Interrupt Service Routines

An interrupt service routine (ISR) is a piece of code written to execute during interrupts. ISR’s typically respond to the interrupt source that requested attention. Examples could be the processor filling a FIFO with data when it becomes empty or responding to a button press.

The ARM core has multiple exception vectors, but only a single vector dedicated to standard interrupt requests (IRQ). Thus, the code that executes during an interrupt must determine the source of the interrupt and service it accordingly.

The IRQ port on the ARM is a single wire input. To be able to handle multiple interrupt sources, there needs to be external decoding logic.The Zynq system implements the Generic Interrupt Controller to arbitrate interrupt Requests.

When the GIC passes an interrupt to the core, it keeps track of the source’s interrupt ID. This enables the processor to quickly determine the source of the interrupt and service it accordingly. Additionally, Since the GIC is arbitrating multiple interrupt sources it must keep track of when the processor enters and exits the interrupt handler. The mechanism for this is implemented via a pair of registers in the GIC.

The GIC’s ICCIAR and ICCEOIR Regsiters

When the processor reads the ID of the current interrupt, it reads from the Interrupt Acknowledge Register (ICCIAR). This provides the processor with the interrupt source’s ID as well as provides the GIC with acknowledgement that processor is in the interrupt service routine. At the end of the interrupt, the processor writes back the same ID to the End of Interrupt Register (ICCEOIR), signaling to the GIC, that the interrupt service routine has finished.

Since the ARM has only a single interrupt vector, the ID read from the GIC is very useful in determining the source of the interrupt and, consequently, what to do within the ISR.

Writing an Interrupt Handler

Below is the shell for an interrupt handler on the Zynq system. It has 2 defined interrupts: ID 27 (the Global Timer interrupt) and ID 52 (the GPIO interrupt). At the beginning of the call, the ID is read from the ICCIAR. The ID is then used in a switch, which executes different code based on the given value. At the end of the routine, the id is written to the ICCEOR. This signals to the GIC that the interrupt service routine has completed.

//GIC Interrupt Acknoledge registers
#define ICCIAR *((uint32_t *) 0xF8F0010C)
#define ICCEOIR *((uint32_t *) 0xF8F00110)


//Interrupt ID definitions
#define GTC_INT_ID 27
#define GPIO_INT_ID 52

void irq_handler(void)
{
	uint32_t id;
	
	id = ICCIAR;	//get the interrupt ID

	//switch for different interrupt ds
	switch(id)
	{
		case GTC_INT_ID:	//Global Timer interrupt
			//code for GTC Interrupt Service goes here
			break;


		case GPIO_INT_ID:	//interrupt ID 52 (GPIO)
			//code for GPIO Interrupt Service goes here
			break;


		default:
			//other interrupts
			break;

	}


	ICCEOIR = id;	//acknowledge end of interrupt by giving it the id

	return;
}

Notice the default case in the switch; It’s a good idea to have this case in-case an unexpected interrupt occurs. A programmer may use this to enter a fault state or reconfigure the system so further undefined interrupts don’t occur. We don’t have a real need to employ the default case, so we can leave it blank. In the case of a interrupt ID that doesn’t match the GTC or GPIO’s ID’s, nothing will happen.

In C, the interrupt handler will have its own scope. That is, it will only have access to its own variables and global variables. If you need to send data back to the main program, you will need to make use of global variables.

Registering a Handler to the Vector Table

Now that we have a handler function written, we need to register it to the vector table. This let’s the processor know that this function is where to jump to when an interrupt occurs.

The ARM has a defined vector table, but in SDK, Xilinx defines it’s own software vector table. Both tables are detailed in this document: The Zynq’s Vector Table

After a handler is written, it can be registered to the IRQ entry in Xilinx’s vector table by calling:

Xil_ExceptionRegisterHandler(5, irq_handler, NULL);

For an explanation of how this funnction works, see the Document on Xilinx’s Vector table.

Note that the 2nd argument irq_handler is the same function name as defined above. The name of the function written as an interrupt handler needs to be the same as the one registered through this function.

After calling this, your written handler function will be registered as the IRQ service handler and will execute whenever an interrupt is accepted.