打印

ARM-Linux 中断分析

[复制链接]
2090|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wufangtan|  楼主 | 2007-7-2 18:05 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

                            
                        保定慧通科技  garyguo
                        http://www.witech.com.cn
                       *版权所有,引用请注明出处!

     ARM体系结构中,把复位、中断、快速中断等都看作‘异常’,当这些‘异常’发生时,CPU会到固定地址处去找指令,他们对应的地址如下:
          地址                异常类型      进入时的工作模式
          0x00000000         Reset                 Supervisor
          0x00000004         Und                  Undefined
          0x00000008         Soft interupt           Supervisor
          0x0000000c         Abort(prefetch)        Abort
          0x00000010         Abort(data)            Abort
          0x00000014         Reserved              Reserved
          0x00000018         IRQ                   IRQ
          0x0000001c         FIQ                   FIQ
     首先要明确的一点就是,无论内存地址空间是如何映射的,以上这些地址都不会变,比如当有快速中断发生时,ARM将铁定到0X0000001C这个地址处取指令。这也是BOOTLOADER把操作系统引导以后,内存必须重映射的原因!否则操作系统不能真正接管整套系统!
     LINUX启动以后要初始化这些区域,初始化代码在main.c中的start_kernel()中,具体是调用函数trap_ini()来实现的。如下面所示(具体可参照entry-armv.S):
     .LCvectors:    swi    SYS_ERROR0
        b    __real_stubs_start + (vector_undefinstr - __stubs_start)
        ldr    pc, __real_stubs_start + (.LCvswi - __stubs_start)
        b    __real_stubs_start + (vector_prefetch - __stubs_start)
        b    __real_stubs_start + (vector_data - __stubs_start)
        b    __real_stubs_start + (vector_addrexcptn - __stubs_start)
        b    __real_stubs_start + (vector_IRQ - __stubs_start)
        b    __real_stubs_start + (vector_FIQ - __stubs_start)

     ENTRY(__trap_init)
        stmfd    sp!, {r4 - r6, lr}

        adr    r1, .LCvectors            @ set up the vectors
        ldmia    r1, {r1, r2, r3, r4, r5, r6, ip, lr}
        stmia    r0, {r1, r2, r3, r4, r5, r6, ip, lr}

        add    r2, r0, #0x200
        adr    r0, __stubs_start        @ copy stubs to 0x200
        adr    r1, __stubs_end
1:        ldr    r3, [r0], #4
        str    r3, [r2], #4
        cmp    r0, r1
        blt    1b
        LOADREGS(fd, sp!, {r4 - r6, pc})

     以上可以看出这个函数初始化了中断向量,实际上把相应的跳转指令拷贝到了对应的地址。
(保定慧通科技  garyguo     http://www.witech.com.cn   *版权所有,引用请注明出处!)
当发生中断时,不管是从用户模式还是管理模式调用的,最终都要调用do_IRQ():
     __irq_usr:    sub    sp, sp, #S_FRAME_SIZE
        stmia    sp, {r0 - r12}            @ save r0 - r12
        ldr    r4, .LCirq
        add    r8, sp, #S_PC
        ldmia    r4, {r5 - r7}            @ get saved PC, SPSR
        stmia    r8, {r5 - r7}            @ save pc, psr, old_r0
        stmdb    r8, {sp, lr}^
        alignment_trap r4, r7, __temp_irq
        zero_fp
1:        get_irqnr_and_base r0, r6, r5, lr
        movne    r1, sp
        adrsvc    ne, lr, 1b
        @
        @ routine called with r0 = irq number, r1 = struct pt_regs *
        @
        bne    do_IRQ    @ 调用do_IRQ来实现具体的中断处理
        mov    why, #0
        get_current_task tsk
        b    ret_to_user

     对于以上代码,在很多**中都有过分析,这里不再赘述。
      (保定慧通科技  garyguo     http://www.witech.com.cn   *版权所有,引用请注明出处!)

     Linux每个中断通过一个结构irqdesc来描述,各中断的信息都在这个结构中得以体现:
  struct irqdesc {
    unsigned int     nomask   : 1;        /* IRQ does not mask in IRQ   */
    unsigned int     enabled  : 1;        /* IRQ is currently enabled   */
    unsigned int     triggered: 1;        /* IRQ has occurred          */
    unsigned int     probing  : 1;        /* IRQ in use for a probe     */
    unsigned int     probe_ok : 1;        /* IRQ can be used for probe  */
    unsigned int     valid    : 1;        /* IRQ claimable          */
    unsigned int     noautoenable : 1;    /* don't automatically enable IRQ */
    unsigned int     unused   :25;
    void (*mask_ack)(unsigned int irq);    /* Mask and acknowledge IRQ   */
    void (*mask)(unsigned int irq);        /* Mask IRQ              */
    void (*unmask)(unsigned int irq);    /* Unmask IRQ              */
    struct irqaction *action;
    /*
     * IRQ lock detection
     */
    unsigned int     lck_cnt;
    unsigned int     lck_pc;
    unsigned int     lck_jif;
     };

     在具体的ARM芯片中会有很多的中断类型,每一种类型的中断用以上结构来表示:
struct irqdesc irq_desc[NR_IRQS];   /* NR_IRQS根据不同的MCU会有所区别*/
     在通过request_irq()函数注册中断服务程序的时候,将会把中断向量和中断服务程序对应起来。
     我们来看一下request_irq的源码:
     int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),
         unsigned long irq_flags, const char * devname, void *dev_id)
     {
    unsigned long retval;
    struct irqaction *action;

    if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||
        (irq_flags & SA_SHIRQ && !dev_id))
        return -EINVAL;

    action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);
    if (!action)              /*  生成action结构*/
        return -ENOMEM;

    action->handler = handler;
    action->flags = irq_flags;
    action->mask = 0;
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;

    retval = setup_arm_irq(irq, action);   /*把中断号irq和action 对应起来*/

    if (retval)
        kfree(action);
    return retval;
     }
     其中第一个参数irq就是中断向量,第二个参数即是要注册的中断服务程序。很多同仁可能疑惑的是,我们要注册的中断向量号是怎么确定的呢?这要根据具体芯片的中断控制器,比如三星的S3C2410,需要          通过读取其中的中断状态寄存器,来获得是哪个设备发生了中断:
      (保定慧通科技  garyguo     http://www.witech.com.cn   *版权所有,引用请注明出处!)

     if defined(CONFIG_ARCH_S3C2410)
     #include <asm/hardware.h>

        .macro  disable_fiq
        .endm
        .macro  get_irqnr_and_base, irqnr, irqstat, base, tmp
        mov    r4, #INTBASE        @ virtual address of IRQ registers
        ldr    \irqnr, [r4, #0x8]    @ read INTMSK   中断掩码寄存器
        ldr    \irqstat, [r4, #0x10]   @ read INTPND  中断寄存器
        bics    \irqstat, \irqstat, \irqnr
        bics    \irqstat, \irqstat, \irqnr
        beq    1002f        
        mov    \irqnr, #0
1001:        tst    \irqstat, #1
        bne    1002f            @ found IRQ
        add    \irqnr, \irqnr, #1
        mov    \irqstat, \irqstat, lsr #1
        cmp    \irqnr, #32
        bcc    1001b
1002:
        .endm
        .macro  irq_prio_table
        .endm        
     以上代码也告诉了我们,中断号的确定,其实是和S3C2410手册中SRCPND寄存器是一致的,即:

     /* Interrupt Controller */
     #define IRQ_EINT0        0    /* External interrupt 0 */
     #define IRQ_EINT1        1    /* External interrupt 1 */
     #define IRQ_EINT2        2    /* External interrupt 2 */
     #define IRQ_EINT3        3    /* External interrupt 3 */
     #define IRQ_EINT4_7        4    /* External interrupt 4 ~ 7 */
     #define IRQ_EINT8_23        5    /* External interrupt 8 ~ 23 */
     #define IRQ_RESERVED6        6    /* Reserved for future use */
     #define IRQ_BAT_FLT        7
     #define IRQ_TICK        8    /* RTC time tick interrupt  */
     #define IRQ_WDT            9    /* Watch-Dog timer interrupt */
     #define IRQ_TIMER0        10    /* Timer 0 interrupt */
     #define IRQ_TIMER1        11    /* Timer 1 interrupt */
     #define IRQ_TIMER2        12    /* Timer 2 interrupt */
     #define IRQ_TIMER3        13    /* Timer 3 interrupt */
     #define IRQ_TIMER4        14    /* Timer 4 interrupt */
     #define IRQ_UART2        15    /* UART 2 interrupt  */
     #define IRQ_LCD            16    /* reserved for future use */
     #define IRQ_DMA0        17    /* DMA channel 0 interrupt */
     #define IRQ_DMA1        18    /* DMA channel 1 interrupt */
     #define IRQ_DMA2        19    /* DMA channel 2 interrupt */
     #define IRQ_DMA3        20    /* DMA channel 3 interrupt */
     #define IRQ_SDI            21    /* SD Interface interrupt */
     #define IRQ_SPI0        22    /* SPI interrupt */
     #define IRQ_UART1        23    /* UART1 receive interrupt */
     #define IRQ_RESERVED24        24
     #define IRQ_USBD        25    /* USB device interrupt */
     #define IRQ_USBH        26    /* USB host interrupt */
     #define IRQ_IIC            27    /* IIC interrupt */
     #define IRQ_UART0        28    /* UART0 transmit interrupt */
     #define IRQ_SPI1        29    /* UART1 transmit interrupt */
     #define IRQ_RTC            30    /* RTC alarm interrupt */
     #define IRQ_ADCTC        31    /* ADC EOC interrupt */
     #define NORMAL_IRQ_OFFSET    32
     这些宏定义在文件irqs.h中,大家可以看到它的定义取自S3C2410的文档。
     (慧通科技低价提供ARM开发板,欢迎来电咨询洽谈,03123113161)
     总结: linux在初始化的时候已经把每个中断向量的地址准备好了!就是说添加中断服务程序的框架已经给出,当某个中断发生时,将会到确定的地址处去找指令,所以我们做驱动程序时,只需要经过request_irq()来挂接自己编写的中断服务程序即可。

     另:对于快速中断,linux在初始化时是空的,所以要对它挂接中断处理程序,就需要单独的函数set_fiq_handler()来实现,此函数在源文件fiq.c中,有兴趣的读者可进一步研究。

相关帖子

沙发
zhang369761094| | 2014-10-9 13:35 | 只看该作者
dingle

使用特权

评论回复
板凳
zhang369761094| | 2014-10-9 13:48 | 只看该作者
学习了

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

3

主题

16

帖子

1

粉丝