Tuesday, 12 February 2013

ARM interrupt context switching in Linux


Complete IRQ handling flow on ARM Linux:

Once ARM core receives interrupt, it stops execution in current context. From there on the sequence of events:

  • disables IRQ
  • copies CPSR to SPSR
  • copies current PC to LR
  • switches to IRQ mode
  • places the interrupt vector table address into PC
  • jumps to vector handler code
  • if interrupt occurred while processor was in supervisor mode, processor will switch to supervisor mode
  • irq_svc saves the registers on kernel stack, the registers(r0-r12)
  • next step is to identify irq number
  • jumps to asm_do_IRQ
  • calls handle_level_irq or handle_simple_irq or handle_edge_irq
  • calls our ISR routine
  • once ISR is completed, irq_svc will return and restore processor state by restoring registers(r0-r12), pc, cpsr

The first 6 steps is done by ARM core, not by Linux. Other steps are done by Linux. Refer to the figure below.


Kernel IRQ flow on ARM Linux:

When interrupt is detected:
  • __irq_svc - arch/arm/kernel/entry-armv.S
  • asm_do_IRQ - arch/arm/kernel/irq.c
  • handle_IRQ - arch/arm/kernel/irq.c
  • generic_handle_irq - kernel/irq/irqdesc.c
  • generic_handle_irq_desc - include/linux/irqdesc.h
  • desc->handle_irq

In arch/arm/mach-ka2000/irq.c, ka2000_irq_init(),
    irq_set_handler() is called.
In kernel/irq/chip.c, __irq_set_handler(),
    desc->handle_irq = handle
    handle can be: handle_edge_irq, handle_level_irq or handle_simple_irq. So the call to
Therefore, desc->handle_irq will jump to one of these.


For ARM Linux, the interrupt vector table is in arch/arm/kernel/entry-armv.S:

       .globl  __vectors_start
__vectors_start:
 ARM(   swi     SYS_ERROR0      )
 THUMB( svc     #0              )
 THUMB( nop                     )
        W(b)    vector_und + stubs_offset
        W(ldr)  pc, .LCvswi + stubs_offset
        W(b)    vector_pabt + stubs_offset
        W(b)    vector_dabt + stubs_offset
        W(b)    vector_addrexcptn + stubs_offset
        W(b)    vector_irq + stubs_offset
        W(b)    vector_fiq + stubs_offset

        .globl  __vectors_end
__vectors_end:

For ARM Linux, the vector handler code that runs in IRQ mode and switches to SVC mode.

        .macro  vector_stub, name, mode, correction=0
        .align  5

vector_\name:
        .if \correction
        sub     lr, lr, #\correction
        .endif

        @
        @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
        @ (parent CPSR)
        @
        stmia   sp, {r0, lr}            @ save r0, lr
        mrs     lr, spsr
        str     lr, [sp, #8]              @ save spsr

        @
        @ Prepare for SVC32 mode.  IRQs remain disabled.
        @
        mrs     r0, cpsr
        eor     r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
        msr     spsr_cxsf, r0

        @
        @ the branch table must immediately follow this code
        @
        and     lr, lr, #0x0f
 THUMB( adr     r0, 1f                  )
 THUMB( ldr     lr, [r0, lr, lsl #2]    )
        mov     r0, sp
 ARM(   ldr     lr, [pc, lr, lsl #2]    )
        movs    pc, lr                  @ branch to handler in SVC mode
ENDPROC(vector_\name)

        .align  2
        @ handler addresses follow this label
1:
        .endm

Problem encountered:

A practical problem encounter in USB debugging. Linux will hang after USB controller is initialised and USB irq 32 is unmasked, when USB cable is plugged.


1.      The vector handler code will call __irq_svc, which call arch_irq_handler_default (arch/arm/include/asm/entry-macro-multi.S), which will call get_irqnr_and_base (arch/arm/mach-ka2000/include/mach/entry-macro.S).

2.      Look at get_irqnr_and_base (the code is attached below). For usb irq32, it is the first bit of the interrupt pending 2 register. The macro is entered, it branch to 6f, then branch to 4b, the condition is fulfilled, so branch to 7f. Then macro is exited with zero flag set to 1. After that, the “bne asm_do_IRQ” will not be executed, because the condition does not fulfil, and Linux is stuck in that macro.

  .macro  arch_irq_handler_default
                  get_irqnr_and_base r0, r2, r6, lr
  ……
 bne     asm_do_IRQ
 …..
.endm

3.      To fix the Linux interrupt stuck issue, add a line before the macro exit. The purpose is to set the zero flag to 0.

               .macro  get_irqnr_and_base, irqnr, irqstat, base, tmp
                ldr \irqstat, [\base, #0x18]     @ interrupt pending 1
                mov \tmp, #0
                mov \irqnr, #0
                teq \irqstat, #0
                beq 6f
4:              teq \irqstat, #1
                beq 7f
5:              mov \irqstat, \irqstat, lsr #1
                teq \irqstat, #1
                addne \irqnr, #1
                bne 5b
                add \irqnr, #1
                teq \irqstat, #0                  @irqstat=1
                bne 7f
6:              ldr \irqstat, [\base, #0x1c]      @ interrupt pending 2
                cmp \irqstat, #0
                movne \tmp, #32
                bne 4b
7:              add \irqnr, \tmp
teq \irqstat, #0
                .endm



No comments:

Post a Comment