免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2968 | 回复: 0
打印 上一主题 下一主题

arm linux中断分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-12-25 09:46 |只看该作者 |倒序浏览
一、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
              *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
             {
               
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_ (parent
PC) and spsr_
       @ (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__
#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_, already fixed up for correct return/restart
       @
r3 - spsr_
       @
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 finishing!            
              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、
内核进程被中断的处理情况(待续)
               
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/99507/showart_2130311.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP