1,entry-armv.S
.macro irq_handler
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, 1b @返回到1处,asm_do_IRQ返回后将再次查询发生的中断
bne asm_do_IRQ @kernel的中断处理函数
...
.endm
2,entry-macro.S
下面这个宏查询ISPR(IRQ待定中断服务寄存器,当有需要处理的中断时,这个寄存器的相应位会置位,任意时刻,最多一个位会置位),计算出的中断号放在irqnr指定的寄存器中;这个宏在不同的ARM芯片上是不一样的,下面是s3c44b0x的定义
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
ldr \base, =S3C44B0X_I_ISPR @装入中断服务寄存器地址
ldr \base, [\base] @装入中断服务寄存器值
mov \irqnr, #0
2222:
tst \base, #1 @测试中断位
bne 1111f @OK,找到
add \irqnr, \irqnr, #1 @中断号加1
mov \base, \base, lsr #1 @中断服务寄存器值右移1位,比较下一位
cmp \irqnr, #NR_IRQS @比较一下,是不是超出了总的中断号
bcc 2222b @还没找到,继续
1111:
.endm
3,下面这个宏是所有中断服务过程进入时的前序,主要是在当前堆栈上分配一个pt_regs结构,把r0-r15以及cpsr等保存到这个结构中,在进入irq_handler时,sp指向pt_regs底端
.macro svc_entry
sub sp, sp, #S_FRAME_SIZE @sizeof(pt_regs),分配一个pt_regs结构
stmib sp, {r1 - r12} @保存r1-r12
ldmia r0, {r1 - r3} @r0指向何处?这个地方的内容是什么?参见4节(1)(2)处
add r5, sp, #S_SP @S_SP为sp寄存器在pt_regs中的偏移
mov r4, #-1 @ ORIG_r0=-1?add r0, sp, #S_FRAME_SIZE @r0为中断前的堆栈指针,将成为pt_regs中的sp的值
str r1, [sp] @ 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}
.endm
4,
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction @修正返回地址,如果必要的话
.endif
@
@ Save r0, lr_ (parent PC) and spsr_
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr (1)(为什么向上长呢?)
mrs lr, spsr
str lr, [sp, #8] @ save spsr
@
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f @这里时,lr的值是spsr的值,因此,这个指令之后lr的值为spsr[0-3],即,系统模式的低4位
mov r0, sp (2)
ldr lr, [pc, lr, lsl #2] @lr = pc+lr<<2,usr:pc,fiq:pc+4,irq:pc+8,svc:pc+12,abt:pc+28,und:pc+44,system:pc+60
movs pc, lr @ branch to handler in SVC mode
.endm
对于irq中断,会这样调用上面这个宏:
vector_stub irq, IRQ_MODE, 4
5,中断处理过程总体结构,当中断发生后控制先转移到4,然后跳转到__irq_svc
__irq_svc:
svc_entry
irq_handler
ldr r0, [sp, #S_PSR] @ irqs are already disabled,S_PSR为cpsr在pt_regs中的偏移;sp指向在svc_entry中分配的pt_regs结构
msr spsr_cxsf, r0 @恢复spsr
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
...
.global __stub_start
__stub_start:
vector_stub irq , IRQ_MODE ,4
.long __irq_usr
.long __irq_invalid
.long __irq_invalid
.long __irq_svc
.long __irq_invalid
...
@以下与irq中断无关,但是是arm整个异常处理结构的一部分,因此列示在这里
vector_stub dabt, ABT_MODE, 8
...
vector_stub pabt, ABT_MODE, 4
...
vector_stub und, UND_MODE
...
vector_fiq:
...
vector_addrexcptn:
...
.LCvswi:
...
.globl __stubs_end
__stubs_end:
...
@下面这些才是最初的入口点,__vector_start和__vector_end之间的代码会被移动到CONFIG_VECTORS_BASE开始的地方,例如0xc000000
.globl __vectors_start
__vectors_start:
swi SYS_ERROR0 @Reset
b vector_und + stubs_offset @Undefined instruction
ldr pc, .LCvswi + stubs_offset @swi instruction
b vector_pabt + stubs_offset @Prefetch Abort
b vector_dabt + stubs_offset @Data Abort
b vector_addrexcptn + stubs_offset @ARM reserved
b vector_irq + stubs_offset @IRQ
b vector_fiq + stubs_offset @FIQ
.globl __vectors_end
__vectors_end:
...
附录1,arm体系下pt_regs结构
struct pt_regs {
long uregs[18];
};
uregs[0] - uregs[17]分别对应,r0 - r15,cpsr,ORIG_r0
附录1,irq中断时堆栈的变化
--------
spsr
--------
lr ,中断返回地址,修正后的
--------
r0
-------- <-进入irq_svc之前,sp的值,也是r0的值
pt_regs
--------- <-进入svc_entry后,sp的值
浅析armlinux 2.4.19中断irq分发例程的派发流程之根基
文章来源:http://gliethttp.cublog.cnj97Linux联盟
j97Linux联盟
应用程序运行在user模式,对应arm的cpsr&15的值为0,而内核代码运行在svc模式,对应arm的cpsr&15的值为3,j97Linux联盟
所以,如果应用程序在运行期间,即usr模式下,arm发生了irq中断,那么中断处理代码得知后会调用__irq_usr分发处理例程处理irq中断,如果在系统调用syscall之类使程序运行在内核空间执行内核程序的时候,即svc模式下,arm发生了irq中断,那么中断处理代码得知后会调用__irq_svc分发处理例程处理irq中断,中断程序是怎么识别svc和usr模式,进而派发相应的__irq_usr和__irq_svc分发例程的呢,来看看代码部分:j97Linux联盟
//1.arch\arm\kernel\entry-armv.Sj97Linux联盟
...j97Linux联盟
.LCvswi: .word vector_swij97Linux联盟
j97Linux联盟
.LCsirq: .word __temp_irqj97Linux联盟
.LCsund: .word __temp_undj97Linux联盟
.LCsabt: .word __temp_abtj97Linux联盟
j97Linux联盟
__stubs_end:j97Linux联盟
j97Linux联盟
.equ __real_stubs_start, .LCvectors + 0x200j97Linux联盟
j97Linux联盟
.LCvectors: swi SYS_ERROR0j97Linux联盟
b __real_stubs_start + (vector_undefinstr - __stubs_start)j97Linux联盟
ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)j97Linux联盟
b __real_stubs_start + (vector_prefetch - __stubs_start)j97Linux联盟
b __real_stubs_start + (vector_data - __stubs_start)j97Linux联盟
b __real_stubs_start + (vector_addrexcptn - __stubs_start)j97Linux联盟
//irq中断发生后,cpu捕获中断,跳转到这里执行vector_IRQ处理程序[gliethttp_20071225]j97Linux联盟
//中断处理代码位于0xFFFF0000地址之后的空间中,j97Linux联盟
//为了更透彻的理解,最好先看看另3篇文章j97Linux联盟
//《浅析arm-linux中断vector向量表的建立流程》j97Linux联盟
//《浅析armlinux-setup_arch()->create_mapping()函数5-2-2》j97Linux联盟
//《浅析arm-linux系统调用的流程》j97Linux联盟
//文章1:http://blog.chinaunix.net/u1/38994/showart_333925.htmlj97Linux联盟
//文章2:http://blog.chinaunix.net/u1/38994/showart_353785.htmlj97Linux联盟
//文章3:http://blog.chinaunix.net/u1/38994/showart_331915.htmlj97Linux联盟
//为了更好理解上面的这几篇文章,最好先看看《浅析armlinux2.4.19启动程序[head-armv.s文件]》j97Linux联盟
//文章4:http://blog.chinaunix.net/u1/38994/showart_346701.htmlj97Linux联盟
b __real_stubs_start + (vector_IRQ - __stubs_start)j97Linux联盟
b __real_stubs_start + (vector_FIQ - __stubs_start)j97Linux联盟
...j97Linux联盟
//2.arch\arm\kernel\entry-armv.Sj97Linux联盟
...j97Linux联盟
vector_IRQ: @j97Linux联盟
@ save mode specific registersj97Linux联盟
@j97Linux联盟
ldr r13, .LCsirq //取出LCsirq变量的地址,用来存放lrj97Linux联盟
sub lr, lr, #4 //lr-4,这是arm必须的j97Linux联盟
str lr, [r13] //将计算之后的返回地址lr存入LCsirq变量j97Linux联盟
//读取此次irq中断发生时,cpu所处模式spsr,即,中断发生时cpu正在用户空间运行用户应用程序,j97Linux联盟
//还是在内核空间svc模式下运行内核代码,lr&15的数值为arm进入irq之前cpu运行的模式值j97Linux联盟
//如果lr&15=0表示,此次irq中断发生时,cpu正运行用户空间的用户程序,j97Linux联盟
//如果lr&15=3表示,此次irq中断发生时,cpu正运行内核空间的内核代码[gliethttp_20071225]j97Linux联盟
mrs lr, spsrj97Linux联盟
str lr, [r13, #4]j97Linux联盟
j97Linux联盟
mrs r13, cpsrj97Linux联盟
bic r13, r13, #MODE_MASKj97Linux联盟
orr r13, r13, #I_BIT | MODE_SVCj97Linux联盟
//切换到svc模式,因为当前cpsr为irq模式,所以可以修改cpsr,j97Linux联盟
//注意在usr模式下cpsr的数值,即使使用了msr spsr_c, r13赋值语句,j97Linux联盟
//因为usr模式不允许修改cpsr,所以r13的数值并不能被真正赋值给spsr,j97Linux联盟
//执行完赋值语句之后,spsr的数值不会发生任何改变,仍然是原来的值[gliethttp_20071225]j97Linux联盟
msr spsr_c, r13 j97Linux联盟
j97Linux联盟
and lr, lr, #15j97Linux联盟
//lr&15的值为发生irq之前cpu所在的空间,j97Linux联盟
//0:在用户空间发生了irq中断j97Linux联盟
//3:在内核空间发生了irq中断j97Linux联盟
//lr<<2=lr*4也就是pc+0和pc+12j97Linux联盟
//分别对应LCtab_irq的.word __irq_usr域和.word __irq_svc域j97Linux联盟
ldr lr, [pc, lr, lsl #2]j97Linux联盟
//跳转到__irq_usr或者__irq_svc处理此次irq中断[gliethttp_20071225]j97Linux联盟
movs pc, lrj97Linux联盟
j97Linux联盟
.LCtab_irq: .word __irq_usr @ 0 (USR_26 / USR_32)//用户空间发生irq中断j97Linux联盟
.word __irq_invalid @ 1 (FIQ_26 / FIQ_32)j97Linux联盟
.word __irq_invalid @ 2 (IRQ_26 / IRQ_32)j97Linux联盟
.word __irq_svc @ 3 (SVC_26 / SVC_32)//内核空间发生irq中断j97Linux联盟
.word __irq_invalid @ 4 j97Linux联盟
.word __irq_invalid @ 5j97Linux联盟
.word __irq_invalid @ 6j97Linux联盟
.word __irq_invalid @ 7j97Linux联盟
.word __irq_invalid @ 8j97Linux联盟
.word __irq_invalid @ 9j97Linux联盟
.word __irq_invalid @ aj97Linux联盟
.word __irq_invalid @ bj97Linux联盟
.word __irq_invalid @ cj97Linux联盟
.word __irq_invalid @ dj97Linux联盟
.word __irq_invalid @ ej97Linux联盟
.word __irq_invalid @ fj97Linux联盟
...j97Linux联盟
.LCvswi: .word vector_swij97Linux联盟
.LCsirq: .word __temp_irqj97Linux联盟
.LCsund: .word __temp_undj97Linux联盟
.LCsabt: .word __temp_abt
ARM Linux中断分析
ARM体系结构中,把复位、中断、快速中断等都看作‘异常’,当这些‘异常’发生时,CPU会到固定地址处去找指令,他们对应的地址如下:
地址 |
异常类型 |
进入时的工作模式 |
0x00000000 |
Reset |
Supervisor |
0x00000004 |
Und |
Undefined |
0x00000008 |
Soft interupt |
Supervisor |
0x0000000c |
Abort(prefetch) |
Abort |
0x00000010 |
Abort(data) |
Abort |
0x00000014 |
Reserved |
Reserved |
0x00000018 |
IRQ |
IRQ |
0x0000001c |
FIQ |
FIQ |
首先要明确的一点就是,无论内存地址空间是如何映射的,以上这些地址都不会变,比如当有快速中断发生时,ARM将铁定到0X0000001C这
个地址处取指令。这也是BOOTLOADER把操作系统引导以后,内存必须重映射的原因!否则操作系统不能真正接管整套系统!
LINUX启动以后要初始化这些区域,初始化代码在main.c中的start_kernel()中,具体是调用函数trap_ini()来实现的。如下面所示(具体可参照entry-armv.S):
.LCvectors: swi SYS_ERROR0
b __real_stubs_start + (vector_undefinstr - __stubs_start)
ldr pc, __real_stubs_start + (.LCvswi - __stubs_start)
b __real_stubs_start + (vector_prefetch - __stubs_start)
b __real_stubs_start + (vector_data - __stubs_start)
b __real_stubs_start + (vector_addrexcptn - __stubs_start)
b __real_stubs_start + (vector_IRQ - __stubs_start)
b __real_stubs_start + (vector_FIQ - __stubs_start)
ENTRY(__trap_init)
stmfd sp!, {r4 - r6, lr}
adr r1, .LCvectors @ set up the vectors
ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}
add r2, r0, #0x200
adr r0, __stubs_start @ copy stubs to 0x200
adr r1, __stubs_end
1: ldr r3, [r0], #4
str r3, [r2], #4
cmp r0, r1
blt 1b
LOADREGS(fd, sp!, {r4 - r6, pc})
以上可以看出这个函数初始化了中断向量,实际上把相应的跳转指令拷贝到了对应的地址。
当发生中断时,不管是从用户模式还是管理模式调用的,最终都要调用do_IRQ():
__irq_usr: sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ save r0 - r12
ldr r4, .LCirq
add r8, sp, #S_PC
ldmia r4, {r5 - r7} @ get saved PC, SPSR
stmia r8, {r5 - r7} @ save pc, psr, old_r0
stmdb r8, {sp, lr}^
alignment_trap r4, r7, __temp_irq
zero_fp
1: get_irqnr_and_base r0, r6, r5, lr
movne r1, sp
adrsvc ne, lr, 1b
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
bne do_IRQ @ 调用do_IRQ来实现具体的中断处理
mov why, #0
get_current_task tsk
b ret_to_user
对于以上代码,在很多文章中都有过分析,这里不再赘述。
Linux每个中断通过一个结构irqdesc来描述,各中断的信息都在这个结构中得以体现:
struct irqdesc {
unsigned int nomask : 1; /* IRQ does not mask in IRQ */
unsigned int enabled : 1; /* IRQ is currently enabled */
unsigned int triggered: 1; /* IRQ has occurred */
unsigned int probing : 1; /* IRQ in use for a probe */
unsigned int probe_ok : 1; /* IRQ can be used for probe */
unsigned int valid : 1; /* IRQ claimable */
unsigned int noautoenable : 1; /* don't automatically enable IRQ */
unsigned int unused :25;
void (*mask_ack)(unsigned int irq); /* Mask and acknowledge IRQ */
void (*mask)(unsigned int irq); /* Mask IRQ */
void (*unmask)(unsigned int irq); /* Unmask IRQ */
struct irqaction *action;
/*
* IRQ lock detection
*/
unsigned int lck_cnt;
unsigned int lck_pc;
unsigned int lck_jif;
};
在具体的ARM芯片中会有很多的中断类型,每一种类型的中断用以上结构来表示:
struct irqdesc irq_desc[NR_IRQS]; /* NR_IRQS根据不同的MCU会有所区别*/
在通过request_irq()函数注册中断服务程序的时候,将会把中断向量和中断服务程序对应起来。
我们来看一下request_irq的源码:
int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *),
unsigned long irq_flags, const char * devname, void *dev_id)
{
unsigned long retval;
struct irqaction *action;
if (irq >= NR_IRQS || !irq_desc[irq].valid || !handler ||
(irq_flags & SA_SHIRQ && !dev_id))
return -EINVAL;
action = (struct irqaction *)kmalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action) /* 生成action结构*/
return -ENOMEM;
action->handler = handler;
action->flags = irq_flags;
action->mask = 0;
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
retval = setup_arm_irq(irq, action); /*把中断号irq和action 对应起来*/
if (retval)
kfree(action);
return retval;
}
其中第一个参数irq就是中断向量,第二个参数即是要注册的中断服务程序。很多同仁可能疑惑的是,我们要注册的中断向量号是怎么确定的呢?这要根据具体芯片的中断控制器,比如三星的S3C2410,需要通过读取其中的中断状态寄存器,来获得是哪个设备发生了中断:
if defined(CONFIG_ARCH_S3C2410)
#include <asm/hardware.h>
.macro disable_fiq
.endm
.macro get_irqnr_and_base, irqnr, irqstat, base, tmp
mov r4, #INTBASE @ virtual address of IRQ registers
ldr irqnr, [r4, #0x8] @ read INTMSK 中断掩码寄存器
ldr irqstat, [r4, #0x10] @ read INTPND 中断寄存器
bics irqstat, irqstat, irqnr
bics irqstat, irqstat, irqnr
beq 1002f
mov irqnr, #0
1001: tst irqstat, #1
bne 1002f @ found IRQ
add irqnr, irqnr, #1
mov irqstat, irqstat, lsr #1
cmp irqnr, #32
bcc 1001b
1002:
.endm
.macro irq_prio_table
.endm
以上代码也告诉了我们,中断号的确定,其实是和S3C2410手册中SRCPND寄存器是一致的,即:
/* Interrupt Controller */
#define IRQ_EINT0 0 /* External interrupt 0 */
#define IRQ_EINT1 1 /* External interrupt 1 */
#define IRQ_EINT2 2 /* External interrupt 2 */
#define IRQ_EINT3 3 /* External interrupt 3 */
#define IRQ_EINT4_7 4 /* External interrupt 4 ~ 7 */
#define IRQ_EINT8_23 5 /* External interrupt 8 ~ 23 */
#define IRQ_RESERVED6 6 /* Reserved for future use */
#define IRQ_BAT_FLT 7
#define IRQ_TICK 8 /* RTC time tick interrupt */
#define IRQ_WDT 9 /* Watch-Dog timer interrupt */
#define IRQ_TIMER0 10 /* Timer 0 interrupt */
#define IRQ_TIMER1 11 /* Timer 1 interrupt */
#define IRQ_TIMER2 12 /* Timer 2 interrupt */
#define IRQ_TIMER3 13 /* Timer 3 interrupt */
#define IRQ_TIMER4 14 /* Timer 4 interrupt */
#define IRQ_UART2 15 /* UART 2 interrupt */
#define IRQ_LCD 16 /* reserved for future use */
#define IRQ_DMA0 17 /* DMA channel 0 interrupt */
#define IRQ_DMA1 18 /* DMA channel 1 interrupt */
#define IRQ_DMA2 19 /* DMA channel 2 interrupt */
#define IRQ_DMA3 20 /* DMA channel 3 interrupt */
#define IRQ_SDI 21 /* SD Interface interrupt */
#define IRQ_SPI0 22 /* SPI interrupt */
#define IRQ_UART1 23 /* UART1 receive interrupt */
#define IRQ_RESERVED24 24
#define IRQ_USBD 25 /* USB device interrupt */
#define IRQ_USBH 26 /* USB host interrupt */
#define IRQ_IIC 27 /* IIC interrupt */
#define IRQ_UART0 28 /* UART0 transmit interrupt */
#define IRQ_SPI1 29 /* UART1 transmit interrupt */
#define IRQ_RTC 30 /* RTC alarm interrupt */
#define IRQ_ADCTC 31 /* ADC EOC interrupt */
#define NORMAL_IRQ_OFFSET 32
这些宏定义在文件irqs.h中,大家可以看到它的定义取自S3C2410的文档。
总结
linux在初始化的时候已经把每个中断向量的地址准备好了!就是说添加中断服务程序的框架已经给出,当某个中断发生时,将会到确定的地址处去找指令,所以我们做驱动程序时,只需要经过request_irq来挂接自己编写的中断服务程序即可。
对于快速中断,linux在初始化时是空的,所以要对它挂接中断处理程序,就需要单独的函数set_fiq_handler来实现,此函数在源文件fiq.c中,有兴趣的读者可进一步研究。
ARM LINUX的中断系统
0 概述
本文描述ARM PXA255系统中断部分的实现原理和代码分析。
1 ARM系统异常中断介绍
1.1 异常中断种类
ARM支持7类异常中断,所以中断向量表设8个条目,每个条目4字节,共32字节。
异常名称
|
中断向量
|
异常中断模式
|
优先级
|
复位
|
0x0
|
特权模式
|
1
|
未定义的指令
|
0x4
|
未定义指令中止模式
|
6
|
软件中断
|
0x8
|
特权模式
|
6
|
指令预取中止
|
0x0c
|
中止模式
|
5
|
数据访问中止
|
0x10
|
中止模式
|
2
|
保留
|
0x14
|
|
|
外部中断请求IRQ
|
0x18
|
IRQ模式
|
4
|
快速中断请求FIQ
|
0x1c
|
FIQ模式
|
3
|
1.2 GPIO引脚
PXA255拥有81个GPIO引脚。另外各个外围模块有自己的中断线。
The PXA255 processor enables and controls its 81 GPIO
pins through the use of 27 registers which configure the pin direction (input
or output), pin function, pin state (outputs only), pin level detection (inputs
only), and selection of alternate functions.
由Interrupt Controller Pending Register (ICPR)寄存器可知:前8个中断保留,其它的都分配相应的外围模块。且其中8和9是反映GPIO0/1的。IS10用来指示GPIO2-80是否有中断触发?
这样,ARM linux的IRQ中断数组struct irqdesc irq_desc[NR_IRQS]中NR_IRQS应该为:
#define NR_IRQS (IRQ_GPIO(80) + 1) //32-2+80 –8 +1 =103 。
GPIO引脚的一个问题:
alternate function??
2 linux对中断的支持
2.1 中断向量表初始化
在kernel_start()初始化中的trap_init()完成ARM的中断向量表的构造。
trap_init()执行的是entry-armv.S文件中的ENTRY(__trap_init)。
其中.Lcvectors处给出了ARM启动时的中断向量表内容。系统通过R1-R7,ip,lr等8个寄存器做中转,把向量表转存到r0(是传入的参数,即向量表的基址)对应的地址中。
另外,在向量表基址后面512字节处,拷贝了向量表具体的处理代码。
.LCvectors: swi SYS_ERROR0
b __real_stubs_start + (vector_undefinstr -
__stubs_start)
ldr pc, __real_stubs_start + (.LCvswi -
__stubs_start)
b __real_stubs_start + (vector_prefetch -
__stubs_start)
b __real_stubs_start + (vector_data -
__stubs_start)
b __real_stubs_start + (vector_addrexcptn -
__stubs_start)
b __real_stubs_start + (vector_IRQ -
__stubs_start)
b __real_stubs_start + (vector_FIQ -
__stubs_start)
ENTRY(__trap_init)
stmfd sp!, {r4 - r6, lr}
adr r1, .LCvectors @ set up the vectors
ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr}
stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr}
add r2, r0, #0x200
adr r0, __stubs_start @ copy stubs to 0x200
adr r1, __stubs_end
1: ldr r3, [r0], #4
str r3, [r2], #4
cmp r0, r1
blt 1b
LOADREGS(fd,
sp!, {r4 - r6, pc})
2.2 IRQ中断处理流程
2.2.1 区分系统模式
下面我们以IRQ为例,分析中断处理的过程。根据向量表,其跳转到:vector_IRQ
把lr值减4后保存到.Lcsirq位置。
再保存spsr的值到.Lcsirq后4字节的位置。
再通过设置spsr_c切换系统到特权模式。
最后一步就是跳转到优先级最高的中断对应的处理函数。这里要解释一下,
and lr,
lr, #15 取出lr(实际是spsr)的低4位。即M0-M3,也就是原来系统所处的处理器模式。ldr lr, [pc, lr, lsl #2] 将lr的值乘4后加到PC,再将其所指地址赋给lr。注意,这里的pc值为当前指针,其加上模式值后,就会指向.LCtab_irq:表中的对应项。应该只有用户模式和特权模式会产生IRQ中断,所以就只有两个有效的对应指针:__irq_usr和__irq_svc。
vector_IRQ: @
@
save mode specific registers
@
ldr r13, .LCsirq
sub lr, lr, #4
str lr, [r13] @
save lr_IRQ
mrs lr, spsr
str lr, [r13, #4] @ save spsr_IRQ
@
@
now branch to the relevent MODE handling routine
@
mov r13, #I_BIT | MODE_SVC
msr spsr_c, r13 @
switch to SVC_32 mode
and lr, lr, #15
ldr lr, [pc, lr, lsl #2]
movs pc, lr @
Changes mode and branches
.LCtab_irq: .word __irq_usr @ 0
(USR_26 / USR_32)
.word __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.word __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.word __irq_svc @ 3
(SVC_26 / SVC_32)
.word __irq_invalid @ 4
.word __irq_invalid @ 5
.word __irq_invalid @ 6
.LCsirq: .word __temp_irq
__temp_irq: .word 0 @
saved lr_irq
.word 0 @
saved spsr_irq
.word -1 @
old_r0
2.2.2 用户模式下的IRQ处理
上一小节根据IRQ发生时系统模式的不同跳转到不同的处理函数。
处理流程如下:
首先从堆栈中留出S_FRAME_SIZE大小的空间,保存了r0-r12.
再把用户模式下的sp,lr值保存起来。(note:表面位置在下一句之后,但保存位置却更靠栈顶)。
再把原来保存在.Lcirq中的lr_irq,spsr_irq,old_r0保存到堆栈中。
get_irqnr_and_base是一个宏,负责从中断的寄存器中读取ICIP,ICMR值,再判断是否中断,判断方法是:首先看是否存在中断,没有返回0。若系统有中断,判断ICIP的8-15位,若在低位中找到中断,不再查找高位的中断标志。其中,r0返回最小的中断号,r6等于ICIP&ICMR。R5是ICMR寄存器的虚拟地址。Lr没有使用。由此可以得到:中断号越小,中断程序越优先执行。
然后,通过对lr的赋值,目的是实现中断的串行化处理。
然后,针对找出的中断号,调用do_IRQ处理函数。
get_current_task tsk 把sp的低13清0。?? tsk .req r9 @
current task
__irq_usr: sub sp, sp, #S_FRAME_SIZE // S_FRAME_SIZE=72
stmia sp, {r0 - r12} @ save r0 - r12
ldr r4, .LCirq
add r8, sp, #S_PC
ldmia r4, {r5 - r7} @ get saved PC, SPSR
stmia r8, {r5 - r7} @ save pc, psr, old_r0
stmdb r8,
{sp, lr}^
alignment_trap
r4, r7, __temp_irq
zero_fp
1: get_irqnr_and_base
r0, r6, r5, lr
movne r1, sp
@将当前堆栈指针赋给r1
adrsvc ne, lr, 1b @通过设置lr,使实现了对中断的串行处理
@
@
routine called with r0 = irq number, r1 = struct pt_regs *
@
bne do_IRQ
mov why, #0
get_current_task
tsk
b ret_to_user
2.2.3 do_irq处理
主要的处理流程为:
desc = irq_desc + irq; //从IRQ全局表中查找对应中断条目。
desc->mask_ack(irq); //该函数的功能是负责清除对应的中断状态位。
//下面就是执行挂在该中断号上的所有IRQ函数。当然,在这之前还需进行一些标志的处理。
action = desc->action;
do
{
status
|= action->flags;
action->handler(irq,
action->dev_id, regs);
action
= action->next;
}
while (action);
//存在软中断,就执行软中断。
if
(softirq_pending(cpu))
do_softirq();
return;
//系统返回。
2.3 FIQ中断处理
根据中断向量表,进入vector_FIQ处理。
将lr-4可以实现中断程序的返回。这里什么也没有做。
Linux不支持ARM的 FIQ.
vector_FIQ: disable_fiq
subs pc, lr, #4
2.4 PXA255开发板中断的初始化
start_kernel()函数的初始化过程中,调用init_IRQ()进行开发板具体中断的初始化。
{
for (irq = 0; irq < NR_IRQS; irq++) {
irq_desc[irq].probe_ok
= 0;
irq_desc[irq].valid = 0;
irq_desc[irq].noautoenable
= 0;
irq_desc[irq].mask_ack
= dummy_mask_unmask_irq;
irq_desc[irq].mask = dummy_mask_unmask_irq;
irq_desc[irq].unmask = dummy_mask_unmask_irq;
}
init_arch_irq();
init_dma();
}
其中关键函数为:init_arch_irq();
在/arch/arm/setup.c函数中有定义:init_arch_irq =
mdesc->init_irq;
而mdesc->init_irq就是在/match-pxa/pxa255.c中定义的。
static void __init
xhyper255_init_irq(void)
{
pxa_init_irq();
set_GPIO_IRQ_edge(
0, GPIO_RISING_EDGE); /* Ethernet
Interrupt */
#ifdef CONFIG_ARCH_XHYPER255B
set_GPIO_IRQ_edge(IRQ_TO_GPIO_2_80(IRQ_GPIO_ADS7843),
GPIO_FALLING_EDGE); /* ADS7843 touch controller */
set_GPIO_IRQ_edge(IRQ_TO_GPIO_2_80(IRQ_GPIO_EZHOST), GPIO_RISING_EDGE); /* EZ-Host USB HOST controller */
#elif defined(CONFIG_ARCH_XHYPER255A)
set_GPIO_IRQ_edge(IRQ_TO_GPIO_2_80(IRQ_GPIO_ADS7843),
GPIO_FALLING_EDGE); /* ADS7843 touch controller */
#endif
}
通过调用pxa_init_irq()完成PXA255的中断初始化。包括:
设置相关寄存器:
/*
disable all IRQs */
ICMR
= 0; //Interrupt Controller Mask
Register 中断掩码寄存器
/*
all IRQs are IRQ, not FIQ */
ICLR
= 0; // 用来指示对应位中断发送给IRQ还是FIQ。
/*
clear all GPIO edge detects */
/*Six control whether rising edges and/or falling edges are detected (GRER
& GFER)
6个上升沿与下降沿检测控制寄存器*/
GFER0
= GFER1 = GFER2 = 0; //
GRER0
= GRER1 = GRER2 = 0;
/*GPIO Edge Detect Status Register
3个状态寄存器,和GRER/GFER配合使用的。读出的是状态,对应位写入1为清除状态。
GEDR0
= GEDR0;
GEDR1
= GEDR1;
GEDR2
= GEDR2;
//Disable Idle Mask 若为0,没有被ICMR打开的中断也能使系统退出IDLE状态。
/*
only unmasked interrupts kick us out of idle */
ICCR
= 1;
初始化irq_desc[]数组:GPIO2到GPIO80中断号的valid都被初始化为0。
/*
* Note: GPIO IRQs are initially invalid until
set_GPIO_IRQ_edge()
* is called at least once.
*/
for
(irq = IRQ_GPIO0; irq <= IRQ_GPIO1; irq++) {
irq_desc[irq].valid = 0;
irq_desc[irq].probe_ok = 1;
irq_desc[irq].mask_ack = pxa_mask_and_ack_GPIO_0_1_irq;
irq_desc[irq].mask = pxa_mask_GPIO_0_1_irq;
irq_desc[irq].unmask = pxa_unmask_GPIO_0_1_irq;
}
for
(irq = IRQ_GPIO_2_80; irq <= IRQ_RTCAlrm; irq++) {
irq_desc[irq].valid = 1;
irq_desc[irq].probe_ok = 0;
irq_desc[irq].mask_ack = pxa_mask_irq;
irq_desc[irq].mask = pxa_mask_irq;
irq_desc[irq].unmask = pxa_unmask_irq;
}
/*
Those are reserved */
irq_desc[PXA_IRQ(15)].valid
= 0;
irq_desc[PXA_IRQ(16)].valid
= 0;
for
(irq = IRQ_GPIO(2); irq <= IRQ_GPIO(80); irq++) {
irq_desc[irq].valid = 0;
irq_desc[irq].probe_ok = 1;
irq_desc[irq].mask_ack = pxa_mask_and_ack_GPIO_2_80_irq;
irq_desc[irq].mask = pxa_mask_GPIO_2_80_irq;
irq_desc[irq].unmask = pxa_unmask_GPIO_2_80_irq;
}
设置ARM的ICPR的第10位,用来指示GPIO2-80引脚是否有中断发生。
GPIO[80:2] Edge Detect Interrupt
Pending
0 – Interrupt NOT pending due to
edge detect on one (or more) of GPIO[80:2].
1 – Interrupt pending due to edge
detect on one (or more) of GPIO[80:2].
注意,GPIO2-GPIO80这些引脚是没有直接接中断线的,它们是否有边沿触发,完全通过ICPR的第10来判断。当发现GPIO2-80引脚有中断时,首先触发的是ICPR中的第10位中断,该中断通过调用setup_arm_irq(),安装了一个比较特殊的中断处理函数:GPIO_2_80_irqaction,在GPIO_2_80_irqaction处理函数中,系统读取GPIO2-80引脚的状态值:GPDR。再根据引脚状态去执行对应的IRQ函数。
setup_arm_irq(
IRQ_GPIO_2_80, &GPIO_2_80_irqaction );
2.5 如何设置用于中断的GPIO 引脚
继续回到/match-pxa/pxa255.c中定义的static void __init
xhyper255_init_irq(void)函数,前面已经分析了pxa_init_irq();下面讨论如何设置用于中断的GPIO引脚。
以太网中断:
电路图上可以看到,CS8900的中断信号接到PXA255的GPIO0引脚上。
set_GPIO_IRQ_edge( 0,
GPIO_RISING_EDGE); /* Ethernet Interrupt
*/
不过需要说明的是,这里只设置GPIO的方向,上升沿还是下降沿触发等。中断函数并没有注册。
void set_GPIO_IRQ_edge (int gpio_nr, int
edge)
{
long
flags;
local_irq_save(flags);
set_GPIO_mode(gpio_nr
| GPIO_IN);
if
(edge & GPIO_FALLING_EDGE)
set_bit
(gpio_nr, GPIO_IRQ_falling_edge);
else
clear_bit
(gpio_nr, GPIO_IRQ_falling_edge);
if
(edge & GPIO_RISING_EDGE)
set_bit
(gpio_nr, GPIO_IRQ_rising_edge);
else
clear_bit
(gpio_nr, GPIO_IRQ_rising_edge);
irq_desc[IRQ_GPIO(gpio_nr)].valid
= 1;
local_irq_restore(flags);
}
ADS7843触摸屏中断:
set_GPIO_IRQ_edge(IRQ_TO_GPIO_2_80(IRQ_GPIO_ADS7843),
GPIO_FALLING_EDGE); /* ADS7843 touch controller */
USB中断:
set_GPIO_IRQ_edge(IRQ_TO_GPIO_2_80(IRQ_GPIO_EZHOST), GPIO_RISING_EDGE); /* EZ-Host USB HOST controller */