首页 > 代码库 > 基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
作者:彭东林
邮箱: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 162 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; // 全局唯一的virq10 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); //返回映射到的virq6 }
---> 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->xlate20 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 =http://www.mamicode.com/ irq_get_irq_data(virq);36 /* Store trigger type */37 irqd_set_trigger_type(irq_data, type);38 return virq; //返回映射到的virq39 }
看一下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属于SPI11 *hwirq = fwspec->param[1] + 16;12 // 从这里可以看到,描述GIC中断的三个参数中第一个表示中断种类,0表示的是SPI,非0表示PPI13 // 这里加16的意思是跳过PPI14 // 同时我们也知道了,第二个参数表示某种类型的中断(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 = http://www.mamicode.com/radix_tree_lookup(&domain->revmap_tree, hwirq); // hwirq作为key10 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_tree10 // 迅速定位到irq_desc11 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中快速找到virq18 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_irq13 // 对于SPI类型的中断,将irq_desc的handle_irq设置为handle_fasteoi_irq14 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和domain16 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 = http://www.mamicode.com/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,用于处理普通可以产生中断的gpio16 ret = samsung_gpiolib_register(pdev, drvdata); // 这个需要分析17 ret = samsung_pinctrl_register(pdev, drvdata); // 这个函数主要是跟gpio配置相关,跟中断关系不大,暂不分析18 if (ctrl->eint_gpio_init) // 普通的GPIO19 ctrl->eint_gpio_init(drvdata);20 if (ctrl->eint_wkup_init) // 非空的话,表示这一pinctrl含有具备wakeup功能的gpio21 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"中的114 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, // 其他普通的并且具备中断功能的gpio21 .eint_wkup_init = exynos_eint_wkup_init, // 这个pinctrl含有具备wakeup功能的gpio,其实就是XEINT0-3122 .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_GPIO36 EXYNOS_PIN_BANK_EINTN会将eint_type设置为EINT_TYPE_NONE37 EXYNOS_PIN_BANK_EINTW会将eint_type设置为EINT_TYPE_WKUP38 */39 match = of_match_node(samsung_pinctrl_dt_match, node);40 ctrl = (struct samsung_pin_ctrl *)match->data + id; // id表示第几个pinctrl41 d->suspend = ctrl->suspend;42 d->resume = ctrl->resume;43 d->nr_banks = ctrl->nr_banks; // 含有几组bank44 d->pin_banks = devm_kcalloc(&pdev->dev, d->nr_banks, sizeof(*d->pin_banks), GFP_KERNEL);45 bank = d->pin_banks;46 bdata = http://www.mamicode.com/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_WKUP53 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 =http://www.mamicode.com/ 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 node68 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_GPIO13 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 node14 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_chip19 wkup_np = np;20 break;21 }22 }23 if (!wkup_np) // 既然这个pinctrl定义了exynos_eint_wkup_init,那么就一定会能够找到wakeup-interrupt-controller的node24 return -ENODEV;25 bank = d->pin_banks; // 遍历这个pinctrl的每一个bank26 for (i = 0; i < d->nr_banks; ++i, ++bank) {27 if (bank->eint_type != EINT_TYPE_WKUP) // 前面说过,具备唤醒功能的gpio的bank的eint_type才是EINT_TYPE_WKUP28 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-3134 // 这里要区分开的原因是, 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这两个bank39 continue;40 }41 weint_data = http://www.mamicode.com/devm_kzalloc(dev, bank->nr_pins42 * 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_1548 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-3254 irq = irq_of_parse_and_map(wkup_np, 0);55 muxed_data = http://www.mamicode.com/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.c32 index e4bf07d..b6cdef3 10064433 --- a/drivers/of/platform.c34 +++ b/drivers/of/platform.c35 @@ -11,7 +11,7 @@36 * 2 of the License, or (at your option) any later version.37 *38 */39 -40 +#define DEBUG41 #define pr_fmt(fmt) "OF: " fmt42 43 #include <linux/errno.h>44 diff --git a/kernel/irq/irqdesc.c b/kernel/irq/irqdesc.c45 index 00bb0ae..1687c20 10064446 --- a/kernel/irq/irqdesc.c47 +++ b/kernel/irq/irqdesc.c48 @@ -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.c57 index 8c0a0ae..6927617 10064458 --- a/kernel/irq/irqdomain.c59 +++ b/kernel/irq/irqdomain.c60 @@ -1,3 +1,4 @@61 +#define DEBUG62 #define pr_fmt(fmt) "irq: " fmt63 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: 6310 [ 0.228782] irq: Added domain (null)11 [ 0.228788] irq: Added domain (null)12 [ 0.228793] exynos_eint_wkup_init enter, line: 55113 [ 0.228806] __irq_alloc_descs: alloc virq: 86, cnt: 114 [ 0.228838] gic_irq_domain_alloc enter, virq: 86, hwirq: 6415 ... ...16 [ 0.233316] __irq_alloc_descs: alloc virq: 100, cnt: 117 [ 0.233351] irq: irq 2 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 10018 [ 0.233370] irq: irq_create_mapping(0xef205d00, 0x3)19 [ 0.233376] irq: -> using domain @ef205d0020 [ 0.233381] __irq_alloc_descs: alloc virq: 101, cnt: 121 [ 0.233414] irq: irq 3 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 10122 [ 0.233433] irq: irq_create_mapping(0xef205d00, 0x4)23 [ 0.233438] irq: -> using domain @ef205d0024 [ 0.233444] __irq_alloc_descs: alloc virq: 102, cnt: 125 [ 0.233477] irq: irq 4 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 10226 [ 0.233495] irq: irq_create_mapping(0xef205d00, 0x5)27 [ 0.233500] irq: -> using domain @ef205d0028 [ 0.233506] __irq_alloc_descs: alloc virq: 103, cnt: 129 [ 0.233544] irq: irq 5 on domain /pinctrl@11000000/gpx3 mapped to virtual irq 10330 [ 0.233683] irq: irq_create_mapping(0xef205b80, 0x6)31 [ 0.233689] irq: -> using domain @ef205b8032 [ 0.233695] __irq_alloc_descs: alloc virq: 104, cnt: 133 [ 0.233730] irq: irq 6 on domain /pinctrl@11000000/gpx1 mapped to virtual irq 10434 [ 0.233749] irq: irq_create_mapping(0xef205b80, 0x7)35 [ 0.233753] irq: -> using domain @ef205b8036 [ 0.233759] __irq_alloc_descs: alloc virq: 105, cnt: 137 [ 0.233792] irq: irq 7 on domain /pinctrl@11000000/gpx1 mapped to virtual irq 10538 [ 0.233916] irq: irq_create_mapping(0xef205a00, 0x0)39 [ 0.233922] irq: -> using domain @ef205a0040 [ 0.233928] __irq_alloc_descs: alloc virq: 106, cnt: 141 [ 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_rst3 W(b) vector_und4 W(ldr) pc, .L__vectors_start + 0x10005 W(b) vector_pabt6 W(b) vector_dabt7 W(b) vector_addrexcptn8 W(b) vector_irq9 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是0x3ff11 // 我们知道 PPI和SPI的范围是16到102012 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_SMP21 ... ...22 handle_IPI(irqnr, regs); // 这个是处理SGI中断用的,SGI中断暂不分析,因为不归kernel的中断子系统管理23 #endif24 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_desc4 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_irq16 ... ...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自己定义的type12 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_irq32 irq_set_handler_locked(irqd, handle_edge_irq);33 else // 如果是电平触发,就设置为handle_level_irq34 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和gpx311 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, 获得virq13 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 =http://www.mamicode.com/ 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 =http://www.mamicode.com/ 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 = http://www.mamicode.com/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 resource39 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.pdma10 53: 0 0 0 0 GIC-0 68 Edge 12690000.pdma11 54: 0 0 0 0 GIC-0 66 Edge 12850000.mdma12 67: 0 0 0 0 GIC-0 144 Edge 10830000.sss13 68: 0 0 0 0 GIC-0 79 Edge 11400000.pinctrl14 69: 1 0 0 0 GIC-0 78 Edge 11000000.pinctrl15 87: 0 0 0 0 COMBINER 80 Edge 3860000.pinctrl16 88: 0 0 0 0 GIC-0 104 Edge 106e0000.pinctrl17 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-119 102: 2 0 0 0 exynos4210_wkup_irq_chip 4 Edge tiny4412,xint26_29-220 103: 2 0 0 0 exynos4210_wkup_irq_chip 5 Edge tiny4412,xint26_29-321 104: 393 0 0 0 exynos4210_wkup_irq_chip 6 Edge tiny4412,xeint14_15-022 105: 0 0 0 0 exynos4210_wkup_irq_chip 7 Edge tiny4412,xeint14_15-123 106: 1 0 0 0 exynos_gpio_irq_chip 0 Edge tiny4412,gpm4_0-024 IPI0: 0 1 1 1 CPU wakeup interrupts25 IPI1: 0 0 0 0 Timer broadcast interrupts26 IPI2: 1338 1509 468 549 Rescheduling interrupts27 IPI3: 0 3 2 2 Function call interrupts28 IPI4: 0 0 0 0 CPU stop interrupts29 IPI5: 931 86 264 50 IRQ work interrupts30 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_irqs10 {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就是SGIs20 /* 输出第一行,并计算第一列的宽度 */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_desc30 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) // 输出中断控制器的name42 seq_printf(p, " %8s", desc->irq_data.chip->name);43 else44 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 #endif53 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)