Configure the interrupt controller

Vector tables have a relatively small and fixed number of entries, because the number and type of exceptions are architecturally defined.

But we might require a great number of different interrupts that are triggered by different sources. An additional piece of hardware is needed to manage these interrupts. The Arm Generic Interrupt Controller (GIC), does exactly this. We will not discuss the GIC and its features in this guide, but you can learn more in our programmers guide.

In gic.s, add the following code to enable the GIC, and define a source of interrupts from the physical timer:

  .global gicInit
  .type gicInit, "function"
gicInit:
  // Configure Distributor
  MOV      x0, #GICDbase  // Address of GIC

  // Set ARE bits and group enables in the Distributor
  ADD      x1, x0, #GICD_CTLRoffset
  MOV      x2,     #GICD_CTLR.ARE_NS
  ORR      x2, x2, #GICD_CTLR.ARE_S
  STR      w2, [x1]

  ORR      x2, x2, #GICD_CTLR.EnableG0
  ORR      x2, x2, #GICD_CTLR.EnableG1S
  ORR      x2, x2, #GICD_CTLR.EnableG1NS
  STR      w2, [x1]
  DSB      SY

  // Configure Redistributor
  // Clearing ProcessorSleep signals core is awake
  MOV      x0, #RDbase
  MOV      x1, #GICR_WAKERoffset
  ADD      x1, x1, x0
  STR      wzr, [x1]
  DSB      SY
1:   // We now have to wait for ChildrenAsleep to read 0
  LDR      w0, [x1]
  AND      w0, w0, #0x6
  CBNZ     w0, 1b

  // Configure CPU interface
  // We need to set the SRE bits for each EL to enable
  // access to the interrupt controller registers
  MOV      x0, #ICC_SRE_ELn.Enable
  ORR      x0, x0, ICC_SRE_ELn.SRE
  MSR      ICC_SRE_EL3, x0
  ISB
  MSR      ICC_SRE_EL1, x0
  MRS      x1, SCR_EL3
  ORR      x1, x1, #1  // Set NS bit, to access Non-secure registers
  MSR      SCR_EL3, x1
  ISB
  MSR      ICC_SRE_EL2, x0
  ISB
  MSR      ICC_SRE_EL1, x0

  MOV      w0, #0xFF
  MSR      ICC_PMR_EL1, x0 // Set PMR to lowest priority

  MOV      w0, #3
  MSR      ICC_IGRPEN1_EL3, x0
  MSR      ICC_IGRPEN0_EL1, x0

//-------------------------------------------------------------
//Secure Physical Timer source defined
  MOV      x0, #SGIbase       // Address of Redistributor registers

  ADD      x1, x0, #GICR_IGROUPRoffset
  STR      wzr, [x1]          // Mark INTIDs 0..31 as Secure

  ADD      x1, x0, #GICR_IGRPMODRoffset
  STR      wzr, [x1]          // Mark INTIDs 0..31 as Secure Group 0

  ADD      x1, x0, #GICR_ISENABLERoffset
  MOV      w2, #(1 << 29)     // Enable INTID 29
  STR      w2, [x1]           // Enable interrupt source

  RET

// ------------------------------------------------------------

  .global readIAR0
  .type readIAR0, "function"
readIAR0:
  MRS       x0, ICC_IAR0_EL1  // Read ICC_IAR0_EL1 into x0
  RET

// ------------------------------------------------------------

  .global writeEOIR0
  .type writeEOIR0, "function"
writeEOIR0:
  MSR        ICC_EOIR0_EL1, x0 // Write x0 to ICC_EOIR0_EL1
  RET

We have defined the functions readIAR0() and writeEOIR0(), using the .global and .type assembler directives. The .global directive makes the label visible to all files given to the linker, while the .type directive allows us to declare that the label is a function.

As you will see in Rebuild and test, when you modify hello_world.c to call these functions, using these directives lets us call these assembly functions from C code, following the Procedure Call Standard (PCS). The PCS defines a number of things, including how values are passed and returned. In particular:

  • Arguments are passed in x0 to x7 in the same order as the function prototype.
  • Values are returned to the registers x0 and x1.

Using readIAR0(), we read the value of the Interrupt Controller Interrupt Acknowledge Register 0, ICC_IAR0_EL1. The lower 24 bits of this register give the interrupt identifier, INTID. By calling readIAR0() in C, we can get the INTID from the GIC and then handle different interrupts case by case. Later in the C code fiqHandler() is defined, and you will see a call to writeEOIR0(). The INTID is passed to x0. INTID is then written to the Interrupt Controller End of Interrupt Register 0, ICC_EOIR0_EL1, which tells the processor that that interrupt is complete.

Previous Next