上面在说到安装中断的时候说过,调用request_irq()时的参数中irq的确定是个难题,为什么?
你如果到网络上查一下关于linux的资料,十有八九是关于i386体系结构上的,但linux是可以运行在多种cpu上的,比如采用arm内核的s3c2410,在i386体系上的经验在这里可以用么?我们试验一下:硬件准备,使用s3c2410的EINT0引脚作中断测试,为它编写一个中断驱动程序,最后将面临中断的安装,调用intrequest_irq(unsignedintirq,void (*handler)(int,void *,structpt_regs *),unsignedlongirq_flags,constchar *devname,void *dev_id),其余都好说,irq取什么值?很容易查到类似信息:在i386体系结构中,LINUX内核对中断向量号的使用规定如下:
0-31号中断向量对应于异常类型,32-47号中断向量分配给可屏蔽硬件中断,48-255号分配给软件中断。所以中断控制器上的IRQ0对应于中断向量32。
在ARM的手册上,所有的中断都叫异常,在S3C2410的手册上,其中断控制器的外部中断都叫EINTn,看起来EINT0就像是IRQ0 。
那我们不如试试吧,按照经验,取irq=32,request_irq返回一个值,乃是-EINVAL,意思是说irq的取值非法,要么试试其他值,反正都不能正常工作。
怎么办?让我们寻根求源吧!
文章开头说过:“当中断系统硬件产生一个中断信号,LINUX的中断处理系统将根据从硬件获得的中断号调用用户编写的中断处理程序”,我们看一下linux是如何取得中断号的过程,从中寻觅硬件的中断号如何对应到中断安装的系统调用中的参数的数值。中断产生后cpu进入中断向量指向的总的中断入口程序,对于arm的cpu来说,这个程序在arch/arm/kernel/entry-armv.S中的这部分:
vector_IRQ: @
@savemodespecificregisters
@
ldrr13, .LCsirq
sublr,lr, #4
strlr, [r13] @savelr_IRQ
mrslr,spsr
strlr, [r13, #4] @savespsr_IRQ
@
@nowbranchtothereleventMODEhandlingroutine
@
movr13, #I_BIT |MODE_SVC
msrspsr_c,r13 @switchtoSVC_32mode
andlr,lr, #15
ldrlr, [pc,lr,lsl #2]
movspc,lr @Changesmodeandbranches
.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
.word __irq_invalid @ 7
.word __irq_invalid @ 8
.word __irq_invalid @ 9
.word __irq_invalid @ a
.word __irq_invalid @ b
.word __irq_invalid @ c
.word __irq_invalid @ d
.word __irq_invalid @ e
.word __irq_invalid @ f
.align5
根据注释并仔细研究这部分可知,中断产生后,程序保存一些于中断相关的特殊寄存器,改变cpu模式为svc模式并转到__irq_usr,这部分代码如下:
__irq_usr:subsp,sp, #S_FRAME_SIZE
stmiasp, {r0 -r12} @saver0 -r12
ldrr4, .LCirq
addr8,sp, #S_PC
ldmiar4, {r5 -r7} @getsavedPC,SPSR
stmiar8, {r5 -r7} @savepc,psr,old_r0
stmdbr8, {sp,lr}^
alignment_trapr4,r7, __temp_irq
zero_fp
1: get_irqnr_and_baser0,r6,r5,lr
movner1,sp
adrsvcne,lr,1b
@
@routinecalledwithr0 =irqnumber,r1 =structpt_regs *
@
bnedo_IRQ
movwhy, #0
get_current_tasktsk
bret_to_user
注意get_irqnr_and_baser0,r6,r5,lr一句,这是一个宏,顾名思义,它将取得中断号,将这个宏展开,对于s3c2410来说,是其中的这部分代码:
#elifdefined(CONFIG_ARCH_S3C2410)
#include <asm/hardware.h>
.macro disable_fiq
.endm
.macro get_irqnr_and_base,irqnr,irqstat,base,tmp
movr4, #INTBASE @virtualaddressofIRQregisters
ldr /irqnr, [r4, #0x8] @readINTMSK
ldr /irqstat, [r4, #0x10] @readINTPND
bics /irqstat, /irqstat, /irqnr
bics /irqstat, /irqstat, /irqnr
beq1002f
mov /irqnr, #0
1001: tst /irqstat, #1
bne1002f @foundIRQ
add /irqnr, /irqnr, #1
mov /irqstat, /irqstat,lsr #1
cmp /irqnr, #32
bcc1001b
1002:
.endm
.macro irq_prio_table
.endm
分析这段代码可知,这个宏取得中断悬挂寄存器的值,将它转换为中断位对应的数字值即EINTn对应n并返回到第一个参数中,比如EINT0上发生中断,在宏的第一个参数中返回0,EINT1上发生中断,在宏的第一个参数中返回1。
再返回头来看__irq_usr的代码,根据注释和分析可知,代码在r0中存储irq号,r1指向一个包含发生中断时各种寄存器值的结构pt_regs,然后专向执行do_IRQ,函数do_IRQ()的代码在arch/arm/kernel/irq.c中:
asmlinkagevoiddo_IRQ(intirq,structpt_regs *regs)
{
structirqdesc *desc;
structirqaction *action;
intcpu;
irq =fixup_irq(irq);
……
……
}
注意这个函数中的irq =fixup_irq(irq);一句,他对刚才取得的irq值进行了修正,对于s3c2410,这个动作在arch/arm/mach-s3c2410/irq.c中:
unsignedintfixup_irq(intirq) {
unsignedintret;
unsignedlongsub_mask,ext_mask;
if (irq ==OS_TIMER)
returnirq;
switch (irq) {
caseIRQ_UART0:
sub_mask =SUBSRCPND & ~INTSUBMSK;
ret =get_subIRQ(sub_mask,0,2,irq);
break;
caseIRQ_UART1:
sub_mask =SUBSRCPND & ~INTSUBMSK;
ret =get_subIRQ(sub_mask,3,5,irq);
break;
caseIRQ_UART2:
sub_mask =SUBSRCPND & ~INTSUBMSK;
ret =get_subIRQ(sub_mask,6,8,irq);
break;
caseIRQ_ADCTC:
sub_mask =SUBSRCPND & ~INTSUBMSK;
ret =get_subIRQ(sub_mask,9,10,irq);
break;
caseIRQ_EINT4_7:
ext_mask =EINTPEND & ~EINTMASK;
ret =get_extIRQ(ext_mask,4,7,irq);
break;
caseIRQ_EINT8_23:
ext_mask =EINTPEND & ~EINTMASK;
ret =get_extIRQ(ext_mask,8,23,irq);
break;
default:
ret =irq;
}
returnret;
}
可见,对于EINT0,并不进行修正,而只对几个特殊中断号和多个中断脚复用一个中断号的情况进行修正。由此,我们可以得出S3C2410中,外部中断脚和中断号的对应关系是:
中断脚位 中断号
INT_ADC [31]
INT_RTC [30]
INT_SPI1 [29]
INT_UART0 [28]
INT_IIC [27]
INT_USBH [26]
INT_USBD [25]
Reserved [24]
INT_UART1 [23]
INT_SPI0 [22]
INT_SDI [21]
INT_DMA3 [20]
INT_DMA2 [19]
INT_DMA1 [18]
INT_DMA0 [17]
INT_LCD [16]
INT_UART2 [15]
INT_TIMER4 [14]
INT_TIMER3 [13]
INT_TIMER2 [12]
INT_TIMER1 [11]
INT_TIMER0 [10]
INT_WDT [9]
INT_TICK [8]
nBATT_FLT [7]
Reserved [6]
EINT8_23 [5]
EINT4_7 [4]
EINT3 [3]
EINT2 [2]
EINT1
[1]
EINT0 [0]
那么,在前面的例子中,我们使用irq=0申请中断为什么也不行呢?我们来看一下系统是如何初始化中断数据的,对于s3c2410,系统在初始化中要调用下面的函数:
void __inits3c2410_init_irq(void) {
intirq;
request_resource(&iomem_resource, &irq_resource);
request_resource(&iomem_resource, &eint_resource);
/*disableallIRQs */
INTMSK =0xffffffff;
INTSUBMSK =0x7ff;
EINTMASK =0x00fffff0;
/*allIRQsareIRQ,notFIQ
0 :IRQmode
1 :FIQmode
*/
INTMOD =0x00000000;
/*clearSource/InterruptPendingRegister */
SRCPND =0xffffffff;
INTPND =0xffffffff;
SUBSRCPND =0x7ff;
EINTPEND =0x00fffff0;
/*Defineirqhandler */
for (irq=0;irq <NORMAL_IRQ_OFFSET;irq++) {
irq_desc[irq].valid =1;
irq_desc[irq].probe_ok =1;
irq_desc[irq].mask_ack =s3c2410_mask_ack_irq;
irq_desc[irq].mask =s3c2410_mask_irq;
irq_desc[irq].unmask =s3c2410_unmask_irq;
}
irq_desc[IRQ_RESERVED6].valid =0;
irq_desc[IRQ_RESERVED24].valid =0;
irq_desc[IRQ_EINT4_7].valid =0;
irq_desc[IRQ_EINT8_23].valid =0;
irq_desc[IRQ_EINT0].valid =0;
irq_desc[IRQ_EINT1].valid =0;
irq_desc[IRQ_EINT2].valid =0;
irq_desc[IRQ_EINT3].valid =0;
for (irq=NORMAL_IRQ_OFFSET;irq <EXT_IRQ_OFFSET;irq++) {
irq_desc[irq].valid =0;
irq_desc[irq].probe_ok =1;
irq_desc[irq].mask_ack =EINT4_23mask_ack_irq;
irq_desc[irq].mask =EINT4_23mask_irq;
irq_desc[irq].unmask =EINT4_23unmask_irq;
}
for (irq=EXT_IRQ_OFFSET;irq <SUB_IRQ_OFFSET;irq++) {
irq_desc[irq].valid =1;
irq_desc[irq].probe_ok =1;
irq_desc[irq].mask_ack =SUB_mask_ack_irq;
irq_desc[irq].mask =SUB_mask_irq;
irq_desc[irq].unmask =SUB_unmask_irq;
}
}
可以看出,初始化中断描述符表时,对EINT0、EINT1、EINT2、EINT3、EINT4_7、EINT8_23等的.valid元素复位为0,表示这几个中断不可用。结合request_irq()源码可以看出,在注册时就会出现这几个中断不能成功注册的现象,本来这几个值是1的,我真不知道开发者为何这样做!不过还得感谢他们,是他们做的这个手脚促使我学得了这么多原来不知道的东东。
现在将这促人学习的几句注释掉,重新编译这个LINUX,重新启动这个新的内核,重新运行你的测试程序,一切都重新来,你将会兴奋的说:OK!