Chinaunix首页 | 论坛 | 博客
  • 博客访问: 535322
  • 博文数量: 86
  • 博客积分: 1076
  • 博客等级: 准尉
  • 技术积分: 1018
  • 用 户 组: 普通用户
  • 注册时间: 2011-11-02 19:15
文章分类

全部博文(86)

文章存档

2013年(15)

2012年(69)

2011年(2)

分类:

2012-05-02 22:57:14

原文地址:linux中断机制简单分析 作者:gc5084

驱动程序使用一个中断,一般首先要申请一个中断,申请中断的函数是
  1. int request_irq(unsigned int irq,
  2.         irqreturn_t (*handler) (int, void*, struct pt_regs *),
  3.         unsigned long flags,
  4.         const char *dev_name,
  5.         void *dev_id);
这个函数在interrupt.h中声明,其中的参数,irq是要申请的中断号,handler是要安装的中断处理函数,flags是中断的标志,dev_name标识一个中断的名称。dev_id是将传递给中断处理函数。
简单的看一下此函数的实现和相关部分,
第一个参数irq中断号。内核中已经给我们定义好了中断号的宏,来表示中断在内核中的表示。
mini2440开发板上6个按键分别被连接到了EINT8,EINT11,EINT13,EINT14,EINT15,EINT19。芯片数据手册中,可以得知EINT8-23都共用SRCPND挂起源寄存器中的第5位,在INTPND,INTMASK等寄存器中也都在第5位。如果要进一步确定是那个中断源,则需要查看EINTPEND外部中断挂起寄存器(数据手册并没有将这个寄存器介绍写入中断一章,而在输入输出章,其他方面也感觉2440的数据手册也很不详细,很多东西都没有介绍)。
再看linux中在include\asm-arm\arch-s3c2410\Irq.h中定义了中断的宏,如下,
  1. #define S3C2410_CPUIRQ_OFFSET     (16)
  2. #define S3C2410_IRQ(x) ((x) + S3C2410_CPUIRQ_OFFSET)

  3. #define IRQ_EINT0 S3C2410_IRQ(0)     /* 16 */
  4. #define IRQ_EINT1 S3C2410_IRQ(1)
  5. #define IRQ_EINT2 S3C2410_IRQ(2)
  6. #define IRQ_EINT3 S3C2410_IRQ(3)
  7. #define IRQ_EINT4t7 S3C2410_IRQ(4)     /* 20 */
  8. #define IRQ_EINT8t23 S3C2410_IRQ(5)
  9. #define IRQ_RESERVED6 S3C2410_IRQ(6)     /* for s3c2410 */
  10. ..................
  11. #define IRQ_EINT4 S3C2410_IRQ(32)     /* 48 */
  12. #define IRQ_EINT5 S3C2410_IRQ(33)
  13. #define IRQ_EINT6 S3C2410_IRQ(34)
  14. #define IRQ_EINT7 S3C2410_IRQ(35)
  15. #define IRQ_EINT8 S3C2410_IRQ(36)
  16. #define IRQ_EINT9 S3C2410_IRQ(37)
  17. #define IRQ_EINT10 S3C2410_IRQ(38)
  18. #define IRQ_EINT11 S3C2410_IRQ(39)
  19. #define IRQ_EINT12 S3C2410_IRQ(40)
  20. #define IRQ_EINT13 S3C2410_IRQ(41)
  21. #define IRQ_EINT14 S3C2410_IRQ(42)
  22. #define IRQ_EINT15 S3C2410_IRQ(43)
可以看到 EINT0号中断IRQ_EINT0将宏展开后就等于16,而IRQ_EINT8展开后等于52.这些数字编号是linux内核定义的中断数组的编号。linux内核将所有中断统一编号。使用irq_desc结构数组来描述中断。每个数组项对应一个中断。这样IRQ_EINT0就对应数组的第16项。irq_desc结构如下
  1. struct irq_desc {
  2.     irq_flow_handler_t    handle_irq;
  3.     struct irq_chip        *chip;
  4.     struct msi_desc        *msi_desc;
  5.     void            *handler_data;
  6.     void            *chip_data;
  7.     struct irqaction    *action;    /* IRQ action list */
  8.     unsigned int        status;        /* IRQ status */

  9.     unsigned int        depth;        /* nested irq disables */
  10.     unsigned int        wake_depth;    /* nested wake enables */
  11.     unsigned int        irq_count;    /* For detecting broken IRQs */
  12.     unsigned int        irqs_unhandled;
  13.     spinlock_t        lock;
  14. #ifdef CONFIG_SMP
  15.     cpumask_t        affinity;
  16.     unsigned int        cpu;
  17. #endif
  18. #if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
  19.     cpumask_t        pending_mask;
  20. #endif
  21. #ifdef CONFIG_PROC_FS
  22.     struct proc_dir_entry    *dir;
  23. #endif
  24.     const char        *name;
  25. }
分析较为重要的一些成员,成员handle_irq表示当前处理函数,执行到这个irq_desc数组代表的中断时首先调用此函数,此函数再调用chip成员链接的底层函数包括清楚,屏蔽,使能等操作。做一些操作,然后调用action链表中用户注册的处理函数,request_irq函数主要任务就是在此结构中的action链表中添加定义的中断处理函数。这里有三个主要问题有待分析。一.linux中断从异常如何执行到irq_desc结构代表的数据项中,二,irq_desc数组在linux中如何建立初始化。三,request_irq实现的简略分析。

逐项简单分析,
一。linux中断从异常如何执行到irq_desc结构代表的数据项中。
在内核start_kernel(在init/main.c)中调用tarp_init(在arch/arm/kernel/Traps.c中)设置异常处理函数。函数如下
  1. void __init trap_init(void)
  2. {
  3.     unsigned long vectors = CONFIG_VECTORS_BASE;
  4.     extern char __stubs_start[], __stubs_end[];
  5.     extern char __vectors_start[], __vectors_end[];
  6.     extern char __kuser_helper_start[], __kuser_helper_end[];
  7.     int kuser_sz = __kuser_helper_end - __kuser_helper_start;

  8.     /*
  9.      * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
  10.      * into the vector page, mapped at 0xffff0000, and ensure these
  11.      * are visible to the instruction stream.
  12.      */
  13.     memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
  14.     memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
  15.     memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

  16.     /*
  17.      * Copy signal return handlers into the vector page, and
  18.      * set sigreturn to be a pointer to these.
  19.      */
  20.     memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
  21.      sizeof(sigreturn_codes));

  22.     flush_icache_range(vectors, vectors + PAGE_SIZE);
  23.     modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
  24. }
中断是异常的一种,在CPU出现一样时会跳转到指定地址。ARM V4版本以后,异常向量基地址有两个一是0x00000000,一是0xffff0000。这个通过设置CP15协处理器C1寄存器中v位(bit[13]位)控制。 linux内核使用后者(?)。
CONFIG_VECTORS_BASE是一个配置项在.config文件中可以找到CONFIG_VECTORS_BASE = 0xffff0000.
?--------?
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
这句的目的是将异常向量代码部分,复制到vectors地址处。
__vectors_start标号在arch/arm/kernel/entry_asmv.S中,如下,
  1. .globl    __vectors_start
  2. __vectors_start:
  3.     swi    SYS_ERROR0
  4.     b    vector_und + stubs_offset
  5.     ldr    pc, .LCvswi + stubs_offset
  6.     b    vector_pabt + stubs_offset
  7.     b    vector_dabt + stubs_offset
  8.     b    vector_addrexcptn + stubs_offset
  9.     b    vector_irq + stubs_offset
  10.     b    vector_fiq + stubs_offset
  11.     .globl    __vectors_end
  12. __vectors_end:
这就是异常向量表,b vector_irq + stubs_offset表示跳到中断异常,vector_irq是一个定义的宏vector_stub扩展出的程序标识的,下面看一下这个宏并简要注释。

  1. .macro    vector_stub, name, mode, correction=0
  2.     @name:名字 mode:那种异常模式 correction:返回地址需减去的长度
  3.     .align    5

  4. vector_\name:
  5.     .if \correction
  6.     sub    lr, lr, #\correction    
  7.     @不同的异常预读取的指令到异常跳转的长度不同,返回地址应减去#\correction
  8.     .endif

  9.     @
  10.     @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
  11.     @ (parent CPSR)
  12.     @
  13.     stmia    sp, {r0, lr}        @ save r0, lr to point sp
  14.     mrs    lr, spsr            @spsr存入lr
  15.     str    lr, [sp, #8]        @ save spsr,将lr存入sp+8地址,sp不变
  16.                                 @+8因为要存到r0,r1后
  17.     @
  18.     @ Prepare for SVC32 mode. IRQs remain disabled.
  19.     @将spsr设置成svc模式,用于将来
  20.     mrs    r0, cpsr
  21.     eor    r0, r0, #(\mode ^ SVC_MODE) @SVC_MODE:0x13,异或
  22.     msr    spsr_cxsf, r0     @spsr_cxsf指整个spsr,它可以单独操作c,x,s,f

  23.     @
  24.     @ the branch table must immediately follow this code @次级跳转表要紧跟其后。
  25.     @
  26.     and    lr, lr, #0x0f    @保留低4位
  27.     mov    r0, sp        @栈指针保存到r0
  28.     ldr    lr, [pc, lr, lsl #2]    @ lr = (lr<<2)+pc,现在lr中就仅保存的就是中断前cpu模式代码
  29.                                     @右移两位就是跳转到下面对应次级表的偏移地址
  30.     movs    pc, lr            @ branch to handler in SVC mode
  31.     .endm
  32. 再看一个使用这个宏的实例
  33. __stubs_start
  34.     ......
  35.     vector_stub    irq, IRQ_MODE, 4    @用上述宏定义了个irq的次级跳转处理程序

  36.     .long    __irq_usr            @ 0 (USR_26 / USR_32) 用户模式的中断
  37.     .long    __irq_invalid            @ 1 (FIQ_26 / FIQ_32)
  38.     .long    __irq_invalid            @ 2 (IRQ_26 / IRQ_32)
  39.     .long    __irq_svc            @ 3 (SVC_26 / SVC_32) 管理模式的中断
  40.     .long    __irq_invalid            @ 4
  41.     .long    __irq_invalid            @ 5
  42.     ......
  43. __stubs_end
  44. .....
看回trap_init函数
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
这句就中的__stubs_start到__stubs_end的区域就是调用上面分析的宏展开的跳转代码的地方,将这一段地址复制 的跳转表代码段复制到vectors + 0x200地址。
关于stubs_offset的分析,
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
引用网上摘抄的一段话,
当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量(±32M)写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。我们把搬移前的中断向量表中的irq入口地址记irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start, vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后 vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相对于中断 向量中的中断入口地址的偏移量就是,200+vector_irq在stubs中的偏移量再减去中断入口在向量表中的偏移量,即200+ vector_irq-stubs_start-irq_PC+vectors_start = (vector_irq-irq_PC) + vectors_start+200-stubs_start,对于括号内的值实际上就是中断向量表中写的vector_irq,减去irq_PC是由汇编器完成的,而后面的 vectors_start+200-stubs_start就应该是stubs_offset,实际上在entry-armv.S中也是这样定义的。

由此可见
b vector_irq + stubs_offset 将跳转到__irq_usr或__irq_svc的分支。

__irq_usr的分支会继续处理,会保存寄存器等,然后跳转到真正的处理程序,摘取片段
  1. __irq_usr:
  2.     usr_entry    @一个宏

  3. #ifdef CONFIG_TRACE_IRQFLAGS
  4.     bl    trace_hardirqs_off
  5. #endif
  6.     get_thread_info tsk
  7. #ifdef CONFIG_PREEMPT
  8.     ldr    r8, [tsk, #TI_PREEMPT]        @ get preempt count
  9.     add    r7, r8, #1            @ increment it
  10.     str    r7, [tsk, #TI_PREEMPT]
  11. #endif

  12.     irq_handler    @中断处理函数(这是一个宏)
  13. #ifdef CONFIG_PREEMPT
  14.     ldr    r0, [tsk, #TI_PREEMPT]
  15.     str    r8, [tsk, #TI_PREEMPT]
  16.     teq    r0, r7
  17.     strne    r0, [r0, -r0]
  18. .......
代码中的irq_handler这个是个宏就在entry_asmv.S文件开始处,会调用到asm_do_IRQ,

  1. .macro    irq_handler
  2.     get_irqnr_preamble r5, lr
  3. 1:    get_irqnr_and_base r0, r6, r5, lr @此宏获得中断号(这个宏是具体体系芯片相关的实现)
  4. ?--------?

  5.     movne    r1, sp
  6.     @
  7.     @ routine called with r0 = irq number, r1 = struct pt_regs *
  8.     @
  9.     adrne    lr, 1b
  10.     bne    asm_do_IRQ
以一款芯片为例的get_irqnr_and_base
  1. .macro    get_irqnr_and_base, irqnr, irqstat, base, tmp

  2.         mov    \base, #AIC_BA //中断寄存器组起始地址

  3.         ldr    \irqnr, [ \base, #AIC_IPER] //保存产生中断优先级的寄存器
  4.         ldr    \irqnr, [ \base, #AIC_ISNR] //保存产生的中断号的寄存器
  5.         cmp    \irqnr, #0        //中断号和0比较,是0说明没有中断,是个误报的中断
  6.         streq \tmp, [\base, #AIC_EOSCR]    @ fix the fake interrupt issue
  7.                         //AIC_EOSCR寄存器写入任何值,表示中断服务程序已经结束
  8.     .endm
asm_do_IRQ(在arch/arm/kernel/irq.c中),

  1. asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
  2. {
  3.     struct pt_regs *old_regs = set_irq_regs(regs);
  4.     struct irq_desc *desc = irq_desc + irq;

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

  11.     irq_enter();

  12.     desc_handle_irq(irq, desc);

  13.     /* AT91 specific workaround */
  14.     irq_finish(irq);

  15.     irq_exit();
  16.     set_irq_regs(old_regs);
  17. }
在这个函数中会看到irq_desc变量,desc_handle_irq(irq, desc);则会调用到相应的中断,所以到这里我们已经分析了第一个问题,到了要分析第二个问题的时候了,

二,irq_desc数组在linux中如何建立初始化
irq_desc数组的的定义在kernel/irq/handler.c中,

  1. struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
  2.     [0 ... NR_IRQS-1] = {
  3.         .status = IRQ_DISABLED,
  4.         .chip = &no_irq_chip,
  5.         .handle_irq = handle_bad_irq,
  6.         .depth = 1,
  7.         .lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
  8. #ifdef CONFIG_SMP
  9.         .affinity = CPU_MASK_ALL
  10. #endif
  11.     }
  12. };
我们可以结合文章开始处提到的irq_desc结构体的定义,看下都在定义时初始化了那些成员。进一步,在inin/main.c的start_kernel中,也即trap_init调用的后面会调用的init_IRQ(arch/arm/kernel/irq.c).此函数初始化irq_desc数组中一些成员,如下,

  1. void __init init_IRQ(void)
  2. {
  3.     int irq;

  4.     for (irq = 0; irq < NR_IRQS; irq++)        (1)
  5.         irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

  6.     init_arch_irq(); (2)
  7. }
(1)将irq_desc数组中所有成员状态加上|= IRQ_NOREQUEST | IRQ_NOPROBE;
(2)调用相应体系的中断初始化函数,(函数的关联在machine_desc结构中的init_irq成员),在这里它实际指向s3c24xx_init_irq(arch/arm/plat-s3c24xx/irq.c)函数。
这个函数先设置寄存器清除所有中断。然后设置各个中断,我们以IRQ_EINT4到IRQ_EINT23为例。

  1. for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
  2.     irqdbf("registering irq %d (extended s3c irq)\n", irqno);
  3.     set_irq_chip(irqno, &s3c_irqext_chip);    (1)
  4.     set_irq_handler(irqno, handle_edge_irq);
  5.     set_irq_flags(irqno, IRQF_VALID);
  6. }
(1)set_irq_chip(irqno, &s3c_irqext_chip),在kernel\irq\Chip.c中。这个函数的作用相当于desc[irqno].chip = &s3c_irqext_chip.这里贴出struct irq_chip的定义的注释。

  1. /**
  2.  * struct irq_chip - hardware interrupt chip descriptor
  3.  *
  4.  * @name:        name for /proc/interrupts
  5.  * @startup:        start up the interrupt (defaults to ->enable if NULL)
  6.  * @shutdown:        shut down the interrupt (defaults to ->disable if NULL)
  7.  * @enable:        enable the interrupt (defaults to chip->unmask if NULL)
  8.  * @disable:        disable the interrupt (defaults to chip->mask if NULL)
  9.  * @ack:        start of a new interrupt
  10.  * @mask:        mask an interrupt source
  11.  * @mask_ack:        ack and mask an interrupt source
  12.  * @unmask:        unmask an interrupt source
  13.  * @eoi:        end of interrupt - chip level
  14.  * @end:        end of interrupt - flow level
  15.  * @set_affinity:    set the CPU affinity on SMP machines
  16.  * @retrigger:        resend an IRQ to the CPU
  17.  * @set_type:        set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
  18.  * @set_wake:        enable/disable power-management wake-on of an IRQ
  19.  *
  20.  * @release:        release function solely used by UML
  21.  * @typename:        obsoleted by name, kept as migration helper
  22.  */
可以看出这个结构体封装了一些底层的操作函数。我们再继续看s3c_irqext_chip的定义,

  1. static struct irq_chip s3c_irqext_chip = {
  2.     .name        = "s3c-ext",
  3.     .mask        = s3c_irqext_mask,    //(1)
  4.     .unmask        = s3c_irqext_unmask,
  5.     .ack        = s3c_irqext_ack,
  6.     .set_type    = s3c_irqext_type,
  7.     .set_wake    = s3c_irqext_wake
  8. };
(1)看一个示例,屏蔽此中断号对应的中断屏蔽寄存器,s3c_irqext_mask,如下,

  1. s3c_irqext_mask(unsigned int irqno)
  2. {
  3.     unsigned long mask;

  4.     irqno -= EXTINT_OFF;

  5.     mask = __raw_readl(S3C24XX_EINTMASK);    //读取EINTMASK寄存器的值 (?)
  6. ?------------?
  7.     mask |= ( 1UL << irqno);
  8.     __raw_writel(mask, S3C24XX_EINTMASK);
  9. }
初始化里所有的irq_desc数组成员,第二个问题差不多就分析到这里,在看第三个问题之前,再看一下asm_do_IRQ函数中,desc_handle_irq(irq, desc)这句,它调用desc中的handler_irq函数。在这里说明一下像EINT8-EINT23这样的中断,asm_do_IRQ函数中irq中断号只有32个取值。所以如果是EINT8引起的中断,在asm_do_IRQ通过寄存器INTOFFSET寄存器,在这里首先只能确定到是IRQ_EINT8t32,进而再调用irq_desc[IRQ_EINT8t32].handler_irq。这个函数设置为s3c_irq_demux_extint8,它可以进一步通过寄存器EITPEND等,确定是EINT8引起的中断,软后重新计算中断号,调用新计算出的中断号的hanlde_irq函数。
三。request_irq实现的简略分析

  1. action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
  2.     if (!action)
  3.         return -ENOMEM;

  4.     action->handler = handler;
  5.     action->flags = irqflags;
  6.     cpus_clear(action->mask);
  7.     action->name = devname;
  8.     action->next = NULL;
  9.     action->dev_id = dev_id;

  10.     select_smp_affinity(irq);
  11.     retval = setup_irq(irq, action);
函数申请了struct irqaction的一个结构体,在irq_desc数组成员中包含此结构体,此结构体可以构成一个链表。即一个irq_desc数组成员可以关联上多个action。
setup_irq(irq, action);函数会检查相应的条件,然后将新申请的action结构添加到此链表中。然后会调用chip->startup等函数来使能中断。总的来数request_irq函数的任务就是,
1.将新的中断处理函数链接到irq_desc数组相应中断号成员的action链表中。
2.设置中断触发方式等
3.中断被使能,此时中断已经可以使用了。

阅读(754) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~