从外到内,看中断处理

原创文章,转载请注明出处.转载自: Li Haifeng's Blog
本文链接地址: 从外到内,看中断处理

仍旧以s3c2410来看。中断来了以后,找到一个表项,假如找到了vector_irq。
/home/sda3/linux-2.6.21.1/arch/arm/kernel/entry-armv.S


.globl __vectors_start
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset

.globl __vectors_end
__vectors_end:


即,执行了b vector_irq + sbus_offset
vector_irq是什么东东呢?
我们在该文件中,可以看到一个宏:


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

vector_name:
.if correction
sub lr, lr, #correction
.endif

@
@ Save r0, lr_ (parent PC) and spsr_
@ (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)
msr spsr_cxsf, r0

@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #2]
movs pc, lr @ branch to handler in SVC mode
.endm


即,程序会跳转到这里玩一会儿,然后去哪里呢?我们看到movs pc,lr 也就是通过改变pc的值来进行跳转,那么依据是什么捏?该句上面为ldr lr,[pc,lr,lsl #2]也即是根据lr的值然后逻辑左移2位。lr是什么,lr在上面的语句 and lr,lr,#0x0f与了一下,其实是取其mode.假如,我们得到的值是0,则:


.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4

.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f


我们会到__irq_usr里面去处理。__irq_usr在哪里捏,我们就用cscope的神功去ctrl + ]去寻找。
/home/sda3/linux-2.6.21.1/arch/arm/kernel/entry-armv.S


__irq_usr:
usr_entry

#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_info tsk
#ifdef CONFIG_PREEMPT
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
add r7, r8, #1 @ increment it
str r7, [tsk, #TI_PREEMPT]
#endif

irq_handler
#ifdef CONFIG_PREEMPT
ldr r0, [tsk, #TI_PREEMPT]
str r8, [tsk, #TI_PREEMPT]
teq r0, r7
strne r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_on
#endif

mov why, #0
b ret_to_user

.ltorg


在__irq_usr里面进行一番云雨之后,跳转到了__irq_handler.我们继续追踪:
在同一文件中:


.macro irq_handler
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, 1b
bne asm_do_IRQ

#ifdef CONFIG_SMP
/*
* XXX
*
* this macro assumes that irqstat (r6) and base (r5) are
* preserved from get_irqnr_and_base above
*/
test_for_ipi r0, r6, r5, lr
movne r0, sp
adrne lr, 1b
bne do_IPI

#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movne r0, sp
adrne lr, 1b
bne do_local_timer
#endif
#endif

.endm


看到了木有,调用了asm_do_irq.到这里,基本上,我们就可以出一口气了,毕竟arm_do_irq的语义已经很明显了嘛。可是,且慢。让我们看看arm_do_irq到底干了什么事情:
/home/sda3/linux-2.6.21.1/arch/arm/kernel/irq.c


asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq;

/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;

irq_enter();

desc_handle_irq(irq, desc);

/* AT91 specific workaround */
irq_finish(irq);

irq_exit();
set_irq_regs(old_regs);
}


在上面的这个函数中,我们要注意两个东西:其一,desc_handle_irq(irq,desc).其二,set_irq_regs(old_regs).即处理完成后,要将regs复原。
让我们看看desc_handle_irq吧。


static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}


这里,我们就不能随便追踪啦。因为要和desc有关系啦,也就是中断描述符有关系。我们先看一下struct irq_desc的结构吧:


struct irq_desc {
irq_flow_handler_t handle_irq;
struct irq_chip *chip;
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */

unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
} ____cacheline_aligned;


从结构体来看,handler是irq的处理函数。还记得初始化irq的时候么?init_IRQ->init_arch_irq 而init_arch_irq因平台不同而不同。在setup_arch()中,
/home/sda3/linux-2.6.21.1/arch/arm/kernel/setup.c


/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;


s3c2410平台下,我们姑且看一种init_arch_irq吧:


MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
* to SMDK2410 */
/* Maintainer: Jonas Dietsche */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io,
.init_irq = s3c24xx_init_irq,
.init_machine = smdk_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END


也就是其init_irq为s3c24xx_init_irq。我们就去找一下:cs f s s3c24xx_init_irq


void __init s3c24xx_init_irq(void)
{
unsigned long pend;
unsigned long last;
int irqno;
int i;

irqdbf("s3c2410_init_irq: clearing interrupt status flagsn");

/* first, clear all interrupts pending... */

last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C24XX_EINTPEND);

if (pend == 0 || pend == last)
break;

__raw_writel(pend, S3C24XX_EINTPEND);
printk("irq: clearing pending ext status %08xn", (int)pend);
last = pend;
}

last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_INTPND);

if (pend == 0 || pend == last)
break;

__raw_writel(pend, S3C2410_SRCPND);
__raw_writel(pend, S3C2410_INTPND);
printk("irq: clearing pending status %08xn", (int)pend);
last = pend;
}

last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_SUBSRCPND);

if (pend == 0 || pend == last)
break;
}
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_SUBSRCPND);

if (pend == 0 || pend == last)
break;

printk("irq: clearing subpending status %08xn", (int)pend);
__raw_writel(pend, S3C2410_SUBSRCPND);
last = pend;
}

/* register the main interrupts */

irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlersn");

for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
/* set all the s3c2410 internal irqs */

switch (irqno) {
/* deal with the special IRQs (cascaded) */

case IRQ_EINT4t7:
case IRQ_EINT8t23:
case IRQ_UART0:
case IRQ_UART1:
case IRQ_UART2:
case IRQ_ADCPARENT:
set_irq_chip(irqno, &s3c_irq_level_chip);
set_irq_handler(irqno, handle_level_irq);
break;

case IRQ_RESERVED6:
case IRQ_RESERVED24:
/* no IRQ here */
break;

default:
//irqdbf("registering irq %d (s3c irq)n", irqno);
set_irq_chip(irqno, &s3c_irq_chip);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
}

/* setup the cascade irq handlers */

set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);

set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

/* external interrupts */

for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irqdbf("registering irq %d (ext int)n", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq)n", irqno);
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}

/* register the uart interrupts */

irqdbf("s3c2410: registering external interruptsn");

irqdbf("s3c2410: registering external interruptsn");

for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
irqdbf("registering irq %d (s3c uart0 irq)n", irqno);
set_irq_chip(irqno, &s3c_irq_uart0);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
irqdbf("registering irq %d (s3c uart1 irq)n", irqno);
set_irq_chip(irqno, &s3c_irq_uart1);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
irqdbf("registering irq %d (s3c uart2 irq)n", irqno);
set_irq_chip(irqno, &s3c_irq_uart2);
set_irq_handler(irqno, handle_level_irq);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
irqdbf("registering irq %d (s3c adc irq)n", irqno);
set_irq_chip(irqno, &s3c_irq_adc);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}

irqdbf("s3c2410: registered interrupt handlersn");
}


在这里我们的默认irq_handler为handler_edge_irqhandle_level_irq.我们姑且看一下handler_edge_irq吧:


void fastcall
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
unsigned int cpu = smp_processor_id();
struct irqaction *action;
irqreturn_t action_ret;

spin_lock(&desc->lock);
mask_ack_irq(desc, irq);

if (unlikely(desc->status & IRQ_INPROGRESS))
goto out_unlock;
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
kstat_cpu(cpu).irqs[irq]++;

/*
* If its disabled or no action available
* keep it masked and get out of here
*/
action = desc->action;
if (unlikely(!action || (desc->status & IRQ_DISABLED))) {
desc->status |= IRQ_PENDING;
goto out_unlock;
}

desc->status |= IRQ_INPROGRESS;
desc->status &= ~IRQ_PENDING;
spin_unlock(&desc->lock);

action_ret = handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);

spin_lock(&desc->lock);
desc->status &= ~IRQ_INPROGRESS;
if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
desc->chip->unmask(irq);
out_unlock:
spin_unlock(&desc->lock);
}


我们看到,会调用handle_IRQ_event(irq, action);继续追踪之


irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;

handle_dynamic_tick(action);

if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();

do {
ret = action->handler(irq, action->dev_id);
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;
} while (action);

if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();

return retval;
}


我们看到 handle_IRQ_event会有一个do{}while()循环,依次处理该irq线上的action。好,就是这样啦。

From Li Haifeng's Blog, post 从外到内,看中断处理

Post Footer automatically generated by wp-posturl plugin for wordpress.

分享到: