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()
|
page_init()
|
devicemaps_init() (Set up the
device mappings. Since we clear out the page tables for all mappings
above VMALLOC_START, except early fixmap, we might remove debug
device mappings. This
means earlycon can be used to debug this function any other function or
debugging method which may touch any device _will_ crash the kernel.)
|
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() funcs moves
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 has do_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 through request_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);
}