Configuring the GIC

Step-by-step procedure for setting up ZYNQ's GIC

2371

Configuring the GIC

For the ARM core to accept an interrupt the system needs to be configured properly: the ARM core, GIC, and interrupt source must all be configured correctly. In addition, code to execute during interrupts must be written and the vector table must be setup to point to this code.

Steps for configuring the ARM core are detailed in this document: ARM Interrupts

The configuration of the GPIO module is covered in this document: GPIO Module Interrupts

Writing and properly configuring an interrupt handler is covered here: Interrupt Handlers

This Document covers configuring the Generic Interrupt Controller for the specific interrupt ID #52, which is the GPIO module of the Zynq system. The steps in the document can be followed to configure other interrupt sources, but care must be taken to ensure that the correct registers and bitfields are written to for each interrupt being configured.

There are multiple steps related to configuring the GIC. Listed below is the general procedure for configuring the GIC.

  1. Disable the GIC Distributor.
  2. Enable secure interrupts.
  3. Set the GIC priority mask.
  4. Configure priority and sensitivity for individual interrupt(s).
  5. reenable the GIC Distributor.

Configuring the GIC involves multiple registers. It’s a good idea to break up your code into separate steps and files. You can create a header/source file pair just for interrupts (including configuring the ARM core) or you can make seperate files for configuring the ARM core and the GIC. How you organize your source files is a coding style choice.

Below are c preprocessor statements defining macros to addresses in the GIC block you will need. These addresses are found in the Zynq technical reference manual as well as info on their purpose.

//definitions for GIC configuration regisjters
#define ICCPMR		*((uint32_t *) 0xF8F00104)		//Priority mask reg
#define ICCICR		*((uint32_t *) 0xF8F00100)		//CPU interface control reg
#define ICDDCR		*((uint32_t *) 0xF8F01000)		//Distributor Control reg
#define ICDISER(n)	*(((uint32_t *) 0xF8F01100) + n )	//Int. set enable reg
#define ICDICER(n)	*(((uint32_t *) 0xF8F01180) + n )	//Int. Clear Enable reg
#define ICDIPR(n)	*(((uint32_t *) 0xF8F01400) + n )	//Int. Priority reg
#define ICDIPTR(n)	*(((uint32_t *) 0xF8F01800) + n )	//Int. Processor Targets reg
#define ICDICFR(n)	*(((uint32_t *) 0xF8F01C00) + n )	//Int. Configuration reg

Using Macros

Note the use of Macros in the above code (uint32_t *) 0xF8F00104 casts the number given as a 32-bit pointer, then the pointer is dereferenced via the outer parenthesis and deference operator. Writing macros this way enables simple reads and writes to addresses.

The latter 5 macros are defined with a variable, this allows an offset to be added to the base address. This is handy for groups of related registers in contiguous memory space, such as the DISER,DICER,DIPR,DIPTR, and DICFR of the GIC defined above. The value passed to the macro will be the offset added to the base address. In the above statements the constant value is cast as a 32 bit pointer before having the offset added to it, thus the final address will be the base address + 4*offset.

Examples of reading and writing using these macros are given below.

int val =0;

//reading from ICCPMR
val = ICCPMR;

//writing to ICCPMR
ICCPMR = 5;

//writing to ICDISER0
ICDISER(0) = 7;

//reading from ICDISER2
val = ICDISER(2);

1.Disable the CPU Interrupt distributor

The Interrupt distributor determines which interrupts target which CPU when in a multi core system. In a single core system it can effectively be thought of as a master enable/disable for the GIC. Simply write a 0 to disable interrupts for processor 0 (the only processor in the system) and write a 1 to enable it. Below is code that disables the GIC distributor.

//disables GIC distributor
void disable_gic_dist() { (ICDDCR = 0); }

2. Configure GIC to drive IRQ in CPU interface Control Register

The ICCICR Configures how interrupts are passed to the processor from the GIC. The Zynq includes a pair of signals from the Programmable logic which can bypass the GIC interface for interrupts (one for IRQ and one for FIQ).

Since we want IRQ interrupts to be driven by the GIC, we can just set the lowest 2 bits in the register (Drives IRQ from GIC, passes FIQ from PL).

//Drive IRQ from the GIC
void disable_IRQ_passthrough() { (ICCICR =0x3); }

3.Configure the Priority Mask

The zynq supports interrupts with priorities from 0-255, priority level 0 is the highest priority, while priority 255 is the lowest priority. The ‘interrupt priority mask’ register (ICCPMR) allows interrupt ID’s above the specified value to be disabled. Only the 8 lowest bits in this register are meaningful. Since we are configuring a single interrupt we can leave all other interrupts unmasked. The below code unmasks all interrupt priorities (255 and below are enabled)

//sets the GIC priority mask to ‘priority’
void set_gic_pmr(uint32_t priority) { ICCPMR = (priority & 0xFF); }

4.Configure interrupt sensitivity and priority

4A.Disable interrupts for specific int ID

Before you make changes to a specific interrupt’s configuration registers, disable the source via the corresponding registers in the GIC. This is done through the 24 ICDIPTR (processor targets registers) and the 3 ICDICER registers. each of the 24 ICDIPTR registers has 16 2-bit wide fields, each bit enabling it for a specific processor (1:enabled 0:disabled). Our device only has a single processor, so we only need to worry about the least significant bit in each pair. Since we are configuring the GPIO interrupt (ID 52), the corresponding proc. targets register is ICDIPTR13, and the 2 least significant bits. For the corresponding ICDICER, ID 52 falls under ICDICER1, bit 20. Writing a 1 to this bit, disabled the interrupt. The code below accomplishes this.

//disables interrupt ID 52
void disable_interrupt52(void)
{
	ICDIPTR(13) &= ~0x3; //remove processors from interrupt
	ICDICER(1) = 0x00100000; //disable interrupts from GPIO module
}

4B.Configure Interrupt Sensitivity

Table 7-4 in the Zynq’s Technical Reference manual lists the interrupt ID’s and the required ‘sensitivity’ for each interrupt. From the table, the sensitivity required for the GPIO interrupt is ‘High Level’. This means an interrupt will trigger whenever the GPIO module’s interrupt request signal is high. This is the type for most interrupts in the Zynq system. interrupts of this type typically require service from the processor, as they can continue to generate the interrupt request even after the processor has gone into interrupt. Typically servicing a module requires the processor to read or clear a flag, but other action may be required to clear an interrupt. The code below sets interrupt 52 for the given sensitivity (passed as a 3-bit value). The fields for different sensitivities are defined as 00: 01: 10: 11: .

//sets interrupt sensitivity of interrupt52 to ‘sens’
void set_interrupt52_sensitivity(uint8_t sens)
{
	ICDICFR(3) &= ~0x300;
	ICDICFR(3) |= (sens&0x3)<<8;
}

4C.Set priority for interrupt sources

Each interrupt ID has it’s own priority. This is configured via the set of ICDIPR registers, of which there are 24. The priority field for interrupt 52 is found in the first byte of ICDIPR13. The top 5-bits define the priority, while the lower 3 bits are always 0. This gives each interrupt 32 different priority levels. The priority level, however is defined from 0-255, where 0 is the highest priority and 255 is the lowest priority. The example below sets the GPIO interrupt to priority level to 160 (0xA0). since the lower 3 bits are zero, the value of the upper 5 bits is multiplied by 8 to get the priority level (in this case 5’b10100 (decimal 20) multiplied by 8 is 160).

//sets the interrupt priority of 52 to ‘priority_val’
void set_interrupt27_priority(uint8_t priority_val)
{
	ICDIPR(6) &= ~0xFF000000; //clear priority bits for interrupt 52
	ICDIPR(6) |= ((priority_val) & 0xF8)<<24; //set top 5 bits based on passed value
}

4D.Enable Interrupts for source

Now that the polarity (sensitivity) and priority of the interrupt are setup, we can enable interrupts from our source. To do so we need to enable the the interrupt for cpu0 using the processor targets registers (ICDIPTRn) and enable the source using the ‘set interrupt enable’ registers (ICDISERn). For interrupt ID 52, set the first bit in ICDIPTR13 and write a 1 to bit 20 of ICDISER.

//enables interrupt 52
void enable_interrupt52()
{
	ICDIPTR(13) |= 1; //set bit 1 of ICDIPTR13 (enable for cpu0)
	ICDISER(1) = 0x00100000;
}

5.Enable CPU Distributor

Enable Distributor Now that you’ve configured the GIC for interrupts from the GPIO module, simply reenabling it by setting the first bit in the distributor control register.

//enables GIC distributor
void enable_gic_dist() { (ICDDCR = 1); }

Summary

Now the GIC is configured properly for interrupt source 52. The above code is gathered again below in 2 sections:

Macro definitions

//definitions for GIC configuration regisjters
#define ICCPMR	*((uint32_t *) 0xF8F00104)	//Priority mask reg
#define ICCICR	*((uint32_t *) 0xF8F00100)	//CPU interface control reg
#define ICDDCR	*((uint32_t *) 0xF8F01000)	//Distributor Control reg
#define ICDISER(n)	*(((uint32_t *) 0xF8F01100) + n )	//Int. set enable reg
#define ICDICER(n)	*(((uint32_t *) 0xF8F01180) + n )	//Int. Clear Enable reg
#define ICDIPR(n)	*(((uint32_t *) 0xF8F01400) + n )	//Int. Priority reg
#define ICDIPTR(n)	*(((uint32_t *) 0xF8F01800) + n )	//Int. Processor Targets reg
#define ICDICFR(n)	*(((uint32_t *) 0xF8F01C00) + n )	//Int. Configuration reg

C Functions

//disables the GIC distributor
void disable_gic_dist() { (ICDDCR = 0); }

//enables the GIC distributor
void enable_gic_dist() { (ICDDCR = 1); }

//Drive IRQ from the GIC
void disable_IRQ_passthrough() { (ICCICR =0x3); }

//sets the priority mask to the passed value 'priority'
void set_gic_pmr(uint32_t priority) { ICCPMR = (priority & 0xFF); }

//disables interrupt ID 52
void disable_interrupt52(void)
{
	ICDIPTR(13) &= ~0x3; //remove processors from interrupt
	ICDICER(1) = 0x00100000; //disable interrupts from GPIO module
}

//enables interrupt 52
void enable_interrupt52()
{
	ICDIPTR(13) |= 1; //set bit 1 of ICDIPTR13 (enable for cpu0)
	ICDISER(1) = 0x00100000;
}

//sets the interrupt priority of 52 to ‘priority_val’
void set_interrupt52_priority(uint8_t priority_val)
{
	ICDIPR(13) &= ~0xFF; //clear priority bits for interrupt 52
	ICDIPR(13) |= (priority_val) & 0xF8; //set top 5 bits based on passed value
}

//sets interrupt sensitivity of interrupt52 to ‘sens’
void set_interrupt52_sensitivity(uint8_t sens)
{
	ICDICFR(3) &= ~0x300;
	ICDICFR(3) |= (sens&0x3)<<8;
}

Putting it all together, an example function below uses the previous code to configure the GIC for the single interrupt (ID #52) and enables the GIC.

void configure_GIC_interrupt52(void)
{

	//disable and configure GIC
	disable_gic_dist(); //disable GIC interrupt generation
	disable_IRQ_passthrough();	//drive IRQ from GIC

	set_gic_pmr(0xFF); //set priority mask

	//disable and configure interrupt 52
	disable_interrupt52();
	set_interrupt52_priority(0xA0); //set 52’s priority to 160
	set_interrupt52_sensitivity(1); //set to high level

	enable_interrupt52();	//reenable interrupt52

	enable_gic_dist();		//reenable distributor

}

The code above configures only interrupt ID 52. To configure a different interrupt, you would need to setup the corresponding registers and bitfields for that specific interupt’s ID.

If you wanted to add an interrupt in addtion to interrupt 52, you can simply configure it’s GIC registers before or after you configure interrupt 52. The GIC supports multiple interrupts, and uses priority to arbitrate when multiple interrupts are requested at once.