基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识

Posted 摩斯电码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识相关的知识,希望对你有一定的参考价值。

作者:彭东林

邮箱:pengdonglin137@163.com

QQ:405728433

平台

tiny4412 ADK

Linux-4.9

 

概述

前面几篇博文列举了在有设备树的时候,gpio中断的用法示例。下面我们尝试分析一下Linux内核是如何做到的,如果哪写的有问题,欢迎大家批评指正,谢谢。

还是以GPIO中断为例分析,对于tiny4412,gpio中断可以分为两种,外部中断和普通的GPIO中断

外部可唤醒中断:exynos4412提供了32个外部可唤醒中断,分别是GPX0到GPX3。按键中断分别使用了外部可唤醒中断XEINT26、XEINT27、XEINT28以及XEINT29,对应的GPIO分别是GPIOX3_2、GPIOX3_3、GPIOX3_4和GPIO3_5,当按下键的时候,会在对应的GPIO上面产生一个下降沿.

其余的GPIO也可以产生中断,但是不具备唤醒功能,从中断的物理连接来看,外部中断可以直接对应的GIC上面的一个SPI物理中断号,而普通的GPIO中断是多个GPIO对应GIC上的同一个SPI中断。

关于GIC的知识请参考exynos4412的datasheet的"9 Interrupt Controller",这里简单说明一下:exynos4412使用的GIC是v2版本,支持16个SGI中断、16个PPI中断以及128个SPI中断。

框图

 

结合上面的一张图说明一下:

  • 对于外部中断XEINT0-15,每一个都对应的SPI中断,但是XEINT16-31共享了同一个SPI中断。这里引脚上产生中断后,会直接通知GIC,然后GIC会通过irq或者firq触发某个CPU中断。

  • 对于其他的pinctrl@11000000中的其他普通的GPIO来说,它们产生中断后,并没有直接通知GIC,而是先通知pinctrl@11000000,然后pinctrl@11000000再通过SPI-46通知GIC,然后GIC会通过irq或者firq触发某个CPU中断。

  • 其中涉及到了多个irq domain, GIC模块的irq domain 1, 三星为每一组GPIO都创建了一个irq domain, 好处是通过gpio引脚的编号就可以知道对应的hwirq是多少(如gpx3_2在gpx3这个bank对应的domain中的hwriq就是2),irq domain存放的的hwirq(来自硬件寄存器)到virq(逻辑中断号,全局唯一)的映射

  • 上面的每一个irq_domain都对应一个irq_chip,irq_chip是kernel对中断控制器的软件抽象

  • 上面SPI中断括号中的数字表示的发生中断后,实际从gic的ICCIAR_CPUn寄存器中读取出来的中断号,可以参考4412的datasheet的9.2.2 GIC Interrupt Table

 

关于Linux的中断子系统这部分知识可以参考下面几篇蜗窝科技的博文,这几篇讲的比较偏理论,结合实例的话,会更容易理解。

Linux kernel的中断子系统之(一):综述

Linux kernel的中断子系统之(二):IRQ Domain介绍

linux kernel的中断子系统之(三):IRQ number和中断描述符

linux kernel的中断子系统之(四):High level irq event handler

Linux kernel中断子系统之(五):驱动申请中断API

Linux kernel的中断子系统之(六):ARM中断处理过程

linux kernel的中断子系统之(七):GIC代码分析

正文

首先看一下涉及到的设备树中的节点:

 1 / {
 2     interrupt-parent = <&gic>;
 3     #address-cells = <0x1>;
 4     #size-cells = <0x1>;
 5     compatible = "friendlyarm,tiny4412", "samsung,exynos4412", "samsung,exynos4";
 6     model = "FriendlyARM TINY4412 board based on Exynos4412";
 7     aliases {
 8         pinctrl1 = "/pinctrl@11000000";
 9     };
10     gic: interrupt-controller@10490000 {
11         compatible = "arm,cortex-a9-gic";
12         #interrupt-cells = <0x3>;
13         interrupt-controller;
14         reg = <0x10490000 0x10000>, <0x10480000 0x10000>;
15         cpu-offset = <0x4000>;
16     };
17     pinctrl@11000000 {
18         compatible = "samsung,exynos4x12-pinctrl";
19         reg = <0x11000000 0x1000>;
20         interrupts = <0x0 0x2e 0x0>;
21         gpm4: gpm4 {
22             gpio-controller;
23             #gpio-cells = <0x2>;
24             interrupt-controller;
25             #interrupt-cells = <0x2>;
26         };
27         gpx1: gpx1 {
28             gpio-controller;
29             #gpio-cells = <2>;
30             interrupt-controller;
31             interrupt-parent = <&gic>;
32             interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
33                      <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
34             #interrupt-cells = <2>;
35         };
36         gpx3: gpx3 {
37             gpio-controller;
38             #gpio-cells = <0x2>;
39             interrupt-controller;
40             #interrupt-cells = <0x2>;
41         };
42         wakeup-interrupt-controller {
43             compatible = "samsung,exynos4210-wakeup-eint";
44             interrupt-parent = <0x1>;
45             interrupts = <0x0 0x20 0x0>;
46         };
47     };
48     interrupt_xeint26_29: interrupt_xeint26_29 {
49             compatible = "tiny4412,interrupt_xeint26_29";
50             interrupt-parent = <&gpx3>;
51             interrupts = <2 IRQ_TYPE_EDGE_FALLING>, <3 IRQ_TYPE_EDGE_FALLING>,\\
52                     <4 IRQ_TYPE_EDGE_FALLING>, <5 IRQ_TYPE_EDGE_FALLING>;
53     };
54     interrupt_xeint14_15: interrupt_xeint14_15 {
55             compatible = "tiny4412,interrupt_xeint14_15";
56             interrupt-parent = <&gpx1>;
57             interrupts = <6 IRQ_TYPE_EDGE_FALLING>, <7 IRQ_TYPE_EDGE_FALLING>;
58     };
59     interrupt_gpm4_0: interrupt_gpm4_0 {
60             compatible = "tiny4412,interrupt_gpm4_0";
61             interrupt-parent = <&gpm4>;
62             interrupts = <0 IRQ_TYPE_EDGE_FALLING>;
63     };
64 }; 

说明:

  • tiny4412上的root gic就是上面的"arm,cortex-a9-gic",它的interrupt cells是3, 表示引用gic上的一个中断需要三个参数

  • pinctrl@11000000的interrupt parent是interrupt-controller@10490000,可以看到,它的interrupts属性含有三个参数,含义是引用GIC的SPI-46

  • gpx3本身也充当一个中断控制器,它的interrupt parent也是interrupt-controller@10490000,gpx3的interrupt cell是2, 表示引用gpx3的一个中断需要2个参数

  • interrupt_xeint26_29的interrupt parent是gpx3,它的interrupts含有四组参数,分别对应gpiox3_2、gpiox3_3、gpiox3_4和gpiox3_5,每组的第二个参数表示的是中断类型,IRQ_TYPE_EDGE_FALLING表示下降沿触发,可以参考arch/arm/boot/dts/include/dt-bindings/interrupt-controller/irq.h

  • wakeup-interrupt-controller我觉得只是一个软件上面的抽象,对应的是XEINT16-31,其interrupts对应的就是SPI-32,从datasheet上也可以看到,EINT16-31对应的都是SPI-32.

下面分几个部分来说明一下,这里不适合把大段的内核代码贴过来,只把一些关键的部分列出来,对于自己详细分析内核代码有帮助。

第一部分: GIC中断控制器的注册

第二部分:设备树的device node在向platfomr_device转化的过程中节点的interrupts属性的处理

第三部分:GPIO控制器驱动的注册,大部分GPIO控制器同时具备interrupt controller的功能,就像上面的GPIOX3和GPIOM4等等

第四部分:引用GPIO中断的节点的解析

第一部分 gic中断控制器的注册

相关代码:

drivers/irqchip/irq-gic.c

arch/arm/mach-exynos/exynos.c

arch/arm/kernel/entry-armv.S

gic中断控制器的初始化和注册是在函数gic_of_init中做的,这个函数是怎么被执行到的呢?这个文件中定义了下面的结构:

IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);

分析发现,IRQCHIP_DECLARE宏会定义出一个__of_table_cortex_a9_gic的变量,gic_of_init被赋值给其data成员,这个变量被存放到了内核镜像的__irqchip_of_table段,在kernel启动时平台代码exynos.c中的函数exynos_init_irq会被调用,这个函数会调用irqchip_init --> of_irq_init,of_irq_init就会遍历__irqchip_of_table,按照interrupt controller的连接关系从root开始,依次初始化每一个interrupt controller,此时gic_of_init会被调用,比如以下面这张图为例:

上图中每一个圆圈都代表一个interrupt-controller,以此都成了系统的中断树,其中的数字表示的是of_irq_init函数初始化中断控制器的顺序。

gic_of_init主要做如下几件事:

  • 设置__smp_cross_call为gic_raise_softirq, 它的作用是触发SGI中断,用于CPU之间通信

set_smp_cross_call(gic_raise_softirq)
  • 设置handle_arch_irq为gic_handle_irq。在kernel发生中断后,会跳转到汇编代码entry-armv.S中__irq_svc处,进而调用handle_arch_irq,从而进入GIC驱动,进行后续的中断处理

set_handle_irq(gic_handle_irq)
  • 计算这个GIC模块所支持的中断个数gic_irqs,然后创建一个linear irq domain。此时尚未分配virq,也没有建立hwirq跟virq的映射

 1     /*
 2      * Find out how many interrupts are supported.
 3      * The GIC only supports up to 1020 interrupt sources.
 4      */    
 5 gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
 6     gic_irqs = (gic_irqs + 1) * 32;
 7     if (gic_irqs > 1020)
 8         gic_irqs = 1020;
 9     gic->gic_irqs = gic_irqs;
10         gic->domain = irq_domain_create_linear(handle, gic_irqs,
11                                &gic_irq_domain_hierarchy_ops,  gic);

在初始化的时候既没有给hwirq分配对应的virq,也没有建立二者之间的映射,这部分工作会到后面有人引用GIC上的某个中断时再分配和建立。

第二部分 device node转化为platform_device

相关代码:

drivers/of/platform.c

这个转化过程是调用of_platform_populate开始的,以pinctrl@11000000为例,暂时只关心interrupts属性的处理,函数调用关系:

of_platform_populate

  ---> of_platform_bus_create

    ---> of_platform_device_create_pdata

      ---> of_device_alloc:

 1 struct platform_device *of_device_alloc(struct device_node *np,
 2                   const char *bus_id,
 3                   struct device *parent)
 4 {
 5     struct platform_device *dev;
 6     int rc, i, num_reg = 0, num_irq;
 7     struct resource *res, temp_res;
 8     dev = platform_device_alloc("", -1);
 9     /* count the io and irq resources */
10 ... ...
11     num_irq = of_irq_count(np);  // 统计这个节点的interrupts属性中描述了几个中断
12     /* Populate the resource table */
13     if (num_irq || num_reg) {
14         res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
15 ... ...
16         dev->num_resources = num_reg + num_irq;
17         dev->resource = res;
18 ... ...
19    of_irq_to_resource_table(np, res, num_irq)  // 解析interrupts属性,将每一个中断转化为resource结构体
20     }
21 ... ...
22 }

这里主要涉及到两个函数of_irq_count和of_irq_to_resource_table,传入的np就是pinctrl@11000000节点。

  • of_irq_count

这个函数会解析interrupts属性,并统计其中描述了几个中断。

简化如下:找到pinctrl@11000000节点的所隶属的interrupt-controller,即interrupt-controller@10490000节点,然后获得其#interrupt-cells属性的值,因为只要知道了这个值,也就知道了在interrupts属性中描述一个中断需要几个参数,也就很容易知道interrupts所描述的中断个数。这里关键的函数是of_irq_parse_one:

1 int of_irq_count(struct device_node *dev)
2 {
3     struct of_phandle_args irq;
4     int nr = 0;
5     while (of_irq_parse_one(dev, nr, &irq) == 0)
6         nr++;
7     return nr;
8 }

nr表示的是index,of_irq_parse_one每次成功返回,都表示成功从interrupts属性中解析到了第nr个中断,同时将关于这个中断的信息存放到irq中,struct of_phandle_args的含义如下:

1 #define MAX_PHANDLE_ARGS 16
2 struct of_phandle_args {
3     struct device_node *np;  // 用于存放赋值处理这个中断的中断控制器的节点
4     int args_count;  // 就是interrupt-controller的#interrupt-cells的值
5     uint32_t args[MAX_PHANDLE_ARGS];  // 用于存放具体描述某一个中断的参数的值
6 };

最后将解析到的中断个数返回。

  • of_irq_to_resource_table

知道interrupts中描述了几个中断后,这个函数开始将这些中断转换为resource,这个是由of_irq_to_resource函数完成。

1     for (i = 0; i < nr_irqs; i++, res++)
2         if (!of_irq_to_resource(dev, i, res))
3             break;

第二个参数i表示的是index,即interrupts属性中的第i个中断。

 1 int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)
 2 {
 3     int irq = irq_of_parse_and_map(dev, index);  // 返回interrupts中第index个hwirq中断映射到的virq
 4     if (r && irq) {  // 将这个irq封装成resource
 5         const char *name = NULL;
 6         memset(r, 0, sizeof(*r));
 7         of_property_read_string_index(dev, "interrupt-names", index,
 8                           &name);  // 一般这个"interrupt-names"属性是可选的
 9         r->start = r->end = irq;  // 全局唯一的virq
10         r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));  // 这个中断的属性,如上升沿还是下降沿触发
11         r->name = name ? name : of_node_full_name(dev); 
12     }
13     return irq;
14 }

所以,分析重点是irq_of_parse_and_map,这个函数会获得pinctrl@11000000节点的interrupts属性的第index个中断的参数,这是通过of_irq_parse_one完成的,然后获得该中断所隶属的interrupt-controller的irq domain,也就是前面GIC注册的那个irq domain,利用该domain的of_xlate函数从前面表示第index个中断的参数中解析出hwirq和中断类型,最后从系统中为该hwriq分配一个全局唯一的virq,并将映射关系存放到中断控制器的irq domain中,也就是gic的irq domain。

下面结合kernel代码分析一下:

1 unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
2 {
3     struct of_phandle_args oirq;
4  of_irq_parse_one(dev, index, &oirq); // 获得interrupts的第index个中断参数,并封装到oirq中
5     return irq_create_of_mapping(&oirq); //返回映射到的virq
6 }

    ---> irq_create_of_mapping

1 unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
2 {
3     struct irq_fwspec fwspec;
4     of_phandle_args_to_fwspec(irq_data, &fwspec); // 将irq_data中的数据转存到fwspec,没必要分析
5     return irq_create_fwspec_mapping(&fwspec);
6 }

        ---> irq_create_fwspec_mapping

 1 unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
 2 {
 3     struct irq_domain *domain;
 4     struct irq_data *irq_data;
 5     irq_hw_number_t hwirq;
 6     unsigned int type = IRQ_TYPE_NONE;
 7     int virq;
 8 // 根据中断控制器的device_node找到所对应的irq domain,在前面GIC驱动注册irq domian的时候,
 9 // 会将irq_domain的fwnode设置为中断控制器的device_node的fwnode成员
10     if (fwspec->fwnode) { 
11         domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
12         if (!domain)
13             domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
14     } else {
15         domain = irq_default_domain;
16     }
17 // 对于GIC的irq domain来说,会调用d->ops->translate(d, fwspec, hwirq, type)
18 // 也就是gic_irq_domain_translate,这个单独分析.对于没有定义translate的irq_domain,
19 // 会调用d->ops->xlate
20 irq_domain_translate(domain, fwspec, &hwirq, &type);
21 ... ...
22 // 从这个irq domain查询看该hwirq之前是否已经映射过,一般情况下都没有
23     virq = irq_find_mapping(domain, hwirq);
24     if (virq) {
25 ... ...
26             return virq;
27 ... ...
28     }
29     if (irq_domain_is_hierarchy(domain)) {  // 对于GIC的irq domain这样定义了alloc的domain来说,走这个分支
30         virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
31     } else {  // 其他没有定义irq_domain->ops->alloc的domain,走这个分支
32         /* Create mapping */
33         virq = irq_create_mapping(domain, hwirq);
34     }
35     irq_data = irq_get_irq_data(virq);
36     /* Store trigger type */
37     irqd_set_trigger_type(irq_data, type);
38     return virq; //返回映射到的virq
39 }

看一下gic irq domain的translate的过程:

            --->gic_irq_domain_translate

 1 static int gic_irq_domain_translate(struct irq_domain *d,
 2                     struct irq_fwspec *fwspec,
 3                     unsigned long *hwirq,
 4                     unsigned int *type)
 5 {
 6     if (is_of_node(fwspec->fwnode)) {  // 走这个分支
 7         if (fwspec->param_count < 3)  // 检查描述中断的参数个数是否合法
 8             return -EINVAL;
 9 // 这里加16的目的是跳过SGI中断,因为SGI用于CPU之间通信,不归中断子系统管
10 // GIC支持的中断中从0-15号属于SGI,16-32属于PPI,32-1020属于SPI
11         *hwirq = fwspec->param[1] + 16;
12 // 从这里可以看到,描述GIC中断的三个参数中第一个表示中断种类,0表示的是SPI,非0表示PPI
13 // 这里加16的意思是跳过PPI
14 // 同时我们也知道了,第二个参数表示某种类型的中断(PPI or SPI)中的第几个(从0开始)
15         if (!fwspec->param[0])
16             *hwirq += 16;
17 // 第三个参数表示的中断的类型,如上升沿、下降沿或者高低电平触发
18         *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
19         return 0;  // 成功
20     }
21 ... ...
22     return -EINVAL;
23 }

通过这个函数,我们就获得了fwspec所表示的hwirq和type

接着看一下irq_find_mapping,如果hwirq之前跟virq之间发生过映射,会存放到irq domain中,这个函数就是查询irq domain,以hwirq为索引,寻找virq

            ---> irq_find_mapping

 1 unsigned int irq_find_mapping(struct irq_domain *domain,
 2                   irq_hw_number_t hwirq)
 3 {
 4     struct irq_data *data;
 5 ... ...
 6     if (hwirq < domain->revmap_size)  // 如果满足linear irq domain的条件,hwirq作为数字下标
 7         return domain->linear_revmap[hwirq];
 8 ... ...
 9     data = radix_tree_lookup(&domain->revmap_tree, hwirq); // hwirq作为key
10     return data ? data->irq : 0;
11 }

 下面分析virq的分配以及映射,对于GIC irq domain,由于其ops定义了alloc,在注册irq domain的时候会执行domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY

            ---> irq_domain_alloc_irqs

 1 int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,  // 这里的irq_base是-1
 2                 unsigned int nr_irqs, int node, void *arg,  // 这里的nr_irqs是1
 3                 bool realloc, const struct cpumask *affinity)
 4 {
 5     int i, ret, virq;
 6 ... ...
 7 // 下面这个函数会从系统中一个唯一的virq,其实就是全局变量allocated_irqs从低位到高位第一个为0的位的位号
 8 // 然后将allocated_irqs的第virq位置为1, 然后会为这个virq分配一个irq_desc, virq会存放到irq_desc的irq_data.irq中
 9 // 最后将这个irq_desc存放到irq_desc_tree中,以virq为key,函数irq_to_desc就是以virq为key,查询irq_desc_tree
10 // 迅速定位到irq_desc
11         virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node,
12                           affinity);
13 ... ...
14 irq_domain_alloc_irq_data(domain, virq, nr_irqs);
15 irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg);
16     for (i = 0; i < nr_irqs; i++)
17         irq_domain_insert_irq(virq + i);  // 将virq跟hwirq的映射关系存放到irq domain中,这样就可以通过hwirq在该irq_domain中快速找到virq
18     return virq;
19 }      

                ----> irq_domain_alloc_irq_data 会根据virq获得对应的irq_desc,然后将domain赋值给irq_desc->irq_data->domain

                ----> irq_domain_alloc_irqs_recursive 这个函数会调用gic irq domain的domain->ops->alloc,即gic_irq_domain_alloc

 1 static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
 2                 unsigned int nr_irqs, void *arg)
 3 {
 4     int i, ret;
 5     irq_hw_number_t hwirq;
 6     unsigned int type = IRQ_TYPE_NONE;
 7     struct irq_fwspec *fwspec = arg;
 8     ret = gic_irq_domain_translate(domain, fwspec, &hwirq, &type); // 参考之前的分析
 9 // 这个函数主要做了如下工作:
10 // 根据virq找到对应的irq_desc,将hwirq存放到irq_desc的irq_data.hwirq中
11 // 将irq chip存放到irq_desc的irq_data.chip中
12 // 对于PPI类型的中断(hwirq<32),将irq_desc的handle_irq设置为handle_percpu_devid_irq
13 // 对于SPI类型的中断,将irq_desc的handle_irq设置为handle_fasteoi_irq
14

以上是关于基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识的主要内容,如果未能解决你的问题,请参考以下文章

基於tiny4412的Linux內核移植--- 中斷和GPIO學習

基于tiny4412的Linux内核移植 -- MMA7660驱动移植

基于tiny4412的Linux内核移植 -- SD卡驱动移植

基于tiny4412的Linux内核移植 -- MMA7660驱动移植(九-2)

基于tiny4412的Linux内核移植 -- DM9621NP网卡驱动移植

基于tiny4412的Linux内核移植 -- PWM子系统学习