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

摘要:
以GPIO中断为例,对于tiny4412,GPIO中断可分为两种类型:外部中断和普通GPIO中断外部唤醒中断:exynos412提供32个外部唤醒中断,即GPX0到GPX3。有关GIC的信息,请参阅exynos4421数据表中的“9InterruptController”。这里有一个简短的描述:exynos412使用GIC的v2版本,它支持16个SGI中断、16个PPI中断和128个SPI中断。对于其他人pinctrl@11000000对于中的其他通用GPIO,它们不会在生成中断后直接通知GIC,而是先通知GICpinctrl@11000000和pinctrl@11000000然后通过SPI-46通知GIC,GIC将通过irq或firq触发CPU中断。

作者:彭东林

邮箱: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中断。

框图

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

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

  • 对于外部中断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

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

关于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会被调用,比如以下面这张图为例:

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

上图中每一个圆圈都代表一个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     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 }

上面第44和第54行调用irq_of_parse_and_map是必须的,此时相当于两级中断控制器的级联(第一级是gic,第二级是gpx0、gpx1、gpx2和gpx3),它们虽然在物理上面确实连接上了,但是还是必须调用该函数将这两级中断控制器在软件上面连接配置起来,这样在第二级中断发生以后,才能顺利地从第一级中断进入第二级中断,要完成这一功能,还有一个很重要的函数是48和58行的irq_set_chained_handler_and_data,这个函数会修改virq对应irq_desc的handle_irq,如exynos_irq_demux_eint16_31,这个函数是从第一级中断gic进入第二级中断gpx3的入口函数。

下面开始第四部分。

第四部分 引用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
", __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
", __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
", __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
", __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

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

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

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

中断触发和处理

相关代码:

arch/arm/kernel/entry-armv.S

arch/arm/kernel/traps.c 

arch/arm/mm/mmu.c

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

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

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

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
");
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
", __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.
", __func__);
28     if (!dev->of_node) {
29         dev_err(dev, "no platform data.
");
30         goto err0;
31     }
32     data = devm_kmalloc(dev, sizeof(*data)*4, GFP_KERNEL);
33     if (!data) {
34         dev_err(dev, "no memory.
");
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
",
44                 irq, ret);
45             goto err0;
46         }
47         printk("request irq: %d
", 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.
", __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
", 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, '
');
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, '
');
61 out:
62     return 0;
63 }
64 #endif

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

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

说明:

  • 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
", 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
", ipi_types[i]);
20     }
21 }

完。

免责声明:文章转载自《基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Centos7创建CA和申请证书生成uuid 和 检验下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Linux下安装Python3.x和第三方库

如果本机安装了python2,尽量不要管他,使用python3运行python脚本就好,因为可能有程序依赖目前的python2环境, 比如yum!!!!! 不要动现有的python2环境! 不要动现有的python2环境! 不要动现有的python2环境! 重要的使用说三遍! 一、安装python3.6 1. 安装依赖环境 #yum -y install...

kvm安装

需要准备的有 1.环境:Centos7 2.准备虚拟机硬盘 3.需要系统iso镜像 (这里用到的是CentOS-7-x86_64-Minimal-1810.iso) 4vnc 5xming 一. 准备环境 1.关闭防火墙和selinux [root@ localhost ~]# systemctl stop firewalld [root@ localh...

Linux上iptables防火墙的基本应用教程

http://www.vpser.net/security/linux-iptables.html iptables是Linux上常用的防火墙软件,下面vps侦探给大家说一下iptables的安装、清除iptables规则、iptables只开放指定端口、iptables屏蔽指定ip、ip段及解封、删除已添加的iptables规则等iptables的基...

简单入门Linux设备驱动之第二部分:第一个linux设备驱动程序

声明:内容搬自阿三哥网站,只是翻译了一下。侵删。https://embetronicx.com/tutorials/linux/device-drivers/ 正文如下: 这是“linux设备驱动系列”的教程。本系列的目的是提供简单实用的示例,使每个人都能以简单的方式理解这些概念。现在让我们即将学习“linux设备驱动第二部分-第一个linux设备驱动程序...

虚拟化技术实现 — KVM 的 CPU 虚拟化

目录 文章目录 目录 前文列表 x86 体系结构的虚拟化 硬件辅助的 CPU 虚拟化 由 VMX 切换支撑的 CPU 虚拟化技术 KVM 的 CPU 虚拟化实现 vCPU 的调度方式 客户机 CPU 拓扑和模型 虚拟机 vCPU 数量分配原则 总结 参考文档 前文列表 《虚拟化技术实现 — 虚拟化技术发展编年史》《虚拟化技术实现 — QEMU-...

hadoop集群的搭建与配置(1)

前言             首先hadoop是在linux系统上进行搭建的,我们首先要在自己电脑上装上虚拟机然后装linux系统,因为我们是测试:在本地虚拟机上装两个linux分别搭建好hadoop环境能实现简单的数据处理就可以了,最终的效果类似于我们c#中的hello world!能做到这里我们的搭建测试环境就算基本上完成了。虚拟机以及linux系统...