码迷,mamicode.com
首页 > 系统相关 > 详细

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

时间:2017-01-25 12:37:36      阅读:1013      评论:0      收藏:0      [点我收藏+]

标签:and   条件   字段   switch   store   添加   相关   bank   bind   

作者:彭东林

邮箱:pengdonglin137@163.com

QQ:405728433

平台

tiny4412 ADK

Linux-4.9

 

概述

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

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

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

其余的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, 这样也是可以的,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 = <0x1>;
 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,也没有建立二者之间的映射,这部分工作会到后面有人引用某个中断时在分配和建立。

第二部分 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中。

下面结合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     for (i = 0; i < nr_irqs; i++)
15         gic_irq_domain_map(domain, virq + i, hwirq + i);
16     return 0;
17 }

下面分析irq_create_mapping,对于irq domain的ops中没有定义alloc的domain,会执行这个函数

            ---> irq_create_mapping 为hwirq分配virq,并存放映射到irq domain中

 1 unsigned int irq_create_mapping(struct irq_domain *domain,
 2                 irq_hw_number_t hwirq)
 3 {
 4     struct device_node *of_node;
 5     int virq;
 6 ...
 7  // 获得GIC中断控制器的device node
 8  // 在注册irq domain的时候,domain的fwnode成员就指向了device node的fwnode
 9     of_node = irq_domain_get_of_node(domain); 
10     virq = irq_find_mapping(domain, hwirq);  // 查看映射关系是否已经存在
11     if (virq)
12         return virq;
13  // 这个函数之前分析过
14     virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL); 
15  // 将hwirq跟virq的映射关系存放到irq domain中, virq对应的irq_desc的irq_data的irq、hwirq、domain分别传入的virq、hwirq和domain
16  irq_domain_associate(domain, virq, hwirq);
17     return virq;
18 }

至此,device node在转化为platform_device过程中的interrupts属性的处理就暂时分析完毕,后面会注册该platform_device,然后对应platform_driver的probe就会被调用。

第三部分 GPIO控制器驱动

相关代码:

drivers/pinctrl/samsung/pinctrl-samsung.c

drivers/pinctrl/samsung/pinctrl-exynos.c

在pinctrl@11000000节点转化成的platform_device被注册的时候,samsung_pinctrl_probe会被调用。这个函数目前我们先只分析跟中断相关的。

 1 static int samsung_pinctrl_probe(struct platform_device *pdev)
 2 {
 3     struct samsung_pinctrl_drv_data *drvdata;
 4     const struct samsung_pin_ctrl *ctrl;
 5     struct device *dev = &pdev->dev;
 6     struct resource *res;
 7     int ret;
 8     drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
 9     ctrl = samsung_pinctrl_get_soc_data(drvdata, pdev);  // 这个也需要分析
10     drvdata->dev = dev;
11     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
12     drvdata->virt_base = devm_ioremap_resource(&pdev->dev, res);
13     res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 
14     if (res)
15         drvdata->irq = res->start;  // 这个所获得的就是SPI-46对应的virq,用于处理普通可以产生中断的gpio
16     ret = samsung_gpiolib_register(pdev, drvdata); // 这个需要分析
17     ret = samsung_pinctrl_register(pdev, drvdata);  // 这个函数主要是跟gpio配置相关,跟中断关系不大,暂不分析
18     if (ctrl->eint_gpio_init)  // 普通的GPIO
19         ctrl->eint_gpio_init(drvdata);
20     if (ctrl->eint_wkup_init) // 非空的话,表示这一pinctrl含有具备wakeup功能的gpio
21         ctrl->eint_wkup_init(drvdata);
22     platform_set_drvdata(pdev, drvdata);
23     list_add_tail(&drvdata->node, &drvdata_list);
24     return 0;
25 }

首先分析一个samsung_pinctrl_get_soc_data

    ----> samsung_pinctrl_get_soc_data

 1 static const struct samsung_pin_ctrl *
 2 samsung_pinctrl_get_soc_data(struct samsung_pinctrl_drv_data *d,
 3                  struct platform_device *pdev)
 4 {
 5     int id;
 6     const struct of_device_id *match;
 7     struct device_node *node = pdev->dev.of_node;
 8     struct device_node *np;
 9     const struct samsung_pin_bank_data *bdata;
10     const struct samsung_pin_ctrl *ctrl;
11     struct samsung_pin_bank *bank;
12     int i;
13 //这里的id就是alias节点中pinctrl1 = "/pinctrl@11000000";中属性名"pinctrl1"中的1
14     id = of_alias_get_id(node, "pinctrl");
15 // node的compatiable的值是"samsung,exynos4x12-pinctrl",最终ctrl的指向的是exynos4x12_pin_ctrl[1]
16 /* 即:
17 {
18         .pin_banks    = exynos4x12_pin_banks1,
19         .nr_banks    = ARRAY_SIZE(exynos4x12_pin_banks1),
20         .eint_gpio_init = exynos_eint_gpio_init,  // 其他普通的并且具备中断功能的gpio
21         .eint_wkup_init = exynos_eint_wkup_init,  // 这个pinctrl含有具备wakeup功能的gpio,其实就是XEINT0-31
22         .suspend    = exynos_pinctrl_suspend,
23         .resume        = exynos_pinctrl_resume,
24     }, 
25 其中exynos4x12_pin_banks1的内容如下:
26 static const struct samsung_pin_bank_data exynos4x12_pin_banks1[] __initconst = {
27     EXYNOS_PIN_BANK_EINTG(7, 0x040, "gpk0", 0x08),
28 ... ...
29     EXYNOS_PIN_BANK_EINTG(8, 0x2E0, "gpm4", 0x34),
30  EXYNOS_PIN_BANK_EINTN(6, 0x120, "gpy0"),
31 ... ...
32     EXYNOS_PIN_BANK_EINTW(8, 0xC60, "gpx3", 0x0c),
33 };
34 这个跟datasheet是对应的,可以自己看看
35 这里关注这三个宏: EXYNOS_PIN_BANK_EINTG 会将eint_type设置为EINT_TYPE_GPIO
36 EXYNOS_PIN_BANK_EINTN会将eint_type设置为EINT_TYPE_NONE
37 EXYNOS_PIN_BANK_EINTW会将eint_type设置为EINT_TYPE_WKUP
38 */
39     match = of_match_node(samsung_pinctrl_dt_match, node);
40     ctrl = (struct samsung_pin_ctrl *)match->data + id;  // id表示第几个pinctrl
41     d->suspend = ctrl->suspend;
42     d->resume = ctrl->resume;
43     d->nr_banks = ctrl->nr_banks; // 含有几组bank
44     d->pin_banks = devm_kcalloc(&pdev->dev, d->nr_banks,    sizeof(*d->pin_banks), GFP_KERNEL);
45     bank = d->pin_banks;
46     bdata = ctrl->pin_banks;
47     for (i = 0; i < ctrl->nr_banks; ++i, ++bdata, ++bank) {
48         bank->type = bdata->type;
49         bank->pctl_offset = bdata->pctl_offset;
50         bank->nr_pins = bdata->nr_pins;  // 这个bank含有的gpio的个数
51         bank->eint_func = bdata->eint_func;
52         bank->eint_type = bdata->eint_type;  // 如 EINT_TYPE_GPIO、EINT_TYPE_NONE、EINT_TYPE_WKUP
53         bank->eint_mask = bdata->eint_mask;
54         bank->eint_offset = bdata->eint_offset;
55         bank->name = bdata->name;  // 如"gpx3"
56         spin_lock_init(&bank->slock);
57         bank->drvdata = d;
58         bank->pin_base = d->nr_pins;  // pin_base存放的是该bank中的第一个gpio的逻辑gpio号
59         d->nr_pins += bank->nr_pins;
60     }
61     for_each_child_of_node(node, np) { // 遍历pinctrl@11000000的子节点,记录含有"gpio-controller"属性的节点
62         if (!of_find_property(np, "gpio-controller", NULL))  // 
63             continue;
64         bank = d->pin_banks;
65         for (i = 0; i < d->nr_banks; ++i, ++bank) {
66             if (!strcmp(bank->name, np->name)) {
67                 bank->of_node = np;  // 获得可以作为gpio控制器的子节点的device node
68                 break;
69             }
70         }
71     }
72     d->pin_base = pin_base;  // pin_base存放的是当前系统的gpio的总个数,    d->pin_base存放的是当前pinctrl的第一个gpio的逻辑gpio号
73     pin_base += d->nr_pins;  // d->nr_pins存放的是当前pinctrl含有的gpio的总个数
74     return ctrl;
75 }

接着分析samsung_gpiolib_register

    ----> samsung_gpiolib_register

对于普通的可以产生中断的gpio,会由exynos_eint_gpio_init处理

    ----> exynos_eint_gpio_init

 1 static int exynos_eint_gpio_init(struct samsung_pinctrl_drv_data *d)
 2 {
 3     struct samsung_pin_bank *bank;
 4     struct device *dev = d->dev;
 5     int ret;
 6     int i;
 7 // 这里的d->irq其实就是pinctrl@11000000节点的interrupts属性所映射到的virq,对应的hwirq就是SPI-46
 8     ret = devm_request_irq(dev, d->irq, exynos_eint_gpio_irq,
 9                     0, dev_name(dev), d); // 这里申请了中断,在中断处理函数exynos_eint_gpio_irq中会获得发生中断的引脚,转化为hwirq,再进行一步处理
10     bank = d->pin_banks;  // pinctrl@11000000含有的banks的数量
11     for (i = 0; i < d->nr_banks; ++i, ++bank) {
12         if (bank->eint_type != EINT_TYPE_GPIO)  // 前面已经说过,普通可以产生中断的gpio的eint_type是EINT_TYPE_GPIO
13             continue;
14 // 创建一个linear irq domain,从这里看到,每一个bank都会有一个irq domain,nr_pins是这个bank含有的gpio的个数,
15 // 也是这个irq domain支持的中断的个数
16         bank->irq_domain = irq_domain_add_linear(bank->of_node,
17                 bank->nr_pins, &exynos_eint_irqd_ops, bank);  
18         bank->soc_priv = devm_kzalloc(d->dev,        sizeof(struct exynos_eint_gpio_save), GFP_KERNEL);
19         bank->irq_chip = &exynos_gpio_irq_chip;  // irq_chip用于抽象一个中断控制器
20     }
21     return 0;
22 }

上面也只是创建了irq domain,还没有存放任何中断映射关系,在需要的时候才会映射。

对于具备唤醒功能的外部中断功能的gpio,由exynos_eint_wkup_init处理

    ----> exynos_eint_wkup_init

 1 static int exynos_eint_wkup_init(struct samsung_pinctrl_drv_data *d)
 2 {
 3     struct device *dev = d->dev;
 4     struct device_node *wkup_np = NULL;
 5     struct device_node *np;
 6     struct samsung_pin_bank *bank;
 7     struct exynos_weint_data *weint_data;
 8     struct exynos_muxed_weint_data *muxed_data;
 9     struct exynos_irq_chip *irq_chip;
10     unsigned int muxed_banks = 0;
11     unsigned int i;
12     int idx, irq;
13     for_each_child_of_node(dev->of_node, np) {  // 寻找wakeup-interrupt-controller节点的device node
14         const struct of_device_id *match;
15         match = of_match_node(exynos_wkup_irq_ids, np);
16         if (match) {
17             irq_chip = kmemdup(match->data,
18                 sizeof(*irq_chip), GFP_KERNEL);  // 获得irq_chip
19             wkup_np = np;
20             break;
21         }
22     }
23     if (!wkup_np)  // 既然这个pinctrl定义了exynos_eint_wkup_init,那么就一定会能够找到wakeup-interrupt-controller的node
24         return -ENODEV;
25     bank = d->pin_banks; // 遍历这个pinctrl的每一个bank
26     for (i = 0; i < d->nr_banks; ++i, ++bank) {
27         if (bank->eint_type != EINT_TYPE_WKUP)  // 前面说过,具备唤醒功能的gpio的bank的eint_type才是EINT_TYPE_WKUP
28             continue;
29 // 创建linear irq domain,还没有映射关系
30         bank->irq_domain = irq_domain_add_linear(bank->of_node,
31                 bank->nr_pins, &exynos_eint_irqd_ops, bank);
32         bank->irq_chip = irq_chip;
33 // 对于gpx0和gpx1,具备"interrutps", 描述了XEINT0-15, gpx2和gpx3没有, 描述了XEINT16-31
34 // 这里要区分开的原因是, XEINT0-15每一个都对应到GIC上面的一个SPI中断
35 // 而XEINT16-31共用了GIC上面的SPI-32, 所以需要做mux处理
36         if (!of_find_property(bank->of_node, "interrupts", NULL)) {
37             bank->eint_type = EINT_TYPE_WKUP_MUX;  // 将eint_type修改为EINT_TYPE_WKUP_MUX,其实就是对gpx2和gpx3这两个bank的eint_type进行了修改
38             ++muxed_banks;  // 这个值其实就是2,即gpx2和gpx3这两个bank
39             continue;
40         }
41         weint_data = devm_kzalloc(dev, bank->nr_pins
42                     * sizeof(*weint_data), GFP_KERNEL);
43         for (idx = 0; idx < bank->nr_pins; ++idx) {
44             irq = irq_of_parse_and_map(bank->of_node, idx);  // 获得该node中,为第idx个中断创建对应的virq并返回
45             weint_data[idx].irq = idx;
46             weint_data[idx].bank = bank;
47 // 会将irq对应的irq_desc的handle_irq设置为exynos_irq_eint0_15
48             irq_set_chained_handler_and_data(irq, exynos_irq_eint0_15, &weint_data[idx]);
49         }
50     }
51     if (!muxed_banks) // 对于XEINT0-15,到这里就返回了
52         return 0;
53 // 获得wakeup-interrupt-controller的interrupts属性中的第一个中断,对应的hwirq就是GIC上面的SPI-32
54     irq = irq_of_parse_and_map(wkup_np, 0);
55     muxed_data = devm_kzalloc(dev, sizeof(*muxed_data)
56         + muxed_banks*sizeof(struct samsung_pin_bank *), GFP_KERNEL);
57 // 会将irq对应的irq_desc的handle_irq设置为exynos_irq_demux_eint16_31, 从这里我们可以猜测,函数exynos_irq_demux_eint16_31一定会通过读取寄存器获得具体哪个EINT触发了
58     irq_set_chained_handler_and_data(irq, exynos_irq_demux_eint16_31,
59                      muxed_data);
60     bank = d->pin_banks;
61     idx = 0;
62     for (i = 0; i < d->nr_banks; ++i, ++bank) {
63         if (bank->eint_type != EINT_TYPE_WKUP_MUX)
64             continue;
65         muxed_data->banks[idx++] = bank; // 将gpx2和gpx3对应的bank存放到了muxed_data中
66     }
67     muxed_data->nr_banks = muxed_banks;
68     return 0;
69 }

GPIO控制器的注册先分析到这里

下面开始第四部分。

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

这里对应的节点就是上面设备树中的interrupt_xeint26_29,这个节点的interrupt-parent就是gpx3,其interrupts属性中一共描述了四个中断,分别是gpx3_2、gpx3_3、gpx3_4和gpx3_5, 分别对应XEINT26到XEINT29.

有个前面第二部分和第三部分的基础,在将interrupt_xeint26_29转换成为platform_device的时候,会解析其interrupts属性,这部分请参考第二部分的分析,不同之处是此时的irq domain是gpx3对应的irq domain,期间会调用该domain的ops->xlate函数,即从第三部分的分析知道,domain的ops就是exynos_eint_irqd_ops,查看定义可以知道xlate是irq_domain_xlate_twocell,这是kernel提供的对#interrupt-cells为2的中断控制器的通用处理:

 1 int irq_domain_xlate_twocell(struct irq_domain *d, struct device_node *ctrlr,
 2             const u32 *intspec, unsigned int intsize,
 3             irq_hw_number_t *out_hwirq, unsigned int *out_type)
 4 {
 5     if (WARN_ON(intsize < 2))  // 参数个数正常情况下为2
 6         return -EINVAL;
 7     *out_hwirq = intspec[0]; // 可以看到,第一个参数表示的是hwirq
 8     *out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;  // 第二个参数表示的是中断类型
 9     return 0;
10 }

在从interrupts获得第index个中断的hwirq和irq type后,就会为这个hwirq从kernel中分配一个全局为一个virq,以及对应的irq_desc,然后将它们的映射关系存放到对应gpx3的irq domain中。

从这里知道了,在Samsung平台上面,每一个gpio bank都对应自己的irq_chip和irq_domain的好处,以gpx3_2为例,它的hwirq就是2,但是要注意,这里的hwirq仅仅在所处的irq_domain或者说irq_chip内才有意义,不同的irq_domain可能会有相同的hwirq,比如gpx2_2的hwirq也是2,但是每一个hwirq对应的virq是系统唯一的,virq其实就是全局变量allocated_irqs的一个位号,hwirq和virq的映射关系存放在hwirq所处的irq domain中,通过hwirq在所属的irq domain内可以迅速索引到virq,然后用virq可以索引到对应的唯一的irq_desc,在irq_desc中也有专门的变量用于存放virq、hwirq以及irq_domain,我们在驱动中申请中断时看到的都是virq,没有必要关心hwirq或者irq_desc。

中断映射图

下面我们结合开机log,看一下上面框图中的中断映射:

要看到这些log,需要打开部分代码的log或者自己添加一些log语句,下面是patch:

 1 diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c
 2 index d6c404b..f308213 100644
 3 --- a/drivers/irqchip/irq-gic.c
 4 +++ b/drivers/irqchip/irq-gic.c
 5 @@ -1027,6 +1027,8 @@ static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
 6      if (ret)
 7          return ret;
 8  
 9 +    printk("%s enter, virq: %d, hwirq: %d\n", __func__, virq, hwirq);
10 +
11      for (i = 0; i < nr_irqs; i++)
12          gic_irq_domain_map(domain, virq + i, hwirq + i);
13  
14 @@ -1113,6 +1115,7 @@ static int gic_init_bases(struct gic_chip_data *gic, int irq_start,
15          gic_irqs = 1020;
16      gic->gic_irqs = gic_irqs;
17  
18 +    printk("%s enter, handle: %p\n", __func__, handle);
19      if (handle) {        /* DT/ACPI */
20          gic->domain = irq_domain_create_linear(handle, gic_irqs,
21                                 &gic_irq_domain_hierarchy_ops,
22 @@ -1367,6 +1370,8 @@ gic_of_init(struct device_node *node, struct device_node *parent)
23      struct gic_chip_data *gic;
24      int irq, ret;
25  
26 +    printk("%s enter, node: %s\n", __func__, node->full_name);
27 +
28      if (WARN_ON(!node))
29          return -ENODEV;
30  
31 diff --git a/drivers/of/platform.c b/drivers/of/platform.c
32 index e4bf07d..b6cdef3 100644
33 --- a/drivers/of/platform.c
34 +++ b/drivers/of/platform.c
35 @@ -11,7 +11,7 @@
36   *  2 of the License, or (at your option) any later version.
37   *
38   */
39 -
40 +#define DEBUG
41  #define pr_fmt(fmt)    "OF: " fmt
42  
43  #include <linux/errno.h>
44 diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c
45 index 00bb0ae..1687c20 100644
46 --- a/kernel/irq/irqdesc.c
47 +++ b/kernel/irq/irqdesc.c
48 @@ -712,6 +712,7 @@ __irq_alloc_descs(int irq, unsigned int from, unsigned int cnt, int node,
49      }
50  
51      bitmap_set(allocated_irqs, start, cnt);
52 +    printk("%s: alloc virq: %d, cnt: %d\n", __func__, start, cnt);
53      mutex_unlock(&sparse_irq_lock);
54      return alloc_descs(start, cnt, node, affinity, owner);
55  
56 diff --git a/kernel/irq/irqdomain.c b/kernel/irq/irqdomain.c
57 index 8c0a0ae..6927617 100644
58 --- a/kernel/irq/irqdomain.c
59 +++ b/kernel/irq/irqdomain.c
60 @@ -1,3 +1,4 @@
61 +#define DEBUG
62  #define pr_fmt(fmt)  "irq: " fmt
63  
64  #include <linux/debugfs.h>

结合开机log,

 1 [    0.224531] __irq_alloc_descs: alloc virq: 69, cnt: 1
 2 [    0.224566] gic_irq_domain_alloc enter, virq: 69, hwirq: 78
 3 ... ...
 4 [    0.228379] exynos_eint_wkup_init enter, line: 532
 5 ... ...
 6 [    0.228689] __irq_alloc_descs: alloc virq: 84, cnt: 1
 7 [    0.228721] gic_irq_domain_alloc enter, virq: 84, hwirq: 62
 8 [    0.228737] __irq_alloc_descs: alloc virq: 85, cnt: 1
 9 [    0.228773] gic_irq_domain_alloc enter, virq: 85, hwirq: 63
10 [    0.228782] irq: Added domain (null)
11 [    0.228788] irq: Added domain (null)
12 [    0.228793] exynos_eint_wkup_init enter, line: 551
13 [    0.228806] __irq_alloc_descs: alloc virq: 86, cnt: 1
14 [    0.228838] gic_irq_domain_alloc enter, virq: 86, hwirq: 64
15 ... ...
16 [    0.233316] __irq_alloc_descs: alloc virq: 100, cnt: 1
17 [    0.233351] irq: irq 2 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 100
18 [    0.233370] irq: irq_create_mapping(0xef205d00, 0x3)
19 [    0.233376] irq: -> using domain @ef205d00
20 [    0.233381] __irq_alloc_descs: alloc virq: 101, cnt: 1
21 [    0.233414] irq: irq 3 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 101
22 [    0.233433] irq: irq_create_mapping(0xef205d00, 0x4)
23 [    0.233438] irq: -> using domain @ef205d00
24 [    0.233444] __irq_alloc_descs: alloc virq: 102, cnt: 1
25 [    0.233477] irq: irq 4 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 102
26 [    0.233495] irq: irq_create_mapping(0xef205d00, 0x5)
27 [    0.233500] irq: -> using domain @ef205d00
28 [    0.233506] __irq_alloc_descs: alloc virq: 103, cnt: 1
29 [    0.233544] irq: irq 5 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 103
30 [    0.233683] irq: irq_create_mapping(0xef205b80, 0x6)
31 [    0.233689] irq: -> using domain @ef205b80
32 [    0.233695] __irq_alloc_descs: alloc virq: 104, cnt: 1
33 [    0.233730] irq: irq 6 on domain /pinctrl@11000000/gpx1 mapped to virtual irq 104
34 [    0.233749] irq: irq_create_mapping(0xef205b80, 0x7)
35 [    0.233753] irq: -> using domain @ef205b80
36 [    0.233759] __irq_alloc_descs: alloc virq: 105, cnt: 1
37 [    0.233792] irq: irq 7 on domain /pinctrl@11000000/gpx1 mapped to virtual irq 105
38 [    0.233916] irq: irq_create_mapping(0xef205a00, 0x0)
39 [    0.233922] irq: -> using domain @ef205a00
40 [    0.233928] __irq_alloc_descs: alloc virq: 106, cnt: 1
41 [    0.233968] irq: irq 0 on domain /pinctrl@11000000/gpm4 mapped to virtual irq 106

可以得到下面的中断映射图:

技术分享

可以看到,每一个hwirq在kernel中都会对应一个唯一的virq,它们的映射关系存放在所属的irq domain中,每一个virq又可以找到唯一的irq_desc.

中断触发和处理

相关代码:

arch/arm/kernel/entry-armv.S

arch/arm/kernel/traps.c 

arch/arm/mm/mmu.c

分析中断触发和处理过程,结合log,打印栈

我们以上面框图中三个比较典型的中断为例分析: 

1. XEINT15: 因为这个中断直接对应到了GIC模块上面的SPI-31

2. XEINT26:因为XEINT24-XEINT31共用了GIC模块上面的SPI-32,在处理过程中会涉及到mux

3. GPM4-0:: 这是一个普通的可以产生中断的gpio,在上图中的pinctrl中具备这个功能的gpio共享的是pinctrl在GIC上面的中断SPI-46

关于ARM的中断知识可以参考下面的一篇博客: Exynos4412裸机开发——中断处理

XEINT15

汇编部分不打算过多分析,这部分在网上有大量的文章(如Exynos4412 中断处理流程详解)。这里只需要知道,在irq中断发生后,PC指针会跳转到中断向量表(起始地址0xffff0000)中负责处理irq中断的位置:

1 .L__vectors_start:
2     W(b)    vector_rst
3     W(b)    vector_und
4     W(ldr)    pc, .L__vectors_start + 0x1000
5     W(b)    vector_pabt
6     W(b)    vector_dabt
7     W(b)    vector_addrexcptn
8     W(b)    vector_irq
9     W(b)    vector_fiq

在vector_irq中会跳转到__irq_svc执行, 紧接着从__irq_svc又跳到irq_handler,irq_handler其实是个宏,它完成的操作是将PC赋值为handle_arch_irq的地址。

handle_arch_irq这个之前在第一部分 GIC控制器中说过,在GIC驱动中会将handle_arch_irq设置为gic_handle_irq,这样GIC就接管了剩下的工作。

gic_handle_irq

 1 static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
 2 {
 3     u32 irqstat, irqnr;
 4     struct gic_chip_data *gic = &gic_data[0];
 5     void __iomem *cpu_base = gic_data_cpu_base(gic);  // cpu interface的基地址
 6     do {
 7 // GIC_CPU_INTACK是0x0c,参考4412的datasheet的第9节可以知道, ICCIAR_CPUn的[9:0]存放的是发生中断的中断号
 8 // 所以,irqnr中就是发生中断的那个中断号,当然这个获得的是hwirq,而不是virq。对于XEINT15,hwirq就是SPI-31,由于是跟PPI和SGI统一编号,就是63
 9         irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
10         irqnr = irqstat & GICC_IAR_INT_ID_MASK;  // GICC_IAR_INT_ID_MASK是0x3ff
11 // 我们知道 PPI和SPI的范围是16到1020
12         if (likely(irqnr > 15 && irqnr < 1020)) {
13 ... ...
14             handle_domain_irq(gic->domain, irqnr, regs);
15             continue;
16         }
17 // SGI中断号的范围是0到15, SGI用于CPU之间通讯用的,当然只有SMP才有可能
18         if (irqnr < 16) {
19 ... ...
20 #ifdef CONFIG_SMP
21 ... ...
22             handle_IPI(irqnr, regs);  // 这个是处理SGI中断用的,SGI中断暂不分析,因为不归kernel的中断子系统管理
23 #endif
24             continue;
25         }
26         break;
27     } while (1);
28 }

    ---> handle_domain_irq

        ---> __handle_domain_irq(domain, hwirq, true, regs)

 1 int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
 2             bool lookup, struct pt_regs *regs)
 3 {
 4     struct pt_regs *old_regs = set_irq_regs(regs);
 5     unsigned int irq = hwirq;
 6     int ret = 0;
 7 ...
 8 // 看到了吧,irq_domain派上用场了,在gic的irq domain中利用从寄存器中得到的hwirq
 9 // 查询得到virq,知道了virq,剩下的处理就好办了
10         irq = irq_find_mapping(domain, hwirq); 
11 ...
12         generic_handle_irq(irq);
13 ...
14 }

            ----> generic_handle_irq

1 int generic_handle_irq(unsigned int irq)
2 {
3 // 根据virq,查询irq_desc_tree,就可以迅速定位到之前分配的irq_desc
4     struct irq_desc *desc = irq_to_desc(irq);
5 ... ...
6 // 下面的这个函数就干了一件事,调用desc->handle_irq(desc)
7     generic_handle_irq_desc(desc);
8     return 0;
9 }

以XEINT15为例,在第三部分 GPIO控制器驱动注册中exynos_eint_wkup_init-->irq_set_chained_handler_and_data(irq, exynos_irq_eint0_15, &weint_data[idx])

函数irq_set_chained_handler_and_data完成的一个作用就是将virq对应的irq_desc的handle_irq设置为exynos_irq_eint0_15

                ---> exynos_irq_eint0_15

 1 static void exynos_irq_eint0_15(struct irq_desc *desc)
 2 {
 3 // eintd就是在调用irq_set_chained_handler_and_data传递的第三个参数
 4     struct exynos_weint_data *eintd = irq_desc_get_handler_data(desc);
 5 // 对于XEINT15, 对应的GPIO是GPX1_7,对应的就是bank就是gpx1
 6 // 第三部分的分析中,每一组bank都有自己的irq_chip和irq_domain
 7     struct samsung_pin_bank *bank = eintd->bank;
 8     struct irq_chip *chip = irq_desc_get_chip(desc);
 9     int eint_irq;
10 ... ...
11 // 这里的eintd->irq就是gpx1_7在gpx1这个bank中的编号,即7, 也就是hwirq,
12 // 通过hwirq在对应的domain中查询到virq,从这里应该能够体会到hwirq只有在
13 // 所属的irq domain或者说irq_chip内才有意义
14     eint_irq = irq_linear_revmap(bank->irq_domain, eintd->irq);
15     generic_handle_irq(eint_irq);  // 这个函数上面分析过,最终调用的是virq对应的irq_desc的handle_irq
16 ... ...
17 }

 到这里似乎分析不下去了,怎么又到generic_handle_irq?eint_irq对应的irq_desc的handle_irq是什么东东?

不要忘了,要想使用XEINT15,一般的做法是,现在设备树中配置,如:

1     interrupt_xeint15 {
2         compatible = "tiny4412,interrupt_xeint15";
3         interrupt-parent = <&gpx1>;
4         interrupts = <0x7 0x2>;
5     };

可以看到,实际在设备树中配置的是gpx1_7,其中gpx1的定义如下:

1         gpx1: gpx1 {
2             gpio-controller;
3             #gpio-cells = <2>;
4             interrupt-controller;
5             interrupt-parent = <&gic>;
6             interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
7                      <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
8             #interrupt-cells = <2>;
9         };

这里可以算是两级中断控制器的级联了,第一级是GIC中断控制器,第二级是gpx1这个中断控制器,只不过这gpx1上面引用了gic的8个中断,从spi-24一直到spi-31。这些中断跟gpx1的8个gpio引脚是一一对应的。而且,gpx1定义里的interrupts属性并不是多余,在第三部分gpio控制器驱动中,函数exynos_eint_wkup_init会处理interrupts属性,映射关系存放在gic irq domain中,可以想象一下,如果这里没有interrupts属性,gpx1跟gic之间虽然有物理上的连接,但是软件上没有配置,根本无法完成级联工作。还有,我们能否直接越过gpx1_7,直接去申请XEINT15?答案是不能?尽管软件上面在映射时将SPI-31对应的virq的irq_desc的handle_irq初始化为了handle_fasteoi_irq,但是如果不经过gpx1_7,怎么触发这个中断呢?

回到正题,设备树里配置好之后,接下来kernel会将interrupt_xeint15节点的interrupts属性转为resource,期间会进行中断映射。在对应的驱动程序中,我们要做的就剩下获得这个irq resource,然后调用request_irq,简单看一下。

requset_irq 

    ---> request_threaded_irq 

        ---> __setup_irq 

            ---> __irq_set_trigger(desc, new->flags & IRQF_TRIGGER_MASK) 

                ---> chip->irq_set_type(&desc->irq_data, flags)

这里会调用virq所属的irq_chip的irq_set_type函数,对于gpx1_7就是exynos4210_wkup_irq_chip,它的irq_set_type是exynos_irq_set_type

 1 static int exynos_irq_set_type(struct irq_data *irqd, unsigned int type)
 2 {
 3     struct irq_chip *chip = irq_data_get_irq_chip(irqd);
 4     struct exynos_irq_chip *our_chip = to_exynos_irq_chip(chip);
 5     struct samsung_pin_bank *bank = irq_data_get_irq_chip_data(irqd);
 6     struct samsung_pinctrl_drv_data *d = bank->drvdata;
 7     unsigned int shift = EXYNOS_EINT_CON_LEN * irqd->hwirq;
 8     unsigned int con, trig_type;
 9 // 获得中断配置寄存器的地址
10     unsigned long reg_con = our_chip->eint_con + bank->eint_offset;
11     switch (type) {  // 将kernel的type转换成为samsung自己定义的type
12     case IRQ_TYPE_EDGE_RISING:
13         trig_type = EXYNOS_EINT_EDGE_RISING;
14         break;
15     case IRQ_TYPE_EDGE_FALLING:
16         trig_type = EXYNOS_EINT_EDGE_FALLING;
17         break;
18     case IRQ_TYPE_EDGE_BOTH:
19         trig_type = EXYNOS_EINT_EDGE_BOTH;
20         break;
21     case IRQ_TYPE_LEVEL_HIGH:
22         trig_type = EXYNOS_EINT_LEVEL_HIGH;
23         break;
24     case IRQ_TYPE_LEVEL_LOW:
25         trig_type = EXYNOS_EINT_LEVEL_LOW;
26         break;
27     default:
28         pr_err("unsupported external interrupt type\n");
29         return -EINVAL;
30     }
31     if (type & IRQ_TYPE_EDGE_BOTH)  // 根据触发类型,设置irq_desc的handle_irq,如果是边沿触发,就设置为handle_edge_irq
32         irq_set_handler_locked(irqd, handle_edge_irq);
33     else  // 如果是电平触发,就设置为handle_level_irq
34         irq_set_handler_locked(irqd, handle_level_irq);
35 // 配置gpio的中断配置寄存器
36     con = readl(d->virt_base + reg_con);
37     con &= ~(EXYNOS_EINT_CON_MASK << shift);
38     con |= trig_type << shift;
39     writel(con, d->virt_base + reg_con);
40     return 0;
41 }

好了,知道这个后,前面的分析就有眉目了,刚才分析到gpx1_7对应的eint_irq的irq_desc的handle_irq, 其实就是handle_edge_irq

handle_edge_irq

    ---> handle_irq_event

        ---> handle_irq_event_percpu(desc)

            ---> __handle_irq_event_percpu(desc, &flags)

 1 irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
 2 {
 3     irqreturn_t retval = IRQ_NONE;
 4     unsigned int irq = desc->irq_data.irq;  // virq
 5     struct irqaction *action;
 6 // 遍历这个irq_desc的act链表,依次处理每一个action
 7 // 这里的action->handler就是在request_irq的时候传递的中断处理函数
 8     for_each_action_of_desc(desc, action) {
 9         irqreturn_t res;
10 ... ...
11         res = action->handler(irq, action->dev_id);
12 ... ...
13         switch (res) {
14         case IRQ_WAKE_THREAD:  // 返回IRQ_WAKE_THREAD意味着需要唤醒底半部处理的线程
15 ... ...
16             __irq_wake_thread(desc, action);
17         case IRQ_HANDLED:  // 正常情况下返回这个
18             *flags |= action->flags;
19             break;
20         default:
21             break;
22         }
23         retval |= res;
24     }
25     return retval;
26 }

到这里XEINT15的处理就分析完了。

XEINT26

有了分析XEINT15的基础,我们只需要注意不同点。

前面我们知道,XEINT16-31共享了GIC上面的SPI-32,按照分析XEINT15的逻辑:

vector_irq

    ---> __irq_svc

        ---> irq_handler

            ---> gic_handle_irq

                ---> handle_domain_irq

                    ---> __handle_domain_irq

                        ---> exynos_irq_demux_eint16_31

从名字上面都可以看出,这里要做demux处理:

 1 static void exynos_irq_demux_eint16_31(struct irq_desc *desc)
 2 {
 3     struct irq_chip *chip = irq_desc_get_chip(desc);
 4     struct exynos_muxed_weint_data *eintd = irq_desc_get_handler_data(desc);
 5     struct samsung_pinctrl_drv_data *d = eintd->banks[0]->drvdata;
 6     unsigned long pend;
 7     unsigned long mask;
 8     int i;
 9 ... ...
10     for (i = 0; i < eintd->nr_banks; ++i) {  // 这里的nr_banks是2, 即gpx2和gpx3
11         struct samsung_pin_bank *b = eintd->banks[i];
12         pend = readl(d->virt_base + b->irq_chip->eint_pend    + b->eint_offset);
13         mask = readl(d->virt_base + b->irq_chip->eint_mask    + b->eint_offset);
14 // peng & ~mask 就可以知道是那个gpio中断被触发了
15 // 可以参考4412的datasheet的gpio那一节
16         exynos_irq_demux_eint(pend & ~mask, b->irq_domain);  
17     }
18 ... ...
19 }

    ---> exynos_irq_demux_eint

 1 static inline void exynos_irq_demux_eint(unsigned long pend,
 2                         struct irq_domain *domain)
 3 {
 4     unsigned int irq;
 5     while (pend) {
 6         irq = fls(pend) - 1;  // 获得hwirq
 7   // 用hwirq查询domain,获得virq,然后调用标准的generic_handle_irq
 8         generic_handle_irq(irq_find_mapping(domain, irq));
 9         pend &= ~(1 << irq);
10     }
11 }

 根据分析XEINT15的逻辑,如果在申请gpx3_2对应的中断时是选的是边沿触发,就是handle_edge_irq,如果是电平触发,就是handle_level_irq。剩下的分析跟XEINT15一样了。

GPM4-0

这个跟XEINT不同之处是这是一个普通的可以产生中断的gpio,这些gpio将来都会pinctrl在GIC上面的SPI-46触发GIC中断。在第三部分GPIO控制器驱动中会调用exynos_eint_gpio_init,这个函数首先调用devm_request_irq对SPI-46对应的virq进行了申请,中断处理函数是exynos_eint_gpio_irq,在这个函数中会查询到底是那个中断被触发了,然后进行demux处理。之后,同样也创建了irq_domain和irq_chip,这里的irq_chip是exynos_gpio_irq_chip,它的irq_set_type也是exynos_irq_set_type。

跟XEINT还有一个不同的是,并没有对SPI-46对应的virq的irq_desc->handle_irq进行修改,保持的还是映射时的初始化值handle_fasteoi_irq。

vector_irq

    ---> __irq_svc

        ---> irq_handler

            ---> gic_handle_irq

                ---> handle_domain_irq

                    ---> __handle_domain_irq

                        ---> handle_fasteoi_irq

                            ---> handle_irq_event

                                ---> handle_irq_event_percpu

                                    ---> __handle_irq_event_percpu

前面说过,在__handle_irq_event_percpu中会遍历irq_desc的act链表,此时就会调用到刚才注册的中断处理函数exynos_eint_gpio_irq

                                        ---> exynos_eint_gpio_irq

 1 static irqreturn_t exynos_eint_gpio_irq(int irq, void *data)
 2 {
 3     struct samsung_pinctrl_drv_data *d = data;
 4     struct samsung_pin_bank *bank = d->pin_banks;
 5     unsigned int svc, group, pin, virq;
 6     svc = readl(d->virt_base + EXYNOS_SVC_OFFSET);
 7     group = EXYNOS_SVC_GROUP(svc);
 8     pin = svc & EXYNOS_SVC_NUM_MASK;  // 获得实际发生中断的gpio号,也即是hwirq
 9     if (!group)
10         return IRQ_HANDLED;
11     bank += (group - 1);
12 // 用hwirq查询irq domain, 获得virq
13     virq = irq_linear_revmap(bank->irq_domain, pin);
14     if (!virq)
15         return IRQ_NONE;
16 // 下面是标准逻辑,之前分析过了
17     generic_handle_irq(virq);
18     return IRQ_HANDLED;
19 }

如果是按照边沿方式申请的,后面会调用handle_edge_irq,否则是handle_level_irq。

驱动程序

可以将上面三个设备树节点的驱动都放到一个驱动里,也可以分开。为了简单起见,这里分开。

这里仅以interrupt_xeint26_29.c为例,这个是interrupt_xeint26_29对应的驱动程序,其他两个基本类似,下载地址: http://files.cnblogs.com/files/pengdonglin137/interrupts_demo_drivers.tar.gz

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/platform_device.h>
 4 #include <linux/gpio.h>
 5 #include <linux/of.h>
 6 #include <linux/of_gpio.h>
 7 #include <linux/interrupt.h>
 8 typedef struct 
 9 {
10     int gpio;
11     int irq;
12     char name[20];
13 }xint26_29_data_t;
14 static irqreturn_t xint26_29_isr_pdev(int irq, void *dev_id)
15 {
16     xint26_29_data_t *data = dev_id;
17  // 可以在这里加 WARN_ON(1)或者WARN_ON_ONCE(1),将调用栈打印出来,看看上面的分析是否正确。
18     printk("%s enter, %s irq: %d\n", __func__, data->name, irq);   // 将中断的name和virq打印出来
19     return IRQ_HANDLED;
20 }
21 static int xint26_29_probe(struct platform_device *pdev) {
22     struct device *dev = &pdev->dev;
23     int irq = -1;
24     int ret = 0;
25     int i = 0;
26     xint26_29_data_t *data = NULL;
27     printk("%s enter.\n", __func__);
28     if (!dev->of_node) {
29         dev_err(dev, "no platform data.\n");
30         goto err0;
31     }
32     data = devm_kmalloc(dev, sizeof(*data)*4, GFP_KERNEL);
33     if (!data) {
34         dev_err(dev, "no memory.\n");
35         goto err0;
36     }
37     for (i = 0; i < 4; i++) {
38         irq = platform_get_irq(pdev, i);  // 获得irq resource
39         sprintf(data[i].name, "tiny4412,xint26_29-%d", i);
40         ret = devm_request_any_context_irq(dev, irq,
41             xint26_29_isr_pdev, IRQF_TRIGGER_FALLING, data[i].name, data+i);  // 申请中断
42         if (ret < 0) {
43             dev_err(dev, "Unable to claim irq %d; error %d\n",
44                 irq, ret);
45             goto err0;
46         }
47         printk("request irq: %d\n", irq);
48     }
49     return 0;
50 err0:
51     return -EINVAL;
52 }
53 static int xint26_29_remove(struct platform_device *pdev) {
54     printk("%s enter.\n", __func__);
55     return 0;
56 }
57 static const struct of_device_id xint26_29_dt_ids[] = {
58     { .compatible = "tiny4412,interrupt_xeint26_29", },
59     {},
60 };
61 MODULE_DEVICE_TABLE(of, xint26_29_dt_ids);
62 static struct platform_driver xint26_29_driver = {
63     .driver        = {
64         .name    = "interrupt_xeint26_29",
65         .of_match_table    = of_match_ptr(xint26_29_dt_ids),
66     },
67     .probe        = xint26_29_probe,
68     .remove        = xint26_29_remove,
69 };
70 static int __init xint26_29_init(void)
71 {
72     int ret;
73     ret = platform_driver_register(&xint26_29_driver);
74     if (ret)
75         printk(KERN_ERR "xint26_29: probe failed: %d\n", ret);
76     return ret;
77 }
78 module_init(xint26_29_init);
79 static void __exit xint26_29_exit(void)
80 {
81     platform_driver_unregister(&xint26_29_driver);
82 }
83 module_exit(xint26_29_exit);
84 MODULE_LICENSE("GPL");

上面的驱动非常简单,没什么好说的,在中断处理函数中可以将调用栈打印出来,验证一下我们上面的分析是否正确。

对于interrupt_xeint26_29分别对应的是tiny4412开发板底板上面的四个按键,对于interrupt_xeint14_15,当点击tiny4412的触摸屏的时候,XEINT14会被触发,对于interrupt_gpm4_0,在加载驱动时会被触发(因为这个gpio接到了led上面,这里只是示例)

下面是这三个驱动申请的中断被触发时的调用栈:

interrupt_xeint26_29:

 1 [ 1742.934663] [<bf010154>] (xint26_29_isr_pdev [interrupt_xeint26_29]) from [<c0163144>] (__handle_irq_event_percpu+0x40/0x118)
 2 [ 1742.945928] [<c0163144>] (__handle_irq_event_percpu) from [<c0163238>] (handle_irq_event_percpu+0x1c/0x58)
 3 [ 1742.955555] [<c0163238>] (handle_irq_event_percpu) from [<c01632ac>] (handle_irq_event+0x38/0x5c)
 4 [ 1742.964413] [<c01632ac>] (handle_irq_event) from [<c0166838>] (handle_edge_irq+0xf4/0x1b8)
 5 [ 1742.972673] [<c0166838>] (handle_edge_irq) from [<c01625f8>] (generic_handle_irq+0x24/0x34)
 6 [ 1742.981010] [<c01625f8>] (generic_handle_irq) from [<c03620f8>] (exynos_irq_demux_eint16_31+0xb4/0x13c)
 7 [ 1742.990373] [<c03620f8>] (exynos_irq_demux_eint16_31) from [<c01625f8>] (generic_handle_irq+0x24/0x34)
 8 [ 1742.999662] [<c01625f8>] (generic_handle_irq) from [<c01629b4>] (__handle_domain_irq+0x7c/0xe8)
 9 [ 1743.008336] [<c01629b4>] (__handle_domain_irq) from [<c010149c>] (gic_handle_irq+0x54/0x98)
10 [ 1743.016662] [<c010149c>] (gic_handle_irq) from [<c010c8cc>] (__irq_svc+0x6c/0xa8)

interrupt_xeint14_15:

 1 [ 1791.978441] [<c011c4c0>] (warn_slowpath_null) from [<bf014198>] (xeint14_15_isr_pdev+0x60/0x6c [interrupt_xeint14_15])
 2 [ 1791.989126] [<bf014198>] (xeint14_15_isr_pdev [interrupt_xeint14_15]) from [<c0163144>] (__handle_irq_event_percpu+0x40/0x118)
 3 [ 1792.000477] [<c0163144>] (__handle_irq_event_percpu) from [<c0163238>] (handle_irq_event_percpu+0x1c/0x58)
 4 [ 1792.010106] [<c0163238>] (handle_irq_event_percpu) from [<c01632ac>] (handle_irq_event+0x38/0x5c)
 5 [ 1792.018965] [<c01632ac>] (handle_irq_event) from [<c0166838>] (handle_edge_irq+0xf4/0x1b8)
 6 [ 1792.027226] [<c0166838>] (handle_edge_irq) from [<c01625f8>] (generic_handle_irq+0x24/0x34)
 7 [ 1792.035561] [<c01625f8>] (generic_handle_irq) from [<c0361f88>] (exynos_irq_eint0_15+0x44/0x98)
 8 [ 1792.044229] [<c0361f88>] (exynos_irq_eint0_15) from [<c01625f8>] (generic_handle_irq+0x24/0x34)
 9 [ 1792.052910] [<c01625f8>] (generic_handle_irq) from [<c01629b4>] (__handle_domain_irq+0x7c/0xe8)
10 [ 1792.061585] [<c01629b4>] (__handle_domain_irq) from [<c010149c>] (gic_handle_irq+0x54/0x98)
11 [ 1792.069911] [<c010149c>] (gic_handle_irq) from [<c010c8cc>] (__irq_svc+0x6c/0xa8)

interrupt_gpm4_0:

 1 [   45.897679] [<bf000140>] (gpm4_0_isr_pdev [interrupt_gpm4_0]) from [<c0163144>] (__handle_irq_event_percpu+0x40/0x118)
 2 [   45.908340] [<c0163144>] (__handle_irq_event_percpu) from [<c0163238>] (handle_irq_event_percpu+0x1c/0x58)
 3 [   45.917967] [<c0163238>] (handle_irq_event_percpu) from [<c01632ac>] (handle_irq_event+0x38/0x5c)
 4 [   45.926828] [<c01632ac>] (handle_irq_event) from [<c0166838>] (handle_edge_irq+0xf4/0x1b8)
 5 [   45.935086] [<c0166838>] (handle_edge_irq) from [<c01625f8>] (generic_handle_irq+0x24/0x34)
 6 [   45.943421] [<c01625f8>] (generic_handle_irq) from [<c036202c>] (exynos_eint_gpio_irq+0x50/0x68)
 7 [   45.952174] [<c036202c>] (exynos_eint_gpio_irq) from [<c0163144>] (__handle_irq_event_percpu+0x40/0x118)
 8 [   45.961629] [<c0163144>] (__handle_irq_event_percpu) from [<c0163238>] (handle_irq_event_percpu+0x1c/0x58)
 9 [   45.971261] [<c0163238>] (handle_irq_event_percpu) from [<c01632ac>] (handle_irq_event+0x38/0x5c)
10 [   45.980116] [<c01632ac>] (handle_irq_event) from [<c016666c>] (handle_fasteoi_irq+0xd0/0x1a8)
11 [   45.988631] [<c016666c>] (handle_fasteoi_irq) from [<c01625f8>] (generic_handle_irq+0x24/0x34)
12 [   45.997228] [<c01625f8>] (generic_handle_irq) from [<c01629b4>] (__handle_domain_irq+0x7c/0xe8)
13 [   46.005903] [<c01629b4>] (__handle_domain_irq) from [<c010149c>] (gic_handle_irq+0x54/0x98)
14 [   46.014228] [<c010149c>] (gic_handle_irq) from [<c010c8cc>] (__irq_svc+0x6c/0xa8)

可以对照一下,跟上面的分析是否一致。

查看/proc/interrupts信息

加载并测试完成上面的三个驱动后,我们可以看一下此时系统的interrupt触发情况

 1 [root@tiny4412 ]# cat /proc/interrupts 
 2            CPU0       CPU1       CPU2       CPU3       
 3  36:          0          0          0          0     GIC-0  89 Edge      mct_comp_irq
 4  37:      17478       2373       4988       1714     GIC-0  28 Edge      MCT
 5  44:         34          0          0          0     GIC-0 107 Edge      mmc0
 6  45:          1          0          0          0     GIC-0 103 Edge      12480000.hsotg, 12480000.hsotg, dwc2_hsotg:usb1
 7  46:       5035          0          0          0     GIC-0 102 Edge      ehci_hcd:usb2, ohci_hcd:usb3
 8  48:        226          0          0          0     GIC-0  84 Edge      13800000.serial
 9  52:          4          0          0          0     GIC-0  67 Edge      12680000.pdma
10  53:          0          0          0          0     GIC-0  68 Edge      12690000.pdma
11  54:          0          0          0          0     GIC-0  66 Edge      12850000.mdma
12  67:          0          0          0          0     GIC-0 144 Edge      10830000.sss
13  68:          0          0          0          0     GIC-0  79 Edge      11400000.pinctrl
14  69:          1          0          0          0     GIC-0  78 Edge      11000000.pinctrl
15  87:          0          0          0          0  COMBINER  80 Edge      3860000.pinctrl
16  88:          0          0          0          0     GIC-0 104 Edge      106e0000.pinctrl
17 100:          6          0          0          0  exynos4210_wkup_irq_chip   2 Edge      tiny4412,xint26_29-0  
18 101:          2          0          0          0  exynos4210_wkup_irq_chip   3 Edge      tiny4412,xint26_29-1
19 102:          2          0          0          0  exynos4210_wkup_irq_chip   4 Edge      tiny4412,xint26_29-2
20 103:          2          0          0          0  exynos4210_wkup_irq_chip   5 Edge      tiny4412,xint26_29-3
21 104:        393          0          0          0  exynos4210_wkup_irq_chip   6 Edge      tiny4412,xeint14_15-0
22 105:          0          0          0          0  exynos4210_wkup_irq_chip   7 Edge      tiny4412,xeint14_15-1
23 106:          1          0          0          0  exynos_gpio_irq_chip   0 Edge      tiny4412,gpm4_0-0
24 IPI0:          0          1          1          1  CPU wakeup interrupts
25 IPI1:          0          0          0          0  Timer broadcast interrupts
26 IPI2:       1338       1509        468        549  Rescheduling interrupts
27 IPI3:          0          3          2          2  Function call interrupts
28 IPI4:          0          0          0          0  CPU stop interrupts
29 IPI5:        931         86        264         50  IRQ work interrupts
30 IPI6:          0          0          0          0  completion interrupts

图中加红的部分就是我们在驱动中申请到的中断在kernel里的记录信息,一般只有被request的中断才会出现在上面的记录之中。

这里第14行的11000000.pinctrl的中断触发计数需要注意一下,可以看到它在CPU0上触发了1次,其实也就是23行的gpm4_0在CPU0上面的触发次数,即11000000.pinctrl其实是其下的像gpm4_0这样的普通gpio中断总和,也容易理解。

上面显示的信息太多,每个字段有时啥意思?我们结合代码看看。

生成interrupts这个文件的代码是 fs/proc/interrupts.c

在 fs/proc/interrupts.c中会调用proc_create("interrupts", 0, NULL, &proc_interrupts_operations)创建,代码如下:

 1 static void *int_seq_start(struct seq_file *f, loff_t *pos)
 2 {
 3     return (*pos <= nr_irqs) ? pos : NULL;  // nr_irqs是目前系统中跟hwirq映射成功的virq的个数
 4 }
 5 static void *int_seq_next(struct seq_file *f, void *v, loff_t *pos)
 6 {
 7     (*pos)++;  // *pos 会从0开始一直遍历到nr_irqs
 8     if (*pos > nr_irqs)
 9         return NULL;
10     return pos;
11 }
12 static void int_seq_stop(struct seq_file *f, void *v)
13 {
14     /* Nothing to do */
15 }
16 static const struct seq_operations int_seq_ops = {
17     .start = int_seq_start,
18     .next  = int_seq_next,
19     .stop  = int_seq_stop,
20     .show  = show_interrupts  // 这个是分析的重点,读取/proc/interrupts时的信息也就是这个函数打印的
21 };
22 static int interrupts_open(struct inode *inode, struct file *filp)
23 {
24     return seq_open(filp, &int_seq_ops);  // 在读取interrupts,会回调int_seq_ops中的函数
25 }
26 static const struct file_operations proc_interrupts_operations = {
27     .open        = interrupts_open,
28     .read        = seq_read,
29     .llseek        = seq_lseek,
30     .release    = seq_release,
31 };
32 static int __init proc_interrupts_init(void)
33 {
34     proc_create("interrupts", 0, NULL, &proc_interrupts_operations);  // 在/proc下创建一个名为interrupts的节点
35     return 0;
36 }
37 fs_initcall(proc_interrupts_init);  // 在kernel启动到一定阶段会调用proc_interrupts_init

结合上面/proc/interrupts的输出分析一下show_interrupts

 1 #ifdef CONFIG_GENERIC_IRQ_SHOW
 2 int __weak arch_show_interrupts(struct seq_file *p, int prec)
 3 {
 4     return 0;
 5 }
 6 #ifndef ACTUAL_NR_IRQS
 7 # define ACTUAL_NR_IRQS nr_irqs   // 当前系统中映射到hwirq的virq的个数
 8 #endif
 9 int show_interrupts(struct seq_file *p, void *v)   // 这里*v会从0开始一直遍历到nr_irqs
10 {
11     static int prec;
12     unsigned long flags, any_count = 0;
13     int i = *(loff_t *) v, j;
14     struct irqaction *action;
15     struct irq_desc *desc;
16     if (i > ACTUAL_NR_IRQS)   // 保证当前要解析的virq是合法的
17         return 0;
18     if (i == ACTUAL_NR_IRQS)   // 如果i等于ACTUAL_NR_IRQS,表示通用的中断已经输出完毕(SPIs和PPIs),接下来需要输出跟CPU架构相关的一些中断(SGIs)
19         return arch_show_interrupts(p, prec);  // 上面/proc/interrupts输出的log中第74到82行就是该函数输出的,IPIs就是SGIs
20     /* 输出第一行,并计算第一列的宽度 */
21     if (i == 0) {
22         for (prec = 3, j = 1000; prec < 10 && j <= nr_irqs; ++prec)
23             j *= 10;
24         seq_printf(p, "%*s", prec + 8, "");
25         for_each_online_cpu(j)
26             seq_printf(p, "CPU%-8d", j);
27         seq_putc(p, \n);
28     }
29     desc = irq_to_desc(i);  // 跟据virq查询irq_desc_tree,获得对应的irq_desc
30     for_each_online_cpu(j)
31         any_count |= kstat_irqs_cpu(i, j);  // 统计virq为i的中断在系统中所有的CPU上被触发的次数
32     action = desc->action; // 如果virq为i的中断被某个驱动申请过(如request_irq)的话,其desc的action字段非空
33     if (!action && !any_count)  // 这里的意思很明确,只有当virq为i的中断既没有被申请同时其在所有的CPU上触发中断的总数为0,那么就不需要输出这个virq的信息
34         goto out;
35     seq_printf(p, "%*d: ", prec, i);  // 输出第一列,表示的是虚拟中断号
36     for_each_online_cpu(j)  // 依次输出该virq在每个CPU上面被触发的次数
37         seq_printf(p, "%10u ", kstat_irqs_cpu(i, j));
38     if (desc->irq_data.chip) {  // chip指向该virq所隶属的中断控制器
39         if (desc->irq_data.chip->irq_print_chip)  // 输出这个irq_chip自定义的信息,大部分irq_chip都没有定义这个函数
40             desc->irq_data.chip->irq_print_chip(&desc->irq_data, p);
41         else if (desc->irq_data.chip->name)  // 输出中断控制器的name
42             seq_printf(p, " %8s", desc->irq_data.chip->name);
43         else
44             seq_printf(p, " %8s", "-"); // 如果irq_chip既没有定义irq_print_chip,同时其name字段又为NULL的话,输出一个‘-’
45     } else {
46         seq_printf(p, " %8s", "None");  // 如果该virq没有指定irq_chip的话,输出‘None’字符串
47     }
48     if (desc->irq_data.domain)  // 如果该virq隶属于某个irq_domain的话,就输出这个virq在这个domain内所对应的hwirq(只在该domain内有意义)
49         seq_printf(p, " %*d", prec, (int) desc->irq_data.hwirq);  // 
50 #ifdef CONFIG_GENERIC_IRQ_SHOW_LEVEL  // 如果这个宏有效的话,还会输出该viq的中断触发类型
51     seq_printf(p, " %-8s", irqd_is_level_type(&desc->irq_data) ? "Level" : "Edge");
52 #endif
53     if (desc->name) // 输出irq_desc的名字
54         seq_printf(p, "-%-8s", desc->name); 
55     if (action) {  // 前提当然是这个virq被request了,下面会一次输出这个virq下面的所有中断处理程序的名字(request的时候设置的名字)
56         seq_printf(p, "  %s", action->name);
57         while ((action = action->next) != NULL)
58             seq_printf(p, ", %s", action->name);
59     }
60     seq_putc(p, \n);
61 out:
62     return 0;
63 }
64 #endif

我们以tiny4412,xint26_29-0为例解释一下每个字段的含义:

技术分享

说明:

  • virq 是全局唯一的,映射关系存放在所属的中断控制的irq_domain内

  • hwirq 只在所属的中断控制器的domain内才有意义

  • 列出的中断控制器是该virq中断的直属上级

接下来说一下arch_show_interrupts(p, prec),对应的函数实现在arch/arm/kernel/irq.c中,是:

 1 int arch_show_interrupts(struct seq_file *p, int prec)
 2 {
 3 #ifdef CONFIG_FIQ    // 这个宏没有定义
 4     show_fiq_list(p, prec); 
 5 #endif
 6 #ifdef CONFIG_SMP
 7     show_ipi_list(p, prec);
 8 #endif
 9     seq_printf(p, "%*s: %10lu\n", prec, "Err", irq_err_count);
10     return 0;
11 }

可以看到,对于SMP,才输出IPI,这个好理解,IPIs存在的目的是CPU之间通信用的,如果只有一个CPU,当然就不需要了。

函数show_ipi_list定义在arch/arm/kernel/smp.c中:

 1 static const char *ipi_types[NR_IPI] __tracepoint_string = {
 2 #define S(x,s)    [x] = s
 3     S(IPI_WAKEUP, "CPU wakeup interrupts"),
 4     S(IPI_TIMER, "Timer broadcast interrupts"),
 5     S(IPI_RESCHEDULE, "Rescheduling interrupts"),
 6     S(IPI_CALL_FUNC, "Function call interrupts"),
 7     S(IPI_CPU_STOP, "CPU stop interrupts"),
 8     S(IPI_IRQ_WORK, "IRQ work interrupts"),
 9     S(IPI_COMPLETION, "completion interrupts"),
10 };
11 void show_ipi_list(struct seq_file *p, int prec)
12 {
13     unsigned int cpu, i;
14     for (i = 0; i < NR_IPI; i++) {
15         seq_printf(p, "%*s%u: ", prec - 1, "IPI", i);
16         for_each_online_cpu(cpu)
17             seq_printf(p, "%10u ",
18                    __get_irq_stat(cpu, ipi_irqs[i]));
19         seq_printf(p, " %s\n", ipi_types[i]);
20     }
21 }

 

完。

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

标签:and   条件   字段   switch   store   添加   相关   bank   bind   

原文地址:http://www.cnblogs.com/pengdonglin137/p/6349209.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!