Configuring the GIC

setting up the GIC to use programmable logic interrupts

2635

Configuring the ZYNQ GIC for Programmable Logic Interrupts

Interrupts allow a processor to change context in response to one or more ‘events’. A CPU can execute a main body of code, and when an interrupt occurs it will automatically save it’s current state and start to execute [an interrupt handler]. In the handler, the processor often executes code in response to whatever caused the interrupt. Some architectures employ multiple interrupt vectors, allowing seperate handlers for each interrupt.

The ARM A9 CPU only has a single (standard) interrupt line and a single vector. However, the GIC, which controls CPU interrupts in the Zynq processing system, arbitrates interrupts from up to X different sources.

To setup an interrupt in Zynq, there are 3 steps;

  1. Configure the GIC
  2. Configure the Specific interrupt
  3. Enable Interrupts in the ARM processor.

Configuring the GIC

The GIC must be setup before it passes interrupts to the ARM core.

first, configure the GIC Priority mask register. the GIC will allow interrupts with priority below the value in the register.

#define ICCPMR *((uint32_t *) 0xF8F00104)
set_GIC_priority_mask(uint32_t priority_mask)
{
	ICCPMR = priority_mask;
}

Next, modify the vector table, so the interrupt vector points to your interrupt handler.

#include <xil_exception.h>

void set_intr_handler(void *handler(void *))
{
	Xil_ExceptionRegisterHandler(5, (void *)handler, NULL);
}

The Xilinx function, Xil_ExceptionRegisterHandler registers the 2nd argument (passed as a pointer to a function) as the handler to the execption given as the 1st argument, In this case 5, for interrupts.

Configure GIC for a specific interrupt id

The GIC has configuration registers with fields for each interrupt ID. These allow the programer to set the sensitivity type and the priority for each interrupt.

For the private interrupt from programmable logic, the ID is interrupt 31.

We will need to configure the corresponding ICDIPR (Interrupt Priorty Register). You would typically need to configure the corresponding interrupt sensitivity register, but Programmable Logic Interrupts are fixed sensitivity.

#define ICDIPR(x) *((uint32_t *)0xF8F01400+(x))

void set_intr31_priority(uint8_t pr)
{
	//priority field for id31 is in ICDIPR7
	//In the most significant byte
	ICDIPR(7) &= 0x00FFFFFF;	//clear 8 MSB
	ICDIPR(7) |= pr<<24 ;		//shift priority 24 bits left
}

The module or logic providing the logic may also need to be configured to generate interrupts. This will vary with each device. Typically this will involve writing to an interrupt enable register.

Enable Interrupts

Now, interrupts need to be enabled for the Processor, The GIC, and the individual interrupt.

To enable interupts from Programmable logic, write to the ICDISER (Interrupt enable set) register that contains the fields for interrupt 31.

Conveniently, to disable interrupts for a specific ID, use the correct ICDICER (Interrupt Enable Clear, and write the same value you used to enable it. Examples of how to enable and disable Interrupt 31 are shown below.

#define ICDISER(x) *((uint32_t *)0xF8F01100 +(x))
#define ICDICER(x) *((uint32_t *)0xF8F01180 +(x))

void enable_intr31()
{
	//interrupt id is the MSb of ICDISER0
	ICDISER(0) = 0x80000000;
}

void disable_intr31()
{
	ICDICER(0) = 0x80000000;
}
Enable GIC interupts

The GIC has a ‘Distributor Control’ register. This effectivly works as an enable register and allows interrupts to the ARM core if enabled, and masks them if it is disabled.

Simply write a 1 to enable the distributor, or write 0 to disable the distributor.

Also write 3 to the CPU Interface Control Register (ICCICR), This enables non-seecure interrupts.

#define ICDDCR = *((uint32_t*) 0xF8F01000)
#define ICCICR = *((uint32_t*) 0xF8F00100)
void enable_GIC_intr()
{
	ICCICR=3;
	ICDDCR=1;
}

void disable_GIC_intr()
{
	ICDDCR=0;
}
Enable ARM interrupts

Finally, the ARM core must be configured before it will accept interrupts.

To enable ARM interrupts, bit 7 of the CPSR (current program status register), needs to be cleared. In C This can be easily accomplished using inline assembly.

void  enable_ARM_interrupts(void)
{
	uint32_t cpsr_temp;	//temporary cpsr value

	//get the current value of the status register
	__asm__ __volatile__("mrs %0, cpsr\n" : "=r" (cpsr_temp) );

	cpsr_temp &= ~0x80; //clear bit 7

	//write back the updated temp cpsr
	__asm__ __volatile__("msr cpsr, %0\n": : "r" (cpsr_temp) ) ;
}

Similary to disable interrupts, set bit 7 in the cpsr.

void  disable_ARM_interrupts(void)
{
	uint32_t cpsr_temp;	//temporary cpsr value

	//get the current value of the status register
	__asm__ __volatile__("mrs %0, cpsr\n" : "=r" (cpsr_temp) );

	cpsr_temp |= 0x80; //set bit 7
	
	//write back the updated temp cpsr
	__asm__ __volatile__("msr cpsr, %0\n": : "r" (cpsr_temp)) ;

	
}

Writing an Interrupt Handler

Now that the system is configured to accept interrupts,we will need to write a interrupt service routine that executes when the processor takes an interrupt.

Below the very basic shell of an interrupt handler is given; At the beginning of the interrupt, the id of the source of the interrupt is retreived from the interrupt acknowledge register. At the end, the id is written to the End of interrupt register. the return ends the interrupt and the main program resumes where it was left of.

Your function will need to be registered into the vector table(as described above), before it can be exectued in response to an interrupt.


#define ICCIAR *((uint32_t *) 0xF8F0010C)

#define ICCEOIR *((uint32_t *) 0xF8F00110)


void IRQ_handler(void *data)
{
	uint32_t id;
	id =  ICCIAR;	//get the ID of the interrupt

	
	//interrupt actions go here


	ICCEOIR = id;	//acknowledge end of this interupt

	return;
}

Remember that there is only a single interrupt handler in ARM, and in your handler you will need to check the ID to decide what to do in the handler.