[玩转kernel系列]中断
ARM里的中断通常是指IRQ和FIQ,以IRQ来讲,ARM对IRQ的处理过程大概是这样:
外部设备遇到某一事件发出一个IRQ中断给中断控制器,中断控制器对这个IRQ进行硬件上的处理,把一些信息记在中断控制器的寄存器上,然后中断控制器通过IRQ中断线给ARM发一个信号。ARM收到信号,开始进行以下处理:
(1)将当前状态的cpsr拷贝到IRQ状态的spsr中。
(2)将pc拷贝到IRQ状态的lr中。
(3)屏蔽cpsr中的IRQ位和FIQ位。
(4)跳转入中断向量表的IRQ表项执行(改变pc的值)。
以上都是ARM cpu做的事,不需要程序员插手,程序员编的代码需要接在后面处理。
程序员需要做的是接下来的步骤:
(5) 备份上下文。
e.g.:
sub lr, lr, #4
stmfd sp!, {r0 - r12, lr}
(6)跳入handler。
e.g.:
bl IRQ_Handler
(7):恢复上下文。
e.g.:
ldmfd sp!, {r0 - r12, sp}^
就这么简单!
来看kernel对应的代码
/* arch/arm/kernel/entry-armv.S */
-
__irq_svc:
-
svc_entry
-
-
#ifdef CONFIG_TRACE_IRQFLAGS
-
bl trace_hardirqs_off
-
#endif
-
#ifdef CONFIG_PREEMPT
-
get_thread_info tsk
-
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
-
add r7, r8, #1 @ increment it
-
str r7, [tsk, #TI_PREEMPT]
-
#endif
-
-
irq_handler
-
#ifdef CONFIG_PREEMPT
-
str r8, [tsk, #TI_PREEMPT] @ restore preempt count
-
ldr r0, [tsk, #TI_FLAGS] @ get flags
-
teq r8, #0 @ if preempt count != 0
-
movne r0, #0 @ force flags to 0
-
tst r0, #_TIF_NEED_RESCHED
-
blne svc_preempt
-
#endif
-
ldr r0, [sp, #S_PSR] @ irqs are already disabled
-
msr spsr_cxsf, r0
-
#ifdef CONFIG_TRACE_IRQFLAGS
-
tst r0, #PSR_I_BIT
-
bleq trace_hardirqs_on
-
#endif
-
ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr
就是高亮的三句。
首尾两句简单,关键是中间的handler,来看handler的源码。
/* arch/arm/kernel/entry-armv.S */
handler里最重要的事情就是取得中断号,然后根据中断号去索引到具体的ISR。具体的ISR就在Do_asm_IRQ里啦,来看这个函数的源码。
/* arch/arm/kernel/irq.c */
-
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
-
{
-
struct pt_regs *old_regs = set_irq_regs(regs);
-
struct irq_desc *desc = irq_desc + irq;
-
-
/*
-
* Some hardware gives randomly wrong interrupts. Rather
-
* than crashing, do something sensible.
-
*/
-
if (irq >= NR_IRQS)
-
desc = &bad_irq_desc;
-
-
irq_enter();
-
-
desc_handle_irq(irq, desc);
-
-
/* AT91 specific workaround */
-
irq_finish(irq);
-
-
irq_exit();
-
set_irq_regs(old_regs);
-
}
其 中核心的这句话是desc_handle_irq(irq,desc);跳入后发现这个函数就调用了一句 话,desc->handle_irq(irq, desc),好,具体的ISR就在里面,是通过多态函数的方法调用的,而这个多态函数是在start_kernel中的一些初始化程序中挂上去的。
也 许你想看一个具体的handle_irq(irq, desc),看一下desc->handle_irq()到底指到哪里去了。但是kernel里的东西就是复杂,你想一眼看穿它,是不那么容易的。 所以我们暂且把代码扔掉,来想一想,这个handle_irq()到底是做什么的。很明显,它至少会起ISR的作用,不管它另外做了什么乱七八糟的事 情,ISR是它的重要目的。那我们先来找一个ISR,我们的开发板At91Sam9261上的网卡是DM9000,我们就到DM9000的驱动里去找 ISR。
/* drivers\net\dm9000.c */
好,找到request_irq()就行了,其中有一个参数就是ISR,也就是dm9000_interrupt(),让我们来看这个ISR。
/* drivers\net\dm9000.c */
这 个很明显就是做的一些中断之后真正需要干的事,所以这是整个中断体系中非常重要的一个环节。你可以不知道kernel是怎么绕来绕去绕到这个函数,但要知 道怎么去写这个函数。处理每一种中断的ISR具有很大的差异,需要不断的去认识,而且难度相对于kernel来说,ISR还是比较低的,但是如我前面所 说,它很重要。继续我们的kernel之旅。
我们看到dm9000_interrupt()这个ISR,参数和handle_irq(irq, desc)不一样。
static irqreturn_t dm9000_interrupt(int irq, void *dev_id)这个函数,第二个参数是void*,而handle_irq的第二个参数是struct irq_desc*,所以desc->handle_irq(irq, desc)调用的不是ISR。我说过这个多态函数是在start_kernel里的某个init函数里初始化进去的,init_IRQ();
/* arch\arm\kernel\irq.c */
先看NR_IRQS,一个宏,在9261这块板子指定的头文件里,定义的是
/* arch\arm\mach-at91\include\mach\irq.h */
32+5*32=192。跳入init_arch_irq(),可惜init_arch_irq()又是个多态函数,而且kernel这次用全局函数指针的方式来封装了这个多态函数。
/* ar ch\arm\kernel\irq.c */
-
void (*init_arch_irq)(void) __initdata = NULL;
/* arch\arm\kernel\setup.c */
-
void __init setup_arch(char **cmdline_p)
-
{
-
struct tag *tags = (struct tag *)&init_tags;
-
struct machine_desc *mdesc;
-
char *from = default_command_line;
-
-
setup_processor();
-
mdesc = setup_machine(machine_arch_type);
-
machine_name = mdesc->name;
-
-
if (mdesc->soft_reboot)
-
reboot_setup("s");
-
-
if (__atags_pointer)
-
tags = phys_to_virt(__atags_pointer);
-
else if (mdesc->boot_params)
-
tags = phys_to_virt(mdesc->boot_params);
-
-
/*
-
* If we have the old style parameters, convert them to
-
* a tag list.
-
*/
-
if (tags->hdr.tag != ATAG_CORE)
-
convert_to_tag_list(tags);
-
if (tags->hdr.tag != ATAG_CORE)
-
tags = (struct tag *)&init_tags;
-
-
if (mdesc->fixup)
-
mdesc->fixup(mdesc, tags, &from, &meminfo);
-
-
if (tags->hdr.tag == ATAG_CORE) {
-
if (meminfo.nr_banks != 0)
-
squash_mem_tags(tags);
-
save_atags(tags);
-
parse_tags(tags);
-
}
-
-
init_mm.start_code = (unsigned long) &_text;
-
init_mm.end_code = (unsigned long) &_etext;
-
init_mm.end_data = (unsigned long) &_edata;
-
init_mm.brk = (unsigned long) &_end;
-
-
memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
-
boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
-
parse_cmdline(cmdline_p, from);
-
paging_init(&meminfo, mdesc);
-
request_standard_resources(&meminfo, mdesc);
-
-
#ifdef CONFIG_SMP
-
smp_init_cpus();
-
#endif
-
-
cpu_init();
-
-
/*
-
* Set up various architecture-specific pointers
-
*/
-
init_arch_irq = mdesc->init_irq;
-
system_timer = mdesc->timer;
-
init_machine = mdesc->init_machine;
-
-
#ifdef CONFIG_VT
-
#if defined(CONFIG_VGA_CONSOLE)
-
conswitchp = &vga_con;
-
#elif defined(CONFIG_DUMMY_CONSOLE)
-
conswitchp = &dummy_con;
-
#endif
-
#endif
-
early_trap_init();
-
}
其 实就是mdesc->init_irq,经验告诉我们,这个多态函数也是在start_kernel的那一堆初始化函数里挂上去的,但是不容易找到 是哪一个。我们只好来反推,这需要一点经验,其实init_interrupts不止一个地方有,kernel里有很多,我们找到了一个与平台相关的 init_interrupts,对于我的开发板9261来说,是at91sam9261_init_interrupts()这个函数。
/* arch\arm\mach-at91 */
我觉得看代码的时候,不能死盯着代码跳来跳去,这是很低级的,更应该想一想整体的架构,往往都是有一定逻辑的。像我们看到平台相关的初始化,应该能想到,会有其他平台无关的函数来调它,于是grep之,果不其然。
/* arch\arm\mach-at91\board-sam9261ek.c */
再往上grep,我们发现。
-
MACHINE_START(AT91SAM9261EK, "Atmel AT91SAM9261-EK")
-
/* Maintainer: Atmel */
-
.phys_io = AT91_BASE_SYS,
-
.io_pg_offst = (AT91_VA_BASE_SYS >> 18) & 0xfffc,
-
.boot_params = AT91_SDRAM_BASE + 0x100,
-
.timer = &at91sam926x_timer,
-
.map_io = ek_map_io,
-
.init_irq = ek_init_irq,
-
.init_machine = ek_board_init,
-
MACHINE_END
如果你不知道这件马甲似的MACHINE_START...MACHINE_END,把定义找出来。
/* arch\arm\include\asm\mach\arch.h */
额,这件马甲里装的是一个结构体,而且这个结构体的类型是,,,struct machine_desc,mdesc->init_irq 中的mdesc就是这个类型的指针,呵呵,应该想到了吧,ek_init_irq就是mdesc->init_irq的一个特例,这确实证据不够确 凿,不过先不用管这么多,说不定你以后看到MACHINE_START...MACHINE_END就知道是干嘛的了。
现在我们重新来看
/* arch\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_NOPROBE;
-
-
#ifdef CONFIG_SMP
-
bad_irq_desc.affinity = CPU_MASK_ALL;
-
bad_irq_desc.cpu = smp_processor_id();
-
#endif
-
init_arch_irq();
-
}
把上面绕来绕去的在整理一下,得出它的一个特例就是
其实道理很简单,在init_IRQ里需要把ISR和中断号关联起来,通过中断机制是可以读到中断号的,所以只要关联起来就可以索引到ISR。而这里是用一个结构体irq_desc与中断号相关联,而ISR,就包装在这个结构体里面。让我们来看这个诡异的结构体映射方式。
/* include\linux\Irq.h */
ISR就和那个指针有关,详细点说,那个指针是irqaction链表的头指针,而每个irqaction节点里包含一个ISR。
/* include\linux\interrupt.h */
handler是一个函数指针,指向一个类型为irqreturn_t,参数为int和void*的函数。再回过头去看看我们的ISR。
另外把链表这个词记住,以后一提ISR,就想到有那么一个链表。
好,来看那个全局数组,这就是那个索引表,原来建一张索引表就这么容易,就是一个结构体数组。不过数组名和类型名一模一样是不是有点过分阿。
现在看前面那个for循环,就知道,它是针对每个索引,在往空的表项里灌内容,也就是构造表项。其实后面也差不多就是在构造,其中一个核心就应该是把海量的ISR灌到它们应该去的表项里去。最终做到中断号索引和ISR表项能够正确映射。
来看 at91_aic_init()。
/* arch\arm\mach-at91\irq.c */
-
void __init at91_aic_init(unsigned int priority[NR_AIC_IRQS])
-
{
-
unsigned int i;
-
-
/*
-
* The IVR is used by macro get_irqnr_and_base to read and verify.
-
* The irq number is NR_AIC_IRQS when a spurious interrupt has occurred.
-
*/
-
for (i = 0; i < NR_AIC_IRQS; i++) {
-
/* Put irq number in Source Vector Register: */
-
at91_sys_write(AT91_AIC_SVR(i), i);
-
/* Active Low interrupt, with the specified priority */
-
at91_sys_write(AT91_AIC_SMR(i), AT91_AIC_SRCTYPE_LOW | priority[i]);
-
-
set_irq_chip(i, &at91_aic_chip);
-
set_irq_handler(i, handle_level_irq);
-
set_irq_flags(i, IRQF_VALID | IRQF_PROBE);
-
-
/* Perform 8 End Of Interrupt Command to make sure AIC will not Lock out nIRQ */
-
if (i < 8)
-
at91_sys_write(AT91_AIC_EOICR, 0);
-
}
-
-
/*
-
* Spurious Interrupt ID in Spurious Vector Register is NR_AIC_IRQS
-
* When there is no current interrupt, the IRQ Vector Register reads the value stored in AIC_SPU
-
*/
-
at91_sys_write(AT91_AIC_SPU, NR_AIC_IRQS);
-
-
/* No debugging in AIC: Debug (Protect) Control Register */
-
at91_sys_write(AT91_AIC_DCR, 0);
-
-
/* Disable and clear all interrupts initially */
-
at91_sys_write(AT91_AIC_IDCR, 0xFFFFFFFF);
-
at91_sys_write(AT91_AIC_ICCR, 0xFFFFFFFF);
-
}
除了构造索引表就是操作硬件接口,我们只看核心的那句。跳入set_irq_handler()。
/* include\linux\Irq.h */
-
static inline void
-
set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
-
{
-
__set_irq_handler(irq, handle, 0, NULL);
-
}
/* kernel\irq\chip.c */
似曾相识的desc->handle_irq,到上面去找找,之前发现desc->handle_irq(irq, desc)调的不是ISR,那是谁呢?现在看来是handle,handle是什么?传进来的参数,是那个handle_level_irq,跳入。
/ * kernel\irq\chip.c */
-
void
-
handle_level_irq(unsigned int irq, struct irq_desc *desc)
-
{
-
unsigned int cpu = smp_processor_id();
-
struct irqaction *action;
-
irqreturn_t action_ret;
-
-
spin_lock(&desc->lock);
-
mask_ack_irq(desc, irq);
-
-
if (unlikely(desc->status & IRQ_INPROGRESS))
-
goto out_unlock;
-
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
-
kstat_cpu(cpu).irqs[irq]++;
-
-
/*
-
* If its disabled or no action available
-
* keep it masked and get out of here
-
*/
-
action = desc->action;
-
if (unlikely(!action || (desc->status & IRQ_DISABLED)))
-
goto out_unlock;
-
-
desc->status |= IRQ_INPROGRESS;
-
spin_unlock(&desc->lock);
-
-
action_ret = handle_IRQ_event(irq, action);
-
if (!noirqdebug)
-
note_interrupt(irq, desc, action_ret);
-
-
spin_lock(&desc->lock);
-
desc->status &= ~IRQ_INPROGRESS;
-
if (!(desc->status & IRQ_DISABLED) && desc->chip->unmask)
-
desc->chip->unmask(irq);
-
out_unlock:
-
spin_unlock(&desc->lock);
-
}
可爱的参数,终于匹配了,看来desc->handle_irq(irq, desc)调的就是这个handle_level_irq 了,并且,往下看,它还把desc的马甲脱了,唤出了action,并且又调了一个函数。前面说了,action是一个链表头指针,这个链表里就有包含着 ISR阿,看来离调ISR越来越近了。另外要说的是,这里的传进来得desc,是和中断号相匹配的desc表项,不信到前面去看
asmlinkage void __exception asm_do_IRQ()这个函数。这里用全局结构体数组就轻松的实现了一张颇有规模的索引表,还是值得借鉴。好,跳入handle_IRQ_event(irq, action)。
/* kernel\irq\handle.c */
-
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
-
{
-
irqreturn_t ret, retval = IRQ_NONE;
-
unsigned int status = 0;
-
-
handle_dynamic_tick(action);
-
-
if (!(action->flags & IRQF_DISABLED))
-
local_irq_enable_in_hardirq();
-
-
do {
-
ret = action->handler(irq, action->dev_id);
-
if (ret == IRQ_HANDLED)
-
status |= action->flags;
-
retval |= ret;
-
action = action->next;
-
} while (action);
-
-
if (status & IRQF_SAMPLE_RANDOM)
-
add_interrupt_randomness(irq);
-
local_irq_disable();
-
-
return retval;
-
}
多么明显的一个遍历链表的操作阿,我们看到了action->handler(),这就是ISR!看来一个中断号对应的不是一个ISR,而是一群 ISR。那我们在驱动里注册ISR的时候是不是也是把它注册到一个链表呢,当然是啦,只不过request_irq()封装了这一行为。
/* kernel\irq\manage.c */
-
int request_irq(unsigned int irq, irq_handler_t handler,
-
unsigned long irqflags, const char *devname, void *dev_id)
-
{
-
struct irqaction *action;
-
int retval;
-
-
#ifdef CONFIG_LOCKDEP
-
/*
-
* Lockdep wants atomic interrupt handlers:
-
*/
-
irqflags |= IRQF_DISABLED;
-
#endif
-
/*
-
* Sanity-check: shared interrupts must pass in a real dev-ID,
-
* otherwise we'll have trouble later trying to figure out
-
* which interrupt is which (messes up the interrupt freeing
-
* logic etc).
-
*/
-
if ((irqflags & IRQF_SHARED) && !dev_id)
-
return -EINVAL;
-
if (irq >= NR_IRQS)
-
return -EINVAL;
-
if (irq_desc[irq].status & IRQ_NOREQUEST)
-
return -EINVAL;
-
if (!handler)
-
return -EINVAL;
-
-
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
-
if (!action)
-
return -ENOMEM;
-
-
action->handler = handler;
-
action->flags = irqflags;
-
cpus_clear(action->mask);
-
action->name = devname;
-
action->next = NULL;
-
action->dev_id = dev_id;
-
-
#ifdef CONFIG_DEBUG_SHIRQ
-
if (irqflags & IRQF_SHARED) {
-
/*
-
* It's a shared IRQ -- the driver ought to be prepared for it
-
* to happen immediately, so let's make sure....
-
* We do this before actually registering it, to make sure that
-
* a 'real' IRQ doesn't run in parallel with our fake
-
*/
-
unsigned long flags;
-
-
local_irq_save(flags);
-
handler(irq, dev_id);
-
local_irq_restore(flags);
-
}
-
#endif
-
-
retval = setup_irq(irq, action);
-
if (retval)
-
kfree(action);
-
-
return retval;
-
}
创建一个action节点,用ISR等成员去构造它,然后把这个节点和对应的中断号扔给上层函数。
/* kernel\irq\manage.c */
-
int setup_irq(unsigned int irq, struct irqaction *new)
-
{
-
struct irq_desc *desc = irq_desc + irq;
-
struct irqaction *old, **p;
-
const char *old_name = NULL;
-
unsigned long flags;
-
int shared = 0;
-
int ret;
-
-
if (irq >= NR_IRQS)
-
return -EINVAL;
-
-
if (desc->chip == &no_irq_chip)
-
return -ENOSYS;
-
/*
-
* Some drivers like serial.c use request_irq() heavily,
-
* so we have to be careful not to interfere with a
-
* running system.
-
*/
-
if (new->flags & IRQF_SAMPLE_RANDOM) {
-
/*
-
* This function might sleep, we want to call it first,
-
* outside of the atomic block.
-
* Yes, this might clear the entropy pool if the wrong
-
* driver is attempted to be loaded, without actually
-
* installing a new handler, but is this really a problem,
-
* only the sysadmin is able to do this.
-
*/
-
rand_initialize_irq(irq);
-
}
-
-
/*
-
* The following block of code has to be executed atomically
-
*/
-
spin_lock_irqsave(&desc->lock, flags);
-
p = &desc->action;
-
old = *p;
-
if (old) {
-
/*
-
* Can't share interrupts unless both agree to and are
-
* the same type (level, edge, polarity). So both flag
-
* fields must have IRQF_SHARED set and the bits which
-
* set the trigger type must match.
-
*/
-
if (!((old->flags & new->flags) & IRQF_SHARED) ||
-
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
-
old_name = old->name;
-
goto mismatch;
-
}
-
-
#if defined(CONFIG_IRQ_PER_CPU)
-
/* All handlers must agree on per-cpuness */
-
if ((old->flags & IRQF_PERCPU) !=
-
(new->flags & IRQF_PERCPU))
-
goto mismatch;
-
#endif
-
-
/* add new interrupt at end of irq queue */
-
do {
-
p = &old->next;
-
old = *p;
-
} while (old);
-
shared = 1;
-
}
-
-
if (!shared) {
-
irq_chip_set_defaults(desc->chip);
-
-
/* Setup the type (level, edge polarity) if configured: */
-
if (new->flags & IRQF_TRIGGER_MASK) {
-
ret = __irq_set_trigger(desc->chip, irq, new->flags);
-
-
if (ret) {
-
spin_unlock_irqrestore(&desc->lock, flags);
-
return ret;
-
}
-
} else
-
compat_irq_chip_set_default_handler(desc);
-
#if defined(CONFIG_IRQ_PER_CPU)
-
if (new->flags & IRQF_PERCPU)
-
desc->status |= IRQ_PER_CPU;
-
#endif
-
-
desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
-
IRQ_INPROGRESS | IRQ_SPURIOUS_DISABLED);
-
-
if (!(desc->status & IRQ_NOAUTOEN)) {
-
desc->depth = 0;
-
desc->status &= ~IRQ_DISABLED;
-
if (desc->chip->startup)
-
desc->chip->startup(irq);
-
else
-
desc->chip->enable(irq);
-
} else
-
/* Undo nested disables: */
-
desc->depth = 1;
-
-
/* Set default affinity mask once everything is setup */
-
irq_select_affinity(irq);
-
}
-
-
*p = new;
-
-
/* Exclude IRQ from balancing */
-
if (new->flags & IRQF_NOBALANCING)
-
desc->status |= IRQ_NO_BALANCING;
-
-
/* Reset broken irq detection when installing new handler */
-
desc->irq_count = 0;
-
desc->irqs_unhandled = 0;
-
-
/*
-
* Check whether we disabled the irq via the spurious handler
-
* before. Reenable it and give it another chance.
-
*/
-
if (shared && (desc->status & IRQ_SPURIOUS_DISABLED)) {
-
desc->status &= ~IRQ_SPURIOUS_DISABLED;
-
__enable_irq(desc, irq);
-
}
-
-
spin_unlock_irqrestore(&desc->lock, flags);
-
-
new->irq = irq;
-
register_irq_proc(irq);
-
new->dir = NULL;
-
register_handler_proc(irq, new);
-
-
return 0;
-
-
mismatch:
-
#ifdef CONFIG_DEBUG_SHIRQ
-
if (!(new->flags & IRQF_PROBE_SHARED)) {
-
printk(KERN_ERR "IRQ handler type mismatch for IRQ %d\n", irq);
-
if (old_name)
-
printk(KERN_ERR "current handler: %s\n", old_name);
-
dump_stack();
-
}
-
#endif
-
spin_unlock_irqrestore(&desc->lock, flags);
-
return -EBUSY;
-
}
根 据中断号,取下索引表中的对应表项,取出其中action链表的表头指针,遍历链表,直到最后一项,将传进来的action节点加入该链表。这样我们的 DM9000 ISR就加入了中断号相关的ISR链表中了。这样arm一旦收到DM9000发出的中断,便通过中断控制器查出中断号,然后根据这个中断号跳入对应的 ISR链表,遍历执行,其中就会执行到我们的DM9000 ISR。而中断肯定是发生在kernel初始化之后,而我们在初始化的时候将中断号与ISR的索引表建立。为实际的中断跳入ISR做好了准备工作。另外提 一下中断号是通过查中断控制器的寄存器得到的,而前面这个函数叫 at91_aic_init(),aic全称(Advanced Interrupt Controller)。
另外还有个at91_gpio_irq_setup();其实和at91_aic_init()原理差不多,at91_aic_init()映射了中断号 0-31的中断源,31后面的中断源由at91_gpio_irq_setup()负责映射。不过这里面涉及到另外的一些知识,留到后面再讲。