GPT2 Timer Interrupt Not Triggering ISR Execution
The core issue revolves around the failure of the ARM Cortex-A53 processor to execute the Interrupt Service Routine (ISR) for a GPT2 timer interrupt, despite the interrupt being correctly generated and pending in the Generic Interrupt Controller (GICv3). The timer interrupt is configured to trigger after 30 seconds, and while the timer reaches the compare value and sets the interrupt pending bit in the GICD_ISPENDR register, the processor does not transition to the ISR. Instead, the Program Counter (PC) remains in the main loop, indicating that the interrupt is not being acknowledged or handled by the CPU.
The GPT2 timer is configured with a 2 MHz clock frequency and a compare value of 60,000,000, which corresponds to a 30-second delay. The interrupt is assigned Interrupt ID (INTID) 86, which is a Shared Peripheral Interrupt (SPI). The GICv3 is configured to route this interrupt to core 0, set its priority to the highest level (0), and enable it. However, despite these configurations, the ISR is not executed, and the interrupt remains pending in the GIC.
Misconfigured GICv3 CPU Interface and Vector Table Setup
The root cause of the issue lies in the misconfiguration of the GICv3 CPU interface and the potential absence of a properly initialized vector table. The GICv3 CPU interface is responsible for signaling interrupts to the processor core, and the vector table defines the entry points for interrupt handlers. If either of these components is not correctly set up, the processor will not execute the ISR, even if the interrupt is pending in the GIC.
GICv3 CPU Interface Configuration
The GICv3 CPU interface must be enabled and configured to allow the processor to acknowledge and handle interrupts. In this case, the ICC_SRE_EL3 register is set to 1, enabling access to the CPU interface registers. However, there is no explicit confirmation that the CPU interface is fully operational or that the processor is correctly acknowledging interrupts. Additionally, the ICC_IGRPEN0_EL1, ICC_IGRPEN1_EL1, and ICC_IGRPEN1_EL3 registers are configured to enable interrupt groups, but there is no verification that these settings are effective.
Vector Table Initialization
The vector table must be correctly initialized and mapped to the appropriate memory location. The ARM Cortex-A53 processor uses the vector table to determine the entry point for interrupt handlers. If the vector table is not properly set up, the processor will not know where to jump when an interrupt occurs. In this scenario, there is no mention of vector table initialization in the provided code, which strongly suggests that this critical step may have been overlooked.
Interrupt Priority and Routing
While the interrupt priority and routing are configured correctly, with INTID 86 assigned to group 0, routed to core 0, and set to the highest priority, these settings alone are insufficient if the CPU interface or vector table is not properly configured. The interrupt will remain pending in the GIC, and the processor will not execute the ISR.
Verifying GICv3 CPU Interface and Vector Table Initialization
To resolve the issue, the GICv3 CPU interface and vector table must be thoroughly verified and correctly configured. Below are the detailed steps to troubleshoot and fix the problem.
Step 1: Verify GICv3 CPU Interface Configuration
Ensure that the GICv3 CPU interface is fully operational by confirming the following:
- The ICC_SRE_EL3 register is set to 1, enabling access to the CPU interface registers.
- The ICC_IGRPEN0_EL1, ICC_IGRPEN1_EL1, and ICC_IGRPEN1_EL3 registers are correctly configured to enable interrupt groups.
- The ICC_PMR_EL1 register is set to a value that allows the highest priority interrupt (0) to be acknowledged by the processor.
Use the following code snippet to verify the CPU interface configuration:
// Verify ICC_SRE_EL3 configuration
unsigned long long int icc_sre_el3;
asm("mrs %0, "stringify(ICC_SRE_EL3) : "=r" (icc_sre_el3));
if ((icc_sre_el3 & 0x1) != 0x1) {
// ICC_SRE_EL3 is not enabled
asm("msr "stringify(ICC_SRE_EL3)", %0\n; isb" :: "r" ((unsigned long long int)0x1));
}
// Verify ICC_IGRPEN0_EL1, ICC_IGRPEN1_EL1, and ICC_IGRPEN1_EL3 configurations
unsigned long long int icc_igrpen0_el1, icc_igrpen1_el1, icc_igrpen1_el3;
asm("mrs %0, "stringify(ICC_IGRPEN0_EL1) : "=r" (icc_igrpen0_el1));
asm("mrs %0, "stringify(ICC_IGRPEN1_EL1) : "=r" (icc_igrpen1_el1));
asm("mrs %0, "stringify(ICC_IGRPEN1_EL3) : "=r" (icc_igrpen1_el3));
if ((icc_igrpen0_el1 & 0x1) != 0x1 || (icc_igrpen1_el1 & 0x1) != 0x1 || (icc_igrpen1_el3 & 0x1) != 0x1) {
// Re-enable interrupt groups
asm("msr "stringify(ICC_IGRPEN0_EL1)", %0\n; isb" :: "r" ((unsigned long long int)0x1));
asm("msr "stringify(ICC_IGRPEN1_EL1)", %0\n; isb" :: "r" ((unsigned long long int)0x1));
asm("msr "stringify(ICC_IGRPEN1_EL3)", %0\n; isb" :: "r" ((unsigned long long int)0x1));
}
// Verify ICC_PMR_EL1 configuration
unsigned long long int icc_pmr_el1;
asm("mrs %0, "stringify(ICC_PMR_EL1) : "=r" (icc_pmr_el1));
if ((icc_pmr_el1 & 0xFF) != 0xFF) {
// Set priority mask to lowest
asm("msr "stringify(ICC_PMR_EL1)", %0\n; isb" :: "r" ((unsigned long long int)0xFF));
}
Step 2: Initialize the Vector Table
Ensure that the vector table is correctly initialized and mapped to the appropriate memory location. The vector table must contain the entry points for all interrupt handlers, including the GPT2 timer interrupt. Use the following steps to initialize the vector table:
- Define the vector table in memory, ensuring that it is aligned to the required boundary (typically 2 KB for ARMv8-A).
- Populate the vector table with the addresses of the interrupt handlers.
- Set the Vector Base Address Register (VBAR_EL3) to point to the vector table.
Here is an example of how to initialize the vector table:
// Define the vector table
__attribute__((aligned(2048))) void (*vector_table[32])(void);
// Populate the vector table with interrupt handlers
void timer_isr(void) {
// GPT2 timer ISR implementation
for(;;); // Infinite loop for debugging
}
void default_isr(void) {
// Default ISR for unhandled interrupts
for(;;); // Infinite loop for debugging
}
void init_vector_table(void) {
for (int i = 0; i < 32; i++) {
vector_table[i] = default_isr;
}
vector_table[1] = timer_isr; // Assign GPT2 timer interrupt to IRQ vector
}
// Set VBAR_EL3 to point to the vector table
void set_vbar_el3(void *vbar) {
asm("msr "stringify(VBAR_EL3)", %0\n; isb" :: "r" (vbar));
}
// Initialize the vector table and set VBAR_EL3
init_vector_table();
set_vbar_el3(vector_table);
Step 3: Verify Interrupt Handling
After configuring the GICv3 CPU interface and initializing the vector table, verify that the processor correctly handles the GPT2 timer interrupt. Use the following steps to confirm interrupt handling:
- Enable the GPT2 timer and wait for the interrupt to trigger.
- Check the Program Counter (PC) to ensure that it transitions to the ISR.
- Verify that the interrupt is acknowledged and cleared in the GIC.
Here is an example of how to verify interrupt handling:
// Enable GPT2 timer and wait for interrupt
*(unsigned int *)(GPT_2_BASE_ADDR + 0x0) |= (1<<0); // Enable timer
// Wait for interrupt
for(;;) {
unsigned int gpt2_sr = *(unsigned int *)(GPT_2_BASE_ADDR + 0x8);
if ((gpt2_sr & 0x1) == 0x1) {
// Interrupt occurred
break;
}
}
// Verify ISR execution
unsigned long long int pc;
asm("mrs %0, "stringify(PC) : "=r" (pc));
if (pc == (unsigned long long int)timer_isr) {
// ISR executed successfully
} else {
// ISR not executed
}
// Acknowledge and clear interrupt in GIC
*(unsigned int *)(GICD_BASE_ADDR + 0x1008) = 0x400000UL; // Clear INTID 86
By following these steps, the GPT2 timer interrupt should be correctly handled by the ARM Cortex-A53 processor, and the ISR should be executed as expected.