Interrupt handling with push buttons in ARMv7

74 Views Asked by At

I am trying to trigger an interrupt when a push button is pressed but I can't get service_irq to trigger. The push buttons light up green and the _start function seems to work, as far as I can tell. Any ideas what the problem is and how I can fix it?

I am using ARMv7 and the DE1-SoC in CPUlator.

My code:

.org 0x00000000
.equ SVC_MODE_STACK_BASE, 0x3FFFFFFF - 3 // set SVC stack to top of DDR3 memory
.equ IRQ_MODE_STACK_BASE, 0xFFFFFFFF - 3 // set IRQ stack to A9 onchip memory
.equ GIC_CPU_INTERFACE_BASE, 0xFFFEC100
.equ GIC_DISTRIBUTOR_BASE, 0xFFFED000
.equ PUSH_BUTTONS_BASE, 0xff200050
.equ DISPLAYS_BASE, 0xff200020
.equ PUSH_BUTTONS_IRQ, 73

B _start // reset vector
B SERVICE_UND // undefined instruction vector
B SERVICE_SVC // software interrrupt vector
B SERVICE_ABT_INST // aborted prefetch vector
B SERVICE_ABT_DATA // aborted data vector
.word 0 // unused vector
B SERVICE_IRQ // IRQ interrupt vector
B SERVICE_FIQ // FIQ interrupt vector

.text
.global _start
_start:
    // Step 1: Set up the stack for Supervisor Mode
    ldr sp, =SVC_MODE_STACK_BASE

    // Step 2: Configure the GIC
    bl CONFIG_GIC
    
    // Step 3: Configure push buttons interrupts
    ldr r0, =PUSH_BUTTONS_BASE // Load base address of pushbutton registers
    mov r1, #0xF               // Enable interrupts for the keys
    str r1, [r0, #8]           // Write to the appropriate offset to enable interrupts

    // Step 4: Enable IRQ interrupts in the processor
    cpsie i                    // Enable IRQ interrupts

    // Step 5: Jump to main program loop
    B LOOP
    
LOOP:
    B LOOP
    
SERVICE_IRQ:
    PUSH {R0-R7, LR}

    /* Read the Interrupt Acknowledge Register (ICCIAR) from the CPU interface */
    LDR R4, =GIC_CPU_INTERFACE_BASE
    LDR R5, [R4, #0x0C] // read from ICCIAR
    
    // Set the display to 0 to test if service_irq is triggered
    LDR R0, =DISPLAYS_BASE
    MOV R1, #0b00111111
    STR R1, [R0]

    B EXIT_IRQ

EXIT_IRQ:
    /* Write to the End of Interrupt Register (ICCEOIR) */
    STR R5, [R4, #0x10] // write to ICCEOIR

    // Restore registers and return from interrupt.
    POP {R0-R7, LR}
    SUBS PC, LR, #4  // Atomically restores CPSR from SPSR and returns to interrupted instruction.

SERVICE_UND:
    B SERVICE_UND
SERVICE_SVC:
    B SERVICE_SVC
SERVICE_ABT_DATA:
    B SERVICE_ABT_DATA
SERVICE_ABT_INST:
    B SERVICE_ABT_INST
SERVICE_FIQ:
    B SERVICE_FIQ
    

CONFIG_GIC:
    PUSH {LR}
    /* To configure a specific interrupt ID:
    * 1. set the target to cpu0 in the ICDIPTRn register
    * 2. enable the interrupt in the ICDISERn register */
    /* CONFIG_INTERRUPT (int_ID (R0), CPU_target (R1)); */
    MOV R1, #1 // this field is a bit-mask; bit 0 targets cpu0
    BL CONFIG_INTERRUPT
    /* configure the GIC CPU Interface */
    LDR R0, =GIC_CPU_INTERFACE_BASE // base address of CPU Interface, 0xFFFEC100
    /* Set Interrupt Priority Mask Register (ICCPMR) */
    LDR R1, =0xFFFF // enable interrupts of all priorities levels
    STR R1, [R0, #0x04]
    /* Set the enable bit in the CPU Interface Control Register (ICCICR).
    * This allows interrupts to be forwarded to the CPU(s) */
    MOV R1, #1
    STR R1, [R0]
    /* Set the enable bit in the Distributor Control Register (ICDDCR).
    * This enables forwarding of interrupts to the CPU Interface(s) */
    LDR R0, =GIC_DISTRIBUTOR_BASE   // 0xFFFED000
    STR R1, [R0]
    POP {PC}

CONFIG_INTERRUPT:
    PUSH {R4-R5, LR}
    /* Configure Interrupt Set-Enable Registers (ICDISERn).
    * reg_offset = (integer_div(N / 32) * 4
    * value = 1 << (N mod 32) */
    LSR R4, R0, #3 // calculate reg_offset
    BIC R4, R4, #3 // R4 = reg_offset
    LDR R2, =0xFFFED100 // Base address of ICDISERn
    ADD R4, R2, R4 // R4 = address of ICDISER
    AND R2, R0, #0x1F // N mod 32
    MOV R5, #1 // enable
    LSL R2, R5, R2 // R2 = value
    /* Using the register address in R4 and the value in R2 set the
    * correct bit in the GIC register */
    LDR R3, [R4] // read current register value
    ORR R3, R3, R2 // set the enable bit
    STR R3, [R4] // store the new register value
    /* Configure Interrupt Processor Targets Register (ICDIPTRn)
    * reg_offset = integer_div(N / 4) * 4
    * index = N mod 4 */
    BIC R4, R0, #3 // R4 = reg_offset
    LDR R2, =0xFFFED800 // Base address of ICDIPTRn
    ADD R4, R2, R4 // R4 = word address of ICDIPTR
    AND R2, R0, #0x3 // N mod 4
    ADD R4, R2, R4 // R4 = byte address in ICDIPTR
    /* Using register address in R4 and the value in R2 write to
    * (only) the appropriate byte */
    STRB R1, [R4]
    POP {R4-R5, PC}

.end
0

There are 0 best solutions below