Interrupt(2): ARM Interrupt Controller

ARM uses Global Interrupt Controller ( GIC) architecture, which splits GIC logically into two blocks:

  • Distributor: performs interrupt prioritization and distribution to CPU interfaces
  • CPU interface: connects to the processors, performs priority masking and preemption handling

Interrupt type

  • Peripheral Interrupt: an interrupt asserted by a signal to the GIC (Edge-triggered, or Level sensitive)
    • Private (PPI, interrupt IDs 16 – 31): specific to a single processor
    • Shared (SPI, interrupt IDs 32 – 1019): can be routed to any combination of processors
  • Software-Generated Interrupt (SGI, interrupt IDs 0 – 15), generated by software writing to a specific register in GIC.

Initialization after rest or power on

After a reset, the Distributor and CPU interfaces are disabled, and software must initialize the Distributor and all CPU interfaces.

  1. For SPIs (distributor part)
    • specify whether each interrupt is level-sensitive or edge-triggered — reg ICDICFRs.
    • specify the priority value for each interrupt — reg ICDIPRs
    • specify the target processor list for each interrupt, if the GIC is part of a multiprocessor implementation. — reg ICDIPTRs
    • enable the interrupts — reg ICDISERs
  2. for PPIs and SGIs, …..
  3. for each CPU interface
    • set the priority mask for the interface — ICCPMR
    • set the binary point position, that determineds preemption on the interface — ICCBPR
    • enable signalling of interrupts by this interface — ICCICR
  4. Enable the Distributor  — ICDDCR

Sample code:

init_intrinfo() calls ar_gic_init() to initialize the interrupt controller, before filling the syspage_entry intrinfo.

void
arm_gic_init(paddr_t dist_base, paddr_t cpu_base)
{
	unsigned	i;
	unsigned	itn;

	gic_dist_base = dist_base;
	gic_cpu_base = cpu_base;

	// Make sure distributor is disabled
	no_writeback_out32(dist_base + ARM_GICD_CTLR, 0);
	 // Calculate number of interrupt lines
	itn = ((in32(dist_base + ARM_GICD_TYPER) & ARM_GICD_TYPER_ITLN) + 1) * 32;

	 // Disable all interrupts and clear pending state
	for (i = 0; i < itn; i += 32) {
		no_writeback_out32(dist_base + ARM_GICD_ICENABLERn + (i * 4 / 32), 0xffffffff);
		no_writeback_out32(dist_base + ARM_GICD_ICPENDRn + (i * 4 / 32), 0xffffffff);
	}

	 // Set default priority of all interrupts to 0xa0
	for (i = 0; i < itn; i += 4) {
		no_writeback_out32(dist_base + ARM_GICD_IPRIORITYn + i, 0xa0a0a0a0);
	}
	 // Route all SPI interrupts to cpu0
	for (i = 32; i < itn; i += 4) {
		no_writeback_out32(dist_base + ARM_GICD_ITARGETSRn + i, 0x01010101);
	}

	 // Default all SPI interrupts as level triggered
	for (i = 32; i < itn; i += 16) {
		no_writeback_out32(dist_base + ARM_GICD_ICFGRn + (i * 4 / 16), 0);
	}

	 // Enable distributor
	no_writeback_out32(dist_base + ARM_GICD_CTLR, ARM_GICD_CTLR_EN);

	 // Enable cpu interface for cpu0.
	 // note: Init_intrinfo() only initialize the CPU interface for CPU0
         // Secondary cpu interfaces are enabled as those cpus are initialised
	 // Disable all banked PPI interrupts
	 // Enable all SGI interrupts
	out32(gic_dist_base + ARM_GICD_ICENABLERn, 0xffff0000);
	out32(gic_dist_base + ARM_GICD_ISENABLERn, 0x0000ffff);

	 // Set priority mask to allow all interrupts and enable cpu interface
	out32(gic_cpu_base + ARM_GICC_PMR, 0xF0);
	out32(gic_cpu_base + ARM_GICC_CTLR, ARM_GICC_CTLR_EN);
}

How does the GIC operate on Interrupts?

  1. The GIC determines whether each interrupt is enabled. An interrupt that is not enabled has no further effect on the GIC.
  2. For each enabled interrupt that is pending, the Distributor determines the targeted processor or processors.
  3. For each processor, the Distributor determines the highest priority pending interrupt, based on the priority information it holds for each interrupt, and forwards the interrupt to the CPU interface.
  4. The CPU interface compares the interrupt priority with the current interrupt priority for the processor, determined by a combination of the Priority Mask Register, the current preemption settings, and the highest priority active interrupt for the processor. If the interrupt has sufficient priority, the GIC signals an interrupt exception request to the processor.
  5. When the processor takes the interrupt exception, it reads the ICCIAR in its CPU interface to acknowledge the interrupt, This read returns an Interrupt ID that the processor uses to select the correct interrupt handler. When it recognizes this read, the GIC changes the state of the interrupt:
    • if the pending state of the interrupt persists when the interrupt becomes active, or if the
      interrupt is generated again, from pending to active and pending;
    • otherwise, from pending to active
      • A level-sensitive peripheral interrupt persists when it is acknowledged by the processor, because the interrupt signal to the GIC remains asserted until the interrupt service routine (ISR) running on the processor accesses the peripheral asserting the signal.
  6. When the processor has completed handling the interrupt, it signals this completion by writing to the ICCEOIR in the GIC.
    • The GIC requires the order of completion of interrupts by a particular processor to be the reverse of the order of acknowledgement, so the last interrupt acknowledged must be the first interrupt completed.
    • When the processor writes to the ICCEOIR, the GIC changes the state of the interrupt, for the corresponding CPU interface, either:
      • from active to inactive
      • from active and pending to pending

2 thoughts on “Interrupt(2): ARM Interrupt Controller”

Leave a comment