arm-Linux中断处理体系结构与处理流程分析
本文分析了基于S3C2410平台的arm920中断的流程,参考了网上一些分析arm中断流程的文章。
http://blog.chinaunix.net/space.php?uid=14163325&do=blog&cuid=1728394
http://www.cnblogs.com/hoys/archive/2011/04/13/2015318.html
异常,就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义指令、试图修改只读的数据、执行swi指令(Software Interrupt Instruction ,软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。操作系统中经常通过异常来完成一些特定的功能。其中的中断也占有很大的一部分。例如下面的这几种情况:
* 当CPU执行未定义的机器指令时将触发“未定义指令异常”,操作系统可以利用这个特点使用一些自定义的机器指令,它们在异常处理函数中实现。
* 当用户程序试图读写的数据或执行的指令不在内存中时,也会触发一个“数据访问中止异常”或“指令预取中止异常”,在异常处理函数中将这些数据或指令读入内存,然后重新执行被中断的程序,这样可以节省内存,还使得操作系统可以运行这类程序,它们使用的内存远大于实际的物理内存。
一、中断异常向量表的建立(向量表搬移)
在Linux2.6.36的内核版本中,内核在start_kernel函数(源码在init/main.c中)中调用trap_init、init_IRQ两个函数来设置异常和处理函数。trap_init函数在trap.c中,
void __init trap_init(void)
{
return;
}
trap_init是一个空函数,在这个文件中还有一个函数,early_trap_init(void)这个函数才是真正要用到的,在 init/mian.c的start_kernel中可以找到,第591行调用了trap_init(),而early_trap_init()函数在第 569行的setup_arch(&command_line)函数中调用。在setup_arch函数定义在Linux/arch/arm /kernel/setup.c我们先来看看early_trap_init(void)这个函数,在arch/arm/kernel/traps.c
void __init early_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);
/*
* Do processor specific fixups for the kuser helpers
*/
kuser_get_tls_init(vectors);
/*
* 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));
memcpy((void *)KERN_RESTART_CODE, syscall_restart_code,
sizeof(syscall_restart_code));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
earl_tarp_init 函数完成中断向量表搬移,设置各种异常的处理向量,包括中断向量。所谓“向量”,就是一些被安放在固定位置的代码,当发生异常时,CPU会自动执行这些固定位置上的指令。ARM架构的CPU的异常向量基址可以是0x00000000,也可以是 0xffff0000,它由CONFIG_VECTORS_BASE决定,CONFIG_VECTORS_BASE在arch/arm/Kconfig中配置如下:
182 config VECTORS_BASE
183 hex
184 default 0xffff0000 if MMU || CPU_HIGH_VECTOR
185 default DRAM_BASE if REMAP_VECTORS_TO_RAM
186 default 0x00000000
187 help
我们内核开启了MMU功能,所以内核使用后者。earl_trap_init函数将异常向量复制到0xffff0000处,我们可以在该函数中看到下面的两行代码。
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);
vectors 在earl_tarp_init函数第一行定义等于CONFIG_VECTORS_BASE,这里也就是0xffff0000。 __vectors_start、__stubs_start和kuser_sz为异常向量表、子向量等所处的位置,分别拷贝到0xffff0000、 0xffff0200以及0xffff1000处。地址 __vectors_start ~ __vectors_end之间的代码就是异常向量,在arch/arm/kernel/entry-armv.S中定义,第一行代码将它们复制到地址 0xffff0000处。异常向量的代码很简单,它们只是一些跳转指令。发生异常时,CPU自动执行这些指令,跳转去执行更复杂的代码,比如保存被中断程序的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行。这些“更复杂的代码”在地址__stubs_start ~__stubs_end之间,它们也在arch/arm/kernel/entry-armv.S中定义。第二行代码将它们复制到地址 0xffff0000+0x200处。
现在详细说明中断向量表搬移过程:
arm体系结构下Linux中怎样来初始化中断向量表的,因为这个方法很具有通用性,我把它叫做代码大挪移。您说搬代码谁不会阿,不就是拷贝吗,的确如此,但是拷贝也有技巧。拷贝很简单啦,其实就是memcpy,这不用提,我在这里想说的是,你怎么把你的代码设计成能随便拷贝的,换句专业的术语,叫与位置无关的代码,拷到哪都能用。
early_trap_init函数中实际copy动作一目了然,就是两个memcpy(第三个实际上是拷贝一些别的东西,原理是一样的,这里不提了). Copy的源是vectors,这个值当然你可以根据硬件的设定自己配制这个值。把什么东西往那copy呢?第一部分是从__vectors_start到__vectors_end之间的代码,第二部分是从__stubs_start到__stubs_end之间的代码,而第二部分是copy到vectors + 0x200起始的位置。也就是说,两部分之间的距离是0x200,即512个字节。请参看下图:
我们将搬移前的代码组织称为Code/Load 视图,因为这是代码中的(或image中的)组织情况,把搬移后的代码组织称为Exec视图,反映的是代码执行时代码在内存中的情况。我刚才讲过了第一场景的情况,忘了的回到第一场景中去看,两个memcpy的执行过程在图中也有表示,就是蓝色和红色的带箭头的虚线,这就是代码从code view到exec view的拷贝过程,一目了然,不用多说。
现在出现了一个问题,就是我们发现在__vector_start和__vector_end之间的代码有点怪异,我们摘到这里来看:
1223 .equ stubs_offset, __vectors_start + 0x200 - __stubs_start
1224
1225 .globl __vectors_start
1226 __vectors_start:
1227 ARM( swi SYS_ERROR0 ) //复位时,CPU将执行这条指令
1228 THUMB( svc #0 )
1229 THUMB( nop )
1230 W(b) vector_und + stubs_offset //未定义异常时,CPU将执行这条指令
1231 W(ldr) pc, .LCvswi + stubs_offset //swi异常
1232 W(b) vector_pabt + stubs_offset //指令预取中止
1233 W(b) vector_dabt + stubs_offset //数据访问中止
1234 W(b) vector_addrexcptn + stubs_offset //没有用到
1235 W(b) vector_irq + stubs_offset //irq异常
1236 W(b) vector_fiq + stubs_offset //frq异常
1237
1238 .globl __vectors_end
1239 __vectors_end:
这叫做位置无关的代码,因为要拷贝到别的地方。而且里面都是跳转指令。我们发现了除了第三个行代码(1231行)用了绝对地址进行了跳转,其它都是用的b跳转。举个例子,b vector_dabt + stubs_offset,(vector_dabt在__stubs_start和__stubs_end之间),如果你用b vector_dabt, 这肯定是有问题的,因为copy之后exec view的组织(map)是不一样的,所以b后这个偏移就不对了。这里面,我们就要对这个偏移进行一次调整。Stubs_offset就是这个调整值,是可以计算出来的,具体的计算过程在图中讲得比较清楚,这里不提了。大家可以在图中看到详细的推导过程。
其实尽管ldr pc, .LCvswi + stubs_offset这条指令用的是绝对地址跳转,用得跳转表的方法,但找地址的过程也用到了这个技术。我们看到
1215 .align 5
1216
1217 .LCvswi:
1218 .word vector_swi
.LCvswi这个位置存储的是一个地址,就是要跳到这个地方。.align 5的意思是32字节对齐,这个是保证cache line对齐的,不提了。在exec view中找这个地址,就得加上个offset.原理是一样的,因为.LCvswi在__stubs_start和__stubs_end之间,这个区域被搬移了,不能直接用这个标签地址了,vector_swi没有被搬移,所以可以直接用。
总结一下。我觉得我要讲的东西虽然是Linux中的技术细节,描述的确是代码搬移过程原理和注意事项。其实更重要的是,我们如何把这一个过程倒过来,即在涉及到代码搬移的场合中如何进行设计,如何运用这些技术实现这一设计过程。你可以遵循这样的指导步骤:
1. 画出那个大图来,按自己的要求确定Code view和Exec view, 设计搬移区段和设计搬移的位置
2. 写出要搬移的代码,运用位置无关的技术(上面提到的)进行编码和检验
3. 用类似memcpy的代码进行搬移
我们再来看__vectors_start,__vectors_end,font face="Times New Roman">__stubs_start,__stubs_end到底是什么东西,只要知道它们在哪里定义的,就知道怎么回事了。
异常向量、异常向量跳去执行的代码都是使用汇编写的,它们在arch/arm/kernel/entry-armv.S中。异常向量的代码如下,其中的 “stubs_offset”用来重新定位跳转的位置(向量被复制到地址0xffff0000处,跳转的目的代码被复制到地址 0xffff0000+0x200处)。
二、异常处理程序
当 ARM 处理器发生异常(中断是一种异常)时,会跳转到异常向量表,在向量表中找到相应的异常,并跳转到该异常处理程序处执行,这些异常处理程序即是放在以下异常处理程序段中。
1092 .globl __stubs_start
1093 __stubs_start:
1094 /*
1095 * Interrupt dispatcher
1096 */
1097 vector_stub irq, IRQ_MODE, 4
//vector_stub是一个宏,后面会有详细分析,它代表有一段程序放在此处。irq, IRQ_MODE, 4是传递给宏vector_stub的参数。
1098
//以下是跳转表,在宏vector_stub代表的程序段中要用到该表来查找程序要跳转的位置。
//如果在进入中断时是用户模式,则调用__irq_usr例程,如果为系统模式,则调用__irq_svc,如果是其他模式,则说明出错了,则调用__irq_invalid。
1099 .long __irq_usr @ 0 (USR_26 / USR_32)
1100 .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
1101 .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
1102 .long __irq_svc @ 3 (SVC_26 / SVC_32)
1103 .long __irq_invalid @ 4
1104 .long __irq_invalid @ 5
1105 .long __irq_invalid @ 6
1106 .long __irq_invalid @ 7
1107 .long __irq_invalid @ 8
1108 .long __irq_invalid @ 9
1109 .long __irq_invalid @ a
1110 .long __irq_invalid @ b
1111 .long __irq_invalid @ c
1112 .long __irq_invalid @ d
1113 .long __irq_invalid @ e
1114 .long __irq_invalid @ f
................................................................................................................................
1120 vector_stub dabt, ABT_MODE, 8
1121
1122 .long __dabt_usr @ 0 (USR_26 / USR_32)
1123 .long __dabt_invalid @ 1 (FIQ_26 / FIQ_32)
1124 .long __dabt_invalid @ 2 (IRQ_26 / IRQ_32)
1125 .long __dabt_svc @ 3 (SVC_26 / SVC_32)
................................................................................................................................
1143 vector_stub pabt, ABT_MODE, 4
1144
1145 .long __pabt_usr @ 0 (USR_26 / USR_32)
................................................................................................................................
1166 vector_stub und, UND_MODE
1167
1168 .long __und_usr @ 0 (USR_26 / USR_32)
................................................................................................................................
1197 vector_fiq:
1198 disable_fiq
1199 subs pc, lr, #4
................................................................................................................................
1208 vector_addrexcptn:
1209 b vector_addrexcptn
................................................................................................................................
1215 .align 5
1216
1217 .LCvswi:
1218 .word vector_swi
1219
1220 .globl __stubs_end
1221 __stubs_end:
其中,vector_irq、vector_und、vector_pabt等表示要跳转去执行的代码。
stubs_offset,定义为__vectors_start + 0x200 - __stubs_start。在中断初始化函数early_trap_init()中向量表被拷到0xFFFF_0000处,异常处理程序段被拷到 0xFFFF_0200处。比如此时发生中断异常b vector_irq + stubs_offset 将跳转到中断异常处理程序段去执行,由于vector_irq,在异常处理程序段__stubs_start到__stubs_end之间此时跳转的位置将是__vectors_start + 0x200 + vector_irq - __stubs_start处。
宏vector_stub代表的程序段如下:name, mode, correction存储传入的参数
1053 .macro vector_stub, name, mode, correction=0
1054 .align 5
1055
1056 vector_\name:
1057 .if \correction
1058 sub lr, lr, #\correction //修正返回地址,也就是中断处理完之后要执行的指令的地址
1059 .endif
1060
1061 @
1062 @ Save r0, lr_
(parent PC) and spsr_
1063 @ (parent CPSR)
1064 @
//保存返回地址到堆栈,因为很快要使用r0寄存器,所以也要保存r0。sp后没有!所以sp指向的位置并没有变化。
1065 stmia sp, {r0, lr} @ save r0, lr
1066 mrs lr, spsr
1067 str lr, [sp, #8] @ save spsr
1068
// 向上增长的栈。
// 此时的这个栈是中断模式下的栈,ARM下中断模式下和系统模式下的栈是不同的。虽然ARM提供了七个模式,但Linux只使用了两个,一个是用户模式,另一个为系统模式,所以这个栈只是一个临时性的栈。
/*
在arch/arm/include/asm/ptrace.h中有处理器的七种工作模式的定义
#define USR_MODE 0x00000010
#define FIQ_MODE 0x00000011
#define IRQ_MODE 0x00000012
#define SVC_MODE 0x00000013
#define ABT_MODE 0x00000017
#define UND_MODE 0x0000001b
#define SYSTEM_MODE 0x0000001f
*/
1069 @
1070 @ Prepare for SVC32 mode. IRQs remain disabled.
1071 @
1072 mrs r0, cpsr
1073 eor r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
1074 msr spsr_cxsf, r0 //把spsr设置为管理模式。//对spsr的所有控制为进行写操作,将r0的值全部注入spsr
1075
1076 @
1077 @ the branch table must immediately follow this code
1078 @
1079 and lr, lr, #0x0f //lr中当前存储了上一个状态寄存器的值,对后几位做与,就是取在中断前处在用户态还是核心态,这个值用作跳转表的索引,这条指令之后lr中位spsr的低4位,上面跳转表有16项就是对应这16个状态
lr&0x0f的值是表明发生异常之前cpu所在的空间,
0:在用户空间发生了异常(如irq中断)
3:在内核空间发生了异常(如irq中断)
1080 THUMB( adr r0, 1f )
1081 THUMB( ldr lr, [r0, lr, lsl #2] )
1082 mov r0, sp //用r0保存堆栈指针的地址,sp值当第一个参数传给后面函数
//在对这段程序分析时要记住这段程序是以宏vector_stub的形式放在跳转表前面的。将跳转表中对应的地址条目存入lr。因为跳转表中每一个条目都是4个字节long,所以此处左移两位,lr
1083 ARM( ldr lr, [pc, lr, lsl #2] ) // pc是当前执行指令地址加4,即跳转表的基地址,lr是索引,根据前面相与操作,其值为0(用户空间)或3(内核空间)再乘4就是将要跳转的地址。如果是用户用户空间发生了异常(如irq中断),lr<<2=lr*4也就是pc+0,对照跳转表,将加载__irq_usr到pc,如果是内核空间发生了异常,lr<<2=lr*4也就是pc+12,加载__irq_svc到pc
lr<<2=lr*4也就是pc+0和pc+12
对irq中断,分别对应LCtab_irq的.word __irq_usr域和.word __irq_svc域
1084 movs pc, lr @ branch to handler in SVC mode
1085 ENDPROC(vector_\name)
1086
1087 .align 2
1088 @ handler addresses follow this label
1089 1:
1090 .endm
以vector_irq、vector_und为例,它仍在arch/arm/kernel/entry-armv.S中,通过vector_stub宏来定义,首先这部分代码大致都是一样的结构,前面是一些代码,后面跟着一个跳转表。跳转表里面定义了一些地址。它在第1053行到1090行定义,它根据后面的参数"und,UND_MODE"定义了以“vector_\name”为标号的一段代码。
这里我们以vector_irq为例,将这个宏展开来看看:即展开第1097行的宏定义:
1094 /*
1095 * Interrupt dispatcher
1096 */
/*展开 1097 vector_stub irq, IRQ_MODE, 4行如下 */
展开开始:
vector_irq:
sub lr, lr, #4
@
@ 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, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE)
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
展开结束,后面是跳转表
1098
1099 .long __irq_usr @ 0 (USR_26 / USR_32)
1100 .long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
1101 .long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
1102 .long __irq_svc @ 3 (SVC_26 / SVC_32)
1103 .long __irq_invalid @ 4
1104 .long __irq_invalid @ 5
1105 .long __irq_invalid @ 6
1106 .long __irq_invalid @ 7
1107 .long __irq_invalid @ 8
1108 .long __irq_invalid @ 9
1109 .long __irq_invalid @ a
1110 .long __irq_invalid @ b
1111 .long __irq_invalid @ c
1112 .long __irq_invalid @ d
1113 .long __irq_invalid @ e
1114 .long __irq_invalid @ f
真正的跳转在vector_stub的最后一句完成,大家都看得很清楚。跳到哪里去了,如果中断以前是svc模式,就会跳到__irq_svc。我们发现这里不会直接用b(bl,bx等)个,
一是b跳转后面是个偏移,而这个偏移是有限制的,不能太大
二是b跳转后面的偏移你不知道在代码拷贝后还是不是那个样子,因为我们要搬移代码,所以如果你不能确定搬移后的偏移不变,那你就用绝对地址,而上面的代码前三句就是算出绝对地址来,然后用绝对地址赋值给pc直接完成跳转。
这些都是一些技巧,总之要注意的是写位置无关的代码时涉及到跳转部分,用b跳转还是直接赋成绝对地址(通过跳转表实现),如果你不能保证搬移后的偏移一致,写这部分就要注意了,要用一些技巧的。
大家可以去用gcc 的-fPIC和-S选项汇编一个小的函数看看,fPIC就是与位置无关选项,相信编译过动态库的人都熟悉,看看它是怎么做的。你会发现异曲同工。
vector_stub宏的功能为:计算处理完异常后的返回地址、保存影子寄存器(比如r0、lr、spsr),然后进入管理模式,最后根据被中断的工作模式调用下面的某个跳转分支。当发生异常时,CPU会根据异常的类型进入某个工作模式,但是很快vector_stub宏又会强制CPU进行管理模式,在管理模式下进行后续处理,这种方法简化了程序的设计,使得异常发生前的工作模式要么是用户模式,要么是管理模式。
vector_und 代码表示在各个工作模式下执行未定义指令时,发生异常的处理分支。比如__und_usr表示在用户模式下执行未定义指令时,所发生的未定义异常将由它来处理;__und_svc表示在管理模式下执行未定义指令时,所发生的未定义异常将由它来处理。在其他工作模式下不可能发生未定义指令异常,否则使用 “__und_invalid”来处理错误。ARM架构CPU中使用4位数据来表示工作模式,所以共有16个跳转分支,见1168--1183行,目前只有7个工作模式。
现在我们以在用户空间发生中断异常为例,即程序跳转到__irq_usr处
447 .align 5
448 __irq_usr:
449 usr_entry //usr_entry是一个宏,负责寄存器的入栈操作,保存各寄存器,便于返回的时候恢复,后面讲解
450 kuser_cmpxchg_check
451
452 get_thread_info tsk //get_thread_info也是个宏,用来获取保存当前task线程信息的地址。也将在后续分析。tsk存放的是线程结构体的地址。
/* tsk在文件arch/arm/kernel/entry-header.S中定义为
180 why .req r8 @ Linux syscall (!= 0)
181 tsk .req r9 @ current thread_info
*/
453 #ifdef CONFIG_PREEMPT
454 ldr r8, [tsk, #TI_PREEMPT] @ get preempt count//获取preempt_count
//TI_PREEMPT在文件arch\arm\kernel\asm-offsets.c中定义是线程结构体thread_info 的成员preempt_count在结构体thread_info中的偏移
/*
内核态可剥夺内核,只有在 preempt_count 为 0 时, schedule() 才会被调用,其检查
是否需要进行进程切换,需要的话就切换。
*/
455 add r7, r8, #1 @ increment it//将该成员加一
456 str r7, [tsk, #TI_PREEMPT] //将改变后的值存入preempt_count
457 #endif
458
459 irq_handler //调用中断操作函数,irq_handler是一个宏,在后续描述
460 #ifdef CONFIG_PREEMPT
461 ldr r0, [tsk, #TI_PREEMPT]
462 str r8, [tsk, #TI_PREEMPT]
463 teq r0, r7
464 ARM( strne r0, [r0, -r0] )
465 THUMB( movne r0, #0 )
466 THUMB( strne r0, [r0] )
467 #endif
468
469 mov why, #0 //
/* why在文件arch/arm/kernel/entry-header.S中第180行定义为
180 why .req r8 @ Linux syscall (!= 0)
181 tsk .req r9 @ current thread_info
*/
470 b ret_to_user //返回到用户态,该宏在文件 linux/arch/arm/kernel/entry-common.S中定义。
471 UNWIND(.fnend )
472 ENDPROC(__irq_usr)
现在来看一下__irq_usr用到的几个宏
1)
arch/arm/kernel/entry-armv.S
352 /*
353 * User mode handlers
354 *
355 * EABI note: sp_svc is always 64-bit aligned here, so should S_FRAME_SIZE
356 */
357
358 #if defined(CONFIG_AEABI) && (__LINUX_ARM_ARCH__ >= 5) && (S_FRAME_SIZE & 7 )
359 #error "sizeof(struct pt_regs) must be a multiple of 8"
360 #endif
361
362 .macro usr_entry
363 UNWIND(.fnstart )
364 UNWIND(.cantunwind ) @ don't unwind the user space
365 sub sp, sp, #S_FRAME_SIZE //为寄存器pt_regs结构体建立堆栈空间,让堆栈指针sp 指向r0 。S_FRAME_SIZE在文件arch/arm/kernel/asm-offsets.c中定义DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));表示寄存器结构体pt_regs的大小结构体,pt_regs中有 r0~cpsr 18个寄存器即72个字节。
366 ARM( stmib sp, {r1 - r12} ) //stmib为存储前加,所以此处留出了用于存储r0的空间,将r1 - r12存入堆栈。sp后没加!所以sp指向的堆栈位置没有变,一直指向用于存储r0的存储空间。
367 THUMB( stmia sp, {r0 - r12} )
368
369 ldmia r0, {r1 - r3} //将中断前r0,lr,spsr的值取出存放在r1 - r3中,此时的r0是作为堆栈的sp在使用的。它的值是指向中断前r0的值在堆栈中存放的位置。在寄存器结构体pt_regs在堆栈中的位置上面。
370 add r0, sp, #S_PC @ here for interlock avoidance//S_PC即是pt_regs中的PC寄存器位置,让r0指向该位置。虽然S_PC还没有存入堆栈但它在堆栈中的位置存在
371 mov r4, #-1 @ //在r4中放入一个无效值。
372
373 str r1, [sp] @ save the "real" r0 copied//r1中存放的是中断前r0的值,此时将该值存入堆栈,上面已解释过在堆栈中留出r0的位置的问题。
374 @ from the exception stack
375
376 @
377 @ We are now ready to fill in the remaining blanks on the stack:
378 @
379 @ r2 - lr_, already fixed up for correct return/restart
380 @ r3 - spsr_
381 @ r4 - orig_r0 (see pt_regs definition in ptrace.h)
382 @
383 @ Also, separately save sp_usr and lr_usr
384 @
385 stmia r0, {r2 - r4} //此时r2-r4存放的是中断前的lr, spsr的值和无效之。此时将这些值存入pt_regs中寄存器在堆栈中对应的位置,即此时将中断前的lr, spsr的值和无效之存入寄存器结构体pt_regs的ARM_pc ,ARM_cpsr,ARM_ORIG_r0中。存储返回地址,现场状态等(被中断代码处的)
386 ARM( stmdb r0, {sp, lr}^ )//stmdb是递减取值,存储用户模式下的sp,lr,将ARM_lr,ARM_sp存入lr,sp中。ARM体系结构中用户模式和系统模式共用同一个SP和LR
387 THUMB( store_user_sp_lr r0, r1, S_SP - S_PC )
388
389 @
390 @ Enable the alignment trap while in kernel mode
391 @
392 alignment_trap r0
393
394 @
395 @ Clear FP to mark the first stack frame
396 @
397 zero_fp //宏 zero_fp在文件arch/arm/kernel/entry-header.S中定义,清零fp。
398 .endm
上面的提到的struct pt_regs,在arch/arm/include/asm/ptrace.h中定义
#ifndef __KERNEL__
struct pt_regs {
long uregs[18];
};
#else /* __KERNEL__ */
struct pt_regs {
unsigned long uregs[18];
};
#endif /* __KERNEL__ */
#define ARM_cpsr uregs[16]
#define ARM_pc uregs[15]
#define ARM_lr uregs[14]
#define ARM_sp uregs[13]
#define ARM_ip uregs[12]
#define ARM_fp uregs[11]
#define ARM_r10 uregs[10]
#define ARM_r9 uregs[9]
#define ARM_r8 uregs[8]
#define ARM_r7 uregs[7]
#define ARM_r6 uregs[6]
#define ARM_r5 uregs[5]
#define ARM_r4 uregs[4]
#define ARM_r3 uregs[3]
#define ARM_r2 uregs[2]
#define ARM_r1 uregs[1]
#define ARM_r0 uregs[0]
#define ARM_ORIG_r0 uregs[17] 除了SWI调用,其值一直是-1。
2)宏macro get_thread_info在文件arch/arm/kernel/entry-header.S中定义。用来获取当前线程的地址。
111 .macro get_thread_info, rd
112 mov \rd, sp, lsr #13
113 mov \rd, \rd, lsl #13
114 .endm
include/linux/sched.h中:
union thread_union {
struct thread_info thread_info;
unsigned long stack[THREAD_SIZE/sizeof(long)];
};
由它定义的线程是8K字节对齐的, 并且在这8K的最低地址处存放的就是thread_info对象,即该栈拥有者线程的对象,而get_thread_info就是通过把sp低13位清0(8K边 界)来获取当前thread_info对象的地址。
THREAD_SIZE在文件arch/arm/include/asm/thread_info.h中定义:
#define THREAD_SIZE 8192
struct thread_info {
unsigned long flags; /* low level flags */
int preempt_count; /* 0 => preemptable, <0 => bug */
mm_segment_t addr_limit; /* address limit */
struct task_struct *task; /* main task structure */
struct exec_domain *exec_domain; /* execution domain */
__u32 cpu; /* cpu */
__u32 cpu_domain; /* cpu domain */
struct cpu_context_save cpu_context; /* cpu context */
__u32 syscall; /* syscall number */
__u8 used_cp[16]; /* thread used copro */
unsigned long tp_value;
struct crunch_state crunchstate;
union fp_state fpstate __attribute__((aligned(8)));
union vfp_state vfpstate;
#ifdef CONFIG_ARM_THUMBEE
unsigned long thumbee_state; /* ThumbEE Handler Base register */
#endif
struct restart_block restart_block;
};
3)irq_handler
注意__irq_usr第459的irq_handler定义__irq_usr同文件前面的第32行,从irq_handler定义可以看出外部中断异常发生时,最终会调用C函数asm_do_IRQ来进行处理
29 /*
30 * Interrupt handling. Preserves r7, r8, r9
31 */
32 .macro irq_handler
33 get_irqnr_preamble r5, lr //宏get_irqnr_preamble是一个空操作,在文件 arch/arm/mach-s3c2410/include/mach/entry-macro.S中定义
34 1: get_irqnr_and_base r0, r6, r5, lr/宏get_irqnr_and_base通过读取寄存器INTPND来获得中断号。在该宏中获取的一些参量将存于这些寄存器中r0, r6, r5, lr。
35 movne r1, sp
36 @
37 @ routine called with r0 = irq number, r1 = struct pt_regs *
38 @
39 adrne lr, BSYM(1b)
40 bne asm_do_IRQ
41
42 #ifdef CONFIG_SMP
43 /*
44 * XXX
45 *
46 * this macro assumes that irqstat (r6) and base (r5) are
47 * preserved from get_irqnr_and_base above
48 */
49 test_for_ipi r0, r6, r5, lr
50 movne r0, sp
51 adrne lr, BSYM(1b)
52 bne do_IPI
53
54 #ifdef CONFIG_LOCAL_TIMERS
55 test_for_ltirq r0, r6, r5, lr
56 movne r0, sp
57 adrne lr, BSYM(1b)
58 bne do_local_timer
59 #endif
60 #endif
61
62 .endm
get_irqnr_and_base是平台相关的,这个宏查询ISPR(IRQ挂起中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中;这个宏在不同的ARM芯片上是不一样的,这个宏主要作用在于就是获得发生中断的中断号,对于s3c2410,代码在arch/arm/mach-s3c2410/include /entry-macro.S里,该宏处理完后,r0 = 中断号。中断号保存到
r0,sp保存到r1,r0与r1随后作为参数传送给asm_do_IRQ()例程,函数asm_do_IRQ()是中断处理函数的C语言入口,在文件linux/arch/arm/kernel/irq.c中实现,将在后续讨论。其中sp已经被压入了中断上现场的各个寄存器值,也就是pt_reg结构
./arch/arm/mach-s3c2410/include/mach/entry-macro.S
31 .macro get_irqnr_and_base, irqnr, irqstat, base, tmp
32
33 mov \base, #S3C24XX_VA_IRQ
34
35 @@ try the interrupt offset register, since it is there
36
37 ldr \irqstat, [ \base, #INTPND ]
38 teq \irqstat, #0
39 beq 1002f
40 ldr \irqnr, [ \base, #INTOFFSET ]
41 mov \tmp, #1
42 tst \irqstat, \tmp, lsl \irqnr
43 bne 1001f
44
45 @@ the number specified is not a valid irq, so try
46 @@ and work it out for ourselves
47
48 mov \irqnr, #0 @@ start here
49
50 @@ work out which irq (if any) we got
51
52 movs \tmp, \irqstat, lsl#16
53 addeq \irqnr, \irqnr, #16
54 moveq \irqstat, \irqstat, lsr#16
55 tst \irqstat, #0xff
56 addeq \irqnr, \irqnr, #8
57 moveq \irqstat, \irqstat, lsr#8
58 tst \irqstat, #0xf
59 addeq \irqnr, \irqnr, #4
60 moveq \irqstat, \irqstat, lsr#4
61 tst \irqstat, #0x3
62 addeq \irqnr, \irqnr, #2
63 moveq \irqstat, \irqstat, lsr#2
64 tst \irqstat, #0x1
65 addeq \irqnr, \irqnr, #1
66
67 @@ we have the value
68 1001:
69 adds \irqnr, \irqnr, #IRQ_EINT0
70 1002:
71 @@ exit here, Z flag unset if IRQ
72
73 .endm
5)宏ret_to_user
__irq_usr处理完中断后,调用ret_to_user来返回到用户模式,这个例程在arch/arm/kernel/entry-common.S下定义:
61 ENTRY(ret_to_user)
62 ret_slow_syscall:
63 disable_irq @ disable interrupts//禁止中断
64 ldr r1, [tsk, #TI_FLAGS] //获取线程结构体thread_union的flags成员
65 tst r1, #_TIF_WORK_MASK //判断task是否被阻塞
66 bne work_pending //根据需要进行进程的切换,该段代码在下面讲述。
67 no_work_pending: //不需要进程切换
68 /* perform architecture specific actions before user return */
69 arch_ret_to_user r1, lr
70
71 restore_user_regs fast = 0, offset = 0
72 ENDPROC(ret_to_user)
宏restore_user_regs在arch/arm/kernel/entry-header.S中定义:
91 .macro restore_user_regs, fast = 0, offset = 0
92 ldr r1, [sp, #\offset + S_PSR] @ get calling cpsr获取被中断代码处的状态(cpsr)
93 ldr lr, [sp, #\offset + S_PC]! @ get pc
//spsr里保存好被中断代码处的状态(cpsp)
94 msr spsr_cxsf, r1 @ save in spsr_svc
95 #if defined(CONFIG_CPU_32v6K)
96 clrex @ clear the exclusive monitor
97 #elif defined (CONFIG_CPU_V6)
98 strex r1, r2, [sp] @ clear the exclusive monitor
99 #endif
100 .if \fast
101 ldmdb sp, {r1 - lr}^ @ get calling r1 - lr
102 .else
103 ldmdb sp, {r0 - lr}^ @ get calling r0 - lr
104 .endif
105 mov r0, r0 @ ARMv5T and earlier require a nop
106 @ after ldm {}^
//栈地址恢复,避免多个中断后溢出
107 add sp, sp, #S_FRAME_SIZE - S_PC
// 返回被中断代码处继续执行,并把spsr赋给cpsr,即恢复被中断处
// 的现场状态。这样CPU又可以从被中断的地方继续执行了,而且这个
// 时候所有的寄存器值(r0到r12),包括状态寄存器值(cpsr)都是源
// 码被中断时的值。
108 movs pc, lr @ return & move spsr_svc into cpsr
恢复寄存器,完成中断处理,回到用户模式。
109 .endm
顺便看下work_pending,在arch/arm/kernel/entry-common.S中:
44 work_pending:
45 tst r1, #_TIF_NEED_RESCHED //判断是否需要调度进程
46 bne work_resched // 进程调度
47 tst r1, #_TIF_SIGPENDING|_TIF_NOTIFY_RESUME
48 beq no_work_pending //无需调度,返回
49 mov r0, sp @ 'regs'
50 mov r2, why @ 'syscall'
51 tst r1, #_TIF_SIGPENDING @ delivering a signal?
52 movne why, #0 @ prevent further restarts
53 bl do_notify_resume
54 b ret_slow_syscall @ Check work again
55
56 work_resched:
57 bl schedule //调用进程切换函数。
由该汇编代码可知,如果在用户模式下产生中断的话,在返回的时候,会根据需要进行进程调度,而从代码可知,如果中断发生在管理等内核模式下的话是不会进行进程调度的。Ok, 中断的流程大体就是这样的,c函数里的中断流程在系统模式下的中断处理里已经说的很多了,这里不再赘述。
这里只讲了在用户模式下的中断处理,在内核模式下的处理方式也大抵相仿,在后面中断处理中再来分析。
现在再来看一下__und_svc处理代码
260 .align 5
261 __und_svc:
262 #ifdef CONFIG_KPROBES
263 @ If a kprobe is about to simulate a "stmdb sp..." instruction,
264 @ it obviously needs free stack space which then will belong to
265 @ the saved context.
266 svc_entry 64
267 #else
268 svc_entry 负责寄存器的入栈操作
269 #endif
270
271 @
272 @ call emulation code, which returns using r9 if it has emulated
273 @ the instruction, or the more conventional lr if we are to treat
274 @ this as a real undefined instruction
275 @
276 @ r0 - instruction
277 @
278 #ifndef CONFIG_THUMB2_KERNEL
279 ldr r0, [r2, #-4]
280 #else
281 ldrh r0, [r2, #-2] @ Thumb instruction at LR - 2
282 and r9, r0, #0xf800
283 cmp r9, #0xe800 @ 32-bit instruction if xx >= 0
284 ldrhhs r9, [r2] @ bottom 16 bits
285 orrhs r0, r9, r0, lsl #16
286 #endif
287 adr r9, BSYM(1f)
288 bl call_fpe
289
290 mov r0, sp @ struct pt_regs *regs
291 bl do_undefinstr
292
293 @
294 @ IRQs off again before pulling preserved data off the stack
295 @
296 1: disable_irq_notrace
297
298 @
299 @ restore SPSR and restart the instruction
300 @
301 ldr r2, [sp, #S_PSR] @ Get SVC cpsr
302 svc_exit r2 @ return from exception
303 UNWIND(.fnend )
304 ENDPROC(__und_svc)
不同的跳转分支只是在它们的入口下(比如保存被中断程序的寄存器)稍有差别,后续的处理大体相同,都在调用相应的C函数,比如未定义指令异常发生时,最终会调用C函数do_undefinstr来进行处理。各种异常C处理函数可以分为5类,它们分布在不同的文件中
(1)在arch/arm/kernel/traps.c中
未定义指令异常的C处理函数在这个文件中定义,总入口函数为do_undefinstr
(2)在arch/arm/mm/fault.c中
与内存访问相关的异常C处理函数在这个文件中定义,比如数据访问中止异常、指令预取中止异常。总入口函数为do_DataAbort、do_prefetchAbort。
(3)在arch/arm/mm/irq.c中
中断处理函数的在这个文件中定义,总入口函数为asm_do_IRQ,它调用其他文件注册的中断处理函数
(4)在arch/arm/kernel/calls.S
在这个文件中,swi异常的处理函数指针被组织成一个表格;swi指令机器码的位[23:0]被用来作为索引。这样,通过不同的swi index指令就可以调用不同的swi异常处理函数,它们被称为系统调用,比如sys_open、sys_read等。
(5)没有使用的异常
没有使用FIQ异常
trap_init函数搭建了各类异常的处理框架。当发生异常时,各种C处理函数会被调用。这些C函数还要进
一步细分异常发生的情况,分别调用更具体的处理函数。
三、对irq的处理--asm_do_IRQ()例程。
irq中断也是一种异常,之所以把它单独的列出来,是因为中断的处理与具体的开发板密切相关,除一些必须、共用的中断(比如系统时钟中断、片内外设UART中断)外,必须由驱动开发者提供处理函数。内核提炼出中断处理的共性,搭建一个非常容易扩充的中断处理体系。
init_IRQ函数(代码在arch/arm/kernel/irq.c中)被用来初始化中断和处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数asm_do_IRQ就可以调用这些函数进行下一步处理。
1、相关数据结构
我们知道编写设备驱动程序一定要用到中断处理函数,这在驱动程序的编写中,占据很重要的一部分。在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top half),和下半部(bottom half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》的第七章的内容。
中断处理依赖于中断的类型:I/O中断、时钟中断和处理器间中断。
不管引起中断的电路的种类如何,所有I/O中断处理程序都执行四个相同的基本操作:
1、在内核态堆栈中保存IRQ的值和寄存器的内容。
2、为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断。
3、执行共享这个IRQ的所有设备的中断服务例程。
Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;Linux系统里每个中断也是通过irq_desc来管理,各中断的信息都在这个结构中得以体现。每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。
通过irq_desc结构数组就可以了解中断处理体系结构,irq_desc结构的数据类型include/linux/irq.h
中定义,
struct irq_desc {
unsigned int irq; //中断描述符的中断号
struct timer_rand_state *timer_rand_state; //pointer to timer rand state struct
unsigned int *kstat_irqs; //irq stats per cpu
#ifdef CONFIG_INTR_REMAP
struct irq_2_iommu *irq_2_iommu; //iommu with this irq
#endif
irq_flow_handler_t handle_irq; // 当前中断的处理函数入口, 高层的irq时间处理程序(如果为NULL,则默认调用__do_IRQ())
struct irq_chip *chip; //底层的中断硬件访问,指向PIC对象(irq_chip结构),它服务于IRQ线,Linux中断管理系统使用该成员来进行中断控制器的访问。
struct msi_desc *msi_desc; //MSI descriptor
void *handler_data; //irq_chip 方法使用的per-IRQ数据
void *chip_data; //chip 方法使用的特定平台的per-chip 私有数据,以允许共享chip的实现
struct irqaction *action; /*标识当出现IRQ时要调用的中断服务例程。该字段指向IRQ的irqaction链表的第一个元素。我们用request_irq()注册的中断处理方法,会被用来创建相关的irqaction结构体,对于同一个中断号注册的各个中断方法会被链接在该中断号的中断描述符的该字段上。 IRQ action list 用户提供的中断处理函数链表*/
unsigned int status; /*描述IRQ线状态的一组标志 */
unsigned int depth; /* disable-depth用于嵌套的irq_disable()调用,如果IRQ线被激活,则显示0,如果IRQ线被禁止了不止一次,则显示一个正数。*/
unsigned int wake_depth; /* enable depth, for multiple set_irq_wake() callers*/
unsigned int irq_count; /* For detecting broken IRQs 中断计数器,统计IRQ线上发生的中断的次数(仅在诊断时使用)*/
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled; //对在IRQ线上发生的无法处理的中断进行计数
raw_spinlock_t lock; //locking for SMP。用于串行访问IRQ描述符和PIC的自旋锁。
#ifdef CONFIG_SMP
cpumask_var_t affinity;
const struct cpumask *affinity_hint;
unsigned int node;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name; //中断的名称
} ____cacheline_internodealigned_in_smp;
handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数asm_do_IRQ将根据中断号调用相应irq_desc数组项中 handle_irq.handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函数。
在具体的ARM SoC芯片中会有很多的中断线,每一个中断线都会用一个irq_desc结构体来描述。
如果一个中断内核没有处理,那么这个中断就是意外中断,也就是说,与某个IRQ线相关的中断处理例程(ISR)不存在,或者与某个中断线相关的所有例程都识别不出是否是自己的硬件设备发出的中断。通常,内核检查从IRQ线接收的意外中断的数量,当这条IRQ线的有故障设备没完没了的发中断时,就禁用这条IRQ线,内核不会在每监测到一个意外中断时就立刻禁用IRQ线。由于几个设备可能共享IRQ线,更合适的办法是:内核把中断和意外中断的总次数分别放在irq_desc描述符的irq_count和irqs_unhandled字段中,当第100000次中断产生时,如果意外中断的次数超过99900次内核才禁用这条IRQ线。
描述IRQ线状态的标志:
IRQ_INPROGRESS /* IRQ handler active - do not enter! */
IRQ_DISABLED /* IRQ disabled - do not enter! */
IRQ_PENDING /* IRQ pending - replay on enable */
IRQ_REPLAY /* IRQ has been replayed but not acked yet */
IRQ_AUTODETECT /* IRQ is being autodetected */
IRQ_WAITING /* IRQ not yet seen - for autodetection */
IRQ_LEVEL /* IRQ level triggered */
IRQ_MASKED /* IRQ masked - shouldn't be seen again */
IRQ_PER_CPU /* IRQ is per CPU */
IRQ_NOPROBE /* IRQ is not valid for probing */
IRQ_NOREQUEST /* IRQ cannot be requested */
IRQ_NOAUTOEN /* IRQ will not be enabled on request irq */
IRQ_WAKEUP /* IRQ triggers system wakeup */
IRQ_MOVE_PENDING /* need to re-target IRQ destination */
IRQ_NO_BALANCING /* IRQ is excluded from balancing */
IRQ_SPURIOUS_DISABLED /* IRQ was disabled by the spurious trap */
IRQ_MOVE_PCNTXT /* IRQ migration from process context */
IRQ_AFFINITY_SET /* IRQ affinity was set from userspace*/
IRQ_SUSPENDED /* IRQ has gone through suspend sequence */
IRQ_ONESHOT /* IRQ is not unmasked after hardirq */
IRQ_NESTED_THREAD /* IRQ is nested into another, no own handler thread */
irq_desc描述符的depth字段和IRQ_DISABLED标志表示IRQ线是否被禁用。每次调用disable_irq()和disable_irq_nosync()函数,depth字段的值增加,如果depth等于0,函数禁用IRQ线并设置它的IRQ_DISABLED标志。相反,每当调用enable_irq()函数,depth字段的值减少,如果depth变为0,函数激活IRQ线并清除IRQ_DISABLED标志。
Linux支持许多种类的PIC电路,如PC机的8259A、APIC等。为了以统一的方法处理所有这样的设备,Linux用了一个“PIC对象”,由PIC名字和17个标准方法组成。这种面向对象的方法的优点是,驱动程序不必关注安装在系统中的PIC种类。每个驱动程序可见的中断源透明地连接到适当的控制器。定义PIC对象的数据结构叫irq_chip。内核使用irq_chip结构的成员来完成实际的对于PIC控制器的操作,禁用中断,启用中断等。
irq_chip结构类型也是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);//启动中断,如果不设置,缺省为“enable
void (*shutdown)(unsigned int irq);/*关闭中断,如果不设置,缺省为"disable"*/
void (*enable)(unsigned int irq); // 使用中断,如果不设置,缺省为"unmask"
void (*disable)(unsigned int irq); //禁止中断,如果不设置,缺省为“mask”
void (*ack)(unsigned int irq); /*响应中断,通常是清除当前中断使得可以接收下一个中断*/
void (*mask)(unsigned int irq); //屏蔽一个中断源
void (*mask_ack)(unsigned int irq); //确认并屏蔽一个中断源
void (*unmask)(unsigned int irq); //取消屏蔽一个中断源
void (*eoi)(unsigned int irq);
void (*end)(unsigned int irq);
int (*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int (*retrigger)(unsigned int irq); //重新发送一个IRQ给CPU
int (*set_type)(unsigned int irq, unsigned int flow_type);
int (*set_wake)(unsigned int irq, unsigned int on);
void (*bus_lock)(unsigned int irq);
void (*bus_sync_unlock)(unsigned int irq);
/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void (*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char *typename;
};
多个设备能共享一个单独的IRQ。因此,内核要维护多个irqaction描述符,其中的每个描述符涉及一个特定的硬件设备和一个特定的中断。这个结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断
处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的irqaction结
构链接成一个链表,以action为表头。irqation结构定义如下:
struct irqaction {
irq_handler_t handler; //用户注册的中断处理函数
unsigned long flags; //中断标志,比如是否共享中断,电平触发还是边沿触发
const char *name; //用户注册的中断名字
void *dev_id; //用户传给上面的handler的参数,还可以用来区分共享中断
struct irqaction *next; //指向下一个用户注册函数的指针
int irq; //中断号
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};
struct irqaction(中断行为描述符)各字段说明:
handler: 中断处理函数,指向一个I/O设备的中断服务例程。这是允许多个设备共享同一个IRQ的关键字段。
flags: 标志(参考下面的IRQF_*),描述IRQ与I/O设备之间的关系。
name: I/O设备名(通过读/proc/interrupts文件,在列出所服务的IRQ时也显示设备名)。
dev_id: I/O设备的私有数据字段。典型情况下,它标识I/O设备本身(例如,它可能等于其主设备号和此设备号),或者它指向设备驱动程序的数据
next: 共享中断情况下,指向irqaction链表的下一个元素。链表中的元素指向共享同一个IRQ的硬件设备
irq: 中断号
dir: 指向proc/irq/NN/name 入口的指针,指向proc文件系统中IRQn相关的目录项。
thread_fn: 用于线程化中断的中断处理函数
thread: 用于线程化中断的线程指针
thread_flags: 与thread 有关的标志
irqaction描述符的标志,只是被作为irq处理例程的一部分来使用的,各个标志的说明如下:
IRQF_DISABLED – 当调用action handler时保持irqs禁用
IRQF_SAMPLE_RANDOM – 设备可以被看作是事件随机的发生源,一次,内核可以用它做随机数产生器
IRQF_SHARED – 允许在多个设备间共享中断
IRQF_PROBE_SHARED - set by callers when they expect sharing
mismatches to occur
IRQF_TIMER – 标记该中断为时钟中断的标志
IRQF_PERCPU - Interrupt is per cpu
IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing
IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt
that is registered first in an shared interrupt is considered for
performance reasons)
IRQF_ONESHOT - Interrupt is not reenabled after the hardirq
handler finished.Used by threaded interrupts which need to keep the
irq line disabled until the threaded handler has been run.
最后,irq_stat数组包含NR_CPUS个元素,系统中的每个CPU对应一个元素(kernel/softirq.c文件中):
#ifndef __ARCH_IRQ_STAT
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;
EXPORT_SYMBOL(irq_stat);
#endif
每个元素的类型为irq_cpustat_t,该类型包含几个计数器和内核记录CPU正在做什么的标志,该结构体的定义因体系结构而已,对于我们的ARM平台,这个结构在arch/arm/include/asm/hardirq.h文件中定义:
typedef struct {
unsigned int __softirq_pending;
unsigned int local_timer_irqs;
} ____cacheline_aligned irq_cpustat_t;
irq_cpustat_t结构的字段
__softirq_pending:表示挂起的软中断
local_timer_irqs:本地时钟中断发生的次数
在kernel/irq/handle.c中还有定义了一个全局irq_desc结构体数组,用来表示系统中的所有中断:
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.status = IRQ_DISABLED,
.chip = &no_irq_chip,
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
上面的__cacheline_aligned_in_smp在include/linux/cache.h中定义,它是一个宏,在非SMP系统中是空的:
#ifdef CONFIG_SMP
#define __cacheline_aligned_in_smp __cacheline_aligned
#else
#define __cacheline_aligned_in_smp
#endif /* CONFIG_SMP */
用于描述结构体的属性,cache行对齐。
内核用结构数组irq_desc[NR_IRQS]来管理中断处理,每一个成员对应于一个中断线。NR_IRQS是一个因平台而异的宏,对于我们的mini2440来说,该宏在arch/arm/mach-s3c2440/include/mach/irqs.h中定义:
#ifdef CONFIG_CPU_S3C2443
#define NR_IRQS (IRQ_S3C2443_AC97+1)
#else
#define NR_IRQS (IRQ_S3C2440_AC97+1)
#endif
这个宏代表平台上最大的中断号。
irq_desc结构数组、它的成员“struct irq_chip *chip” "struct irqaction *action",这3种数据结构构成了中断处理体系的框架。下图中描述了Linxu中断处理体系结构的关系图:
中断处理流程如下
(1)发生中断时,CPU执行异常向量vector_irq的代码
(2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ
(3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。
(4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等
(5)handle_irq逐个调用用户在aciton链表中注册的处理函数
中断体系结构的初始化就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip等成员;用户注册中断时就是构造action链表;用户卸载中断时就是从action链表中去除不需要的项。
2、中断初始化
2.1 中断处理体系结构的初始化
文章开头说到内核在start_kernel函数(源码在init/main.c中)中调用trap_init()、early_irq_init()和init_IRQ()三个函数来初始化中断管理系统。对于我们的ARM平台来说trap_init()在arch/arm/kernel/traps.c中定义,为一个空函数。
612 early_irq_init();
在start_kernel()函数中第612行调用了early_irq_init()函数,它在调用init_IRQ()函数前,这个函数在kernel/handle.c文件中定义。kernel/irq/handle.c文件中,根据内核配置时是否选择了CONFIG_SPARSE_IRQ,而可以选择两个不同版本的该函数early_irq_init()中的一个进行编译。CONFIG_SPARSE_IRQ配置项,用于支持稀疏irq号,对于发行版的内核很有用,它允许定义一个高CONFIG_NR_CPUS值,但仍然不希望消耗太多内存的情况。对于我们的开发板来说,自然不需要打开这个配置项。
early_irq_init()函数定义如下:
static unsigned int kstat_irqs_all[NR_IRQS][NR_CPUS];
int __init early_irq_init(void)
{
struct irq_desc *desc;
int count;
int i;
init_irq_default_affinity();
printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
desc = irq_desc;
count = ARRAY_SIZE(irq_desc);
for (i = 0; i < count; i++) {
desc[i].irq = i;
alloc_desc_masks(&desc[i], 0, true);
init_desc_masks(&desc[i]);
desc[i].kstat_irqs = kstat_irqs_all[i];
}
return arch_early_irq_init();
}
主要工作即为初始化用于管理中断的irq_desc[NR_IRQS]数组的每个元素,它主要设置数组中每一个成员的中断号,使得数组中每一个元素的kstat_irqs字段(irq stats per cpu),指向定义的二维数组中的对应的行。alloc_desc_masks(&desc[i], 0, true)和init_desc_masks(&desc[i])函数在非SMP平台上为空函数。arch_early_irq_init()在主要用于x86平台和PPC平台,其他平台上为空函数。
现在再来看一下init_IRQ这个函数
arch/arm/kernel/irq.c
155 void __init init_IRQ(void)
156 {
157 struct irq_desc *desc;
158 int irq;
159
160 for (irq = 0; irq < nr_irqs; irq++) {
161 desc = irq_to_desc_alloc_node(irq, 0);
162 desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
163 }
164
165 init_arch_irq();
166 }
160~~162行 初始化irq_desc结构数组中每一项的中断状态,宏定义见下:
include/linux/irq.h
#define IRQ_NOPROBE 0x00020000 /* IRQ is not valid for probing */
#define IRQ_NOREQUEST 0x00040000 /* IRQ cannot be requested */
其中nr_irqs请看arch/arm/kernel/irq.c第171行(nr_irqs = arch_nr_irqs ? arch_nr_irqs : NR_IRQS;),而arch_nr_irqs体系结构确定,它在start_arch函数第838行初始化为arch_nr_irqs = mdesc->nr_irqs; 而mdesc->nr_irqs是移植machine_desc结构中的nr_irqs成员,machine_desc结构在arch/arm/mach-s3c2410/mach-smdk2410.c中初始化。如果mdesc->nr_irqs在这里没有初始化,则使用NR_IRQS。
这个函数主要功能是将irq_desc[NR_IRQS]结构数组各个元素的状态字段设置为IRQ_NOREQUEST | IRQ_NOPROBE,也就是未请求和未探测状态。然后调用特定机器平台的中断初始化init_arch_irq()函数。而 init_arch_irq()实际上是一个函数指针,其定义如下:
arch/arm/kernel/irq.c
50 unsigned int arch_nr_irqs;
51 void (*init_arch_irq)(void) __initdata = NULL;
在前面的博文中我们已经说明了setup_arch()函数是如何获取machine_desc,而又如何初始化init_arch_irq函数指针变量的。我们知道,对于S3C2410开发板,这个函数就是s3c24xx_init_irq,见前面start_arch函数第839行定义init_arch_irq = mdesc->init_irq;而mdesc->init_irq就是移植machine_desc结构中的init_irq成员,它指向这个函数 s3c24xx_init_irq,该函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有中断设置了芯片相关的数据结构(irq_desc[irq].chip),设置了处理函数入口(irq_desc[irq].handle_irq)。以外部中断EINT4- EINT23为例,用来设置它们的代码如下:
535 void __init s3c24xx_init_irq(void)
536 {
537 unsigned long pend;
538 unsigned long last;
539 int irqno;
540 int i;
541
542 #ifdef CONFIG_FIQ
543 init_FIQ();
544 #endif
545
546 irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");
547
548 /* first, clear all interrupts pending... */
549
550 last = 0;
551 for (i = 0; i < 4; i++) {
552 pend = __raw_readl(S3C24XX_EINTPEND);
553
554 if (pend == 0 || pend == last)
555 break;
556
557 __raw_writel(pend, S3C24XX_EINTPEND);
558 printk("irq: clearing pending ext status %08x\n", (int)pend);
559 last = pend;
560 }
561
562 last = 0;
563 for (i = 0; i < 4; i++) {
564 pend = __raw_readl(S3C2410_INTPND);
565
566 if (pend == 0 || pend == last)
567 break;
568
569 __raw_writel(pend, S3C2410_SRCPND);
570 __raw_writel(pend, S3C2410_INTPND);
571 printk("irq: clearing pending status %08x\n", (int)pend);
572 last = pend;
573 }
574
575 last = 0;
576 for (i = 0; i < 4; i++) {
577 pend = __raw_readl(S3C2410_SUBSRCPND);
578
579 if (pend == 0 || pend == last)
580 break;
581
582 printk("irq: clearing subpending status %08x\n", (int)pend);
583 __raw_writel(pend, S3C2410_SUBSRCPND);
584 last = pend;
585 }
586
587 /* register the main interrupts */
588
589 irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlers\n");
590
591 for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
592 /* set all the s3c2410 internal irqs */
593
594 switch (irqno) {
595 /* deal with the special IRQs (cascaded) */
596
597 case IRQ_EINT4t7:
598 case IRQ_EINT8t23:
599 case IRQ_UART0:
600 case IRQ_UART1:
601 case IRQ_UART2:
602 case IRQ_ADCPARENT:
603 set_irq_chip(irqno, &s3c_irq_level_chip);
604 set_irq_handler(irqno, handle_level_irq);
605 break;
606
607 case IRQ_RESERVED6:
608 case IRQ_RESERVED24:
609 /* no IRQ here */
610 break;
611
612 default:
613 //irqdbf("registering irq %d (s3c irq)\n", irqno);
614 set_irq_chip(irqno, &s3c_irq_chip);
615 set_irq_handler(irqno, handle_edge_irq);
616 set_irq_flags(irqno, IRQF_VALID);
617 }
618 }
619
620 /* setup the cascade irq handlers */
621
622 set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
623 set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
624
625 set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
626 set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
627 set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
628 set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);
629
630 /* external interrupts */
631
632 for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
633 irqdbf("registering irq %d (ext int)\n", irqno);
634 set_irq_chip(irqno, &s3c_irq_eint0t4);
635 set_irq_handler(irqno, handle_edge_irq);
636 set_irq_flags(irqno, IRQF_VALID);
637 }
638
639 for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
640 irqdbf("registering irq %d (extended s3c irq)\n", irqno);
641 set_irq_chip(irqno, &s3c_irqext_chip);
642 set_irq_handler(irqno, handle_edge_irq);
643 set_irq_flags(irqno, IRQF_VALID);
644 }
645
646 /* register the uart interrupts */
647
648 irqdbf("s3c2410: registering external interrupts\n");
649
650 for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
651 irqdbf("registering irq %d (s3c uart0 irq)\n", irqno);
652 set_irq_chip(irqno, &s3c_irq_uart0);
653 set_irq_handler(irqno, handle_level_irq);
654 set_irq_flags(irqno, IRQF_VALID);
655 }
656
657 for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
658 irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
659 set_irq_chip(irqno, &s3c_irq_uart1);
660 set_irq_handler(irqno, handle_level_irq);
661 set_irq_flags(irqno, IRQF_VALID);
662 }
663
664 for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
665 irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
666 set_irq_chip(irqno, &s3c_irq_uart2);
667 set_irq_handler(irqno, handle_level_irq);
668 set_irq_flags(irqno, IRQF_VALID);
669 }
670
671 for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
672 irqdbf("registering irq %d (s3c adc irq)\n", irqno);
673 set_irq_chip(irqno, &s3c_irq_adc);
674 set_irq_handler(irqno, handle_edge_irq);
675 set_irq_flags(irqno, IRQF_VALID);
676 }
677
678 irqdbf("s3c2410: registered interrupt handlers\n");
679 }
话说这个函数看上去挺长的,但好像没有什么难以理解的地方,它完成对的主要工作如下:
1、清除外部中断的中断挂起位。读取中断挂起寄存器的内容,并将该内容重新写入中断挂起寄存器(将相应位写入1,可以清除相应的位,更详细的情况可以参考手册)
2、清除中断的中断挂起位。
3、清除子中断源的中断挂起位。
4、进一步的完成irq_desc[NR_IRQS]结构数组的初始化,主要为设置元素的chip字段和handle_irq字段(高层中断处理程序)和标志字段。设置的内容,则因中断类型的不同而不同。
s3c24xx_init_irq()函数调用的用于设置irq_desc结构体的许多相关的函数也是挺有意思的。先看在641行set_irq_chip()函数,在文件kernel/irq/chip.c中定义:
int set_irq_chip(unsigned int irq, struct irq_chip *chip)
{
struct irq_desc *desc = irq_to_desc(irq);
unsigned long flags;
if (!desc) {
WARN(1, KERN_ERR "Trying to install chip for IRQ%d\n", irq);
return -EINVAL;
}
if (!chip)
chip = &no_irq_chip;
raw_spin_lock_irqsave(&desc->lock, flags);
irq_chip_set_defaults(chip);
desc->chip = chip;
raw_spin_unlock_irqrestore(&desc->lock, flags);
return 0;
}
EXPORT_SYMBOL(set_irq_chip);
set_irq_chip函数的作用就是“irq_desc[irno].chip = &s3c_irqext_chip”,以后就可能通过irq_desc[irqno].chip结构中的函数指针设置这些外部中断的触发方式(电平触发,边沿触发),使能中断,禁止中断。这个函数为一个irq设置irq chip。它接收两个参数,irq为中断号,chip则为指向irq chip描述结构的指针。这个函数会首先调用irq_to_desc(irq)来获得相应中断号的中断描述符的指针,然后调用irq_chip_set_defaults(chip)来对做进一步的完善,即对于某些不能为空的成员,则使其指向默认的处理函数。最后设置中断描述符的chip字段为传进来的chip参数,以在中断发生或者管理中断时可以完成对于PIC的操作。
对于IRQ_EINT4t7、IRQ_EINT8t23、IRQ_UART0、IRQ_UART1、IRQ_UART2和IRQ_ADCPARENT等有多个子中断源的中断,其chip设置为s3c_irq_level_chip,其定义如下:
arch/arm/plat-s3c24xx/irq.c
struct irq_chip s3c_irq_level_chip = {
.name = "s3c-level",
.ack = s3c_irq_maskack,
.mask = s3c_irq_mask,
.unmask = s3c_irq_unmask,
.set_wake = s3c_irq_wake
};
对于其他中断线,这其chip被设为s3c_irq_chip
arch/arm/plat-s3c24xx/irq.c
struct irq_chip s3c_irq_chip = {
.name = "s3c",
.ack = s3c_irq_ack,
.mask = s3c_irq_mask,
.unmask = s3c_irq_unmask,
.set_wake = s3c_irq_wake
};
接着看set_irq_handler()函数,其定义如下:
include/linux/irq.h
367 set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
368 {
369 __set_irq_handler(irq, handle, 0, NULL);
370 }
这个函数用于设置给定的IRQ的高层处理程序。它仅仅是对于__set_irq_handler()函数的一个封装。__set_irq_handler()函数定义如下:
kernel/irq/chip.c
void
__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
const char *name)
{
struct irq_desc *desc = irq_to_desc(irq);
unsigned long flags;
if (!desc) {
printk(KERN_ERR
"Trying to install type control for IRQ%d\n", irq);
return;
}
if (!handle)
handle = handle_bad_irq;
else if (desc->chip == &no_irq_chip) {
printk(KERN_WARNING "Trying to install %sinterrupt handler "
"for IRQ%d\n", is_chained ? "chained " : "", irq);
/*
* Some ARM implementations install a handler for really dumb
* interrupt hardware without setting an irq_chip. This worked
* with the ARM no_irq_chip but the check in setup_irq would
* prevent us to setup the interrupt at all. Switch it to
* dummy_irq_chip for easy transition.
*/
desc->chip = &dummy_irq_chip;
}
chip_bus_lock(irq, desc);
raw_spin_lock_irqsave(&desc->lock, flags);
/* Uninstall? */
if (handle == handle_bad_irq) {
if (desc->chip != &no_irq_chip)
mask_ack_irq(desc, irq);
desc->status |= IRQ_DISABLED;
desc->depth = 1;
}
desc->handle_irq = handle;
desc->name = name;
if (handle != handle_bad_irq && is_chained) {
desc->status &= ~IRQ_DISABLED;
desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
desc->depth = 0;
desc->chip->startup(irq);
}
raw_spin_unlock_irqrestore(&desc->lock, flags);
chip_bus_sync_unlock(irq, desc);
}
EXPORT_SYMBOL_GPL(__set_irq_handler);
这个函数就是为特定的中断线设置好一个高层的中断处理例程,这里的处理例程可不是我们调用request_irq()时注册的中断处理例程。这个函数完成如下工作:
1、调用irq_to_desc(irq)获得相应中断号对应的irq_desc结构体的指针。
2、检查传递进来的handle参数,若为空,则设为handle_bad_irq;检查中断描述符的chip字段,若未设置,则设为dummy_irq_chip。
3、获取自旋锁,并在局部变量flags中保存标志。
4、检查handle,若为handle_bad_irq,即未安装,则屏蔽该中断,设置中断描述符状态字段的IRQ_DISABLED位,并设置中断描述符的depth为1。
5、设置中断描述符的handle_irq为handle,并设置中断描述符的name字段为传递进来的name参数。
6、如果传递进来的handle为有效值,同时传递进来的is_chained非零,则清除中断描述符status字段的IRQ_DISABLED,并设置IRQ_NOREQUEST、IRQ_NOPROBE位。设置中断描述符的depth为0。并对中断号调用desc->chip->startup(irq)。
7、释放自旋锁并恢复保存的标志flags。
接着看set_irq_flags()函数,其定义如下:
arch/arm/kernel/irq.c
void set_irq_flags(unsigned int irq, unsigned int iflags)
{
struct irq_desc *desc;
unsigned long flags;
if (irq >= nr_irqs) {
printk(KERN_ERR "Trying to set irq flags for IRQ%d\n", irq);
return;
}
desc = irq_to_desc(irq);
raw_spin_lock_irqsave(&desc->lock, flags);
desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
if (iflags & IRQF_VALID)
desc->status &= ~IRQ_NOREQUEST;
if (iflags & IRQF_PROBE)
desc->status &= ~IRQ_NOPROBE;
if (!(iflags & IRQF_NOAUTOEN))
desc->status &= ~IRQ_NOAUTOEN;
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
该函数主要是为特定的中断号对应的中断描述符设置相应的状态标记,而s3c24xx_init_irq()函数里我们调用它的主要目的就是清掉IRQ_NOREQUEST标记,告诉系,该中断已经可以被申请使用了,中断在申请的时候会查看是否有IRQ_NOREQUEST标记,如有则表面该中断还不能使用。而初始化的时候所有的中断都有这个标记。
最后来看set_irq_chained_handler,其定义如下:
include/linux/irq.h
static inline void set_irq_chained_handler(unsigned int irq,
irq_flow_handler_t handle)
{
__set_irq_handler(irq, handle, 1, NULL);
}
这个函数同样也是__set_irq_handler()函数的封装,所不同的是,这个封装在调用__set_irq_handler()函数时第三个参数is_chained传递的是1,而不是0。在前面我们看到,其差别就是set_irq_chained_handler()在handle参数有效时,会直接清除中断描述符的status字段的IRQ_DISABLED位,以表示相应的中断线可用。
在s3c24xx_init_irq()函数中,我们看到只有为几个中断线设置高层中断处理程序时使用的是set_irq_chained_handler()以使相应的中断线直接可用,这几个中断线分别是 IRQ_EINT4t7、IRQ_EINT8t23、IRQ_UART0、IRQ_UART1、IRQ_UART2和IRQ_ADCPARENT,这与中断控制器有关,这几个中断线的每一个都关联着好几个子中断源。另外,也只有为这几个中断线设置时传递的handle参数为比较独特,而其他的则均为handle_edge_irq。
如在642行设置这些中断的处理函数入口为handle_edge_irq,即“irq_desc[irqno].handle_irq =handle_edge_irq”.发生中断时,handle_edge_irq函数会调用用户注册的具体处理函数; 在643行设置中断标志为 “IRQF_VALID”,表示可以使用它们。init_IRQ函数执行完后,irq_desc数组项的chip,handl_irq成员都被设置
2.2 用户注册中断处理函数的过程
在驱动程序中,要想使设备能够产生中断,则首先需要调用request_irq()来分配中断线。在通过request_irq()函数注册中断服务程序的时候,将会把设备中断处理程序添加进系统,以在中断发生的时候调用相应的中断处理程序。用户驱动程序通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的 action链表添加一个表项。原先的内核中requset_irq函数在kernel/irq/manage.c中定义,而现在2.6.36版本中,进行了改变,可以看这篇文章 ,这里解释了,在2.6.32内核中我们可以看到找不到了request_irq函数的实现,而是用request_threaded_irq()函数给替换了。我们可以在include/linux/interrupt.h中找到这个函数的原型
128 #ifdef CONFIG_GENERIC_HARDIRQS
129 extern int __must_check
130 request_threaded_irq(unsigned int irq, irq_handler_t handler,
131 irq_handler_t thread_fn,
132 unsigned long flags, const char *name, void *dev);
133
134 static inline int __must_check
135 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
136 const char *name, void *dev)
137 {
138 return request_threaded_irq(irq, handler, NULL, flags, name, dev);
139 }
140
141 extern int __must_check
142 request_any_context_irq(unsigned int irq, irq_handler_t handler,
143 unsigned long flags, const char *name, void *dev_id) ;
144
145 extern void exit_irq_thread(void);
146 #else
147
148 extern int __must_check
149 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
150 const char *name, void *dev);
151
152 /*
153 * Special function to avoid ifdeffery in kernel/irq/devres.c which
154 * gets magically built by GENERIC_HARDIRQS=n architectures (sparc,
155 * m68k). I really love these $@%#!* obvious Makefile references:
156 * ../../../kernel/irq/devres.o
157 */
158 static inline int __must_check
159 request_threaded_irq(unsigned int irq, irq_handler_t handler,
160 irq_handler_t thread_fn,
161 unsigned long flags, const char *name, void *dev)
162 {
163 return request_irq(irq, handler, flags, name, dev);
164 }
165
166 static inline int __must_check
167 request_any_context_irq(unsigned int irq, irq_handler_t handler,
168 unsigned long flags, const char *name, void *dev_id)
169 {
170 return request_irq(irq, handler, flags, name, dev_id);
171 }
172
173 static inline void exit_irq_thread(void) { }
174 #endif
其实具体的实现在request_threaded_irq函数中,也是在kernel/irq/manage.c中定义,requset_threaded_irq函数首先使用这4个参数构造一个irqaction结构,然后调用__setup_irq函数将它链入链表中,
1042 /* Flags:
1043 *
1044 * IRQF_SHARED Interrupt is shared
1045 * IRQF_SAMPLE_RANDOM The interrupt can be used for entropy
1046 * IRQF_TRIGGER_* Specify active edge(s) or level
1047 *
1048 */
1049 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
1050 irq_handler_t thread_fn, unsigned long irqflags,
1051 const char *devname, void *dev_id)
1052 {
1053 struct irqaction *action;
1054 struct irq_desc *desc;
1055 int retval;
1056
1057 /*
1058 * Sanity-check: shared interrupts must pass in a real dev-ID,
1059 * otherwise we'll have trouble later trying to figure out
1060 * which interrupt is which (messes up the interrupt freeing
1061 * logic etc).
1062 */
1063 if ((irqflags & IRQF_SHARED) && !dev_id)
1064 return -EINVAL;
1065
1066 desc = irq_to_desc(irq);
1067 if (!desc)
1068 return -EINVAL;
1069
1070 if (desc->status & IRQ_NOREQUEST)
1071 return -EINVAL;
1072
1073 if (!handler) {
1074 if (!thread_fn)
1075 return -EINVAL;
1076 handler = irq_default_primary_handler;
1077 }
1078
1079 action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
1080 if (!action)
1081 return -ENOMEM;
1082
1083 action->handler = handler;
1084 action->thread_fn = thread_fn;
1085 action->flags = irqflags;
1086 action->name = devname;
1087 action->dev_id = dev_id;
1088
1089 chip_bus_lock(irq, desc);
1090 retval = __setup_irq(irq, desc, action);
1091 chip_bus_sync_unlock(irq, desc);
1092
1093 if (retval)
1094 kfree(action);
1095
1096 #ifdef CONFIG_DEBUG_SHIRQ
1097 if (!retval && (irqflags & IRQF_SHARED)) {
1098 /*
1099 * It's a shared IRQ -- the driver ought to be prepared for it
1100 * to happen immediately, so let's make sure....
1101 * We disable the irq to make sure that a 'real' IRQ doesn' t
1102 * run in parallel with our fake.
1103 */
1104 unsigned long flags;
1105
1106 disable_irq(irq);
1107 local_irq_save(flags);
1108
1109 handler(irq, dev_id);
1110
1111 local_irq_restore(flags);
1112 enable_irq(irq);
1113 }
1114 #endif
1115 return retval;
1116 }
1117 EXPORT_SYMBOL(request_threaded_irq);
内核用这个函数来完成分配中断线的工作,其各个参数说明如下:
irq,是要注册的硬件中断号;
handler,是向系统注册的中断处理函数,它是一个回调函数,在相应的中断线发生中断时,系统会调用这个函数;
irqflags,中断类型标志,IRQF_*,是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版中已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED(老版本中的SA_SHIRQ),则表示是多个设备共享同一个中断;若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示将对系统熵有贡献,对系统获取随机数有好处,还可以用IRQF_TRIGGER_*标志来指定中断的触发方式。(这几个flag是可以通过或的方式同时使用的)。;
devname,一个声明的设备的ascii 名字,与中断号相关联的名称,在/proc/interrupts文件中可以看到此名称。
dev_id,I/O设备的私有数据字段,典型情况下,它标识I/O设备本身(例如,它可能等于其主设备号和此设备号),或者它指向设备驱动程序的数据,这个参数会被传回给handler函数。在中断共享时会用到,一般设置为这个设备的驱动程序中任何有效的地址值或者NULL
thread_fn,由irq handler线程调用的函数,如果为NULL,则不会创建线程。
这个调用分配中断资源,并使能中断线和IRQ处理。这个调用完成之后,则注册的中断处理函数随时可能被调用。由于你的中断处理函数必须清除板子产生的一切中断,则你必须注意初始化你的硬件和设置中断处理函数的正确顺序。
如果你想为你的设备设置线程化的irq处理程序,则你需要同时提供handler 和thread_fn。handler仍然在硬中断上下文被调用,所以它需要检查中断是否是由它服务的设备产生的。如果是,它返回IRQ_WAKE_THREAD,这将会唤醒中断处理程序线程并执行thread_fn。这种分开的中断处理程序设计是支持共享中断所必需的。Dev_id必须全局唯一。通常是设备数据结构的地址。如果要使用的共享中断,则必须传递一个非NULL的dev_id,这是在释放中断的时候需要的。
request_threaded_irq()函数返回0表示成功,返回-EINVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断号已经被占用且不能共享。这个函数完成的操作如下:
1、检查传递进来的标志参数irqflags,是否同时设置了IRQF_SHARED和IRQF_DISABLED,若是则发出警告,以说明IRQF_DISABLED在共享的中断上是不被保证的。
2、若irqflags设置了IRQF_SHARED,则还要检查dev_id的有效性。
3、获得对应中断号的中断描述符,并检查其有效性(通过检查中断描述符status字段的IRQ_NOREQUEST位)。
4、检查handler及thread_fn的有效性。
5、为注册的中断处理程序及传递的irqaction标志创建irqaction结构。
6、调用__setup_irq(irq, desc, action),来将创建的irqaction结构添加进相应的中断描述符的action链表里。
__setup_irq函数也是在kernel/irq.manage.c中定义,它完成如下3个主要功能
(1)将新建的irqaction结构链入irq_desc[irq]结构的action链表中,这有两种可能。
如果action链表为空,则直接链入,否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致,即是否都声明为"可共享的"(IRQF_SHARED)、是否都使用相同的触发方式,如果一致,则将新建的irqation结构链入
(2)设置irq_desc[irq]结构中chip成员的还没设置的指针,让它们指向一些默认函数
chip成员在init_IRQ函数初始化中断体系结构的时候已经设置了,这里只是设置其中还没设置的指针这通过irq_chip_set_defaults函数来完成,它在kernel/irq/chip.c中定义
(3)启动中断
如果irq_desc[irq]结构中status成员没有被指明IRQ_NOAUTOEN(表示注册中断时不要使用中断),还要调用chip->startup或chip->enable来启动中断,所谓启动中断通常就是使用中断。一般情况下,只有那些“可以自动使能的”中断对应的irq_desc[irq].status才会被指明为IRQ_NOAUTOEN,所以,无论哪种情况,执行request_irq注册中断之后,这个中断就已经被使能了。
648 static int
649 __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
650 {
651 struct irqaction *old, **old_ptr;
652 const char *old_name = NULL;
653 unsigned long flags;
654 int nested, shared = 0;
655 int ret;
656
657 if (!desc)
658 return -EINVAL;
659
660 if (desc->chip == &no_irq_chip)
661 return -ENOSYS;
662 /*
663 * Some drivers like serial.c use request_irq() heavily,
664 * so we have to be careful not to interfere with a
665 * running system.
666 */
667 if (new->flags & IRQF_SAMPLE_RANDOM) {
668 /*
669 * This function might sleep, we want to call it first,
670 * outside of the atomic block.
671 * Yes, this might clear the entropy pool if the wrong
672 * driver is attempted to be loaded, without actually
673 * installing a new handler, but is this really a problem,
674 * only the sysadmin is able to do this.
675 */
676 rand_initialize_irq(irq);
677 }
678
679 /* Oneshot interrupts are not allowed with shared */
680 if ((new->flags & IRQF_ONESHOT) && (new->flags & IRQF_SHARED))
681 return -EINVAL;
682
683 /*
684 * Check whether the interrupt nests into another interrupt
685 * thread.
686 */
687 nested = desc->status & IRQ_NESTED_THREAD;
688 if (nested) {
689 if (!new->thread_fn)
690 return -EINVAL;
691 /*
692 * Replace the primary handler which was provided from
693 * the driver for non nested interrupt handling by the
694 * dummy function which warns when called.
695 */
696 new->handler = irq_nested_primary_handler;
697 }
698
699 /*
700 * Create a handler thread when a thread function is supplied
701 * and the interrupt does not nest into another interrupt
702 * thread.
703 */
704 if (new->thread_fn && !nested) {
705 struct task_struct *t;
706
707 t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
708 new->name);
709 if (IS_ERR(t))
710 return PTR_ERR(t);
711 /*
712 * We keep the reference to the task struct even if
713 * the thread dies to avoid that the interrupt code
714 * references an already freed task_struct.
715 */
716 get_task_struct(t);
717 new->thread = t;
718 }
719
720 /*
721 * The following block of code has to be executed atomically
722 */
723 raw_spin_lock_irqsave(&desc->lock, flags);
724 old_ptr = &desc->action;
725 old = *old_ptr;
726 if (old) {
727 /*
728 * Can't share interrupts unless both agree to and are
729 * the same type (level, edge, polarity). So both flag
730 * fields must have IRQF_SHARED set and the bits which
731 * set the trigger type must match.
732 */
733 if (!((old->flags & new->flags) & IRQF_SHARED) ||
734 ((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
735 old_name = old->name;
736 goto mismatch;
737 }
738
739 #if defined(CONFIG_IRQ_PER_CPU)
740 /* All handlers must agree on per-cpuness */
741 if ((old->flags & IRQF_PERCPU) !=
742 (new->flags & IRQF_PERCPU))
743 goto mismatch;
744 #endif
745
746 /* add new interrupt at end of irq queue */
747 do {
748 old_ptr = &old->next;
749 old = *old_ptr;
750 } while (old);
751 shared = 1;
752 }
753
754 if (!shared) {
755 irq_chip_set_defaults(desc->chip);
756
757 init_waitqueue_head(&desc->wait_for_threads);
758
759 /* Setup the type (level, edge polarity) if configured: */
760 if (new->flags & IRQF_TRIGGER_MASK) {
761 ret = __irq_set_trigger(desc, irq,
762 new->flags & IRQF_TRIGGER_MASK);
763
764 if (ret)
765 goto out_thread;
766 } else
767 compat_irq_chip_set_default_handler(desc);
768 #if defined(CONFIG_IRQ_PER_CPU)
769 if (new->flags & IRQF_PERCPU)
770 desc->status |= IRQ_PER_CPU;
771 #endif
772
773 desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING | IRQ_ONESHO T |
774 IRQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);
775
776 if (new->flags & IRQF_ONESHOT)
777 desc->status |= IRQ_ONESHOT;
778
779 if (!(desc->status & IRQ_NOAUTOEN)) {
780 desc->depth = 0;
781 desc->status &= ~IRQ_DISABLED;
782 desc->chip->startup(irq);
783 } else
784 /* Undo nested disables: */
785 desc->depth = 1;
786
787 /* Exclude IRQ from balancing if requested */
788 if (new->flags & IRQF_NOBALANCING)
789 desc->status |= IRQ_NO_BALANCING;
790
791 /* Set default affinity mask once everything is setup */
792 setup_affinity(irq, desc);
793
794 } else if ((new->flags & IRQF_TRIGGER_MASK)
795 && (new->flags & IRQF_TRIGGER_MASK)
796 != (desc->status & IRQ_TYPE_SENSE_MASK)) {
797 /* hope the handler works with the actual trigger mode... * /
798 pr_warning("IRQ %d uses trigger mode %d; requested %d\n",
799 irq, (int)(desc->status & IRQ_TYPE_SENSE_MA SK),
800 (int)(new->flags & IRQF_TRIGGER_MASK));
801 }
802
803 new->irq = irq;
804 *old_ptr = new;
805
806 /* Reset broken irq detection when installing new handler */
807 desc->irq_count = 0;
808 desc->irqs_unhandled = 0;
809
810 /*
811 * Check whether we disabled the irq via the spurious handler
812 * before. Reenable it and give it another chance.
813 */
814 if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
815 desc->status &= ~IRQ_SPURIOUS_DISABLED;
816 __enable_irq(desc, irq, false);
817 }
818
819 raw_spin_unlock_irqrestore(&desc->lock, flags);
820
821 /*
822 * Strictly no need to wake it up, but hung_task complains
823 * when no hard interrupt wakes the thread up.
824 */
825 if (new->thread)
826 wake_up_process(new->thread);
827
828 register_irq_proc(irq, desc);
829 new->dir = NULL;
830 register_handler_proc(irq, new);
831
832 return 0;
833
834 mismatch:
835 #ifdef CONFIG_DEBUG_SHIRQ
836 if (!(new->flags & IRQF_PROBE_SHARED)) {
837 printk(KERN_ERR "IRQ handler type mismatch for IRQ %d\n", i rq);
838 if (old_name)
839 printk(KERN_ERR "current handler: %s\n", old_name);
840 dump_stack();
841 }
842 #endif
843 ret = -EBUSY;
844
845 out_thread:
846 raw_spin_unlock_irqrestore(&desc->lock, flags);
847 if (new->thread) {
848 struct task_struct *t = new->thread;
849
850 new->thread = NULL;
851 if (likely(!test_bit(IRQTF_DIED, &new->thread_flags)))
852 kthread_stop(t);
853 put_task_struct(t);
854 }
855 return ret;
856 }
327 /*
328 * Fixup enable/disable function pointers
329 */
330 void irq_chip_set_defaults(struct irq_chip *chip)
331 {
332 if (!chip->enable)
333 chip->enable = default_enable; //调用chip->unmask
334 if (!chip->disable)
335 chip->disable = default_disable; //此函数为空
336 if (!chip->startup)
337 chip->startup = default_startup; //调用chip->enable
338 /*
339 * We use chip->disable, when the user provided its own. When
340 * we have default_disable set for chip->disable, then we need
341 * to use default_shutdown, otherwise the irq line is not
342 * disabled on free_irq():
343 */
344 if (!chip->shutdown)
345 chip->shutdown = chip->disable != default_disable ?
346 chip->disable : default_shutdown;
347 if (!chip->name)
348 chip->name = chip->typename;
349 if (!chip->end)
350 chip->end = dummy_irq_chip.end;
351 }
总结一下request_irq函数注册
(1)irq_des[irq]结构中的action链表中已经链入了用户注册的中断处理函数
(2)中断的触发方式已经被设好
(3)中断已经被使能
关于中断注册的例子,可在内核中搜索下request_irq。
3、中断处理---asm_do_IRQ函数
OK,接下来,终于可以来研究中断处理了,也就是,我们辛辛苦苦添加进系统的中断处理例程被调用的整个过程。
不过,在分析源代码之前,还是让我们先了解一些原理性的东西, 我们都知道在处理中断时要保存现场,然后才能处理中断,处理完之后还要把现场状态恢复过来才能返回到被中断的地方继续执行,这里要说明的是在指令跳转到中断向量的地方开始执行之前,由CPU自动帮我们做了哪些事情:
R14_irq = 要执行的下条指令地址 + 4 //这里的下条指令是相对于被中断指令的下条。即返回地址。
SPSR_irq = CPSR //保存的现场状态,r0到r12要由我们软件来保存(如果需要的话)。
CPSR[4:0] = 0b10010 //进入中断模式
CPSR[5] = 0 //在ARM模式下执行(不是Thumb下)
CPSR[7] = 1 //关掉IRQ中断, FIQ还是开着
PC = 0Xffff0018 //根据异常向量表的位置,跳转到特定的中断向量处去执行。
然后,我们还是要回到异常向量表去看一下。每当中断控制器发出产生一个中断请求,则CPU总是跑到异常向量表的中断向量处取指令来执行。将中断向量中的宏解开,则就像下面这个样子:
/*
* Interrupt dispatcher
*/
.macro vector_stub, name, mode, correction=0
.align 5
vector_irq:
sub lr, lr, #4 //修正返回地址,也就是中断处理完之后要执行的指令的地址
@ Save r0, lr_ (parent PC) and spsr_
@ (parent CPSR)
stmia sp, {r0, lr} @ save r0, lr
// 保存返回地址,因为很快要使用r0寄存器,所以也要保存r0。
mrs lr, spsr
str lr, [sp, #8] @ save spsr
// 向上增长的栈。此时的这个栈是中断模式下的栈,ARM下中断模式下和系统模式下的栈是不同的。虽然ARM提供了七个模式,但Linux只使用了两个,一个是用户模式,另一个为系统模式,所以这个栈只是一个临时性的栈。
@ Prepare for SVC32 mode. IRQs remain disabled.
mrs r0, cpsr
eor r0, r0, #(IRQ_MODE ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0 //把spsr设置为管理模式
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f // 这条指令之后lr中位spsr的低4位
mov r0, sp // 将栈指针保存在r0中,传递为后面的中断处理过程
ldr lr, [pc, lr, lsl #2]
// 而PC寄存器是保存当前正在取值的地址,也就是当前正在执行的指令之后的第二条指令的地址,而不是紧接着当前指令的第一条指令的地址以spsr的低4位为索引,以PC值为基地址来获得相应的中断处理程序的地址
movs pc, lr @ branch to handler in SVC mode
// movs 的目的对象如果是pc的话,则还会把spsr赋值给cpsr,上面我们看到spsr被设成管理模式,因此这条语句过后的代码也就跑在了管理模式下。可以看到该汇编代码主要是把被中断的代码在执行过程中的状态(cpsr), 返回地址(lr)等保存在中断模式下的栈里,然后进 入到管理模式下去执行中断,同时令r0 = sp,这样可以在管理模式下找到该地址,进而获取spsr等信息。该汇编代码最终根据被中断的代码所处的模式跳转到相应的处理程序中去。注意管理模式下的栈和中断模式下的栈不是同一个。同时由于在 上面的代码中栈指针(sp)没有进行移位,因此即使后面的代码没对这个栈进行出栈操作,也不会因为不断的产生中断而导致栈溢出。
.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
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
前面有提到过,这是一段很巧妙的位置无关的代码,它将中断产生时,CPSR的模式位的值作为相对于PC值的索引来调用相应的中断处理程序。如果在进入终中断时是用户模式,则调用__irq_usr例程,如果为系统模式,则调用__irq_svc,如果是其他模式,则说明出错了,则调用__irq_invalid。接下来我们分别瞧一下这些个中断处理程序。
用户模式下的中断处理
先来回顾一下中断发生时系统的处理过程。当中断发生时,系统跳转到vector_irq处执行,它获得返回地址,在sp指针(中断模式下的栈,临时性的)所指的地方保存r0、lr和spsr,之后进入SVC模式,并根据中断产生时CPU的模式,以模式的低4位值为索引,来取相应的处理程序的地址,从而进入中断的处理过程。r0中保存中断时中断模式的SP的值。还是在这里补充一点ARM的CPU模式的东西好。ARM处理器的最低5位用来指示处理当前所在的模式。各模式对应的模式值如下(在arch/arm/include/asm/ptrace.h中):
#define USR_MODE 0x00000010
#define FIQ_MODE 0x00000011
#define IRQ_MODE 0x00000012
#define SVC_MODE 0x00000013
#define ABT_MODE 0x00000017
#define UND_MODE 0x0000001b
#define SYSTEM_MODE 0x0000001f
中断发生时,CPU处于用户模式下,则当然会调用__irq_usr例程,
__irq_usr见前面的异常处理分析部分
内核模式下的中断处理
分析了用户模式中断处理过程,我们再来看一下内核模式下的中断处理,也就是调用__irq_svc例程,__irq_svc例程在文件arch/arm/kernel/entry-armv.S中定义,首先我们来看这个例程的定义:
216 .align 5
217 __irq_svc:
218 svc_entry
219
220 #ifdef CONFIG_TRACE_IRQFLAGS
221 bl trace_hardirqs_off
222 #endif
223 #ifdef CONFIG_PREEMPT
224 get_thread_info tsk
225 ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
226 add r7, r8, #1 @ increment it
227 str r7, [tsk, #TI_PREEMPT]
228 #endif
229
230 irq_handler
231 #ifdef CONFIG_PREEMPT
232 str r8, [tsk, #TI_PREEMPT] @ restore preempt count
233 ldr r0, [tsk, #TI_FLAGS] @ get flags
234 teq r8, #0 @ if preempt count != 0
235 movne r0, #0 @ force flags to 0
236 tst r0, #_TIF_NEED_RESCHED
237 blne svc_preempt
238 #endif
239 ldr r4, [sp, #S_PSR] @ irqs are already disabled
240 #ifdef CONFIG_TRACE_IRQFLAGS
241 tst r4, #PSR_I_BIT
242 bleq trace_hardirqs_on
243 #endif
244 svc_exit r4 @ return from exception
245 UNWIND(.fnend )
246 ENDPROC(__irq_svc)
首先来看上面的svc_entry,这是一个宏,也在arch/arm/kernel/entry-armv.S中定义:
.macro svc_entry, stack_hole=0
UNWIND(.fnstart )
UNWIND(.save {r0 - pc} )
// 在栈中分配一个栈帧的空间用来存储各个寄存器的值。
// S_FRAME_SIZE在arch/arm/kernel/asm-offsets.c中定义,值为:
// DEFINE(S_FRAME_SIZE, sizeof(struct pt_regs));实际上
// 等于72。最后之所以又加了个4,是因为下面保存寄存器是从r1开始的
// 满递减的栈
sub sp, sp, #(S_FRAME_SIZE + \stack_hole - 4)
// 检查栈指针的对齐,内核要求此时的栈指针是8字节对齐的
#ifdef CONFIG_THUMB2_KERNEL
SPFIX( str r0, [sp] ) @ temporarily saved
SPFIX( mov r0, sp )
SPFIX( tst r0, #4 ) @ test original stack alignment
SPFIX( ldr r0, [sp] ) @ restored
#else
SPFIX( tst sp, #4 )
#endif
SPFIX( subeq sp, sp, #4 )
// sp指向struct pt_regs结构底部,简单的多寄存器存储指令
stmia sp, {r1 - r12} //保存r1到r12的值
// 在前面我们看到r0中存储的是进入中断时的临时栈的栈指针,在这个地址
// 处存储有r0,lr和spsr,将这三个值分别加载进r1-r3寄存器中
ldmia r0, {r1 - r3}
// S_SP为sp寄存器在pt_regs中的偏移,在文
// 件arch/arm/kernel/asm-offsets.c中定义,值为:
// DEFINE(S_SP, offsetof(struct pt_regs, ARM_sp));
// struct pt_regs {
// long uregs[18];
// };
// 则寄存器r5中存放的是pt_regs结构中存储SP的位置
add r5, sp, #S_SP - 4 @ here for interlock avoidance
mov r4, #-1 @ "" "" "" ""
// r0为中断发生以前的堆栈指针,将成为pt_regs中的sp的值
add r0, sp, #(S_FRAME_SIZE + \stack_hole - 4)
SPFIX( addeq r0, r0, #4 )
// 保存实际的r0,并使得sp指向栈帧的开始地址。
str r1, [sp, #-4]! @ save the "real" r0 copied
@ from the exception stack
mov r1, lr
@
@ We are now ready to fill in the remaining blanks on the stack:
@
@ r0 - sp_svc
@ r1 - lr_svc
@ r2 - lr_, already fixed up for correct return/restart
@ r3 - spsr_
@ r4 - orig_r0 (see pt_regs definition in ptrace.h)
@
stmia r5, {r0 - r4}
// 这一段代码保存所有的寄存器
asm_trace_hardirqs_off
.endm
这个宏主要就是保存各个寄存器值到栈上相应的位置,这个宏执 行完后的栈如下所示:
接着来看get_thread_info,它也是个宏,用来获取当前线程的地址。如果配置了内核抢占,则会执行宏展开的代码。线程的定义在include/linux/sched.h中:
union thread_union {
struct thread_info thread_info; // 线程属性
unsigned long stack[THREAD_SIZE/sizeof(long)]; // 栈
};
由它定义的线程是8K字节边界对齐的,并且在这8K的最低地址处存放的就是thread_info对象,即该栈拥有者线程的对象,而get_thread_info就是通过把sp低13位清0(8K边 界)来获取当前thread_info对象的地址。get_thread_info宏在arch/arm/kernel/entry-header.S中定义:
.macro get_thread_info, rd
mov \rd, sp, lsr #13
mov \rd, \rd, lsl #13
.endm
调用该宏后寄存器tsk里存放的就是当前线程对象的地址了, tsk是哪个寄存器呢,在arch/arm/kernel/entry-header.S文件中我们看到:
tsk .req r9 @ current thread_info
tsk只是r9的别名而已, 因此这时r9里保存的就是当前线程的地址。上面的那一段代码主要完成的工作即是获得线程对象基地址,进而增加线程对象的抢占计数
接着看irq_handler,它在文件arch/arm/kernel/entry-armv.S中定义:
.macro irq_handler
get_irqnr_preamble r5, lr
1: get_irqnr_and_base r0, r6, r5, lr // 平台相关,获取中断号
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
// 中断处理完成后返回的地方:获得中断号的地方,根据中断控制器中相
// 应寄存器的内容作为退出条件。退出时下面的两行代码就会被略过去。
adrne lr, BSYM(1b)
// 通过上面的宏get_irqnr_and_base为调用asm_do_IRQ准备了参数中断号
// struct pt_regs *参数也早已获得,于是乎调用asm_do_IRQ来处理中断
bne 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, BSYM(1b)
bne do_IPI
#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movne r0, sp
adrne lr, BSYM(1b)
bne do_local_timer
#endif
#endif
.endm
对于我们的平台来说get_irqnr_preamble是空的宏。irq_handler首先通过宏 get_irqnr_and_base获得中断号,存入r0。然后把上面建立的pt_regs结构的指针,也就是sp值赋给r1,把调用宏 get_irqnr_and_base的位置作为返回地址(为了循环地处理挂起的所有中断)。最后调用 asm_do_IRQ进一步处理中断。以上这些操作都建立在获得中断号的前提下,也就是有中断发生,某个外部设备触发中断的时候,kernel最终会调用到asm_do_IRQ()函数。
get_irqnr_and_base是平台相关的,这个宏查询ISPR(IRQ挂起中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中。该宏结束后,r0 = 中断号。这个宏在不同的ARM芯片上是不一样的,它需要读写中断控制器中的寄存器。对于s3c2440,代码在arch/arm/mach-s3c2410/include/entry-macro.S里,用上面的调用参数将宏展开,如下:
1:
mov r5, #S3C24XX_VA_IRQ
@@ try the interrupt offset register, since it is there
ldr r6, [ r5, #INTPND ]
teq r6, #0
beq 1002f
ldr r0, [ r5, #INTOFFSET ]
mov lr, #1
tst r6, lr, lsl r0
bne 1001f
@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves
mov r0, #0 @@ start here
@@ work out which irq (if any) we got
movs lr, r6, lsl#16
addeq r0, r0, #16
moveq r6, r6, lsr#16
tst r6, #0xff
addeq r0, r0, #8
moveq r6, r6, lsr#8
tst r6, #0xf
addeq r0, r0, #4
moveq r6, r6, lsr#4
tst r6, #0x3
addeq r0, r0, #2
moveq r6, r6, lsr#2
tst r6, #0x1
addeq r0, r0, #1
@@ we have the value
1001:
adds r0, r0, #IRQ_EINT0
1002:
@@ exit here, Z flag unset if IRQ
我们把__irq_svc的汇编部分分析完后再来分析asm_do_IRQ()等c函数。宏irq_handler执行完毕,如果配置了抢占,则还会恢复线程对象的抢占计数,获得线程对象的标记字段值,以检查是否需要重新调度。
__irq_svc例程调用svc_exit宏来退出中断处理过程。前面的一条语句,我们看到,中断发生时的CPSR被保存在了r4寄存器中了,这个宏在arch/arm/kernel/entry-armv.S中定义:
.macro svc_exit, rpsr
msr spsr_cxsf, \rpsr
#if defined(CONFIG_CPU_32v6K)
clrex @ clear the exclusive monitor
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
#elif defined (CONFIG_CPU_V6)
ldr r0, [sp]
strex r1, r2, [sp] @ clear the exclusive monitor
ldmib sp, {r1 - pc}^ @ load r1 - pc, cpsr
#else
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
#endif
.endm
这个宏恢复中断时运行环境,也就是各个寄存器中的值,从而推出中断的处理过程。
OK,中断的流程大体就是这样的,下面我们就开始分析c部分的中断处理流程。在上面的汇编语言代码里,我们看到,系统在保存好中断时环境,获得中断号之后,调用了函数asm_do_IRQ(),从而进入中断处理的C程序部分。asm_do_IRQ()函数定义如下:
arch/arm/kernel/irq.c:
108 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *reg s)
109 {
110 struct pt_regs *old_regs = set_irq_regs(regs);
111
112 irq_enter(); //进入中断上下文
113
114 /*
115 * Some hardware gives randomly wrong interrupts. Rather
116 * than crashing, do something sensible.
117 */
118 if (unlikely(irq >= nr_irqs)) {
119 if (printk_ratelimit())
120 printk(KERN_WARNING "Bad IRQ%u\n", irq);
121 ack_bad_irq(irq);
122 } else {
123 generic_handle_irq(irq); //根据中断号获取中断描述结构体,并调用其中断处理函数。
124 }
125
126 /* AT91 specific workaround */
127 irq_finish(irq); //退出中断上下文
128
129 irq_exit();
130 set_irq_regs(old_regs);
131 }
这个函数完成如下操作:
1、调用set_irq_regs(regs)函数更新处理器的当前帧指针,并在局部变量old_regs中保存老的帧指针
---------------------------------------------------------------------
include/asm-generic/irq_regs.h
21 DECLARE_PER_CPU(struct pt_regs *, __irq_regs);
28 static inline struct pt_regs *set_irq_regs(struct pt_regs *new_regs)
29 {
30 struct pt_regs *old_regs, **pp_regs = &__get_cpu_var(__irq_regs);
31
32 old_regs = *pp_regs;
33 *pp_regs = new_regs;
34 return old_regs;
35 }
---------------------------------------------------------------------
2、调用irq_enter()进入一个中断处理上下文。
3、检查中断号的有效性,有些硬件会随机的给一些错误的中断,做一些检查以防止系统崩溃。如果不正确,就调用ack_bad_irq(irq),该函数会增加用来表征发生的错误中断数量的变量irq_err_count,这个变量貌似仅供了解系统状况之用。
4、若传递的中断号有效,则会掉用generic_handle_irq(irq)来处理中断。
5、调用irq_exit()来推出中断处理上下文。
6、调用set_irq_regs(old_regs)来恢复处理器的当前帧指针。
接下来我们来看看函数generic_handle_irq()对于中断的处理,这个函数仅仅是对generic_handle_irq_desc()函数的封装而已:
include/linux/irq.h
static inline void generic_handle_irq(unsigned int irq)
{
generic_handle_irq_desc(irq, irq_to_desc(irq));
}
如果实现了上层中断处理函数desc->handle_irq就调用它,实际上在中断处理函数s3c24xx_init_irq()中已为每一个中断线分配了一个上层中断处理函数。
如果desc->handle_irq为空就调用通用中断处理函数__do_IRQ(irq);,在干函数中调用了函数handle_IRQ_event(),
在函数handle_IRQ_event()中执行了该条中断线上的每一个中断例程。
include/linux/irq.h
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
desc->handle_irq(irq, desc);
#else
if (likely(desc->handle_irq))
desc->handle_irq(irq, desc);
else
__do_IRQ(irq);
#endif
}
这个函数接收两个参数,中断号及对应的中断描述符指针。体系架构相关的中断处理函数调用这个函数来进行通用IRQ层的中断处理。如果中断的irq_desc结构的handle_irq成员非空则调用它。否则,会调用__do_IRQ()来让通用的IRQ层来处理一个中断。中断描述符irq_desc结构的handle_irq成员因中断类型的不同而不同,在我们前面分析的芯片级中断初始化函数s3c24xx_init_irq()中,我们看到这个字段基本上被设置为了这么几个函数:
用来处理具有多个子中断源的中断线的情况(SoC中断控制器的特性,而不是中断共享)的一组函数s3c_irq_demux_extint4t7()、s3c_irq_demux_extint8()、s3c_irq_demux_uart0()、s3c_irq_demux_uart1)、s3c_irq_demux_uart2()、s3c_irq_demux_adc()
其他情况的handle_edge_irq()函数
还有handle_level_irq()函数,只是很快就被第一种情况的几个函数取代。
OMG,这个地方似乎好复杂,如此之多的函数。不过,结构还是蛮清晰的。首先我们来看这几个特定于SoC的函数:
arch/arm/plat-s3c24xx/irq.c
static void s3c_irq_demux_adc(unsigned int irq,
struct irq_desc *desc)
{
unsigned int subsrc, submsk;
unsigned int offset = 9;
/* read the current pending interrupts, and the mask
* for what it is available */
subsrc = __raw_readl(S3C2410_SUBSRCPND);
submsk = __raw_readl(S3C2410_INTSUBMSK);
subsrc &= ~submsk;
subsrc >>= offset;
subsrc &= 3;
if (subsrc != 0) {
if (subsrc & 1) {
generic_handle_irq(IRQ_TC);
}
if (subsrc & 2) {
generic_handle_irq(IRQ_ADC);
}
}
}
static void s3c_irq_demux_uart(unsigned int start)
{
unsigned int subsrc, submsk;
unsigned int offset = start - IRQ_S3CUART_RX0;
/* read the current pending interrupts, and the mask
* for what it is available */
subsrc = __raw_readl(S3C2410_SUBSRCPND);
submsk = __raw_readl(S3C2410_INTSUBMSK);
irqdbf2("s3c_irq_demux_uart: start=%d (%d), subsrc=0x%08x,0x%08x\n",
start, offset, subsrc, submsk);
subsrc &= ~submsk;
subsrc >>= offset;
subsrc &= 7;
if (subsrc != 0) {
if (subsrc & 1)
generic_handle_irq(start);
if (subsrc & 2)
generic_handle_irq(start+1);
if (subsrc & 4)
generic_handle_irq(start+2);
}
}
/* uart demux entry points */
static void
s3c_irq_demux_uart0(unsigned int irq,
struct irq_desc *desc)
{
irq = irq;
s3c_irq_demux_uart(IRQ_S3CUART_RX0);
}
static void
s3c_irq_demux_uart1(unsigned int irq,
struct irq_desc *desc)
{
irq = irq;
s3c_irq_demux_uart(IRQ_S3CUART_RX1);
}
static void
s3c_irq_demux_uart2(unsigned int irq,
struct irq_desc *desc)
{
irq = irq;
s3c_irq_demux_uart(IRQ_S3CUART_RX2);
}
static void
s3c_irq_demux_extint8(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
eintpnd &= ~eintmsk;
eintpnd &= ~0xff; /* ignore lower irqs */
/* we may as well handle all the pending IRQs here */
while (eintpnd) {
irq = __ffs(eintpnd);
eintpnd &= ~(1<
irq += (IRQ_EINT4 - 4);
generic_handle_irq(irq);
}
}
static void
s3c_irq_demux_extint4t7(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);
eintpnd &= ~eintmsk;
eintpnd &= 0xff; /* only lower irqs */
/* we may as well handle all the pending IRQs here */
while (eintpnd) {
irq = __ffs(eintpnd);
eintpnd &= ~(1<
irq += (IRQ_EINT4 - 4);
generic_handle_irq(irq);
}
}
话说这几个函数执行的操作都还是非常相似的:
SoC中断控制器中有一个中断挂起寄存器SRCPND,当相应的中断发生时,这个寄存器中相应的位就被置位,寄存器总共有32位。而实际上SoC支持多得多的中断源,于是中断控制器被扩展,中断挂起寄存器SRCPND中有些位可以表征多个中断源的发生,然后另外有子中断源挂起寄存器SUBSRCPND等来告诉系统到底发生的中断是哪一个。上面的这组函数就是找到产生中断的子中断源的中断号,然后用找到的这个中断号做为参数来调用generic_handle_irq(irq)。更多详细情况可以参考S3C24XX系列SoC的数据手册中断控制器的相关部分内容。
在前面的中断系统初始化函数中我们看到,如果中断线没有子中断源的话,则其中断描述符的handle_irq字段会被设置为handle_edge_irq()函数,接下来我们来看handle_edge_irq()函数:
---------------------------------------------------------------------
krnel/irq/chip.c
void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
/*
* If we're currently running this IRQ, or its disabled,
* we shouldn't process the IRQ. Mark it pending, handle
* the necessary masking and go out
*/
if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
!desc->action)) {
desc->status |= (IRQ_PENDING | IRQ_MASKED);
mask_ack_irq(desc, irq);
goto out_unlock;
}
kstat_incr_irqs_this_cpu(irq, desc);
/* Start handling the irq */
if (desc->chip->ack)
desc->chip->ack(irq);
/* Mark the IRQ currently in progress.*/
desc->status |= IRQ_INPROGRESS;
do {
struct irqaction *action = desc->action;
irqreturn_t action_ret;
if (unlikely(!action)) {
mask_irq(desc, irq);
goto out_unlock;
}
/*
* When another irq arrived while we were handling
* one, we could have masked the irq.
* Renable it, if it was not disabled in meantime.
*/
if (unlikely((desc->status &
(IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
(IRQ_PENDING | IRQ_MASKED))) {
unmask_irq(desc, irq);
}
desc->status &= ~IRQ_PENDING;
raw_spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
raw_spin_lock(&desc->lock);
} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
desc->status &= ~IRQ_INPROGRESS;
out_unlock:
raw_spin_unlock(&desc->lock);
}
这个函数接收两个参数,irq为中断号, desc为相应的中断描述符。中断发生在硬件信号的上升沿或下降沿。中断被锁进中断控制器,中断必须被确认,以重新使能。在中断被确认之后,则另一个在相同的中断源上的中断就可能会发生,即使前面的一个正在被相关的中断处理程序处理。如果这种情况发生了,则通过硬件控制器,禁用中断是必要的。这需要在处理中断处理程序执行时产生的中断的中断处理循环中重新使能中断。如果所有挂起的中断都已经被处理,则循环退出。
这个函数完成如下操作:
1、获得中断描述符的自旋锁。
2、清除中断描述符状态字段status的IRQ_REPLAY和IRQ_WAITING标志。
3、检查desc->status及desc->action,若desc->status设置了IRQ_INPROGRESS或IRQ_DISABLED标志,即中断处理中或中断禁用,或者desc->action为空,则设置desc->status的IRQ_PENDING和IRQ_MASKED标志,屏蔽并确认中断,释放自旋锁并退出。
4、增加中断产生计数值。
5、若desc->chip->ack非空,则调用desc->chip->ack(irq)开始处理中断。
6、标记IRQ处理当前正在进行中。
7、通过一个循环来处理中断。主要完成的工作即是调用中断描述符的irqaction链。
在这里我们可以看一下Linux内核中对于中断嵌套的处理。Linux使用desc->status的IRQ_INPROGRESS来标记中断处理正在进行中,当第一次进入中断处理时,设置相应的中断描述符状态字段的该标志。则在重新使能中断后,即使前面的中断处理过程还没有结束,依然有可能会产生中断会进入中断处理流程。则在后面的中断处理流程里,进入handle_edge_irq()后,检测到前一个中断处理流程没有结束,则仅仅是设置desc->status的IRQ_PENDING和IRQ_MASKED标志便迅速退出。而在前一个中断处理流程的handle_edge_irq()的一个do{}while循环结束后,会检查desc->status的IRQ_PENDING和IRQ_MASKED标志,若设置了这两个标志,则会进行另外的一个中断处理do{}while循环。
8、清除desc->status的IRQ_INPROGRESS标志,释放自旋锁。
我们接着来看handle_IRQ_event()函数,这个函数定义为:
kernel/irq/handle.c
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
do {
trace_irq_handler_entry(irq, action);
ret = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, ret);
switch (ret) {
case IRQ_WAKE_THREAD:
/*
* Set result to handled so the spurious check
* does not trigger.
*/
ret = IRQ_HANDLED;
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
/*
* Wake up the handler thread for this
* action. In case the thread crashed and was
* killed we just pretend that we handled the
* interrupt. The hardirq handler above has
* disabled the device interrupt, so no irq
* storm is lurking.
*/
if (likely(!test_bit(IRQTF_DIED,
&action->thread_flags))) {
set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
wake_up_process(action->thread);
}
/* Fall through to add to randomness */
case IRQ_HANDLED:
status |= action->flags;
break;
default:
break;
}
retval |= ret;
action = action->next;
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
该函数主要的工作即是逐个地调用特定中断号的action链表的handler函数,也就是我们在驱动程序中用request_irq注册的中断例程。这里需要注意的是:如果我们注册中断的时候指明可以共享的话,则必须在我们的中断例程里判断当前产生的中断是否就是我们自己的中断,这可以通过传进来的参数来判断(该参数就是我们注册时提供的action->dev_id)。
接下来来看handle_level_irq()函数,其定义为:
kernel/irq/handle.c
void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
struct irqaction *action;
irqreturn_t action_ret;
raw_spin_lock(&desc->lock);
mask_ack_irq(desc, irq);
if (unlikely(desc->status & IRQ_INPROGRESS))
goto out_unlock;
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
kstat_incr_irqs_this_cpu(irq, desc);
/*
* If its disabled or no action available
* keep it masked and get out of here
*/
action = desc->action;
if (unlikely(!action || (desc->status & IRQ_DISABLED)))
goto out_unlock;
desc->status |= IRQ_INPROGRESS;
raw_spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, action);
if (!noirqdebug)
note_interrupt(irq, desc, action_ret);
raw_spin_lock(&desc->lock);
desc->status &= ~IRQ_INPROGRESS;
if (!(desc->status & (IRQ_DISABLED | IRQ_ONESHOT)))
unmask_irq(desc, irq);
out_unlock:
raw_spin_unlock(&desc->lock);
}
这个函数的功能基本上和handle_edge_irq()相同,只不过这个函数用来处理电平触发的中断,而handle_edge_irq()则用来处理边缘触发的中断。
void set_irq_flags(unsigned int irq, unsigned int iflags)
{
struct irq_desc *desc;
unsigned long flags;
if (irq >= nr_irqs) {
printk(KERN_ERR "Trying to set irq flags for IRQ%d\n", irq);
return;
}
desc = irq_to_desc(irq);
raw_spin_lock_irqsave(&desc->lock, flags);
desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
if (iflags & IRQF_VALID)
desc->status &= ~IRQ_NOREQUEST;
if (iflags & IRQF_PROBE)
desc->status &= ~IRQ_NOPROBE;
if (!(iflags & IRQF_NOAUTOEN))
desc->status &= ~IRQ_NOAUTOEN;
raw_spin_unlock_irqrestore(&desc->lock, flags);
}
generic_handle_irq函数直接调用desc结构中的handle_irq成员函数,它就是irq_desc[irq].handle.irq
asm_do_IRQ函数中参数irq的取值范围为IRQ_EINT0~(IRQ_EINT0 + 31),只有32个取值。它可能是一个实际的中断号,也可能是一组中断的中断号。这里有S3C2410的芯片特性决定的:发生中断时,INTPND寄存器的某一位被置1,INTOFFSET寄存器中记录了是哪一位(0--31),中断向量调用asm_do_IRQ之前要把INTOFFSET寄存器的值确定 irq参数。每一个实际的中断在irq_desc数组中都有一项与它对应,它们的数目不止32.当asm_do_IRQ函数参数irq表示的是“一组”中断时,irq_desc[irq].handle_irq成员函数还需要先分辨出是哪一个中断,然后调用 irq_desc[irqno].handle_irq来进一步处理。
以外部中断EINT8—EINT23为例,它们通常是边沿触发
(1) 它们被触发里,INTOFFSET寄存器中的值都是5,asm_do_IRQ函数中参数irq的值为(IRQ_EINTO+5),即IRQ_EINT8t23,
(2)irq_desc[IRQ_EINT8t23].handle_irq在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irq_demux_extint8.
(3)s3c_irq_demux_extint8函数的代码在arch/arm/plat-s3c24xx/irq.c中,它首先读取 EINTPEND、EINTMASK寄存器,确定发生了哪些中断,重新计算它们的中断号,然后调用irq_desc数组项中的handle_irq成员函数
452 static void
453 s3c_irq_demux_extint8(unsigned int irq,
454 struct irq_desc *desc)
455 {
456 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);//EINT8-EINT23 发生时,相应位被置1
457 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);//屏蔽寄存器
458
459 eintpnd &= ~eintmsk; //清除被屏蔽的位
460 eintpnd &= ~0xff; /* 清除低8位(EINT8对应位8)ignore lower irqs */
461
462 /* we may as well handle all the pending IRQs here */
463 /* 循环处理所有子中断*/
464 while (eintpnd) {
465 irq = __ffs(eintpnd); //确定eintpnd中为1的最高位
466 eintpnd &= ~(1<
467
468 irq += (IRQ_EINT4 - 4); //重新计算中断号,前面计算出irq等于8时,中断号为IRQ_EINT8
469 generic_handle_irq(irq);//调用这中断的真正的处理函数
470 }
471
472 }
(4)IRQ_EINT8--IRQ_EINT23这几个中断的处理函数入口,在init_IRQ函数初始化中断体系结构的时候已经被设置为 handle_edge_irq函数,desc_handle_irq(irq,irq_desc+irq)就是调用这个函数,它在kernel/irq /chip.c中定义,它用来处理边沿触发的中断,中断发生的次数统计
578 void
579 handle_edge_irq(unsigned int irq, struct irq_desc *desc)
580 {
581 raw_spin_lock(&desc->lock);
582
583 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
584
585 /*
586 * If we're currently running this IRQ, or its disabled,
587 * we shouldn't process the IRQ. Mark it pending, handle
588 * the necessary masking and go out
589 */
590 if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
591 !desc->action)) {
592 desc->status |= (IRQ_PENDING | IRQ_MASKED);
593 mask_ack_irq(desc, irq);
594 goto out_unlock;
595 }
596 kstat_incr_irqs_this_cpu(irq, desc);
597
598 /* Start handling the irq */
599 if (desc->chip->ack)
600 desc->chip->ack(irq);
601
602 /* Mark the IRQ currently in progress.*/
603 desc->status |= IRQ_INPROGRESS;
604
605 do {
606 struct irqaction *action = desc->action;
607 irqreturn_t action_ret;
608
609 if (unlikely(!action)) {
610 mask_irq(desc, irq);
611 goto out_unlock;
612 }
613
614 /*
615 * When another irq arrived while we were handling
616 * one, we could have masked the irq.
617 * Renable it, if it was not disabled in meantime.
618 */
619 if (unlikely((desc->status &
620 (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
621 (IRQ_PENDING | IRQ_MASKED))) {
622 unmask_irq(desc, irq);
623 }
624
625 desc->status &= ~IRQ_PENDING;
626 raw_spin_unlock(&desc->lock);
627 action_ret = handle_IRQ_event(irq, action);
628 if (!noirqdebug)
629 note_interrupt(irq, desc, action_ret);
630 raw_spin_lock(&desc->lock);
631
632 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDIN G);
633
634 desc->status &= ~IRQ_INPROGRESS;
635 out_unlock:
636 raw_spin_unlock(&desc->lock);
637 }
响应中断,通常是清除当前中断使得可以接收下一个中断,对于IRQ_EINT8~IRQ_EINT23这几个中断,desc->chip在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irqext_chip.desc->chip->ack就是 s3c_irqext_ack函数,(arch/armplat-s3c24xx/irq.c)它用来清除中断handle_IRQ_event函数来逐个执行action链表中用户注册的中断处理函数,它在kernel/irq/handle.c中定义。
368 irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
369 {
370 irqreturn_t ret, retval = IRQ_NONE;
371 unsigned int status = 0;
372
373 do {
374 trace_irq_handler_entry(irq, action);
375 ret = action->handler(irq, action->dev_id);//执行用户注册的中断处理函数
376 trace_irq_handler_exit(irq, action, ret);
377
378 switch (ret) {
379 case IRQ_WAKE_THREAD:
380 /*
381 * Set result to handled so the spurious check
382 * does not trigger.
383 */
384 ret = IRQ_HANDLED;
385
386 /*
387 * Catch drivers which return WAKE_THREAD but
388 * did not set up a thread function
389 */
390 if (unlikely(!action->thread_fn)) {
391 warn_no_thread(irq, action);
392 break;
393 }
394
395 /*
396 * Wake up the handler thread for this
397 * action. In case the thread crashed and was
398 * killed we just pretend that we handled the
399 * interrupt. The hardirq handler above has
400 * disabled the device interrupt, so no irq
401 * storm is lurking.
402 */
403 if (likely(!test_bit(IRQTF_DIED,
404 &action->thread_flags))) {
405 set_bit(IRQTF_RUNTHREAD, &action->thread_fla gs);
406 wake_up_process(action->thread);
407 }
408
409 /* Fall through to add to randomness */
410 case IRQ_HANDLED:
411 status |= action->flags;
412 break;
413
414 default:
415 break;
416 }
417
418 retval |= ret;
419 action = action->next;
420 } while (action);
421
422 if (status & IRQF_SAMPLE_RANDOM)
423 add_interrupt_randomness(irq);
424 local_irq_disable();
425
426 return retval;
427 }
用户注册的中断处理函数的参数为中断号irq,action->dev_id。后一个参数是通过request_irq函数注册中断时传入的dev_id参数,它由用户自己指定、自己使用,可以为空,当这个中断是“共享中断”时除外。
对于电平触发的中断,它们的irq_desc[irq].handle_irq通常是handle_level_irq函数。它也是在kernel/irq/chip.c中定义,其功能与上述handle_edge_irq函数相似,
对于handle_level_irq函数已经清除了中断,但是它只限于清除SoC内部的的信号,如果外设输入到SoC的中断信号仍然有效,这就会导致当前中断处理完成后,会误认为再次发生了中断,对于这种情况,需要用户注册的中断处理函数中清除中断,先清除外设的中断,然后再清除SoC内部的中断号。
中断的处理流程可以总结如下
(1)中断向量调用总入口函数asm_do_IRQ,传入根据中断号irq
(2)asm_do_IRQ函数根据中断号irq调用irq_desc[irq].handle_irq,它是这个中断的处理函数入口,对于电平触发的中断,这个入口函数通常为handle_level_irq,对于边沿触发的中断,这个入口通常为handle_edge_irq
(3)入口函数首先清除中断,入口函数是handle_level_irq时还要屏蔽中断
(4)逐个调用用户在irq_desc[irq].aciton链表中注册的中断处理函数
(5) 入口函数是handle_level_irq时还要重新开启中断
卸载中断处理函数这通过free_irq函数来实现,它与request_irq一样,也是在kernel/irq/mangage.c中定义。
它需要用到两个参数:irq和dev_id,它们与通过request_irq注册中断函数时使用的参数一样,使用中断号irq定位action链表,再使用dev_id在action链表中找到要卸载的表项。同一个中断的不同中断处理函数必须使用不同的dev_id来区分,在注册共享中断时参数 dev_id必惟一。
free_irq函数的处理过程与request_irq函数相反
(1)根据中断号irq,dev_id从action链表中找到表项,将它移除
(2)如果它是惟一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN 或IRQ_DESC[IRQ].CHIP->DISABLW来关闭中断。
在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top half),和下半部(bottom half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》
四、对数据异常的处理的do_DataAbort函数
以svc模式数据异常的处理为例,说明异常处理
1120 vector_stub dabt, ABT_MODE, 8
1121
1122 .long __dabt_usr @ 0 (USR_26 / USR_32)
1123 .long __dabt_invalid @ 1 (FIQ_26 / FIQ_32)
1124 .long __dabt_invalid @ 2 (IRQ_26 / IRQ_32)
1125 .long __dabt_svc @ 3 (SVC_26 / SVC_32)
数据异常发生时,跳转到__dabt_svc接口处理,如下:
__dabt_svc:
svc_entry
@
@ get ready to re-enable interrupts if appropriate
@
mrs r9, cpsr
tst r3, #PSR_I_BIT
biceq r9, r9, #PSR_I_BIT
@
@ Call the processor-specific abort handler:
@
@ r2 - aborted context pc
@ r3 - aborted context cpsr
@
@ The abort handler must return the aborted address in r0, and
@ the fault status register in r1. r9 must be preserved.
@
#ifdef MULTI_DABORT
ldr r4, .LCprocfns
mov lr, pc
ldr pc, [r4, #PROCESSOR_DABT_FUNC]
#else
bl CPU_DABORT_HANDLER
#endif
@
@ set desired IRQ state, then call main handler
@
msr cpsr_c, r9
mov r2, sp
bl do_DataAbort
@
@ IRQs off again before pulling preserved data off the stack
@
disable_irq_notrace
@
@ restore SPSR and restart the instruction
@
ldr r2, [sp, #S_PSR]
svc_exit r2 @ return from exception
UNWIND(.fnend )
ENDPROC(__dabt_svc)
.align 5
do_DataAbort:为数据异常处理函数,如下:
arch/arm/mm/fault.c
asmlinkage void __exception
do_DataAbort(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{
const struct fsr_info *inf = fsr_info + fsr_fs(fsr);
struct siginfo info;
if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))
return;
printk(KERN_ALERT "Unhandled fault: %s (0x%03x) at 0x%08lx\n",
inf->name, fsr, addr);
info.si_signo = inf->sig;
info.si_errno = 0;
info.si_code = inf->code;
info.si_addr = (void __user *)addr;
arm_notify_die("", regs, &info, fsr, 0);
}
该接口打印相关的信息后,调用arm_notify_die接口,根据用户空间还是内核空间的异常,进行不同的处理操作,如下:
arch/arm/kernel/traps.c
void arm_notify_die(const char *str, struct pt_regs *regs,
struct siginfo *info, unsigned long err, unsigned long trap)
{
if (user_mode(regs)) {
current->thread.error_code = err;
current->thread.trap_no = trap;
force_sig_info(info->si_signo, info, current);
} else {
die(str, regs, err);
}
}
对用户空间的数据异常,调用force_sig_info发送信号;
对内核空间的数据异常,调用die打印相应的异常信息,然后panic内核,如下:
arch/arm/kernel/traps.c
void die(const char *str, struct pt_regs *regs, int err)
{
struct thread_info *thread = current_thread_info();
int ret;
oops_enter();
spin_lock_irq(&die_lock);
console_verbose();
bust_spinlocks(1);
ret = __die(str, err, thread, regs); //打印异常发生时的堆栈等信息
if (regs && kexec_should_crash(thread->task))
crash_kexec(regs);
bust_spinlocks(0);
add_taint(TAINT_DIE);
spin_unlock_irq(&die_lock);
oops_exit();
if (in_interrupt())
panic("Fatal exception in interrupt");
if (panic_on_oops)
panic("Fatal exception");
if (ret != NOTIFY_STOP)
do_exit(SIGSEGV);
}