arm-linux中断初始化分析

这篇文档准备简要的分析下arm平台上linux下的中断是如何运行的,本文将先分析初始化时的中断是如何建立的,然后以一个例子来注册一个中断,并详细分析中断触发到调用我们自己的中断例程的整个流程。
本文linux2.6.18的源码和s3c2410的CPU及smdk2410的板子为例来分析代码。
一 中断初始化:
大家都知道arm下规定,在0x00000000或0xffff0000的地址处必须存放一张跳转表, 其格式如下:


上面的这个表我们称之为”异常中断向量表”,表中的IRQ和FIQ位置就是用来存放处理中断函数的地址。至于具体选择在哪一个地址处存放该表,可由CPU的协处理器完成。如s3c2410下由CP15中寄存器1的位13来决定,我们可以通过设置该位来告诉系统我们的向量表在哪。
include/arch/asm-arm/proc-armv/system.h
#if __LINUX_ARM_ARCH__ >= 4    //at91rm9200是ARMV4结构
#define vectors_base() ((cr_alignment & CR_V) ? 0xffff0000 : 0)
#else
#define vectors_base() (0)
#endif

可以看到ARMv4以下的版本,该地址固定为0;ARMv4及以上版本,ARM中断向量表的地址由CP15协处理器c1寄存器中V位 (bit[13])控制,V和中断向量表的对应关系如下:

V=0    ~    0x00000000~0x0000001C
V=1    ~    0xffff0000~0xffff001C

因此,在中断初始化的时候我们要做的就是在IRQ和FIQ的位置处放置我们的中断处理函数地址或跳转语句跳转到我们的中断处理函数。这个过程是在trap_init中完成的,而它由start_kernel()调用。
arch/arm/kernel/traps.c
void __init trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;/*跳转表的存放位置(即上面那表的存放位置)*/
/*这些都在entry-armv.S下定义
*stub vector kuser_helper具体指什么?
*/
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(dst,src,count)
*/
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);
/*
* 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));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

上面这个函数主要就是在CONFIG_VECTORS_BASE处设置好那张跳转表, CONFIG_VECTORS_BASE在autoconf.h中定义(该文件自动成生),值为0xffff0000, 而CP15下的r1[13]在系统启动的时候在汇编部分就已经设置好 了。
接下来我们就看下__vectors_start,__vectors_end,__stubs_start,__stubs_end之间的内容。
arch/arm/kernel/entry-armv.S
 .globl __vectors_start
__vectors_start:
swi SYS_ERROR0
b vector_und + stubs_offset
ldr pc, .LCvswi + stubs_offset
b vector_pabt + stubs_offset
b vector_dabt + stubs_offset
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset
b vector_fiq + stubs_offset
.globl __vectors_end
__vectors_end:
.data

这就是那张跳转表. vector_irq, vector_fiq等函数我们后面在分析,他们就定义在__stubs_start,__stubs_end中.至此经过traps_init后,在0xffff0000处的跳转表就形成了.当产生IRQ时,将调用b vector_irq + stubs_offset.

在系统初始化的时候除了会调用trap_init外,还会调用init_IRQ函数(也由start_kernel调用),它初始化了一个全局中断描述符表(该表保存了每个中断的所有属性信息),并调用特定平台的中断初始化函数。
arm/arm/kernel/irq.c

void __init init_IRQ(void)
{
int irq;
/*初始化中断描述符表*/
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_DELAYED_DISABLE | IRQ_NOPROBE;
#ifdef CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();
#endif
init_arch_irq(); /*特定平台的中断初始化*/
}

系统中总共有NR_IRQS个中断,并且每个中断都有一个中断描述符,保存在irq_desc中,该描述符保存了该中断的所有属性信息。对于平台smdk2410来说init_arch_irq()就是s3c24xx_init_irq()函数, 这是在setup_arch()里面赋值的--setup_arch也是在start_kernel里面被调用的。后面的内容我们都以中断号IRQ_WDT为例来讲解:
arch/arm/mach-s3c2410/irq.c:

/* s3c24xx_init_irq
* Initialise S3C2410 IRQ system
*/
void __init s3c24xx_init_irq(void)
{
unsigned long pend;
unsigned long last;
int irqno;
int i;

irqdbf("s3c2410_init_irq: clearing interrupt status flags\n");
/* first, clear all interrupts pending... */
/*先清掉所有的pending标志位,该位代表是否系统中触发了一个中断*/
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C24XX_EINTPEND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C24XX_EINTPEND);
printk("irq: clearing pending ext status %08x\n", (int)pend);
last = pend;
}
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_INTPND);
if (pend == 0 || pend == last)
break;
__raw_writel(pend, S3C2410_SRCPND);
__raw_writel(pend, S3C2410_INTPND);
printk("irq: clearing pending status %08x\n", (int)pend);
last = pend;
}
last = 0;
for (i = 0; i < 4; i++) {
pend = __raw_readl(S3C2410_SUBSRCPND);
if (pend == 0 || pend == last)
break;
printk("irq: clearing subpending status %08x\n", (int)pend);
__raw_writel(pend, S3C2410_SUBSRCPND);
last = pend;
}
/* register the main interrupts */
/* 注册主要的中断*/
irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlers\n");
for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) {
/* set all the s3c2410 internal irqs */
switch (irqno) {
/* deal with the special IRQs (cascaded) */
case IRQ_EINT4t7:
case IRQ_EINT8t23:
case IRQ_UART0:
case IRQ_UART1:
case IRQ_UART2:
case IRQ_ADCPARENT:
set_irq_chip(irqno, &s3c_irq_level_chip);
set_irq_handler(irqno, do_level_IRQ);
break;

case IRQ_RESERVED6:
case IRQ_RESERVED24:
/* no IRQ here */
break;

default: /*IRQ_WDT 就是这条通路*/
//irqdbf("registering irq %d (s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_chip); /*为中断号设置chip*/
set_irq_handler(irqno, do_edge_IRQ); /*设 置中断例程*/
set_irq_flags(irqno, IRQF_VALID); /*设置中断ready的标记*/
}
}

/* setup the cascade irq handlers */
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint);
set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint);
set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);
/* external interrupts */
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irqdbf("registering irq %d (ext int)\n", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, do_edge_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}

for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq)\n", irqno);
set_irq_chip(irqno, &s3c_irqext_chip);
set_irq_handler(irqno, do_edge_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
/* register the uart interrupts */
irqdbf("s3c2410: registering external interrupts\n");
for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
irqdbf("registering irq %d (s3c uart0 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart0);
set_irq_handler(irqno, do_level_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart1);
set_irq_handler(irqno, do_level_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_uart2);
set_irq_handler(irqno, do_level_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
irqdbf("registering irq %d (s3c adc irq)\n", irqno);
set_irq_chip(irqno, &s3c_irq_adc);
set_irq_handler(irqno, do_edge_IRQ);
set_irq_flags(irqno, IRQF_VALID);
}
irqdbf("s3c2410: registered interrupt handlers\n");
}

上面这个函数结合s3c2410的data sheet很好理解,就是注册各个必要的中断,注意这里为每个中断号注册的中断例程只是个整体的函数,该函数只是处理一些共性的操作如清中断标记位等,他会进一步调用我们注册的中断例程来处理特定 的中断。如何注册中断会在后面分析。这个初始化函数调用了很多与中断相关的函数,我们逐个分析:
kernel/irq/chip.c:

/*
* set_irq_chip - set the irq chip for an irq
* @irq: irq number
* @chip: pointer to irq chip description structure
*/
/*为某个中断号设置一个chip*/
int set_irq_chip(unsigned int irq, struct irq_chip *chip)
{
struct irq_desc *desc;
unsigned long flags;
if (irq >= NR_IRQS) {
printk(KERN_ERR "Trying to install chip for IRQ%d\n", irq);
WARN_ON(1);
return -EINVAL;
}
if (!chip)
chip = &no_irq_chip;

desc = irq_desc + irq; /*获取保存该中断的中断描述符*/
spin_lock_irqsave(&desc->lock, flags);
irq_chip_set_defaults(chip); /*为chip设置一些默认的公共的操作函数*/
desc->chip = chip; /*为中断保存chip对象*/
/*
* For compatibility only:
*/
desc->chip = chip;
spin_unlock_irqrestore(&desc->lock, flags);

return 0;
}

为特定中断号初始化好chip对象,表示该中断号由这个chip控制,后面会调用到该中断号所属chip的相关函数,各个中断的chip是不同的,以IRQ_WDT为例,它的chip是s3c_irq_chip,请看:
arch/arm/mach-s3c2410/irq.c:

static struct irqchip s3c_irq_chip = {
.ack = s3c_irq_ack,
.mask = s3c_irq_mask,
.unmask = s3c_irq_unmask,
.set_wake = s3c_irq_wake
};

在set_irq_chip()中,还调用了irq_chip_set_defaults,目的是设置一些公共的开关操作。
kernel/irq/Chip.c:

/*
* Fixup enable/disable function pointers
*/
void irq_chip_set_defaults(struct irq_chip *chip)
{
if (!chip->enable)
chip->enable = default_enable;
if (!chip->disable)
chip->disable = default_disable;
if (!chip->startup)
chip->startup = default_startup;
if (!chip->shutdown)
chip->shutdown = chip->disable;
if (!chip->name)
chip->name = chip->typename;
}

从代码中看,很显然,如果chip没有相应的操作函数,则就给chip赋默认的操作函数。
我们仍然回到s3c24xx_init_irq中. 接着看set_irq_handler()
include/linux/Irq.h:

static inline void set_irq_handler(unsigned int irq, void fastcall (*handle)(unsigned int, struct irq_desc *, struct pt_regs *))
{
__set_irq_handler(irq, handle, 0);
}

kernel/irq/chip.c:

void __set_irq_handler(unsigned int irq, void fastcall (*handle)(unsigned int, irq_desc_t *, struct pt_regs *),int is_chained)
{
struct irq_desc *desc;
unsigned long flags;

if (irq >= NR_IRQS) { /*参数检查*/
printk(KERN_ERR
"Trying to install type control for IRQ%d\n", irq);
return;
}

desc = irq_desc + irq; /*获取中断描述符的存储地址*/

if (!handle)
handle = handle_bad_irq; /*赋默认的中断handle*/

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*/
}

spin_lock_irqsave(&desc->lock, flags);
/* Uninstall? */
if (handle == handle_bad_irq) {/*没有设置中断例程,这里先将该中断设置为disable,防止在这时以及在这时之前来了此中断,就进行了错误的处理,这样就悲剧了。*/
if (desc->chip != &no_irq_chip) {
desc->chip->mask(irq);
desc->chip->ack(irq);
}
desc->status |= IRQ_DISABLED; /*没有中断例程则disable掉该中断*/
desc->depth = 1;
}
desc->handle_irq = handle; /*保存中断例程,对于IRQ_WDT来说则是do_edge_IRQ */

/*由上面的调用可知,is_chained 始终等于0*/
if (handle != handle_bad_irq && is_chained) {
desc->status &= ~IRQ_DISABLED;//这个时候,就将IRQ_ENABLE了
desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
desc->depth = 0;
desc->chip->unmask(irq);
}
spin_unlock_irqrestore(&desc->lock, flags);
}

上面这个函数就是为特定的中断设置好一个中断处理例程.
Comment:这里的例程可不是我们request_irq注册的例程.
继续回到那个s3c24xx_init_irq函数,看set_irq_flags
arch/arm/kernel/irq.c:

void set_irq_flags(unsigned int irq, unsigned int iflags)
{
struct irqdesc *desc;
unsigned long flags;

if (irq >= NR_IRQS) {
printk(KERN_ERR "Trying to set irq flags for IRQ%d\n", irq);
return;
}

desc = irq_desc + irq;
spin_lock_irqsave(&desc->lock, flags);
desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
if (iflags & IRQF_VALID)
desc->status &= ~IRQ_NOREQUEST; /*清掉IRQ_NOREQUEST 标记*/
if (iflags & IRQF_PROBE)
desc->status &= ~IRQ_NOPROBE;
if (!(iflags & IRQF_NOAUTOEN))
desc->status &= ~IRQ_NOAUTOEN;
spin_unlock_irqrestore(&desc->lock, flags);
}

该函数主要是为特定的中断设置相应的状态标记, 而这里我们调用它的目的就是清掉IRQ_NOREQUEST标记,告诉系统该中断已经可以被申请使用了,中断在申请的时候会查看是否有IRQ_NOREQUEST标记,如有则表面该中断还不能使用。而初始化的时候所有的中断都有这个标记.
好,告一段落,基本上中断已经初始化差不多了.
参考:http://blog.csdn.net/aaronychen/archive/2008/09/03/2874643.aspx

评论

此博客中的热门博文

Linux/ARM Page Table Entry 属性设置分析

提交了30次才AC ---【附】POJ 2488解题报告

笔记