Interrupt handling in ARM

Interrupt handling in ARM

We'll cover the whole interrupt stuff in two sections:
  • Interrupt setup - Explanation of Generic and architecture specific setup that kernel does.
  • Interrupt handling - Explanation of what happens after processor recieves an interrupt.
1. Interrupt setup 
start_kernel( ) is the first 'C' function that opens its eyes when kernel is booting up. It intializes various subsystems of the kernel, including IRQ system. Intialization of IRQ requires that you have valid vector table in place and you have first level interrupt hadlers in place, both of these things are architecture specifc. Lets setup the vector table first. 
 
 start_kernel()
        |
setup_arch()
       |
early_trap_init()


start_kernel( ) calls a function called early_trap_init( ) which does following:
  •   setup exception vector table at location 0xffff0000. 
  •   Flush the icache in range 0xffff0000 to 0xffff0000 + PAGE_SIZE. This is required because memcpy() funcsmoves the vector table and vector stubs to 0xffff0000. 
      /* * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
         * into the vector page, mapped at 0xffff0000, and ensure these
         * are visible to the instruction stream.
         */
        memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
        memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
        memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
 
        flush_icache_range(vectors, vectors + PAGE_SIZE); 
 
 Vector table and vector stub code for ARM resides in arch/arm/kernel/entry-armv.S file 
 
__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:

 

 As we can see this vector contains branch instruction for branching to exception handler code which also resides in same file (arm-entryV.S). 
Vector table contains the branch instructions for all the exceptions defined in ARM (Undefined instruction, SWI, data abort, prefecth abort, IRQ, and FIQ). The most important thing to note about this vector table is that branch instructions are used for all exceptions except SWI. Using branch instruction instead of loading PC directly with the exceptions handler address makes this code position independent. Since branch instruction take offset (+ive or -ve) from current PC, this code will run fine as long as the offset between vector table instructions and the exception handlers is maintained as desired by this code. And It is assumed here that exception handlers will be at +0x200 offset from starting address of vector table. 
 
So we are done with setting up vector tables and the exception handlers. If you want then you can hook your exception handler directly to the vector table, so that you bypass all linux interrupt handling code, which is pretty heavy. But if you do so then you'll have to get your hands dirty with all the architecture details which kernel handles beautifully and cleanly.
 
After setting up vector tables start_kernel ( ) calls init_IRQ() to set up kernel IRQ handling infrastructure, on ARM we have 32 hard interrupts for which kernel kernel sets up the a default desctiptor called bad_irq_desc, which hasdo_bad_IRQ( ) as IRQ handler. Then init_IRQ( ) calls init_arch_irq( ), here the architecture specfic code has to setup the IRQ handlers for 32 IRQs, if not set then do_bad_IRQ( ) will handle the IRQs.
 
start_kernel()  init/main.c
        |
  init_IRQ()      arch/arm/kernel/irq.c
       |
mdesc->init_irq() 
      |
gic_init_irq() //arch/arm/mach-omap2/boardxx.c(part of func ptrs in MACHINE_STARTmacro) 
     |
gic_init(0,29,base_addr,cpu_base)  //arch/arm/common/gic.c
     |
gic_dist_init(gic, irq_start)
git_cpu_init(gic)

gic_dist_init(gic,irq_start)
{
  for(i=irq_start; i < irq_limit; i++)  //All the interrupts and their handlers registered here
  {
    irq_set_chip_and_handler(i, &gic_chip, handle_fasteoi_irq);  //This is where handler is registered for each interrupt line
    irq_set_chip_data(i, gic);
    set_irq_flags(i, IRQF_VALID | IRQF_PROBE);
}

struct irq_desc {
irq_flow_handler_t handle_irq;  //flow handler
struct irq_data         irq_data;
 struct irqaction        *action;
unsigned int            depth;
const char              *name;
}
 
handle_irq is called by the architecture-specific code whenever an interrupt occurs. The function is then responsible to use the controller-specific methods provided in chip to perform the necessary low-level actions required to process the interrupt.
 
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
           |
 handle_irq_event(desc);
           |
 handle_irq_event_percpu(desc, action); 
  {
     do {
            res = action->handler(irq, action->dev_id); // your interrupt handler gets called
             action = action->next;
        } while (action);    
}
 
So now we have our IRQ infrastructure in place, and various modules can register thier IRQ handlers throughrequest_irq(). When you call request_irq( ) kernel appends your IRQ handler to list of IRQ handlers registered for that particular IRQ line, it does not change the exception vector table.
 
Now lets see what happens after interrupt is recieved. 
 
2. Interrupt Handling
When a IRQ is raised, ARM stops what it is processing ( Asuming it is not processing a FIQ!), disables further IRQs (not FIQs), puts CPSR in SPSR, puts current PC to LR and swithes to IRQ mode, refers to the vector table and jumps to the exception handler. In our case it jumps to the exception handler of IRQ.   
 
when interrupt occurs, jump to the location of b vector_irq + stubs_offset implementation. Note that the current scale of the initial position to 0xffff0000.  
    

And it jumps to the 
  .globl  __stubs_start
__stubs_start:
 
 vector_stub     irq, IRQ_MODE, 4
.long   __irq_usr                       @  0  (USR_26 / USR_32)
.long   __irq_svc                       @  3  (SVC_26 / SVC_32)
......................
vector_stub macro is defined as
/* Vector stubs.
 * This code is copied to 0xffff0200 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not
 * exceed 0x300 bytes.
 *
 * Common stub entry macro:
 * Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 
.macro  vector_stub, name, mode, correction=0
 
vector_\name:
       @
        @ Prepare for SVC32 mode.  IRQs remain disabled.
        @
        mrs     r0, cpsr
        eor     r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
        msr     spsr_cxsf, r0
 
 ARM(   ldr     lr, [pc, lr, lsl #2]    )
        movs    pc, lr                  @ branch to handler in SVC mode
ENDPROC(vector_\name)

with "irq, IRQ_MODE, 4" instead of macro vector_stub the "name, mode, correction", to find the entrance to interrupt our position vector_irq (macro inside the vector_ \ name

The program will jump to the next step  _irq_usr, or __irq_svc and other locations.
 
__irq_svc:
         svc_entry
#ifdef CONFIG_PREEMPT
        get_thread_info tsk
        ldr     r8, [tsk, #TI_PREEMPT]          @ get preempt count
        add     r7, r8, #1                                @ increment it
        str     r7, [tsk, #TI_PREEMPT]
#endif
        irq_handler
    svc_exit r4                             @ return from exception
 UNWIND(.fnend          )
ENDPROC(__irq_svc)
 
svc_entry macro:
/*
 * SVC mode handlers
 */
       .macro  svc_entry, stack_hole=0
       ldmia   r0, {r1 - r3}
        add     r5, sp, #S_SP - 4       @ here for interlock avoidance
        mov     r4, #-1                 @  ""  ""      ""       ""
        add     r0, sp, #(S_FRAME_SIZE + \stack_hole - 4)
        SPFIX( addeq   r0, r0, #4      )
        str     r1, [sp, #-4]!          @ save the "real" r0 copied
                                             @ from the exception stack

        mov     r1, lr
       stmia   r5, {r0 - r4}
        .endm
 
irq_handler the implementation process:
/*Interrupt handling.  Preserves r7, r8, r9 */
        .macro  irq_handler
           arch_irq_handler_default
                |
.macro  arch_irq_handler_default    //arch/arm/include/asm/entry-macro-multi.S
         get_irqnr_preamble r5, lr
1:       get_irqnr_and_base r0, r6, r5, lr
        movne   r1, sp
        @
        @ routine called with r0 = irq number, r1 = struct pt_regs *
        @
        adrne   lr, BSYM(1b)
         bne     asm_do_IRQ (jump to the kernel)
 
------------>---------->-------->------------>

           .macro  get_irqnr_preamble, base, tmp  / / to get the IRQ base address 
            ldr     \base, = OMAPX_IRQ_BASE

  . macro  get_irqnr_and_base, irqnr, irqstat, base, tmp    //finds the interrupt number
                ldr     \irqnr, [\base, #0x98] /* IRQ pending reg 1 */
                cmp     \irqnr, #0x0
                bne     9999f
                ldr     \irqnr, [\base, #0xb8] /* IRQ pending reg 2 */
                cmp     \irqnr, #0x0
                bne     9999f
                ldr     \irqnr, [\base, #0xd8] /* IRQ pending reg 3 */
                cmp     \irqnr, #0x0
                bne     9999f
                ldr     \irqnr, [\base, #0xf8] /* IRQ pending reg 4 */
                cmp     \irqnr, #0x0
9999:
                 ldrne   \irqnr, [\base, #INTCPS_SIR_IRQ_OFFSET]
                and     \irqnr, \irqnr, #ACTIVEIRQ_MASK /* Clear spurious bits */


                .endm

The last step is now you have got the interrupt number in the register JUMP to the KERNEL
 bne     asm_do_IRQ (jump to the kernel)
 
 
Arch specific things are done, now we will move to ARCH Independent things.
  asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
             |
 generic_handle_irq(irq);  //arch/arm/kernel/irq.c
            |
generic_handle_irq_desc(irq, desc);  //kernel/irq/irqdesc.c 
           |
desc->handle_irq(irq,desc) //This is the actual call for the flow handler which we registered as handle_fasteoi_irq
           |
             
handle_fasteoi_irq(unsigned int irq, struct irq_desc *desc)
           |
 handle_irq_event(desc);
           |
 handle_irq_event_percpu(desc, action); 
  {
     do {
            res = action->handler(irq, action->dev_id); // your interrupt handler gets called
             action = action->next;
        } while (action);    
}
 

This is where your registered interrupt handler gets called.


转自: http://venkateshabbarapu.blogspot.com/2012/09/interrupt-handling-in-arm.html

你可能感兴趣的:(linux)