分类: LINUX
2013-05-03 20:28:25
中断子系统是linux内核非常重要的一个子系统,中断是系统对设备进行管理最常见也可以说是最有效的一种方式,在各种设备驱动中我们都可以看到设备向系统申请注册中断的地方。在我们平常使用中断的时候,大多都是根据设备的中断号向系统申请注册(request_irq)一个中断及中断处理函数,然后实现好中断处理函数就差不多了。我们也都知道,当中断产生时,系统就会去执行中断处理函数,从而实现对设备的有效管理。那么,系统是如何实现这些的呢?
异常向量表的创建:
首先我们来了解当中断产生时,系统是如何响应中断的。
中断是作为异常的一种被CPU响应处理的。系统在启动过程中作建立一个异常向量表,当异常产生的时候,CPU就会通过异常向量表获得相应的异常处理地址,从而对相应的异常进行处理。在ARM架构中,异常向量的基地址有两个:0x00000000和0xffff0000。这个地址是可以通过设置而确定的。在我使用的系统当中它是0xffff0000。当异常产生时,CPU就会跳转到异常向量的基地址处,那么这个地方有什么呢?
在系统启动过程中,会定义一个异常向量表,并将表拷贝到0xffff0000这个地址。在traps.c中有void __init early_trap_init(void *vectors_base) {
unsigned long vectors = (unsigned long)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;
vectors_page = vectors_base;
/*
* 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);
……..
};
其中这个vectors_base就是异常向量基地址,它的值就是0xffff0000。其中最重要的两行代码就是:
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
它分别将__vectors_start和__stubs_start的内容拷贝到vectors和vectors+200处,下面我们来看看__vectors_start和__stubs_start,到底有什么。
在entry-armv.s中有:
__vectors_start:
ARM( swi SYS_ERROR0 )
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset
W(ldr) pc, .LCvswi + stubs_offset
W(b) vector_pabt + stubs_offset
W(b) vector_dabt + stubs_offset
W(b) vector_addrexcptn + stubs_offset
W(b) vector_irq + stubs_offset
W(b) vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
.globl __stubs_start
__stubs_start:
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.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
…….
.globl __stubs_end
__stubs_end:
__vectors_start:是定义的异常向量表,它被拷贝到0xffff0000处,当异常产生时,CPU跳转0xffff0000,也就是开始执行__vectors_start之后的代码。根据代码可知,不同的异常,它会跳转到不同的异常处理地址,如当中断产生时它会跳转到:
W(b) vector_irq + stubs_offset
__stubs_start是处理跳转的部分。我们把vector_stub irq, IRQ_MODE, 4 宏展开有:
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
@
@ Save r0, lr_
@ (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, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
msr spsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
THUMB( adr r0, 1f )
THUMB( ldr lr, [r0, lr, lsl #2] )
mov r0, sp
ARM( ldr lr, [pc, lr, lsl #2] )
movs pc, lr @ branch to handler in SVC mode
ENDPROC(vector_\name)
因此我们可以看到,_stubs_start中定义的vector_stub irq, IRQ_MODE, 4就是前面异常向量表中要使用到的vector_irq。它保存了一些寄存器的值,并根据CPU的状态跳转到__irq_usr 或者__irq_svn。
这里又有一个问题,为什么它不直接跳转vector_irq而且是跳转到vector_irq + stubs_offset,我们来看一下stubs_offset的定义:
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start。
前面有说了,__vectors_start被拷贝到0xffff0000处,而__stubs_start被拷贝到0xffff0000+0x200处,画个图来看一下:
如图所示,当中断异常产生时,CPU执行到W(b) vector_irq + stubs_offset时它会从当前位置(pc)跳转到vertor_irq处来处理中断异常,那么从pc到vertor_irq的距离offset是多少呢,就是0x200加上vertor_irq到__stub_start距离再减去vectors_start到pc的位置,也就是offset=0x200+vertor_irq-__stub_start-(pc-vertors_start)
=vertors_start+0x200-__stub_start+(vertor_irq-pc)
CPU执行的当前地址是pc,跳转距离是offset,那么目标地址就是pc+offset了,那么目标地址也就是pc+(vertor_irq-pc)+vertors_start+0x200-__stub_start也就是vertor_irq+stubs_offset了。
跳转到vertor_irq之后根据CPU状态会去执行__irq_usr或者__irq_svc,分析__irq_svc:
__irq_svc:
svc_entry
irq_handler
……
这里重点分析irq_handler:
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
从这里我们看到了,当执行到irq_handler的时候它就会去执行handler_arch_irq(默认是执行arch_irq_handler_default,但在我的系统当中它有定义CONFIG_MULTI_IRQ_HANDLER)。
到这里汇编部分执行完了,开始步入C代码当中。
执行中断处理函数:
根据上面异常向量表的分析可知,当中断产生后系统会去执行handler_arch_irq。那么在handler_arch_irq会做些什么呢?
系统启动里,会执行main.c的start_kernel()来对系统进行初始化,在start_kernel的setup_arch(&command_line)中有:
……
#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif
……
也就是handle_arch_irq保存的是mdesc->handle_irq;
在mdesc中保存是系统体系架构相关的一些初始化配置:
MACHIE_START(EMEDKB,“PXA988”)
.map_io=mmp_map_io,
.init_early=pxa988_init_early,
.init_irq=pxa988_init_irq,
.timer=&pxa988_timer,
.reserve=pxa988_reserve,
.handle_irq=gic_handle_irq,
.init_machine=emeidkb_init_common,
.restart=mmp_arch_reset,
MACHINE_END
这个结构比较重要,每个型号的主板都会有对应的一个这样的结构。
从这里可以看到,handle_arch_irq保存的是machine_desc结构的handle_irq指针:
asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & ~0x1c00;
if (likely(irqnr > 15 && irqnr < 1021)) {
irqnr = irq_find_mapping(gic->domain, irqnr);
handle_IRQ(irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
在这个函数里头,它首先从中断控制器中读取触发中断的硬件中断号,再通过一个映射表获取到软件中断号,然后进入handle_IRQ();再经过generic_handle_irq()->generic_handle_irq_desc():
static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
开头有讲,我们在设备驱动当中会根据中断号申请注册中断和中断处理函数,而中断产生后,在这里系统会根据中断号来执行相应的中断处理函数,也就是desc->handle_irq(irq,desc);到这里,当一次中断产生后,系统根据中断异常从异常向量表中获取并执行中断异常流程,然后根据handler_arch_irq关联到对应平台的中断控制器,从中断控制器中得到中断号,然后执行对应的中断处理函数的大致流程就是这样了。