文章系列
linux中断子系统 - 中断及执行流程
linux中断子系统 - 申请中断
linux中断子系统 - irq_desc的创建
linux中断子系统 - 中断控制器的注册
linux中断子系统系列文章计划总共由4篇文章组成,本篇会通过中断的执行流来整体介绍一下中断,并引出其他文章的内容简介,中断的代码基本在kernel/irq目录下,中断控制器的代码在drivers/irqchip目录下
内核版本linux4.6.3
1. 中断相关结构体介绍
通过参照一下ULK3的IRQ描述符图,本图描述了linux4.6.3版本中断中各个结构体所代表的对象,struct irq_desc代表的是一个中断描述符,一个中断所需要的资源都集中在这个结构体中描述,如果没有定义选项CONFIG_SPARSE_IRQ,irq_desc会在系统初始化的时候被分配到一个数组中存放,其中数组下标代表的就是virq(虚拟中断,而不是硬中断),如果定义了选项那么irq_desc会被分配到radix tree中;
struct irq_data代表的是一个具体中断控制器,包括两个结构体,分别是irq_chip和irq_domain,其中irq_chip中的每个字段是描述了具体操作中断控制器的功能,irq_domain这个比较抽象,它主要的功能是对hwirq和virq创建映射,把某些中断所具有的同样功能抽象出来,然后组成一个domain,这样就可以被其他中断控制器所用,要理解这个结构体还是要多看代码仔细体会了;
struct irqaction代表一个中断要做什么,此结构体组成一个链表,因为一个中断线上可能会挂载好几个设备,这几个设备会共用这个中断,具体是哪个设备需要判断。
2. 中断进入执行流
2.1 ARM64
本文以arm64和gic-v3中断控制器为例来进行中断执行流的介绍,我把整个执行流分成三个阶段,第一阶段和第二阶段如下图所示
2.1.1 第一阶段
第一阶段的执行都是用汇编写的,开始进入el1的中断向量入口,然后会调用到handle_arch_irq,此变量为全局的,会在中断控制器初始化的时候设置函数变量,此处以gic-v3为例介绍,最后会调用gic_handle_irq,代码如下:
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqnr;
do {
irqnr = gic_read_iar();
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) {
int err;
if (static_key_true(&supports_deactivate))
gic_write_eoir(irqnr);
err = handle_domain_irq(gic_data.domain, irqnr, regs);---------进入第二阶段
if (err) {
WARN_ONCE(true, "Unexpected interrupt received!\n");
if (static_key_true(&supports_deactivate)) {
if (irqnr < 8192)
gic_write_dir(irqnr);
} else {
gic_write_eoir(irqnr);
}
}
continue;
}
if (irqnr < 16) {
gic_write_eoir(irqnr);
if (static_key_true(&supports_deactivate))
gic_write_dir(irqnr);
#ifdef CONFIG_SMP
/*
* Unlike GICv2, we don't need an smp_rmb() here.
* The control dependency from gic_read_iar to
* the ISB in gic_write_eoir is enough to ensure
* that any shared data read by handle_IPI will
* be read after the ACK.
*/
handle_IPI(irqnr, regs);
#else
WARN_ONCE(true, "Unexpected SGI received!\n");
#endif
continue;
}
} while (irqnr != ICC_IAR1_EL1_SPURIOUS);
}
2.1.2 第二阶段
第二阶段代码不复杂,首先找到virq,根据virq找到irq_desc,然后调用irq_desc->handle_irq,这里handle_irq是在申请中断的时候被设置,不过不管被设置成哪个函数最后都会调用handle_irq_event,此时进入第三阶段。
2.1.3 第三阶段
此阶段是真正执行申请中断时所要执行的函数,代码如下:
irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;
struct irqaction *action;
for_each_action_of_desc(desc, action) {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);------如果中断是共享的,此函数中一定要判断一下是不是自己设备的中断
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
irq, action->handler))
local_irq_disable();
switch (res) {
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
__irq_wake_thread(desc, action);
/* Fall through to add to randomness */
case IRQ_HANDLED:
flags |= action->flags;
break;
default:
break;
}
retval |= res;
}
add_interrupt_randomness(irq, flags);
if (!noirqdebug)
note_interrupt(desc, retval);
return retval;
}
并不是说arm64一定要以此流程来执行,而是因为arm64是后来出的架构,而handle_arch_irq也是后来才出现的,所以arm64就用新的框架来实现,而不会用以前的arch_irq_handler_default来实现,这个框架在arm32中也会使用,不过是要一定条件,如下一节介绍
2.2 ARM32
3. 中断返回
看一下中断返回的汇编
el1_irq:
kernel_entry 1
enable_dbg
#ifdef CONFIG_TRACE_IRQFLAGS
bl trace_hardirqs_off
#endif
get_thread_info tsk
irq_handler--------------------------------处理完,进入内核抢占
#ifdef CONFIG_PREEMPT
ldr w24, [tsk, #TI_PREEMPT] // get preempt count
cbnz w24, 1f // preempt count != 0
ldr x0, [tsk, #TI_FLAGS] // get flags
tbz x0, #TIF_NEED_RESCHED, 1f // needs rescheduling?
bl el1_preempt
#ifdef CONFIG_PREEMPT
el1_preempt:
mov x24, lr
1: bl preempt_schedule_irq // irq en/disable is done inside
ldr x0, [tsk, #TI_FLAGS] // get new tasks TI_FLAGS
tbnz x0, #TIF_NEED_RESCHED, 1b // needs rescheduling?
ret x24
#endif
change log
时间 | 修改 |
---|---|
2016.11.5 | 增加中断返回 |