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
tox7
in the same order as the function prototype. - Values are returned to the registers
x0
andx1
.
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.