linu型 内核中arm中断流程详细分析

来源:互联网 发布:平面设计好还是java好 编辑:程序博客网 时间:2024/06/07 11:09

linux-2.6.28.7内核中ARM中断流程分析  

2012-04-11 19:12:28|  分类: 跟着国嵌学arm|字号 订阅

(1)首先我们来分析一下下面两行代码,这两行代码定义在arch/arm/kernel/entry-armv.S中
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start;
vectors对应的地址是0xffff0000,地址__vectors_end 和__vectors_start之间的代码为就是异常向量,定义在arch/arm/kernel/entry-armv.S中,异常向量只完成一些简单的工作,更为复杂的工作要跳转到 __stubs_end 和 __stubs_start之间的代码里去执行,而 __stubs_end 和__stubs_start之间的代码被拷贝到0xffff0000+0x200处。0xffff0000实际上是虚拟地址,但虚拟地址和实际的内存物理地址之间存在着映射关系,因此具体实现的时候这两段代码就将相应的代码拷贝到了内存中。
(2)那么下面让我们开始正式分析中断流程吧,对中断流程的分析要从中断向量表开始,中断向量表如下:
.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 + stubs_offset处执行,这也是一条跳转指令,他将跳转到 __stubs_end 和__stubs_start之间的vector_stubirq, IRQ_MODE, 4处去执行,为什么要跳转到这里呢,要说明这个问题,我们必须对这一代码做一下分析,vector_stub其实是一条宏定义,展开如下:

.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)
             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

             .endm

这段代码对vector_stubirq, IRQ_MODE, 4展开就是:(去除了自带的注释代码)

.macrovector_stub, irq, IRQ_MODE, correction=4   
.align5

vector_irq:                 //这就显而易见了,这是跳转入口地址
.if 4          
sublr, lr, #4   //lr=lr-4,lr中存放着子程序的返回地址,减4是为了在stmia  sp, {r0, lr}是,将32位的返回地址入栈
.endif

stmia  sp, {r0, lr}      //入栈,r0,lr内容存入sp指示的内存单元,sp=sp+8
mrslr, spsr             //spsr内容送入lr指示内存单元,这里要注意spsr的后五位是处理器模式标识位,而事实上用后四位就可以
                                       //要记住此时lr内容为中断之前程序状态寄存器的值,这点很重要,因为对下面跳转的分析,基于这句话
strlr, [sp, #8]       //出站,lr指示单元的内容(为spsr内容)送往sp+8所指示内存单元

mrsr0, cpsr                                                  //cpsr内容送入r0,cpsr为当前程序状态寄存器,而当前运行模式                                                                                            //是IRQ_MODE
eorr0, r0, #(IRQ_MODE ^ SVC_MODE)            //展开为IRQ_MODE ^ SVC_MODE^IRQ_MODE=SVC_MODE
                                                                              //以上的展开只考虑cpsr的后五位,”^”为异或,eor也是异或
msrspsr_cxsf, r0                                           //将r0内容存入spsr_cxsf

andlr, lr, #0x0f                 //如果lr&15=0表示,此次irq中断发生时,cpu正运行用户空间的用户程序
                               //如果lr&15=3表示,此次irq中断发生时,cpu正运行内核空间的内核代码
     mov r0, sp                //保存sp,因为下面要用到sp
     ldr lr, [pc, lr, lsl #2]  //lr=pc+lr*4,所以lr=pc+0或则lr=pc+3*4
     movs pc, lr               //跳转指令,若lr=pc+3*4,因为指令吗是32位的,占四个字节,所以将跳转                                                //到.word __irq_svc处。
             
注释1、对于各处理器模式的定义如下:
             @#define USR_MODE 0x00000010
             @#define FIQ_MODE 0x00000011
             @#define IRQ_MODE 0x00000012
             @#define SVC_MODE 0x00000013
             @#define ABT_MODE 0x00000017
             @#define UND_MODE 0x0000001b
             @#define SYSTEM_MODE 0x0000001f
2、上诉代码运行到最后一条代码将会发生跳转,我们知道,跳转位置有两种可能,一是跳转到下一条代码,二是跳转到下一条代码在偏移三条代码,那么这段代码之后将是什么代码呢,让我们拭目以待。
(3)执行完(2)那段宏展开代码后接下来的代码是:(以充irq跳转而来为例)
.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
那我们就知道了,程序接下来或者跳转到.long__irq_usr这条代码处,或者跳转到.long__irq_svc这条代码处。总结一下就是,当在用户模式下发生中断时就跳转到.long__irq_usr处,当在管理模式发生中断时跳转到.long__irq_svc处,那么程序接下来怎么执行呢,我们以跳转到.long__irq_usr处为例继续分析。
(4)__irq_usr是一个函数,它定义在arch/arm/kernel/entry-armv.S中,其代码如下:

__irq_usr:
usr_entry                  //主要实现了将usr模式下的寄存器、中断返回地址保存到堆栈中,具体代码见注释1
kuser_cmpxchg_check

#ifdef CONFIG_TRACE_IRQFLAGS
bltrace_hardirqs_off
#endif
get_thread_info tsk  //获取当前进程的进程描述符中的成员变量thread_info的地址,并将该地址保存到寄存器tsk等于r9
#ifdef CONFIG_PREEMPT   //如果定义了抢占,增加抢占数值 
ldrr8, [tsk, #TI_PREEMPT] @ get preempt count
addr7, r8, #1 @ increment it
strr7, [tsk, #TI_PREEMPT]
#endif

irq_handler            //中断处理,我们最关心的地方
#ifdef CONFIG_PREEMPT
ldrr0, [tsk, #TI_PREEMPT]
strr8, [tsk, #TI_PREEMPT]
teqr0, r7
strner0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bltrace_hardirqs_on
#endif

movwhy, #0
bret_to_user
ENDPROC(__irq_usr)

.ltorg

.align5


注释1:user_entry实际上是一个宏,主要实现了将usr模式下的寄存器、中断返回地址保存到堆栈中,展开如下:

subsp, sp, #S_FRAME_SIZE   //S_FRAME_SIZE实际值为72
stmib sp, {r1 - r12}                 //将r1-r12入栈,每次传送前地址加一

ldmiar0, {r1 - r3}                    //将r0指示的内存单元数据送往r1-r3寄存器
addr0, sp, #S_PC @ here for interlock avoidance
movr4, #-1 @  ""  ""     ""        ""

strr1, [sp] @ save the "real" r0 copied
@ from the exception stack

@
@ We are now ready to fill in the remaining blanks on the stack:
@
@  r2 - lr_<exception>, already fixed up for correct return/restart
@  r3 - spsr_<exception>
@  r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
@ Also, separately save sp_usr and lr_usr
@
stmiar0, {r2 - r4}
stmdbr0, {sp, lr}^

@
@ Enable the alignment trap while in kernel mode
@
alignment_trap r0

@
@ Clear FP to mark the first stack frame
@
zero_fp
.endm
注释2:irq_handler是一个宏,展开如下:

.macroirq_handler
get_irqnr_preamble r5, lr
get_irqnr_and_base r0, r6, r5, lr //判断中断号,通过r0返回。关于这个函数的定义在include/asm/arch-s3c2410/entry-                                                              //macro.s中。具体分析见注释2-1
movner1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrnelr, 1b
bneasm_do_IRQ //进入中断处理,关于这函数的实现在(5)中讲解

#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
movner0, sp
adrnelr, 1b
bnedo_IPI

#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movner0, sp
adrnelr, 1b
bnedo_local_timer
#endif
#endif

.endm
注释2-1:get_irqnr_and_base是一个宏,其展开如下:

.macroget_irqnr_and_base, irqnr, irqstat, base, tmp

mov\base, #S3C24XX_VA_IRQ

@@ try the interrupt offset register, since it is there

ldr\irqstat, [ \base, #INTPND ]
teq\irqstat, #0
beq1002f
ldr\irqnr, [ \base, #INTOFFSET ] //通过判断INTOFFSET寄存器得到中断位置 
mov\tmp, #1
tst\irqstat, \tmp, lsl \irqnr
bne1001f

@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves

mov\irqnr, #0 @@ start here

@@ work out which irq (if any) we got

movs\tmp, \irqstat, lsl#16
addeq\irqnr, \irqnr, #16
moveq\irqstat, \irqstat, lsr#16
tst\irqstat, #0xff
addeq\irqnr, \irqnr, #8
moveq\irqstat, \irqstat, lsr#8
tst\irqstat, #0xf
addeq\irqnr, \irqnr, #4
moveq\irqstat, \irqstat, lsr#4
tst\irqstat, #0x3
addeq\irqnr, \irqnr, #2
moveq\irqstat, \irqstat, lsr#2
tst\irqstat, #0x1
addeq\irqnr, \irqnr, #1

@@ we have the value

adds\irqnr, \irqnr, #IRQ_EINT0// 加上中断号的基准数值,得到最终的中断号,IRQ_EINT0在include/asm/arch-                           s3c2410/irqs.h中定义.从这里可以看出,中断号的具体值是有平台相关的代码决定的,和硬件中断挂起寄存器中的中断号是不                 等的

@@ exit here, Z flag unset if IRQ

.endm
(5)到这一步为止,是不是有点晕了呢,好吧,让我们来回忆一下,现在我们到那一步了,我们进入中断处理函数,在中断处理函数中发现了一条语句:bneasm_do_IRQ,意思是程序将跳转到asm_do_IRQ()函数处去执行。那么好,我们就来找找这个函数,它在arch/arm/kernel/irq.c中,尼玛,是个c代码,哥终于见到亲人了,下面是它的源代码:
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);

irq_enter();  //没做什么特别的工作,可以跳过不看

/*
 * Some hardware gives randomly wrong interrupts.  Rather
 * than crashing, do something sensible.
 */
if (irq >= NR_IRQS)
handle_bad_irq(irq, &bad_irq_desc);
else
generic_handle_irq(irq);  //进入处理函数,在include/linux/irq.h中定义,详见注释1

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

irq_exit();
set_irq_regs(old_regs);
}

注释1:
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
desc->handle_irq(irq, desc);//关于handle_irq的介绍见注释1-2
#else
if (likely(desc->handle_irq))
desc->handle_irq(irq, desc);
else
__do_IRQ(irq);
#endif
}

static inline void generic_handle_irq(unsigned int irq)
{
generic_handle_irq_desc(irq, irq_to_desc(irq));// irq_to_desc定义在include/linux/irq.h中,具体代码如注释1-1所示
}

注释1-1:
static inline struct irq_desc *irq_to_desc(unsigned int irq)
{
return (irq < nr_irqs) ? irq_desc + irq : NULL;//irq_desc为一个数组,也在本文件中定义,代码是:extern struct irq_desc                                                                            //irq_desc[NR_IRQS];其中struct irq_desc是一个结构体,其定义如注释1-1-                                                                          //1所示,一次本函数的返回至使&irq_desc[irq]
}

注释1-2:
desc->handle_irq的赋值出现在kernel/irq/chip.c中,代码如下:desc->handle_irq = handle;
这段代码在函数__set_irq_handler中,而函数__set_irq_handler又被include/linux/irq.h中的set_irq_handler函数调用,代码如下:__set_irq_handler(irq, handle, 0, NULL);
而set_irq_handler又被arch/arm/plat-s3c24xx/irq.c下的s3c24xx_init_irq调用,这下我们就找到源头了,我们贴出一部分代码来分析一下:
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);
}
对于外部中断0,即irqno=IRQ_EINT0,调用set_irq_handler(irqno, handle_edge_irq)-->_set_irq_handler(irq, handle, 0, NULL);-->desc->handle_irq = handle;如此就就把函数handle_edge_irq赋值给了desc->handle_irq ,即desc[16]->handle_irq =handle_edge_irq.那么回到注释1的代码desc->handle_irq(irq, desc);处,我们就知道,这行代码就可以写成handle_edge_irq(irq,desc);handle_edge_irq的源代码在kernel/irq/chip.c中,源码在 注释1-2-1中贴出来,这里只给出其原型:handle_edge_irq(unsigned int irq, struct irq_desc *desc);
对于set_irq_chip也一样,set_irq_chip(irqno, &s3c_irq_eint0t4);--> set_irq_chip(unsigned int irq, struct irq_chip *chip)(在kernel/irq/chip.c中)-->desc->chip = chip;最终desc->chip =s3c_irq_eint0t4;
注释1-1-1:
struct irq_desc {
unsigned intirq;
irq_flow_handler_thandle_irq;
struct irq_chip*chip;
struct msi_desc*msi_desc;
void*handler_data;
void*chip_data;
struct irqaction*action; /* IRQ action list */
unsigned intstatus; /* IRQ status */

unsigned intdepth; /* nested irq disables */
unsigned intwake_depth; /* nested wake enables */
unsigned intirq_count; /* For detecting broken IRQs */
unsigned intirqs_unhandled;
unsigned longlast_unhandled; /* Aging timer for unhandled count */
spinlock_tlock;
#ifdef CONFIG_SMP
cpumask_taffinity;
unsigned intcpu;
#endif
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_tpending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry*dir;
#endif
const char*name;
} ____cacheline_internodealigned_in_smp;

注释1-2-1:

void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
spin_lock(&desc->lock);

desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);

/*
 * If we're currently running this IRQ, or its disabled,
 * we shouldn't process the IRQ. Mark it pending, handle
 * the necessary masking and go out
 */
         /*出错处理*/
if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
    !desc->action)) {
desc->status |= (IRQ_PENDING | IRQ_MASKED);
mask_ack_irq(desc, irq);
goto out_unlock;
}
kstat_incr_irqs_this_cpu(irq, desc);//中断计数

/* Start handling the irq */
desc->chip->ack(irq);

/* Mark the IRQ currently in progress.*/
desc->status |= IRQ_INPROGRESS;

do {
struct irqaction *action = desc->action;
irqreturn_t action_ret;

if (unlikely(!action)) {
desc->chip->mask(irq);
goto out_unlock;
}

/*
 * When another irq arrived while we were handling
 * one, we could have masked the irq.
 * Renable it, if it was not disabled in meantime.
 */
if (unlikely((desc->status &
       (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
      (IRQ_PENDING | IRQ_MASKED))) {
desc->chip->unmask(irq);
desc->status &= ~IRQ_MASKED;
}

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);

} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
对于个函数我们主要分析两个地方,这两个地方都用橙色标记了出来,首先我们来分析desc->chip->ack(irq)
当为外部中断0时,desc->chip =s3c_irq_eint0t4的定义为:
static struct irq_chip s3c_irq_eint0t4 = {
.name= "s3c-ext0",
.ack= s3c_irq_ack,
.mask= s3c_irq_mask,
.unmask= s3c_irq_unmask,
.set_wake= s3c_irq_wake,
.set_type= s3c_irqext_type,
};
其中.ack=s3c_irq_ack的定义又为:
s3c_irq_ack(unsigned int irqno)  
{
unsigned long bitval = 1UL << (irqno - IRQ_EINT0);

__raw_writel(bitval, S3C2410_SRCPND);//清中断
__raw_writel(bitval, S3C2410_INTPND);
}
因此desc->chip->ack(irq);这行代码很简单,就是清中断。
接下来我们再来分析一下action_ret = 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;

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;
}
在分析这段代码之前,先让我们来了解一下action。
Irq_desc_t结构体中的成员action指向该中断号对应的irqaction结构体链表。Irqaction结构体定义在include/linux/interrupt.h中,如下:

truct irqaction {
        irq_handler_t handler; //中断处理函数,注册时提供
        unsigned long flags; //中断标志,注册时提供
        cpumask_t mask; //中断掩码
        const char *name; //中断名称
        void *dev_id; //设备id,本文后面部分介绍中断共享时会详细说明这个参数的作用
        struct irqaction *next; //如果有中断共享,则继续执行,
        int irq; //中断号,注册时提供
        struct proc_dir_entry *dir; //指向IRQn相关的/proc/irq/n目录的描述符
    };

irqreturn_t handle_IRQ_event函数所做的就是不断从action链表中去成员,并执行action->handler。那么现在我们想一想,从发生中断开时,经过一系列的过程,最终中断处理函数对应到了handle_edge_irq,而handle_edge_irq所执行的函数时desc->action->handler,我们很自然的就想到,如果要中断处理我们自己写的程序,就要让handler指向我们所写的函数。关于如何让我们写的程序与中断服务程序关联起来,我们在(6)里面仔细分析。

(6)下面我们来分析一个比较靠近上层的中断注册函数——request_irq(),它定义在kernel/irq/manage.c中,源代码如下:

int request_irq(unsigned int irq, irq_handler_t handler,

unsigned long irqflags, const char *devname, void *dev_id)

{

struct irqaction *action;

struct irq_desc *desc;

int retval;


#ifdef CONFIG_LOCKDEP

/*

 * Lockdep wants atomic interrupt handlers:

 */

irqflags |= IRQF_DISABLED;

#endif

/*

 * Sanity-check: shared interrupts must pass in a real dev-ID,

 * otherwise we'll have trouble later trying to figure out

 * which interrupt is which (messes up the interrupt freeing

 * logic etc).

 */

if ((irqflags & IRQF_SHARED) && !dev_id)

return -EINVAL;


desc = irq_to_desc(irq);//利用irq构造出了desc

if (!desc)

return -EINVAL;


if (desc->status & IRQ_NOREQUEST)

return -EINVAL;

if (!handler)

return -EINVAL;


action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);

if (!action)

return -ENOMEM;


action->handler = handler;//一下几句话很重要,不过应该不用解释了

action->flags = irqflags;

cpus_clear(action->mask);

action->name = devname;

action->next = NULL;

action->dev_id = dev_id;


retval = __setup_irq(irq, desc, action);//然后主要工作在这里做了,由于本人水平太差,对这个函数是在分析不出,所以只将这个函数                                                         //所做的工作写下了,具体分析就不做了,如注释1所示。

if (retval)

kfree(action);


#ifdef CONFIG_DEBUG_SHIRQ

if (irqflags & IRQF_SHARED) {

/*

 * It's a shared IRQ -- the driver ought to be prepared for it

 * to happen immediately, so let's make sure....

 * We disable the irq to make sure that a 'real' IRQ doesn't

 * run in parallel with our fake.

 */

unsigned long flags;


disable_irq(irq);

local_irq_save(flags);


handler(irq, dev_id);


local_irq_restore(flags);

enable_irq(irq);

}

#endif

return retval;

}

注释1:在注册中断号为irq的中断服务程序时,系统会根据注册参数封装相应的irqaction结构体。并把中断号为irqirqaction结构体写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。样当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。

总结:我们现在在来总结一下我们所分析的,这一次我们从request_irq来分析,首先在注册中断的时候根据irq构造出了irq_desc [irq],然后将根据handler,flags等参数将action填充,之后将action填入irq_desc[irq],构造出irq_desc [irq]->action,这样中断与其对应的处理函数的关系就对应起来了。然后从头分析,从发生中断开时,经过一系列的过程,最终中断处理函数对应到了handle_edge_irq,而handle_edge_irq所执行的函数时desc->action->handler,这样就找到了相应的处理函数。

分析完毕,本人也知道这篇博客分析的很混乱,一开始还信心满满,但到后来就分析不下去了,不过终究还是咬着牙分析完了,希望看到这篇日志的人不要骂我