Linux4.x之Gpio分析(五)中断处理流程1

enter description here

Linux每一个外设的中断都由 struct irq_desc 来描述,我们称之中断描述符。
这里我们将中断描述符组织在一起形成一个中断描述符表。

  • 这里在老版本的内核中,习惯用静态的数组来维护所有的中断描述符。
  • 在引入设备树之后,往往我们用动态的方式维护使用函数irq_desc_alloc来分配中断描述符。

当中断发生后,我们硬件中断的编码,然后通过irq domain翻译成IQR Number,最终找到设备描述符
继而调用highlevel irq-events,最终调用到对应的中断处理程序。

这里 high_level irq 是中断控制器的中断处理程序的抽象。

这里话不多说我们将Linux代码的处理流程线罗列出来:

首先,我们在驱动程序中使用API request_irq()注册一个关于GPIO1_1管脚的中断处理函数。

我们打开中断后,当GPIO1_1出现电平变化时,中断信号从GPIO控制器经过GIC然后进入到CPU的IRQ控制线。
于是我们重点关注如下四个部分的处理

  1. CPU的中断处理函数[mechine]
  2. GIC的中断处理函数
  3. GPIO控制器处理函数
  4. 驱动程序注册的中断处理函数

CPU中断控制

当中断发生,首先是通过中断向量表。然后进入真正的处理irq_handler,中断向量表这部分比较晦涩暂不分析。

1
2
3
4
5
6
7
8
9
10
11
    .macro    irq_handler 
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm

这里:MULTI_IRQ_HANDLER 配置项,则意味着允许平台的代码可以动态设置irq处理程序,平台代码可以修改全局变量:handle_arch_irq,从而可以修改irq的处理程序。

首先我们宏MULTI_IRQ_HANDLER的分支:

我们之前分析过arm,cortex-a9-gic的代码,我们看看这个中断控制器是如何将中断控制函数给注册上去。

1
2
3
4
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
gic_of_init
gic_init_bases
set_handle_irq(handle_irq)

我们之前在(三)中分析过:

  • 作为second GIC,需要调用 irq_set_chained_handler 注册irq_handler到root GIC中。
  • 作为root GIC,上级是CPU,调用 set_handle_irq 注册到平台的irq处理接口。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //kernel/irq.c
    #ifdef CONFIG_MULTI_IRQ_HANDLER
    void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
    {
    if (handle_arch_irq)
    return;

    handle_arch_irq = handle_irq;
    }
    #endif

然后GIC的中断处理最终进入 __handle_domain_irq 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);

do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;

if (likely(irqnr > 15 && irqnr < 1021)) {
handle_domain_irq(gic->domain, irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}

//include/liniux/irq_desc.h
static inline int handle_domain_irq(struct irq_domain *domain,
unsigned int hwirq, struct pt_regs *regs)
{
return __handle_domain_irq(domain, hwirq, true, regs);
}

然后我们再接着看ELSE分支的arch_irq_handler_default

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//arch/arm/include/asm/entry_macro_multi.S
.macro arch_irq_handler_default
get_irqnr_preamble r6, lr
1: get_irqnr_and_base r0, r2, r6, lr
movne r1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrne lr, BSYM(1b)
bne asm_do_IRQ

//arch/arm/kernel/irq.c
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
handle_IRQ(irq, regs);
}


void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
__handle_domain_irq(NULL, irq, false, regs);
}

这里我们看到了两条路最终合并成一条都进入到 __handle_domain_irq 函数中,这里唯一的区别是否涉及到irq_domain.
我们知道irq_domain做的关系就是将IRQ number 对应到硬件HW irq。
在GIC控制器初始化的时候,我们做了两件事

  1. 将中断控制器加入到irq domain
  2. 为irq domain建立映射关系

最终真正的中断处理函数 generic_handle_irq(irq);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//kerenel/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();

#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq);
#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);
}

irq_exit();
set_irq_regs(old_regs);
return ret;
}

在介绍 generic_handle_irq 之前我们需要补充一些知识。
我们先回顾一下刚才的流程:

中断发生后,CPU会跳转到中断向量表中查找中断处理函数的位置,然后最终ARM V7架构是irq_handler函数。[中断向量相关的地方比较晦涩先跳过吧]
然后我们看了 CONFIG_MULTI_IRQ_HANDLER 的分支最后都会调用进 __handle_domain_irq函数,最后调用 generic_handle_irq
因为我们的machine imx6q的实现是定义了CONFIG_MULTI_IRQ_HANDLER,所以我们着重分析这部分内容,继续回忆一下gic_of_init是哪里被调用的。

没错,就是内核初始化的时候做的工作,我们可以翻回(二)在回顾一下。

1
2
3
start_kernel
init_IRQ()
machine_desc ->init_irq();

好!暂时先说到这里!我们跟踪代码已经看到了CPU和GIC的对于中断做出的处理函数。
GPIO控制器的处理函数和真正的中断处理函数留在后面再说。

客官,小的这记口碎大石值几个钱?