1. 中断入口

要处理中断,必须为handle_arch_irq赋值。

ARM64自己定义handle_arch_irq的实现。

 1/// arch/arm64/kernel/irq.c
 2void (*handle_arch_irq)(struct pt_regs *) __ro_after_init = default_handle_irq;
 3void (*handle_arch_fiq)(struct pt_regs *) __ro_after_init = default_handle_fiq;
 4
 5int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
 6{
 7    if (handle_arch_irq != default_handle_irq)
 8        return -EBUSY;
 9
10    handle_arch_irq = handle_irq;
11    pr_info("Root IRQ handler: %ps\n", handle_irq);
12    return 0;
13}
14
15int __init set_handle_fiq(void (*handle_fiq)(struct pt_regs *))
16{
17    if (handle_arch_fiq != default_handle_fiq)
18        return -EBUSY;
19
20    handle_arch_fiq = handle_fiq;
21    pr_info("Root FIQ handler: %ps\n", handle_fiq);
22    return 0;
23}

ARM使能CONFIG_GENERIC_IRQ_MULTI_HANDLER

 1/// kernel/irq/handle.c
 2#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
 3void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;
 4#endif
 5/// ... ...
 6#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
 7int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
 8{
 9    if (handle_arch_irq)
10        return -EBUSY;
11
12    handle_arch_irq = handle_irq;
13    return 0;
14}
15
16/**
17 * generic_handle_arch_irq - root irq handler for architectures which do no
18 *                           entry accounting themselves
19 * @regs:	Register file coming from the low-level handling code
20 */
21asmlinkage void noinstr generic_handle_arch_irq(struct pt_regs *regs)
22{
23    struct pt_regs *old_regs;
24
25    irq_enter();
26    old_regs = set_irq_regs(regs);
27    handle_arch_irq(regs);
28    set_irq_regs(old_regs);
29    irq_exit();
30}
31#endif

1.1. arm32

arm32的中断入口分别在__irq_svc__irq_usr中,其内部会使用irq_handler来调用generic_handle_arch_irq,最终调用到handle_arch_irq

 1/// arch/arm/kernel/entry-armv.S
 2/*
 3 * Interrupt handling.
 4 */
 5    .macro  irq_handler, from_user:req
 6    mov r1, sp
 7    ldr_this_cpu r2, irq_stack_ptr, r2, r3
 8    .if \from_user == 0
 9    @
10    @ If we took the interrupt while running in the kernel, we may already
11    @ be using the IRQ stack, so revert to the original value in that case.
12    @
13    subs    r3, r2, r1      @ SP above bottom of IRQ stack?
14    rsbscs  r3, r3, #THREAD_SIZE    @ ... and below the top?
15#ifdef CONFIG_VMAP_STACK
16    ldr_va  r3, high_memory, cc @ End of the linear region
17    cmpcc   r3, r1          @ Stack pointer was below it?
18#endif
19    bcc 0f          @ If not, switch to the IRQ stack
20    mov r0, r1
21    /// 直接调用generic_handle_arch_irq
22    bl  generic_handle_arch_irq
23    b   1f
240:
25    .endif
26
27    /// 将generic_handle_arch_irq作为call_with_stack的参数
28    /// 在call_with_stack内调用
29    mov_l   r0, generic_handle_arch_irq
30    bl  call_with_stack
311:
32    .endm

1.2. arm64

handle_arch_irq作为参数,最终由do_interrupt_handler调用。

2. gic的中断处理函数

2.1. 实际的处理函数

 1# cat /proc/interrupts
 2           CPU0       CPU1
 3 11:       2598       2172     GICv3  27 Level     arch_timer
 4 13:        176          0     GICv3  33 Level     uart-pl011
 5 16:        563          0     GICv3  75 Edge      virtio0
 6 17:          0          0     GICv3  79 Edge      virtio4
 7 18:          0          0     GICv3  34 Level     rtc-pl031
 8 19:          0          0     GICv3  23 Level     arm-pmu
 9 20:          3          0     GICv3  76 Edge      virtio1
10 21:         82          0     GICv3  77 Edge      virtio2
11 22:         33          0     GICv3  78 Edge      virtio3
12 23:          0          0 9030000.pl061   3 Edge      GPIO Key Poweroff
13IPI0:        56         84       Rescheduling interrupts
14IPI1:       503        797       Function call interrupts
15IPI2:         0          0       CPU stop interrupts
16IPI3:         0          0       CPU stop (for crash dump) interrupts
17IPI4:         0          0       Timer broadcast interrupts
18IPI5:         0          0       IRQ work interrupts
19IPI6:         0          0       CPU wake-up interrupts
20Err:          0
21# cd /sys/kernel/debug/irq/irqs
22# grep -w handler * | sort -n
231:handler:  handle_percpu_devid_irq
242:handler:  handle_percpu_devid_irq
253:handler:  handle_percpu_devid_irq
264:handler:  handle_percpu_devid_irq
275:handler:  handle_percpu_devid_irq
286:handler:  handle_percpu_devid_irq
297:handler:  handle_percpu_devid_irq
308:handler:  handle_percpu_devid_irq
319:handler:  handle_percpu_devid_irq
3210:handler:  handle_percpu_devid_irq
3311:handler:  handle_percpu_devid_irq
3412:handler:  handle_percpu_devid_irq
3513:handler:  handle_fasteoi_irq
3614:handler:  pl061_irq_handler
3715:handler:  handle_fasteoi_irq
3816:handler:  handle_fasteoi_irq
3917:handler:  handle_fasteoi_irq
4018:handler:  handle_fasteoi_irq
4119:handler:  handle_percpu_devid_irq
4220:handler:  handle_fasteoi_irq
4321:handler:  handle_fasteoi_irq
4422:handler:  handle_fasteoi_irq
4523:handler:  handle_edge_irq

这里重点关注handle_fasteoi_irq,其最后会调用__handle_irq_event_percpu

__handle_irq_event_percpu中,如果handler返回IRQ_WAKE_THREAD,则会唤醒中断处理线程。

 1irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc)
 2{
 3	irqreturn_t retval = IRQ_NONE;
 4	unsigned int irq = desc->irq_data.irq;
 5	struct irqaction *action;
 6
 7	record_irq_time(desc);
 8
 9	for_each_action_of_desc(desc, action) {
10		irqreturn_t res;
11
12		/*
13		 * If this IRQ would be threaded under force_irqthreads, mark it so.
14		 */
15		if (irq_settings_can_thread(desc) &&
16		    !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
17			lockdep_hardirq_threaded();
18
19		trace_irq_handler_entry(irq, action);
20		res = action->handler(irq, action->dev_id);
21		trace_irq_handler_exit(irq, action, res);
22
23		if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pS enabled interrupts\n",
24			      irq, action->handler))
25			local_irq_disable();
26
27		switch (res) {
28		case IRQ_WAKE_THREAD:
29			/*
30			 * Catch drivers which return WAKE_THREAD but
31			 * did not set up a thread function
32			 */
33			if (unlikely(!action->thread_fn)) {
34				warn_no_thread(irq, action);
35				break;
36			}
37
38			__irq_wake_thread(desc, action);
39			break;
40
41		default:
42			break;
43		}
44
45		retval |= res;
46	}
47
48	return retval;
49}

3. 中断线程化

之前提到,在__setup_irq申请中断时,可以在指定的handler或着通过强制中断线程化来使用irq_default_primary_handler中返回IRQ_WAKE_THREAD来唤醒中断线程。

中断线程服务函数irq_thread

 1/// kernel/irq/manage.c
 2/*
 3 * Interrupt handler thread
 4 */
 5static int irq_thread(void *data)
 6{
 7    struct callback_head on_exit_work;
 8    struct irqaction *action = data;
 9    struct irq_desc *desc = irq_to_desc(action->irq);
10    irqreturn_t (*handler_fn)(struct irq_desc *desc,
11            struct irqaction *action);
12
13    irq_thread_set_ready(desc, action);
14
15    sched_set_fifo(current);
16
17    if (force_irqthreads() && test_bit(IRQTF_FORCED_THREAD,
18                       &action->thread_flags))
19        handler_fn = irq_forced_thread_fn;
20    else
21        handler_fn = irq_thread_fn;
22
23    init_task_work(&on_exit_work, irq_thread_dtor);
24    task_work_add(current, &on_exit_work, TWA_NONE);
25
26    irq_thread_check_affinity(desc, action);
27
28    while (!irq_wait_for_interrupt(action)) {
29        irqreturn_t action_ret;
30
31        irq_thread_check_affinity(desc, action);
32
33        action_ret = handler_fn(desc, action);
34        if (action_ret == IRQ_WAKE_THREAD)
35            irq_wake_secondary(desc, action);
36
37        wake_threads_waitq(desc);
38    }
39
40    /*
41     * This is the regular exit path. __free_irq() is stopping the
42     * thread via kthread_stop() after calling
43     * synchronize_hardirq(). So neither IRQTF_RUNTHREAD nor the
44     * oneshot mask bit can be set.
45     */
46    task_work_cancel(current, irq_thread_dtor);
47    return 0;
48}