Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2974446
  • 博文数量: 401
  • 博客积分: 12926
  • 博客等级: 上将
  • 技术积分: 4588
  • 用 户 组: 普通用户
  • 注册时间: 2009-02-22 14:51
文章分类

全部博文(401)

文章存档

2015年(16)

2014年(4)

2013年(12)

2012年(82)

2011年(98)

2010年(112)

2009年(77)

分类: LINUX

2012-05-25 16:13:35

基于ARM Linux 中断、异常的处理分析

本文是基于ARM S3C2410X系统的Linux 2.6中断、异常和系统调用的处理分析。主要有以下几个部分:

1.      ARM的硬件中断机制

2.      Linux 2.6ARM中断向量表的初始化

3.      Linux 2.6ARM中断、异常的处理(从汇编-->C语言函数;asm_do_IRQ

一、        ARM的硬件中断机制 1、中断的基本概念

在嵌入式系统中外部设备的功能实现主要是依靠中断机制来实现的,即将设备功能程序的实现以中断服务子程序的形式进行组织。

中断interrupt)通常被定义为一个事件,该事件改变处理器执行的指令顺序。这样的事件与CPU芯片外部硬件电路产生的电信号相对应。

中断的产生 每个能够发出中断请求的硬件设备控制器都有一条称为IRQ(Interrupt ReQuest)的输出线。所有的IRQ线都与一个中断控制器的输入引脚相连,中断控制器与CPUINTR引脚相连。

设备

设备

控制器

中断

控制器

IRQ

CPU

INTR

 

中断向量 每个中断和异常由0~255之间的一个数(8位)来标识,Intel称其为中断向量。

中断描述符表Interrupt Descriptor Table ,IDT)是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断或异常处理程序的入口地址。内核在允许中断发生前,必须适当地初始化IDT。表中的每一项对应一个中断或异常向量,每个向量由8个字节组成。因此,最多需要256*8=2048字节来存放IDTCPUidtr寄存器指向IDT表的物理基地址。

2、中断和异常的硬件处理

在内核被Init进程初始化后,CPU运行在保护模式下。当执行了一条指令后,cseip这对寄存器包含了下一条将要执行的指令的逻辑地址。在执行这条指令之前,CPU控制单元会检查在运行前一条指令时是否发生了一个中断或者异常。如果发生了一个中断或异常,那么CPU控制单元执行下列操作:

(1) 确定与中断或者异常关联的向量i0~255)。

(2) 读由idtr寄存器指向的IDT表中的第i项。

(3) gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符所标识的段描述符,这个描述符指定中断或异常处理程序所在段的基地址。

(4) 确定中断是由授权的发生源发出的。

中断:中断处理程序的特权不能低于引起中断的程序的特权当前特权级CPL—对应CS寄存器中的低两位 其值应该小于段描述符—对应GDT表项中的描述符特权级DPL,特权级高于DPL,即当前代码是能够访问相应的段的,产生一个“”异常);

编程异常:还需进一步比较CPL与对应IDT表项中的门描述符的DPL

即当CPL的特权级高于GDT表项中的描述符特权级DPL,但低于IDT表项中的门描述符的DPL,就是异常。

(5) 检查是否发生了特权级的变化,一般指是否由用户态陷入了内核态。也就是说CPL是否不同于所选择的段描述符的DPL,如果是,控制单元必须开始使用与新的特权级相关的堆栈,通过以下操作来做到这点:

A、读tr寄存器,访问运行进程的TSS段;

B、用与新特权级相关的栈段和栈指针装载ssesp寄存器。这些值可以在进程的TSS段中找到;

C、在新的栈中保存ssesp以前的值,这些值指明了与旧特权级相关的栈的逻辑地址。

(6) 若发生的是故障,用引起异常的指令地址修改cseip寄存器的值,以使得这条指令在异常处理结束后能被再次执行。

(7) 在栈中保存eflagscseip的内容。

(8) 如果异常产生一个硬件出错码,则将它保存在栈中。

(9) 装载cseip寄存器,其值分别是IDT表中i项门描述符的段选择符偏移量字段。这对寄存器的值指出中断或者异常处理程序的第一条指令的逻辑地址。

控制单元所执行的最后一步就是跳转到中断或异常处理程序,换句话说,处理完中断信号后,控制单元所执行的指令就是被选中处理程序的第一条指令。

中断/异常处理完后,相应的处理程序会执行一条iret汇编指令,把控制权转交给被中断的进程,这条汇编指令让CPU控制单元做如下事情:

(1) 用保存在栈中的值装载cseipeflags寄存器。如果一个硬件出错码曾被压入栈中,并且在eip内容的上面,那么执行iret指令前必先弹出这个硬件出错码。

(2) 检查处理程序的特权级CPL是否等于cs中最低两位的值(这意味着进程在被中断的时候是运行在内核态还是用户态,即被中断的进程与处理程序是否运行在同一特权级)。若是,iret终止执行;否则,转入下一步。

(3) 从栈中装载ssesp寄存器。这步意味着返回到与旧特权级相关的栈。

(4) 检查dsesfsgs段寄存器的内容,如果其中一个寄存器包含的选择符是一个段描述符,并且特权级比当前特权级高(DPL的值小于CPL的值),则清除相应的寄存器。控制单元这么做是防止怀有恶意的用户程序利用内核以前所用的寄存器访问内核空间。

二、LinuxARM中断向量表的初始化 1、中断向量表的作用

如上在中断的硬件处理的分析中可知:中断首先是一个硬件行为, 而处理中断呢, 显然又是一个软件行为,那么当硬件触发中断的时候,怎么调用到中断处理函数的呢?要搞清楚这个问题,就要搞清楚发生中断的时候 CPU做了什么。以S3C2410X系统的异常中断为例:

S3C2410X是基于ARM920T内核处理器。该系统提供的FIQIRQ异常中断用于外部设备向CPU请求服务,一般情况下都是采用IRQS3C2410X系统中通常在存储区的低端固化了一个32字节的硬件中断向量表,用来指定各异常中断与其处理程序的对应关系。CPU知道一个source触发了中断,怎么调用执行一些函数(汇编,或者c语言),就是靠异常向量表(事实上,exception vector table 也是由汇编组成的)

异常

模式

向量表偏移

复位(reset

SVC

+0x00

未定义指令

UND

+0x04

软件中断(SWI

SVC

+0x08

预取指终止

ABT

+0x0c

数据终止

ABT

+0x10

未分配

--

+0x14

IRQ

IRQ

+0x18

FIQ

FIQ

+0x1c

arm异常表,对应模式及向量表偏移 (摘自arm体系结构与编程一书)

当一个异常/中断出现后, S3C2410X系统中ARM处理器对其的响应过程如下:

 (1)  保存处理器当前状态、中断屏蔽位以及各条件标志位。将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中

(2)  设置当前程序状态寄存器CPSR中相应的位。包括设置CPSR中的位,使处理器进入相应的执行模式;设置CPSR中的位,禁止IRQ中断,当进入FIQ模式时,禁止FIQ中断。

(3) 将寄存器lr_mode设置成返回地址

(4) 将程序计数器值(PC),设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序执行。即处理器跳转到异常向量表中相应的入口(对于IRQ  显然pc=0x18 

所以当触发IRQ后,CPU会最后跳入0x18 这个入口,定制kernel时只需在这个入口填入自己的指令(当然是汇编语句) ,即可调用中断处理函数,可能这样:

触发IRQCPU jump 0x18,同时要把irqno传入相应的寄存器调用一个中断通用处理函数如:asm_do_IRQ(unsigned int irqno)  asm_do_IRQ() 这个函数根据irqno 就可以找到对应的中断描述符,然后调用中断描述符里面的handler()了。

2、中断向量表的初始化

v       arch/arm/kernel/entry-armv.S ——中断向量表放在这个文件里:

__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:

ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表。

v         linux/init/main.c    Start_kernel中的中断向量表初始化

asmlinkage void __init start_kernel(void) 
{ 
..... 
trap_init(); 

init_IRQ(); 
....

中断的初始化主要和这两个函数相关

(1) trap_init()

v       linux/arch/arm/kernel/traps.c

void __init trap_init(void)

{

       unsigned long vectors = CONFIG_VECTORS_BASE;

       extern char __stubs_start[], __stubs_end[];

       extern char __vectors_start[], __vectors_end[];

       extern char __kuser_helper_start[], __kuser_helper_end[];

       int kuser_sz = __kuser_helper_end - __kuser_helper_start;

 

       /*

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

       memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

       memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

 

       /*

        * Copy signal return handlers into the vector page, and

        * set sigreturn to be a pointer to these.

        */

       memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,

              sizeof(sigreturn_codes));

 

       flush_icache_range(vectors, vectors + PAGE_SIZE);

       modify_domain(DOMAIN_USER, DOMAIN_CLIENT);

}

CONFIG_VECTORS_BASE是一个宏,用来获取ARM异常向量的地址,该宏在include/arch/asm-arm/system.h中定义:

v       include/arch/asm-arm/system.h

#define CPU_ARCH_ARMv5         4

 

#define CR_V      (1 << 13)      /* Vectors relocated to 0xffff0000    */

 

extern unsigned long cr_no_alignment;      /* defined in entry-armv.S */

extern unsigned long cr_alignment;     /* defined in entry-armv.S */

 

#if __LINUX_ARM_ARCH__ >= 4

#define vectors_high()  (cr_alignment & CR_V)

#else

#define vectors_high()  (0)

#endif                                                                                                                                                                           

对于ARMv4以下的版本,这个地址固定为0ARMv4及其以上的版本,ARM异常向量表的地址受协处理器CP15c1寄存器(control register)V(bit[13])的控制,如果V=1,则异常向量表的地址为0x00000000~0x0000001C;如果V=0,则为:0xffff0000~0xffff001C。(详情请参考ARM Architecture Reference Manual)

v       arch/arm/kernel/entry-armv.S—找到cr_alignment的定义:

       .globl      cr_alignment

       .globl      cr_no_alignment

cr_alignment:

       .space    4

cr_no_alignment:

       .space    4

v       linux/arch/arm/kernel/head.S—当内核启动时,进入head.S文件:

/*

 * Kernel startup entry point.

 * ---------------------------

 *

 * This is normally called from the decompressor code.  The requirements

 * are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,

 * r1 = machine nr, r2 = atags pointer.

 *

…… …… …… …… ……

 */

       .section ".text.head", "ax"

       .type       stext, %function

ENTRY(stext)

       msr  cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode

                                          @ and irqs disabled

       mrc p15, 0, r9, c0, c0        @ get processor id

       bl    __lookup_processor_type         @ r5=procinfo  r9=cpuid

       movs      r10, r5                         @ invalid processor (r5=0)?

       beq __error_p                    @ yes, error 'p'

       bl    __lookup_machine_type            @ r5=machinfo

       movs      r8, r5                           @ invalid machine (r5=0)?

       beq __error_a                    @ yes, error 'a'

       bl    __vet_atags

       bl    __create_page_tables  //创建arm启动临时使用的前4M页表

 

       /*

        * The following calls CPU specific code in a position independent

        * manner.  See arch/arm/mm/proc-*.S for details.  r10 = base of

        * xxx_proc_info structure selected by __lookup_machine_type

        * above.  On return, the CPU will be ready for the MMU to be

        * turned on, and r0 will hold the CPU control register value.

        */

       ldr   r13, __switch_data             @ address to jump to after  //99行

                                          @ mmu has been enabled

       adr  lr, __enable_mmu        @ return (PIC) address

       add pc, r10, #PROCINFO_INITFUNC         //102行

   …… …… …… …… …… …… ……

__turn_mmu_on:

            mov   r0, r0        //填充armv4中的三级流水线:mov r0r0 //应一个nop,所以对应2nop和一个mov pc,lr刚好三个"无用"操作    

        mcr    p15, 0, r0, c1, c0, 0           @ write control reg  //193行

        mrc    p15, 0, r3, c0, c0, 0           @ read id reg

  …… …… …… …… …… …… ……

mov       pc, lr                   //327行

s3c2410平台中,它将跳转到arch/arm/mm/proc-arm920.S中执行__arm920 _setup函数。即第102行“add    pc, r10, #PROCINFO_INITFUNC”:   执行b _arm920_setup

v       linux/arch/arm/mm/proc-arm920.S: MMU functions for ARM920

.section ".proc.info.init", #alloc, #execinstr

 

       .type       __arm920_proc_info,#object

__arm920_proc_info:

       .long       0x41009200

       .long       0xff00fff0

       .long   PMD_TYPE_SECT | /

              PMD_SECT_BUFFERABLE | /

              PMD_SECT_CACHEABLE | /

              PMD_BIT4 | /

              PMD_SECT_AP_WRITE | /

              PMD_SECT_AP_READ

       .long   PMD_TYPE_SECT | /

              PMD_BIT4 | /

              PMD_SECT_AP_WRITE | /

              PMD_SECT_AP_READ

       b     __arm920_setup

   // add      pc, r10, #PROCINFO_INITFUNC”:将执行b _arm920_setup

       .long       cpu_arch_name

       .long       cpu_elf_name

       .long       HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB

       .long       cpu_arm920_name

       .long       arm920_processor_functions

       …… …… …… …… …… …… ……

         .size __arm920_proc_info, . - __arm920_proc_info

  …… …… …… …… …… …… …… …… …… …… …… …… …… …… ……

.type       __arm920_setup, #function

__arm920_setup:

       mov r0, #0

       mcr p15, 0, r0, c7, c7        @ invalidate I,D caches on v4

       mcr p15, 0, r0, c7, c10, 4         @ drain write buffer on v4

#ifdef CONFIG_MMU

       mcr p15, 0, r0, c8, c7        @ invalidate I,D TLBs on v4

#endif

       adr  r5, arm920_crval

       ldmia      r5, {r5, r6}

       mrc p15, 0, r0, c1, c0        @ get control register v4

       bic   r0, r0, r5

       orr   r0, r0, r6

       mov pc, lr

       .size __arm920_setup, . - __arm920_setup

当在arm920_setup设置完协处理器和返回寄存器r0之后,跳回到linux/arch/arm/kernel/head.S文件中的第99行,在lr寄存器中放置__switch_data中的数据__mmap_switched,第327行程序会跳转到__mmap_switched处。在__turn_mmu_on:后,第193,194行,把r0寄存器中的值写回到cp15control register(c1),再读出来放在r0中。 接下来再来看一下跳转到__mmap_switched处的代码:

v       linux/arch/arm/kernel/head-common.S

.type       __switch_data, %object

__switch_data:

       .long       __mmap_switched

       .long       __data_loc                  @ r4

       .long       __data_start                @ r5

       .long       __bss_start                  @ r6

       .long       _end                            @ r7

       .long       processor_id               @ r4

       .long       __machine_arch_type         @ r5

       .long       __atags_pointer                  @ r6

       .long       cr_alignment                @ r7

       .long       init_thread_union + THREAD_START_SP @ sp

/*

 * The following fragment of code is executed with the MMU on in MMU mode,

 * and uses absolute addresses; this is not position independent.

 *

 *  r0  = cp#15 control register

 *  r1  = machine ID

 *  r2  = atags pointer

 *  r9  = processor ID

 */

.type       __mmap_switched, %function

__mmap_switched:                                         //40

       adr  r3, __switch_data + 4                                //41

 

       ldmia      r3!, {r4, r5, r6, r7}                               //43

       cmp r4, r5                           @ Copy data segment if needed

1:    cmpne    r5, r6

       ldrne       fp, [r4], #4

       strne       fp, [r5], #4

       bne  1b

 

       mov fp, #0                          @ Clear BSS (and zero fp)

1:    cmp r6, r7

       strcc       fp, [r6],#4

       bcc  1b                                                //53

 

       ldmia      r3, {r4, r5, r6, r7, sp}        // sp        (init_task_union)+8192

       str   r9, [r4]                 @ Save processor ID

       str   r1, [r5]                 @ Save machine type

       str   r2, [r6]                 @ Save atags pointer

       bic   r4, r0, #CR_A                    @ Clear 'A' bit

       stmia      r7, {r0, r4}                  @ Save control register values    //60

         b       start_kernel                         // 进入内核C程序

41~43行的结果是:r6=__bss_startr7=__end,...,r7=cr_alignment,..,这里r7保存的是cr_alignment变量的地址。

到了60行,由于之前r0保存的是cp15control register(c1)的值,这里把r0的值写入r7指向的地址,即cr_alignment=r0.到此为止,我们就看清楚了cr_alignment的赋值过程。

让我们回到trap_init()函数,经过上面的分析,我们知道vectors_base返回0xffff0000。函数__trap_init由汇编代码编写,在arch/arm/kernel/entry-arm.S

v         linux/arch/arm/kernel/entry-armv.S

/*============================================================

 * Address exception handler

 *-----------------------------------------------------------------------------

 * These aren't too critical.

 * (they're not supposed to happen, and won't happen in 32-bit data mode).

 */

 

vector_addrexcptn:

       b     vector_addrexcptn

 

/*

 * We group all the following data together to optimise

 * for CPUs with separate I & D caches.

 */

       .align      5

 

.LCvswi:

       .word     vector_swi

 

       .globl      __stubs_end

__stubs_end:

 

       .equ stubs_offset, __vectors_start + 0x200 - __stubs_start

 

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

 

       .data

 

       .globl      cr_alignment

       .globl      cr_no_alignment

cr_alignment:

       .space    4

cr_no_alignment:

       .space    4

当有异常发生时,处理器会跳转到对应的0xffff0000起始的向量处取指令,然后,通过b指令散转到异常处理代码.因为ARMb指令是相对跳转,而且只有+/-32MB的寻址范围,所以把__stubs_start~__stubs_end之间的异常处理代码复制到了0xffff0200起始处.这里可直接用b指令跳转过去,这样比使用绝对跳转(ldr)效率高。

 

 

(2) init_IRQ()

v       linux/arch/arm/kernel/irq.c

void __init init_IRQ(void)

{

       int irq;

 

       for (irq = 0; irq < NR_IRQS; irq++)      // NR_IRQS代表中断数目

              irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

// irq_desc数组是用来描述IRQ的请求队列,每一个中断号分配一个

//irq_desc结构,组成了一个数组。

#ifdef CONFIG_SMP

       bad_irq_desc.affinity = CPU_MASK_ALL;

       bad_irq_desc.cpu = smp_processor_id();

#endif

       init_arch_irq();

}

v       include/linux/irq.h

/**

 * struct irq_desc - interrupt descriptor

 *

 * @handle_irq:          highlevel irq-events handler [if NULL, __do_IRQ()]

 * @chip:            low level interrupt hardware access

 * @msi_desc:            MSI descriptor

 * @handler_data:      per-IRQ data for the irq_chip methods

 * @chip_data:           platform-specific per-chip private data for the chip

 *                 methods, to allow shared chip implementations

 * @action:          the irq action chain

 * @status:          status information

 * @depth:          disable-depth, for nested irq_disable() calls

 * @wake_depth:              enable depth, for multiple set_irq_wake() callers

 * @irq_count:           stats field to detect stalled irqs

 * @irqs_unhandled:   stats field for spurious unhandled interrupts

 * @last_unhandled:   aging timer for unhandled count

 * @lock:            locking for SMP

 * @affinity:         IRQ affinity on SMP

 * @cpu:             cpu index useful for balancing

 * @pending_mask:    pending rebalanced interrupts

 * @dir:        /proc/irq/ procfs entry

 * @affinity_entry:      /proc/irq/smp_affinity procfs entry on SMP

 * @name:           flow handler name for /proc/interrupts output

 */

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;

       unsigned long        last_unhandled;     /* Aging timer for unhandled count */

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

 

 

 

 

 

 

 

三、LinuxARM中断、异常的处理 1、硬件发生中断后,找到中断入口函数

entry-armv.S中的中断向量表定义可以看到,发生中断时系统会跳到 vector_irq + stubs_offset处运行,这个位置实际上就是中断入口函数。

vector_irq已经是中断的入口函数了,为什么又要加上 stubs_offset?是因为b指令实际上是相对当前PC的跳转,也就是说当汇编器看到B指令后会把要跳转的标签转化为相对于当前PC的偏移量写入指令码。从上面的代码可以看到中断向量表和stubs都发生了代码搬移,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。

我们把搬移前的中断向量表中的irq入口地址记irq_PC,它在中断向量表的偏移量就是irq_PC-vectors_start, vector_irqstubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后 vectors_start0xffff0000处,而stubs_start0xffff0200处,所以搬移后的vector_irq相对于中断向量中的中断入口地址的偏移量就是:200+vector_irqstubs中的偏移量再减去中断入口在向量表中的偏移量,即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中也是这样定义的

2根据原来发生中断时CPU所处的工作模式找到相应的入口函数

v       arch/arm/kernel/entry-armv.Svector_irq是通过宏来vector_stub定义的

/*

 * Vector stubs.

 *

 * This code is copied to 0xffff0200 so we can use branches in the

 * vectors, rather than ldr's.  Note that this code must not

 * exceed 0x300 bytes.

 *

 * Common stub entry macro:

 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC

 *

 * SP points to a minimal amount of processor-private memory, the address

 * of which is copied into r0 for the mode specific abort handler.

 */

       .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

 

       .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

......

从上面这段代码可以看出,vector_irq把发生中断时的r0,PC-4以及CPSR压栈(注意,压入的的irq模式的堆栈),把中断栈的栈指针赋给 r0,最后根据原来发生中断时CPU所处的工作模式(CPSR的低4位)找到相应的入口函数,在进入svc模式后进一步处理中断。

3__irq_usr完成的工作

它主要通过调用宏usr_entry进一步保存现场,然后调用irq_handler进行中断处理,保存的栈结构如下:

-1

CPSR

PC-4

LR

SP

R12

...

R2

R1

R0

其中的LRSPUSR模式下的,R0,CPSRPC-4是从中断栈中拷贝的

4irq_handler的作用

它首先通过宏 get_irqnr_and_base获得中断号,存入r0,然后把上面建立的pt_regs结构的指针,也就是sp值赋给r1,把调用宏 get_irqnr_and_base的位置作为返回地址(为了处理下一个中断),然后调用 asm_do_IRQ进一步处理中断,以上这些操作都在建立在获得中断号的前提下,也就是有中断发生,

即当某个外部硬件触发中断的时候,kernel最终会调用到:asm_do_IRQ

v       linux/arch/arm/kernel/entry-armv.S

/*

 * Interrupt handling.  Preserves r7, r8, r9

 */

       .macro    irq_handler

       get_irqnr_preamble r5, lr

1:    get_irqnr_and_base r0, r6, r5, lr  //获得中断号并存入r0

       movne    r1, sp                //sp的值赋给r1,即pt_regs结构的指针

       @

       @ routine called with r0 = irq number, r1 = struct pt_regs *

       @

       adrne      lr, 1b          //调用宏 get_irqnr_and_base的位置作为返回地址

       bne  asm_do_IRQ

 

v       linux/arch/arm/kernel/irq.c

/*

 * do_IRQ handles all hardware IRQ's.  Decoded IRQs should not

 * come via this function.  Instead, they should provide their

 * own 'handler'

 */

asmlinkage void __exception 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);

}

v       linux/include/asm-generic/irq_regs.h

static inline struct pt_regs *set_irq_regs(struct pt_regs *new_regs)

{

       struct pt_regs *old_regs, **pp_regs = &__get_cpu_var(__irq_regs);

 

       old_regs = *pp_regs;

       *pp_regs = new_regs;

       return old_regs;

}

v       linux/kernel/softirq.c

/*

 * Enter an interrupt context.

 */

void irq_enter(void)

{

#ifdef CONFIG_NO_HZ

       int cpu = smp_processor_id();

       if (idle_cpu(cpu) && !in_interrupt())

              tick_nohz_stop_idle(cpu);

#endif

       __irq_enter();

#ifdef CONFIG_NO_HZ

       if (idle_cpu(cpu))

              tick_nohz_update_jiffies();

#endif

}

v       linux/include/asm-arm/mach/irq.h

/*

 * Obsolete inline function for calling irq descriptor handlers.

 */

static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)

{

       desc->handle_irq(irq, desc);

}

v       include/asm-arm/arch-at91/irqs.h

/*

 * Acknowledge interrupt with AIC after interrupt has been handled.

 *   (by kernel/irq.c)

 */

#define irq_finish(irq) do { at91_sys_write(AT91_AIC_EOICR, 0); } while (0)

v       linux/kernel/softirq.c

/*

 * Exit an interrupt context. Process softirqs if needed and possible:

 */

void irq_exit(void)

{

       account_system_vtime(current);

       trace_hardirq_exit();

       sub_preempt_count(IRQ_EXIT_OFFSET);

       if (!in_interrupt() && local_softirq_pending())

              invoke_softirq();

 

#ifdef CONFIG_NO_HZ

       /* Make sure that timer wheel updates are propagated */

       if (!in_interrupt() && idle_cpu(smp_processor_id()) && !need_resched())

              tick_nohz_stop_sched_tick();

       rcu_irq_exit();

#endif

       preempt_enable_no_resched();

}

 

参考文献:

1Bovet & Cesati著,陈莉君等译,深入理解Linux内核,中国电力出版社,2007

2、刘淼,嵌入式系统接口设计与Linux驱动程序开发,北京航天航空大学出版社,2006.

3ARM Architecture Reference Manual, ARM limited,2000. 

4http://jimmy-lee.blog.hexun.com/cate.aspx?cateid=7311&cate=ARM%26Linux 2008

5http://blog.chinaunix.net/u/22617/showart_470098.html

因水平有限,定有不足之处,还望多多交流, 


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