Chinaunix首页 | 论坛 | 博客
  • 博客访问: 588994
  • 博文数量: 199
  • 博客积分: 5087
  • 博客等级: 大校
  • 技术积分: 2165
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-26 21:53
文章存档

2010年(199)

我的朋友

分类: LINUX

2010-07-13 19:13:00

一、arm中断的相关硬件知识:
正常的程序执行流程发生暂时的停止时,称之为异常。例如处理一个外部的中断请求。在处理异常之前,当前处理器的状态必须保留,这样当异常处理完成之后,当前程序可以继续执行。处理器允许多个异常同时发生,它们将会按固定的优先级进行处理。ARM体系结构中的异常,与8位/16位体系结构的中断有很大的相似之处,但异常与中断的概念并不完全等同。
ARM的异常有七种
复位异常、SWI异常、未定义指令异常、数据中止和指令中止异常。外部中断分为FIQ和IRQ两种,分别为快速中断和通用中断。都可以通过CPSR中的相应位来屏蔽。
CPU知道一个source触发了中断,怎么调用执行一些函数(汇编,或者c语言),就是靠异常向量表(事实上,exception vector table 也是由汇编组成的)

Arm的7种异常对应的模式
进入模式
5
个异常模式
优先级
6最低
0x0000,0000
复位
管理模式
1
0x0000,0004
未定义指令
未定义模式
6
0x0000,0008
软件中断
管理模式
6
0x0000,000C
中止(预取指令)
中止模式
5
0x0000,0010
中止(数据)
中止模式
2
0x0000,0014
保留
保留
未使用
0x0000,0018
IRQ
IRQ
4
0x0000,001C
FIQ
FIQ
3
arm异常表,对应模式及向量表偏移 (摘自arm体系结构与编程一书)
 
当一个异常/中断出现后, 4020系统中ARM处理器对其的响应过程如下:
 
 (1)  保存处理器当前状态、中断屏蔽位以及各条件标志位。将当前程序状态寄存器CPSR的内容保存到将要执行的异常中断对应的SPSR寄存器中。
 
(2)  设置当前程序状态寄存器CPSR中相应的位。包括设置CPSR中的位,使处理器进入相应的执行模式;设置CPSR中的位,禁止IRQ中断,当进入FIQ模式时,禁止FIQ中断。
 
(3) 将寄存器lr_mode设置成返回地址。
 
(4) 将程序计数器值(PC),设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序执行。即处理器跳转到异常向量表中相应的入口(对于IRQ , 显然pc=0x18) 。
 
所以当触发IRQ后,CPU会最后跳入0x18 这个入口,定制kernel时只需在这个入口填入自己的指令(当然是汇编语句) ,即可调用中断处理函数,可能这样:
 
而对于4020 linux系统中断向量的含义是:
触发IRQ—CPU jump 到0x18,同时要把irqno传入相应的寄存器调用一个中断通用处理函数如:asm_do_IRQ(unsigned int irqno)  asm_do_IRQ() 这个函数根据irqno 就可以找到对应的中断描述符,然后调用中断描述符里面的handler()了。
 
二、linux中中断向量表的初始化
ARM linux内核启动时,通过start_kernel()->trap_init()的调用关系,初始化内核的中断异常向量表。

linux/init/main.c Start_kernel中的中断向量表初始化
asmlinkage void __init start_kernel(void)
{
.....
trap_init();
init_IRQ();
....
}

 
中断的初始化主要和这两个函数相关
(1)trap_init函数
这个trap_init函数主要起到搬运中断向量到高地址的作用

void __init trap_init(void)
{
       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 *)0xffff0000, __vectors_start, __vectors_end - __vectors_start);
       memcpy((void *)0xffff0200, __stubs_start, __stubs_end - __stubs_start);
       memcpy((void *)0xffff1000 - 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(0xffff0000, 0xffff0000 + PAGE_SIZE);
       modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

(2)init_IRQ函数

void __init init_IRQ(void)
{
       struct irqdesc *desc;
       int irq;

#ifdef CONFIG_SMP
       bad_irq_desc.affinity = CPU_MASK_ALL;
       bad_irq_desc.cpu = smp_processor_id();
#endif
// irq_desc数组是用来描述IRQ的请求队列,每一个中断号分配一个
//irq_desc结构,组成了一个数组。
       for (irq = 0, desc = irq_desc; irq < NR_IRQS; irq++, desc++) {
              *desc = bad_irq_desc; /* 把每个中断先初始化为bad_irq*/
              INIT_LIST_HEAD(&desc->pend);
       }

       init_arch_irq(); /* 调用4020自己sep4020_init_irq函数来初始化各个中断句柄为do_level_IRQ*/
}

其中关键函数为:init_arch_irq();
在/arch/arm/setup.c函数中有定义:init_arch_irq = mdesc->init_irq;
而mdesc->init_irq就是在arch/arm/mach-sep4020/irq.c中定义的

void __init sep4020_init_irq(void)
 {
      unsigned int i;
       unsigned long flags;
       local_save_flags(flags);
       *(RP)(INTC_IER_V) = 0XFFFFFFFF;使能所有的4020中断
       *(RP)(INTC_IMR_V) = 0XFFFFFFFF;同时屏蔽所有的中断
       *(RP)(INTC_IPLR_V) = 0X0;
       local_irq_restore(flags);
      
      for(i = 0; i < NR_IRQS; i++)
             {
                 set_irq_handler(i, do_level_IRQ); /*设置4020各个中断的初始处理句柄*/
               set_irq_chip(i, &sep4020_chip);/*设置中断屏蔽和应答的处理句柄*/
               set_irq_flags(i, IRQF_VALID | IRQF_PROBE);/*设置初始化标志*/
              }
}
static struct irqchip sep4020_chip=
{
       .ack = sep4020int_ack,
       .mask = sep4020int_mask,
       .unmask = sep4020int_unmask,
 };

其实系统中是有个全局变量来保持中断结构指针数组,它是定义在arch/arm/kernel/irq.c
struct irqdesc irq_desc[NR_IRQS];  /*正真的中断处理函数指针数组*/
而表示中断类型的结构体是:

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


三、Linux异常处理流程:
1、  硬件发生中断后,怎样找到中断入口函数
我们看下中断向量表它是定义在arch/arm/kernel/entry-armv.S中:
      

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
@stubs_offset表示中断处理函数与中断向量表的偏移
       .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:

从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_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中也是这样定义的 。
 
2、根据原来发生中断时CPU所处的工作模式找到相应的入口函数
  arch/arm/kernel/entry-armv.S—vector_irq是通过宏来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)保存前一个状态的lr,spsr,r0因为马要将返回模式改为SVC,防止中断嵌套
       @
       stmia sp, {r0, lr} @ save r0, lr
       mrs lr, spsr
       str lr, [sp, #8] @ save spsr

       @
       @ Prepare for SVC32 mode. IRQs remain disabled.
       @ 马上就要进入SVC模式下,准备切换模式,但此时的IRQ还是被禁止的
       mrs r0, cpsr
       eor r0, r0, #(\mode ^ SVC_MODE)
       msr spsr_cxsf, r0

       @
       @ the branch table must immediately follow this code
       @ 分支表必须紧靠下面的代码后面,这是由于表的地址是根据pc计算出来的,这个分支表是为了分别处理各种模式下进入异常的情况
       and lr, lr, #0x0f /*lr作为一个分支表的索引值*/
       mov r0, sp /*r0保存中断前状态的堆栈指针*/
       ldr lr, [pc, lr, lsl #2]
       movs pc, lr @ 转去执行lr处的分支指令,同时由于带有“S”所以模式会切换到SVC模式下执行指令
       .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
       .long __irq_invalid @ 5
       .long __irq_invalid @ 6
       .long __irq_invalid @ 7
......


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

.align 5
__irq_usr:
       usr_entry /*在当前SVC模式的堆栈上分配一个pt_regs(72个字节)结构,保存被中断模式的寄存器现场*/

       get_thread_info tsk
#ifdef CONFIG_PREEMPT @在我们4020中没有开进程抢占
       ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
       add r7, r8, #1 @ increment it
       str r7, [tsk, #TI_PREEMPT]
#endif

       irq_handler /*查询sep4020的中断寄存器获得中断号,和获得pt_regs,然后调用asm_do_IRQ*/

       mov why, #0
       b ret_to_user

       .ltorg

它主要通过调用宏usr_entry进一步保存现场,然后调用irq_handler进行中断处理,保存的用户模式pt_regs的栈现场结构如下:
-1
CPSR
PC-4
LR
SP
R12
...
R2
R1
R0
 
其中的LR,SP是USR模式下的,R0,CPSR,PC-4是从中断栈中拷贝的
(2)usr_entry宏调用
     

.macro usr_entry
       sub sp, sp, #S_FRAME_SIZE /*sizeof(pt_regs),分配一个pt_regs结构*/
       stmib sp, {r1 - r12} /*保存r1-r12*/

       ldmia r0, {r1 - r3} /*r0保存的是被中断模式的堆栈指针*/
       add r0, sp, #S_PC @ S_SP为sp寄存器在pt_regs中的偏移
       mov r4, #-1 @ "" "" "" ""

       str r1, [sp] @ r0为中断前的堆栈指针,将成为pt_regs中的sp的值

#if __LINUX_ARM_ARCH__ < 6 && !defined(CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG)
#ifndef CONFIG_MMU
#warning "NPTL on non MMU needs fixing"
#else
       @ make sure our user space atomic helper is aborted
       cmp r2, #TASK_SIZE
       bichs r3, r3, #PSR_Z_BIT
#endif
#endif

       @
       @ 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
       @
       stmia r0, {r2 - r4} 分别将lr,spsr,orig_r0压入堆栈
       stmdb r0, {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的作用
它首先通过宏 get_irqnr_and_base获得中断号,存入r0,然后把上面建立的pt_regs结构的指针,也就是sp值赋给r1,把调用宏 get_irqnr_and_base的位置作为返回地址(为了处理下一个中断),然后调用 asm_do_IRQ进一步处理中断,以上这些操作都在建立在获得中断号的前提下,也就是有中断发生,
 
即当某个外部硬件触发中断的时候,kernel最终会调用到:asm_do_IRQ
  

linux/arch/arm/kernel/entry-armv.S
/*
 * Interrupt handling. 中断处理函数入口 Preserves r7, r8, r9
 */
       .macro irq_handler
1: get_irqnr_and_base r0, r6, r5, lr /*获得4020的中断号*/
       movne r1, sp //将sp的值赋给r1,即pt_regs结构的指针
       @
       @ routine called with r0 = irq number, r1 = struct pt_regs *
       @
       adrne lr, 1b /*返回到1处,asm_do_IRQ返回后将再次查询发生的中断,这样可以处理优先级比较低的中断*/
       bne asm_do_IRQ /*调用c语言的中断处理函数asm_do_IRQ*/

#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
       movne r0, sp
       adrne lr, 1b
       bne do_IPI

#ifdef CONFIG_LOCAL_TIMERS
       test_for_ltirq r0, r6, r5, lr
       movne r0, sp
       adrne lr, 1b
       bne do_local_timer
#endif
#endif

       .endm

 
(3)get_irqnr_and_base获得4020的中断号的宏
下面这个宏查询ISPR(IRQ待定中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中;这个宏在不同的ARM芯片上是不一样的,下面是sep4020的定义

.macro get_irqnr_and_base, irqnr, irqstat, base, tmp /*获得4020的中断号 */
              //gfd
              ldr \base, =INTC_IFSR_V /*宏参数引用时要用"\"符号 */
              ldr \base, [\base]
              mov \irqnr,#0
1001:
              cmp \base, #0 @ no flags set?
              beq 1002f @ keep "eq" status when
              tst \base, #1 @ lsb set?测试最低位是否为1,这样就可以知道是哪个中断
              addeq \irqnr, \irqnr, #1 @ if not, incr IRQ#
              moveq \base, \base, LSR #1 @ r = r >> 1
              beq 1001b
1002:
              .endm
              .macro irq_prio_table
              .endm

 
(4) asm_do_IRQ的c语言中断处理函数:

Arch/arm/kernel/irq.c
asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
       struct irqdesc *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(); /*禁止被其他任务抢占*/
       spin_lock(&irq_controller_lock);
       desc_handle_irq(irq, desc, regs);/*调用我们设备驱动中自己注册的中断处理函数*/

       
/*
        * Now re-run any pending interrupts.
        */

       if (!list_empty(&irq_pending))
              do_pending_irqs(regs);

       irq_finish(irq);
//中断结束处理函数,在这里我们是空的


       spin_unlock(&irq_controller_lock);
       irq_exit();
}

(5)irq_enter()函数

Include/linux/hardirq.h
#define irq_enter() \
       do { \
              account_system_vtime(current); \
              add_preempt_count(HARDIRQ_OFFSET); \
       } while (0)

(6) desc_handle_irq函数
这个函数正真开始调用我们在各个驱动中注册的中断处理函数

Include/asm/mach/irq.h
/*
 * Helpful inline function for calling irq descriptor handlers.
 */

static inline void desc_handle_irq(unsigned int irq, struct irqdesc *desc, struct pt_regs *regs)
{
       desc->handle(irq, desc, regs);/*调用我们设备驱动中自己注册的中断处理函数*/
}

(7)irq_exit函数

linux/kernel/softirq.c
/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */

void irq_exit(void)
{
       account_system_vtime(current);
       sub_preempt_count(IRQ_EXIT_OFFSET);
       if (!in_interrupt() && local_softirq_pending())
              invoke_softirq();
       preempt_enable_no_resched();
}

3、  内核进程被中断的处理情况(待续)
阅读(668) | 评论(0) | 转发(0) |
0

上一篇:Bootstrap代码分析

下一篇:reading

给主人留下些什么吧!~~