本文基于Linux kernel 4.19.0, 体系结构是aarch64
中断处理入口
在ARM GICv3 GIC代码分析一文中,有分析到在GIC 控制器初始化时会设置ARM中断控制器的中断处理函数 handle_arch_irq。
static int __init gic_init_bases(void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
set_handle_irq(gic_handle_irq); ------- (1)
}
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
return 0;
}
#endif
那么handle_arch_irq被谁调用的呢?
中断可以通过异常向量表的形式提交给 CPU,CPU 在发生硬件中断时跳转到 el1_irq 或者 el0_irq 这样的函数体内
arch/arm64/kernel/entry.S 文件的函数 elx_irq 中进入 irq_handler 这个宏函数
.macro irq_handler
ldr_l x1, handle_arch_irq
mov x0, sp
irq_stack_entry
blr x1
irq_stack_exit
.endm
.text
因此arm64 中断的入口函数从中断向量表中__irq_svc–>irq_handler–>handle_arch_irq–>gic_handle_irq
GIC控制器中断处理过程
gic_handle_irq:[drivers/irqchip/irq-gic-v3.c]
static asmlinkage void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqnr;
do {
irqnr = gic_read_iar(); ------------- (1)
if (likely(irqnr > 15 && irqnr < 1020) || irqnr >= 8192) { ------------ (2)
int err;
if (static_branch_likely(&supports_deactivate_key))
gic_write_eoir(irqnr); ------------(3)
else
isb();
err = handle_domain_irq(gic_data.domain, irqnr, regs); ------------- (4)
if (err) {
WARN_ONCE(true, "Unexpected interrupt received!\n");
if (static_branch_likely(&supports_deactivate_key)) {
if (irqnr < 8192)
gic_write_dir(irqnr);
} else {
gic_write_eoir(irqnr);
}
}
continue;
}
if (irqnr < 16) {
gic_write_eoir(irqnr);
if (static_branch_likely(&supports_deactivate_key))
gic_write_dir(irqnr); ------------- (5)
#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);
}
(1) gic_read_iar。CPU通过读取GIC控制器的GICC_IAR( Interrupt Acknowledge Register)寄存器, 应答该中断, 并且可以得到当前发生中断的是哪一个硬件中断号。
(2) 硬件中断号0-15表示SGI类型的中断,15~1020 表示外设中断(SPI或PPI类型),8192 - MAX表示LPI类型的中断。
硬件中断号 | 中断类型 |
---|---|
0-15 | SGI |
16 - 31 | PPI |
32 - 1019 | SPI |
1020 - 1023 | 用于指示特殊情况的特殊中断 |
1024 - 8191 | Reservd |
8192 - MAX | LPI |
(3) 和 (5) : gic_write_eoir 和 gic_write_dir放在一起分析。这两个函数分别往ICC_EOIR1_EL1和ICC_DIR_EL1寄存器中写入硬件中断号。
ICC_EOIR1_EL1, Interrupt Controller End Of Interrupt Register 1, 对寄存器的写操作表示中断的结束
ICC_DIR_EL1, Interrupt Controller Deactivate Interrupt Register, 对该寄存器的写操作将deactive指定的中断。
那么问题来了? 假设读取到了一个SPI中断, 为什么一开始就写EOI表示中断结束,此时中断处理不是还没有执行么?
在GIC v3协议中定义, 处理完中断后,软件必须通知中断控制器已经处理了中断,以便状态机可以转换到下一个状态。
GICv3架构将中断的完成分为2个阶段:
Priority Drop: 将运行优先级降回到中断之前的值。
**Deactivation:**更新当前正在处理的中断的状态机。 从活动状态转换到非活动状态。
这两个阶段可以在一起完成,也可以分为2步完成。 却决于EOImode的值。
如果EOIMode = 0, 对ICC_EOIR1_EL1寄存器的操作代表2个阶段(priority drop 和 deactivation)一起完成。
如果EOImode = 1, 对ICC_EOIR1_EL1寄存器的操作只会导致Priority Drop, 如果想要表示中断已经处理完成,还需要写ICC_DIR_EL1。
所以回答上面的问题, 当前Linux GIC的代码,默认irq chip是EIOmode=1, 所以单独的写EOIR1_EL1不是代表中断结束。
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
if (static_branch_likely(&supports_deactivate_key))
chip = &gic_eoimode1_chip;
}
(4)handle_domain_irq. 中断控制器中断处理的主体。
handle_domain_irq:[kernel/irq/irqdesc.c]
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;
irq_enter(); ------------------- (1)
#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq); --------------- (2)
#endif
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq); ---------------- (4)
}
irq_exit(); --------------- (3)
set_irq_regs(old_regs);
return ret;
}
(1) irq_enter显式的告诉Linux 内核现在要进入中断上下文了。
#define __irq_enter() \
do { \
account_irq_enter_time(current); \
preempt_count_add(HARDIRQ_OFFSET); \
trace_hardirq_enter(); \
} while (0)
__irq_enter宏通过preempt_count_add()增加当前进程struct thread_info中的preempt_count成员里的HARDIRQ域的值。
(2) irq_find_mapping()通过硬件中断号去查找IRQ中断号。
(3) irq_exit() 表示硬件中断处理已经完成。与irq_enter()相反, 通过preempt_count_sub(),减少HARDIRQ域的值。
(4) 接下来看generic_handle_irq()函数
generic_handle_irq:[kernel/irq/irqdesc.c]
generic_handle_irq–>generic_handle_irq_desc
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc); ------------- (1)
}
(1) 调用desc->handle_irq指向的回调函数.
这个回调函数是在哪里定义的呢?
在解析ACPI表或者DTS时,会枚举映射中断号(参考:硬中断和软中断的映射)
irq_of_parse_and_map-> irq_create_of_mapping->irq_create_fwspec_mapping->irq_domain_alloc_irqs
irq_domain_alloc_irqs会调用irq_domain_ops定义的alloc函数
static const struct irq_domain_ops gic_irq_domain_ops = {
.translate = gic_irq_domain_translate,
.alloc = gic_irq_domain_alloc,
.free = gic_irq_domain_free,
.select = gic_irq_domain_select,
};
gic_irq_domain_alloc()->gic_irq_domain_map(), 然后定义irq_desc->handle_irq的回调函数
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
struct irq_chip *chip = &gic_chip;
if (static_branch_likely(&supports_deactivate_key))
chip = &gic_eoimode1_chip;
/* PPIs */
if (hw < 32) {
irq_set_percpu_devid(irq);
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);
irq_set_status_flags(irq, IRQ_NOAUTOEN);
}
/* SPIs */
if (hw >= 32 && hw < gic_data.irq_nr) {
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
irq_set_probe(irq);
irqd_set_single_target(irq_desc_get_irq_data(irq_to_desc(irq)));
}
/* LPIs */
if (hw >= 8192 && hw < GIC_ID_NR) {
if (!gic_dist_supports_lpis())
return -EPERM;
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
}
return 0;
}
SPI和LPI类型的中断, desc->handler()回调函数是handle_fasteoi_irq()。
PPI类型的中断, desc->handler()回调函数是handle_percpu_devid_irq()。
handle_fasteoi_irq:[kernel/irq/chip.c]
void handle_fasteoi_irq(struct irq_desc *desc)
{
struct irq_chip *chip = desc->irq_data.chip;
raw_spin_lock(&desc->lock);
if (!irq_may_run(desc))
goto out;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
/*
* If its disabled or no action available
* then mask it and get out of here:
*/
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) { -------- (1)
desc->istate |= IRQS_PENDING;
mask_irq(desc);
goto out;
}
kstat_incr_irqs_this_cpu(desc); ------------- (2)
if (desc->istate & IRQS_ONESHOT)
mask_irq(desc);
preflow_handler(desc);
handle_irq_event(desc); -------------- (3)
cond_unmask_eoi_irq(desc, chip); -------------- (4)
raw_spin_unlock(&desc->lock);
return;
out:
if (!(chip->flags & IRQCHIP_EOI_IF_HANDLED))
chip->irq_eoi(&desc->irq_data);
raw_spin_unlock(&desc->lock);
}
EXPORT_SYMBOL_GPL(handle_fasteoi_irq);
(1) 如果某个中断没有定义action描述符或者该中断被关闭了IRQD_IRQ_DISABLED, 那么设置该中断状态为IRQS_PENDING, 并调用irq_mask()函数屏蔽该中断。
(2) 我们一般在终端通过cat /proc/intterrupts查看中断计数, 这个计数是在这里进行增加的。
(3) handle_irq_event()是中断处理的核心函数,开始真正的处理硬件中断了。这一部分放在下面分析。
(4) cond_unmask_eoi_irq(). 呼应gic_handle_irq入口函数的EOI处理, 这里写EOI寄存器,表示完成了硬中断处理。
handle_irq_event:[kernel/irq/handle.c]
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
ret = handle_irq_event_percpu(desc);
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
首先把pending标志位清除,然后设置IRQD_IRQ_INPROGRESS标志,表示正在处理硬件中断。
handle_irq_event()->handle_percpu_devid_irq()->__handle_irq_event_percpu()
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
record_irq_time(desc);
for_each_action_of_desc(desc, action) {
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id); -------------- (1)
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); ----------- (2)
/* Fall through to add to randomness */
case IRQ_HANDLED: ---------- (3)
*flags |= action->flags;
break;
default:
break;
}
retval |= res;
}
return retval;
}
(1) 用irq号找到对应的irq_desc,irq_desc->action->handler就是注册中断时,申请的中断处理函数。
(2) 如果中断号的处理函数返回IRQ_WAKE_THREAD,表示需要唤醒中断线程。
__irq_wake_thread()->唤醒中断线程->irq_thread()
static irqreturn_t irq_thread_fn(struct irq_desc *desc,
struct irqaction *action)
{
irqreturn_t ret;
ret = action->thread_fn(action->irq, action->dev_id);
irq_finalize_oneshot(desc, action);
return ret;
}
action()->thread_fn()对应的是request_threaded_irq()时, 中断线程执行的部分.
中断申请API:request_threaded_irq
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
@handler: Function to be called when the IRQ occurs. Primary handler for threaded interrupts.
If NULL and thread_fn != NULL the default primary handler is installed
@thread_fn: Function called from the irq handler thread.
If NULL, no irq thread is created
(3) 如果中断号的处理函数返回IRQ_HANDLED,说明该action的中断处理函数已经处理完毕。
总结
以一个流程图对本文进行补充: