您的位置:首页 > 运维架构 > Linux

arm-linux中断机制之一:中断产生至进入do_asm_IRQ分析

2014-11-24 20:18 781 查看

一.开发环境介绍

宿主机:

系统环境:ubuntu14.04 
交叉工具链:arm-linux-gcc4.3.3

目标机:

硬件:TQ2440开发板子

内核:linux-2.6.30.4

二.S3C2440中断基础

2.1 概述

        具体介绍可参看《S3C2440 技术手册》,此处不再叙述。对于《手册》中疑问如下,望相关朋友解惑。

        《手册》介绍“S3C2440支持60个中断源,分别是:One Watch dog timer, 5 timers, 9 UARTs, 24external interrupts, 4 DMA, 2 RTC, 2 ADC, 1 IIC, 2 SPI, 1 SDI, 2 USB, 1 LCD, 1 Battery Fault, 1 NAND and 2 Camera,
1 AC97”


          这不是59个中断源嘛?为什么手册中提到是60个呢?难道Reset没列出?

2.2 相关寄存器介绍

Source Pending Register (SRCPND)
             该寄存器表明正在等待处理的中断请求。因为该寄存器是“最靠近”中断源的控制寄存器,且不受中断屏蔽寄存器(INTMSK)的影响;因此,只要有满足相关中断源的中断条件产生,该寄存器中的相关bit位都会被硬件置位,与该中断源是否被屏蔽无关。因此,不能通过直接查询该寄存器进而得到中断源。

Interrupt Pending Register(INTPND)
            该寄存器表明各个中断源的状态,即各个中断源是否有中断请求发生。因为该寄存器“位于优先级寄存器(PRIORITY)之后”;因此,在某一时刻,该寄存器都只有一位有效。ISR可以通过查询该寄存器从而得到中断源。但是,在linux-2.6.29源码中,并不是通过对该寄存器的判断来锁定中断源

Interrupt Offset Register(INTOFFSET)
            该寄存器同样可以表明产生中断的中断源,效果同中断挂起寄存器(INTPND)。linux-2.6.29内核就是通过查询该寄存器进而得到发生中断的中断源

Sub-Source Pending Register(SUBSRCPND)

           该寄存器表明一组中断中具体是那个中断源。以UART0为例说明,在UART接口接收/发送数据过程中,产生以下三种情况都会发生一个中断:接收/发送/错误。这三个子中断源经过“XOR”运算进而转换为一个UART中断送入CPU处理。       

2.3 ExtInt中断说明

        《手册》中提到支持24个外部中断。在INTPND/INTOFFSET寄存器中,这24个外部中断的相关标志位为bit0--bit5。其中,bit0/1/2/3分别对应外部中断0/1/2/3,而外部中断4--7全部对应bit4,外部中断8--23全部对应bit5。

         当外部中断0/1/2/3发生时,在相关寄存器中会有相关的bit位标示。那么当外部中断4---7当中的任一发生时,在INTPND/INTOFFSET寄存器中都是相同的一个bit位,那么ISR如何进一步具体判断是哪个外部中断发生呢?

         这时,需要用到External Interrupt Pending Register(EINTPND),即外部中断挂起寄存器。《手册》中对于该寄存器的相关描述位于“GPIO”一章。

三.裸机程序中的中断机制

        以产生了外部中断0为例,进行分析。大致步骤如下:

        1.跳转至中断向量表,运行中断总入口函数

        2.保护现场

        3.确定产生中断的中断源号

        4.根据中断号,跳转至具体的ISR运行

        5.恢复现场,退出中断总入口函数ISR

        6.转至被中断处,继续执行

四.arm-linux的中断机制

        假设,同上。

        无论裸机驱动程序,亦或带有嵌入式OS的驱动程序,对于中断大致都是上述过程。不同的是实现的方法而已。那么就让我们看看在arm-linux机制中上述各个步骤时如何完成的。

4.1 中断向量表

        S3C2440是基于ARM920T的一款CPU。据网上的各种资料,该CPU的向量表支持地址偏移,其实就是当中断发生时,不再跳至0x0处的向量表执行ISR,而是跳至指定的偏移处的向量表执行ISR。

        在内核启动函数start_kernel(位于main.c)中,调用setup_arch(&command_line)函数(位于arch/arm/kernel/setup.c)。而setup_arch函数又调用early_trap_init函数(位于arch/arm/kernel/traps.c)。

early_trap_init函数部分源码如下:

/*
 *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);
<pre name="code" class="cpp">memcpy((void *)vectors+0x200 ,__stubs_start,__stubs_end-__stubs_start);
memcpy((void *)vectors+0x1000-kuser_sz,__kuser_help_start,kuser_sz);




       该代码就将0x0处的向量表复制到0xFFFF000处,这样当有异常发生时,CPU就会跳转至新的向量表地址(即0xFFFF0000)执行相应程序。

       向量表的具体位置对于今天的分析,没有影响。我们只要知道,当异常发生时,CPU跳至向量表相应地址运行即可。

4.2.IRQ向量

       外部中断隶属于IRQ中断系统。因此,当有外部中断发生时,CPU应该跳至IRQ向量处运行。那么IRQ向量处具体有什么呢?什么又是向量表呢?

        向量表其实就是一条条的跳转指令。linux-2.6.29内核中,arm的向量表在entry_armv.s文件(位于arch/arm/kernel/)中。在该文件件中,向量表定义如下:

.global __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
        .global __vectors_end
__vectors_end:
        从该向量表可以知道,CPU跳转至vector_irq执行,而vector_irq在文件中并未直接给出,而是以下面的形式给出:

/*
 *Interrupt dispatcher
 */
   vector_stub irq , IRQ_MODE,4
   .long __irq_usr           @ 0 (USR_26/USR_32)
   .long __irq_invalid       @ 1 (FIQ_26/FIQ_32)
<pre name="code" class="cpp">   .long __irq_invalid       @ 2 (IRQ_26/IRQ_32)
   .long __irq_svc           @ 3 (SVC_26/SVC_32)
   ...

   ...


      程序跳至__irq_usr继续执行,__irq_usr(位于entry_armv.s)部分源代码如下:

__irq_usr:
usr_entry
kuser_cmpxchg_check
....
....
irq_handler
....
    ....
ENDPROC(__irq_usr)
      程序跳至__irq_handler(位于entry_armv.s)继续执行,__irq_handler部分源代码如下:

.macro  irq_handler
get_irqnr_preamble r5 , lr
1:      get_irqnr_and_base r0,r6,r5,lr
        movve r1,sp
        ....
        ....
        bne asm_do_IRQ
        ....
        ....
.endm
     __irq_handler是个宏定义,而不是函数

     首先,根据get_irqnr_and_base获得中断号,并存于R0寄存器;最终调用asm_do_IRQ函数,即IRQ中断总入口函数,其中R0寄存器存放有发生中断的中断向量号。

     get_irqnr_preamble与get_irqnr_and_base定义于entry_macro.s(位于arch/arm/mach-s3c2410/include),部分源码如下:

.macro get_irqnr_preamble base,tmp
.endm
....
....
.macro get_irqnr_and_base,irqnr,irqstat,base,tmp
       
       mov \irqstat,[\base,#INTPND]        //复制INTPND寄存器的值
       teq \irqstat 0                      //INTPND寄存器是否为0
       beq 1002f                           //为0说明,没有中断发送,则退出
       ldr \irqnr,[\base,#INTOFFSET]       //不为0,说明有中断发生,则读取INTOFFSET寄存器的值,确定具体中断
       mov \tmp , #1
       tst \irqstat,\tmp,lsl \irqnr        //没看懂??
       bne 1001f
       ....
       ....
1001:
       adds \irqnr,\irqnr,#IRQ_EINT0       //还未明白中断号为什么整体便宜#IRQ_EINT0,即16
1002:
       @@ exit here,Z flag unset if IRQ
.endm

.endm
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ARM-Linux 中断机制