We'll cover the whole interrupt stuff in two sections:
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( ) calls a function called early_trap_init( ) which does following:
* 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);
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)
- Interrupt setup - Explanation of Generic and architecture specific setup that kernel does.
- Interrupt handling - Explanation of what happens after processor recieves an interrupt.
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.
* 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).
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);
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
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);
} 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:
__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
* 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
@ 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)
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
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
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)
UNWIND(.fnend )
ENDPROC(__irq_svc)
svc_entry macro:
/*
* SVC mode handlers
*/
* 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
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
.endm
irq_handler the implementation process:
/*Interrupt handling. Preserves r7, r8, r9 */
.macro irq_handler
.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)
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
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);
} while (action);
}
This is where your registered interrupt handler gets called.
转自: http://venkateshabbarapu.blogspot.com/2012/09/interrupt-handling-in-arm.html