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:swiSYS_ERROR0
b__real_stubs_start + (vector_undefinstr - __stubs_start)
ldrpc, __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)
stmfdsp!, {r4 - r6, lr}
adrr1, .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}
addr2, r0, #0x200
adrr0, __stubs_start@ copy stubs to 0x200
adrr1, __stubs_end
1:ldrr3, [r0], #4
strr3, [r2], #4
cmpr0, r1
blt1b
LOADREGS(fd, sp!, {r4 - r6, pc})
以上可以看出这个函数初始化了中断向量,实际上把相应的跳转指令拷贝到了对应的地址。
当发生中断时,不管是从用户模式还是管理模式调用的,最终都要调用do_IRQ():
__irq_usr:subsp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12}@ save r0 - r12
ldrr4, .LCirq
addr8, sp, #S_PC
ldmia r4, {r5 - r7}@ get saved PC, SPSR
stmia r8, {r5 - r7}@ save pc, psr, old_r0
stmdbr8, {sp, lr}^
alignment_trap r4, r7, __temp_irq
zero_fp
1:get_irqnr_and_base r0, r6, r5, lr
movner1, sp
adrsvcne, lr, 1b
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
bnedo_IRQ@ 调用do_IRQ来实现具体的中断处理
movwhy, #0
get_current_task tsk
bret_to_user
对于以上代码,在很多文章中都有过分析,这里不再赘述。
Linux每个中断通过一个结构irqdesc来描述,各中断的信息都在这个结构中得以体现:
struct irqdesc {
unsigned intnomask: 1;/* IRQ does not mask in IRQ*/
unsigned intenabled: 1;/* IRQ is currently enabled*/
unsigned inttriggered: 1;/* IRQ has occurred*/
unsigned intprobing: 1;/* IRQ in use for a probe*/
unsigned intprobe_ok : 1;/* IRQ can be used for probe*/
unsigned intvalid: 1;/* IRQ claimable*/
unsigned intnoautoenable : 1;/* don't automatically enable IRQ */
unsigned intunused: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 intlck_cnt;
unsigned intlck_pc;
unsigned intlck_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
.macrodisable_fiq
.endm
.macroget_irqnr_and_base, irqnr, irqstat, base, tmp
movr4, #INTBASE@ virtual address of IRQ registers
ldrirqnr, [r4, #0x8]@ read INTMSK中断掩码寄存器
ldrirqstat, [r4, #0x10]@ read INTPND中断寄存器
bicsirqstat, irqstat, irqnr
bicsirqstat, irqstat, irqnr
beq1002f
movirqnr, #0
1001:tstirqstat, #1
bne1002f@ found IRQ
addirqnr, irqnr, #1
movirqstat, irqstat, lsr #1
cmpirqnr, #32
bcc1001b
1002:
.endm
.macroirq_prio_table
.endm
以上代码也告诉了我们,中断号的确定,其实是和S3C2410手册中SRCPND寄存器是一致的,即:
/* Interrupt Controller */
#define IRQ_EINT00/* External interrupt 0 */
#define IRQ_EINT11/* External interrupt 1 */
#define IRQ_EINT22/* External interrupt 2 */
#define IRQ_EINT33/* External interrupt 3 */
#define IRQ_EINT4_74/* External interrupt 4 ~ 7 */
#define IRQ_EINT8_235/* External interrupt 8 ~ 23 */
#define IRQ_RESERVED66/* Reserved for future use */
#define IRQ_BAT_FLT7
#define IRQ_TICK8/* RTC time tick interrupt*/
#define IRQ_WDT9/* Watch-Dog timer interrupt */
#define IRQ_TIMER010/* Timer 0 interrupt */
#define IRQ_TIMER111/* Timer 1 interrupt */
#define IRQ_TIMER212/* Timer 2 interrupt */
#define IRQ_TIMER313/* Timer 3 interrupt */
#define IRQ_TIMER414/* Timer 4 interrupt */
#define IRQ_UART215/* UART 2 interrupt*/
#define IRQ_LCD16/* reserved for future use */
#define IRQ_DMA017/* DMA channel 0 interrupt */
#define IRQ_DMA118/* DMA channel 1 interrupt */
#define IRQ_DMA219/* DMA channel 2 interrupt */
#define IRQ_DMA320/* DMA channel 3 interrupt */
#define IRQ_SDI21/* SD Interface interrupt */
#define IRQ_SPI022/* SPI interrupt */
#define IRQ_UART123/* UART1 receive interrupt */
#define IRQ_RESERVED2424
#define IRQ_USBD25/* USB device interrupt */
#define IRQ_USBH26/* USB host interrupt */
#define IRQ_IIC27/* IIC interrupt */
#define IRQ_UART028/* UART0 transmit interrupt */
#define IRQ_SPI129/* UART1 transmit interrupt */
#define IRQ_RTC30/* RTC alarm interrupt */
#define IRQ_ADCTC31/* ADC EOC interrupt */
#define NORMAL_IRQ_OFFSET 32
这些宏定义在文件irqs.h中,大家可以看到它的定义取自S3C2410的文档。
总结: linux在初始化的时候已经把每个中断向量的地址准备好了!就是说添加中断服务程序的框架已经给出,当某个中断发生时,将会到确定的地址处去找指令,所以我们做驱动程序时,只需要经过request_irq()来挂接自己编写的中断服务程序即可。
另:对于快速中断,linux在初始化时是空的,所以要对它挂接中断处理程序,就需要单独的函数set_fiq_handler()来实现,此函数在源文件fiq.c中,有兴趣的读者可进一步研究。