基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识
Posted 摩斯电码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识相关的知识,希望对你有一定的参考价值。
作者:彭东林
邮箱:pengdonglin137@163.com
QQ:405728433
平台
tiny4412 ADK
Linux-4.9
概述
前面几篇博文列举了在有设备树的时候,gpio中断的用法示例。下面我们尝试分析一下Linux内核是如何做到的,如果哪写的有问题,欢迎大家批评指正,谢谢。
还是以GPIO中断为例分析,对于tiny4412,gpio中断可以分为两种,外部中断和普通的GPIO中断
外部可唤醒中断:exynos4412提供了32个外部可唤醒中断,分别是GPX0到GPX3。按键中断分别使用了外部可唤醒中断XEINT26、XEINT27、XEINT28以及XEINT29,对应的GPIO分别是GPIOX3_2、GPIOX3_3、GPIOX3_4和GPIO3_5,当按下键的时候,会在对应的GPIO上面产生一个下降沿.
其余的GPIO也可以产生中断,但是不具备唤醒功能,从中断的物理连接来看,外部中断可以直接对应的GIC上面的一个SPI物理中断号,而普通的GPIO中断是多个GPIO对应GIC上的同一个SPI中断。
关于GIC的知识请参考exynos4412的datasheet的"9 Interrupt Controller",这里简单说明一下:exynos4412使用的GIC是v2版本,支持16个SGI中断、16个PPI中断以及128个SPI中断。
框图
结合上面的一张图说明一下:
-
对于外部中断XEINT0-15,每一个都对应的SPI中断,但是XEINT16-31共享了同一个SPI中断。这里引脚上产生中断后,会直接通知GIC,然后GIC会通过irq或者firq触发某个CPU中断。
-
对于其他的pinctrl@11000000中的其他普通的GPIO来说,它们产生中断后,并没有直接通知GIC,而是先通知pinctrl@11000000,然后pinctrl@11000000再通过SPI-46通知GIC,然后GIC会通过irq或者firq触发某个CPU中断。
-
其中涉及到了多个irq domain, GIC模块的irq domain 1, 三星为每一组GPIO都创建了一个irq domain, 好处是通过gpio引脚的编号就可以知道对应的hwirq是多少(如gpx3_2在gpx3这个bank对应的domain中的hwriq就是2),irq domain存放的的hwirq(来自硬件寄存器)到virq(逻辑中断号,全局唯一)的映射
-
上面的每一个irq_domain都对应一个irq_chip,irq_chip是kernel对中断控制器的软件抽象
-
上面SPI中断括号中的数字表示的发生中断后,实际从gic的ICCIAR_CPUn寄存器中读取出来的中断号,可以参考4412的datasheet的9.2.2 GIC Interrupt Table
关于Linux的中断子系统这部分知识可以参考下面几篇蜗窝科技的博文,这几篇讲的比较偏理论,结合实例的话,会更容易理解。
Linux kernel的中断子系统之(二):IRQ Domain介绍
linux kernel的中断子系统之(三):IRQ number和中断描述符
linux kernel的中断子系统之(四):High level irq event handler
Linux kernel中断子系统之(五):驱动申请中断API
Linux kernel的中断子系统之(六):ARM中断处理过程
linux kernel的中断子系统之(七):GIC代码分析
正文
首先看一下涉及到的设备树中的节点:
1 / { 2 interrupt-parent = <&gic>; 3 #address-cells = <0x1>; 4 #size-cells = <0x1>; 5 compatible = "friendlyarm,tiny4412", "samsung,exynos4412", "samsung,exynos4"; 6 model = "FriendlyARM TINY4412 board based on Exynos4412"; 7 aliases { 8 pinctrl1 = "/pinctrl@11000000"; 9 }; 10 gic: interrupt-controller@10490000 { 11 compatible = "arm,cortex-a9-gic"; 12 #interrupt-cells = <0x3>; 13 interrupt-controller; 14 reg = <0x10490000 0x10000>, <0x10480000 0x10000>; 15 cpu-offset = <0x4000>; 16 }; 17 pinctrl@11000000 { 18 compatible = "samsung,exynos4x12-pinctrl"; 19 reg = <0x11000000 0x1000>; 20 interrupts = <0x0 0x2e 0x0>; 21 gpm4: gpm4 { 22 gpio-controller; 23 #gpio-cells = <0x2>; 24 interrupt-controller; 25 #interrupt-cells = <0x2>; 26 }; 27 gpx1: gpx1 { 28 gpio-controller; 29 #gpio-cells = <2>; 30 interrupt-controller; 31 interrupt-parent = <&gic>; 32 interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>, 33 <0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>; 34 #interrupt-cells = <2>; 35 }; 36 gpx3: gpx3 { 37 gpio-controller; 38 #gpio-cells = <0x2>; 39 interrupt-controller; 40 #interrupt-cells = <0x2>; 41 }; 42 wakeup-interrupt-controller { 43 compatible = "samsung,exynos4210-wakeup-eint"; 44 interrupt-parent = <0x1>; 45 interrupts = <0x0 0x20 0x0>; 46 }; 47 }; 48 interrupt_xeint26_29: interrupt_xeint26_29 { 49 compatible = "tiny4412,interrupt_xeint26_29"; 50 interrupt-parent = <&gpx3>; 51 interrupts = <2 IRQ_TYPE_EDGE_FALLING>, <3 IRQ_TYPE_EDGE_FALLING>,\\ 52 <4 IRQ_TYPE_EDGE_FALLING>, <5 IRQ_TYPE_EDGE_FALLING>; 53 }; 54 interrupt_xeint14_15: interrupt_xeint14_15 { 55 compatible = "tiny4412,interrupt_xeint14_15"; 56 interrupt-parent = <&gpx1>; 57 interrupts = <6 IRQ_TYPE_EDGE_FALLING>, <7 IRQ_TYPE_EDGE_FALLING>; 58 }; 59 interrupt_gpm4_0: interrupt_gpm4_0 { 60 compatible = "tiny4412,interrupt_gpm4_0"; 61 interrupt-parent = <&gpm4>; 62 interrupts = <0 IRQ_TYPE_EDGE_FALLING>; 63 }; 64 };
说明:
-
tiny4412上的root gic就是上面的"arm,cortex-a9-gic",它的interrupt cells是3, 表示引用gic上的一个中断需要三个参数
-
pinctrl@11000000的interrupt parent是interrupt-controller@10490000,可以看到,它的interrupts属性含有三个参数,含义是引用GIC的SPI-46
-
gpx3本身也充当一个中断控制器,它的interrupt parent也是interrupt-controller@10490000,gpx3的interrupt cell是2, 表示引用gpx3的一个中断需要2个参数
-
interrupt_xeint26_29的interrupt parent是gpx3,它的interrupts含有四组参数,分别对应gpiox3_2、gpiox3_3、gpiox3_4和gpiox3_5,每组的第二个参数表示的是中断类型,IRQ_TYPE_EDGE_FALLING表示下降沿触发,可以参考arch/arm/boot/dts/include/dt-bindings/interrupt-controller/irq.h
-
wakeup-interrupt-controller我觉得只是一个软件上面的抽象,对应的是XEINT16-31,其interrupts对应的就是SPI-32,从datasheet上也可以看到,EINT16-31对应的都是SPI-32.
下面分几个部分来说明一下,这里不适合把大段的内核代码贴过来,只把一些关键的部分列出来,对于自己详细分析内核代码有帮助。
第一部分: GIC中断控制器的注册
第二部分:设备树的device node在向platfomr_device转化的过程中节点的interrupts属性的处理
第三部分:GPIO控制器驱动的注册,大部分GPIO控制器同时具备interrupt controller的功能,就像上面的GPIOX3和GPIOM4等等
第四部分:引用GPIO中断的节点的解析
第一部分 gic中断控制器的注册
相关代码:
drivers/irqchip/irq-gic.c
arch/arm/mach-exynos/exynos.c
arch/arm/kernel/entry-armv.S
gic中断控制器的初始化和注册是在函数gic_of_init中做的,这个函数是怎么被执行到的呢?这个文件中定义了下面的结构:
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
分析发现,IRQCHIP_DECLARE宏会定义出一个__of_table_cortex_a9_gic的变量,gic_of_init被赋值给其data成员,这个变量被存放到了内核镜像的__irqchip_of_table段,在kernel启动时平台代码exynos.c中的函数exynos_init_irq会被调用,这个函数会调用irqchip_init --> of_irq_init,of_irq_init就会遍历__irqchip_of_table,按照interrupt controller的连接关系从root开始,依次初始化每一个interrupt controller,此时gic_of_init会被调用,比如以下面这张图为例:
上图中每一个圆圈都代表一个interrupt-controller,以此都成了系统的中断树,其中的数字表示的是of_irq_init函数初始化中断控制器的顺序。
gic_of_init主要做如下几件事:
-
设置__smp_cross_call为gic_raise_softirq, 它的作用是触发SGI中断,用于CPU之间通信
set_smp_cross_call(gic_raise_softirq)
-
设置handle_arch_irq为gic_handle_irq。在kernel发生中断后,会跳转到汇编代码entry-armv.S中__irq_svc处,进而调用handle_arch_irq,从而进入GIC驱动,进行后续的中断处理
set_handle_irq(gic_handle_irq)
-
计算这个GIC模块所支持的中断个数gic_irqs,然后创建一个linear irq domain。此时尚未分配virq,也没有建立hwirq跟virq的映射
1 /* 2 * Find out how many interrupts are supported. 3 * The GIC only supports up to 1020 interrupt sources. 4 */ 5 gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f; 6 gic_irqs = (gic_irqs + 1) * 32; 7 if (gic_irqs > 1020) 8 gic_irqs = 1020; 9 gic->gic_irqs = gic_irqs; 10 gic->domain = irq_domain_create_linear(handle, gic_irqs, 11 &gic_irq_domain_hierarchy_ops, gic);
在初始化的时候既没有给hwirq分配对应的virq,也没有建立二者之间的映射,这部分工作会到后面有人引用GIC上的某个中断时再分配和建立。
第二部分 device node转化为platform_device
相关代码:
drivers/of/platform.c
这个转化过程是调用of_platform_populate开始的,以pinctrl@11000000为例,暂时只关心interrupts属性的处理,函数调用关系:
of_platform_populate
---> of_platform_bus_create
---> of_platform_device_create_pdata
---> of_device_alloc:
1 struct platform_device *of_device_alloc(struct device_node *np, 2 const char *bus_id, 3 struct device *parent) 4 { 5 struct platform_device *dev; 6 int rc, i, num_reg = 0, num_irq; 7 struct resource *res, temp_res; 8 dev = platform_device_alloc("", -1); 9 /* count the io and irq resources */ 10 ... ... 11 num_irq = of_irq_count(np); // 统计这个节点的interrupts属性中描述了几个中断 12 /* Populate the resource table */ 13 if (num_irq || num_reg) { 14 res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL); 15 ... ... 16 dev->num_resources = num_reg + num_irq; 17 dev->resource = res; 18 ... ... 19 of_irq_to_resource_table(np, res, num_irq) // 解析interrupts属性,将每一个中断转化为resource结构体 20 } 21 ... ... 22 }
这里主要涉及到两个函数of_irq_count和of_irq_to_resource_table,传入的np就是pinctrl@11000000节点。
-
of_irq_count
这个函数会解析interrupts属性,并统计其中描述了几个中断。
简化如下:找到pinctrl@11000000节点的所隶属的interrupt-controller,即interrupt-controller@10490000节点,然后获得其#interrupt-cells属性的值,因为只要知道了这个值,也就知道了在interrupts属性中描述一个中断需要几个参数,也就很容易知道interrupts所描述的中断个数。这里关键的函数是of_irq_parse_one:
1 int of_irq_count(struct device_node *dev) 2 { 3 struct of_phandle_args irq; 4 int nr = 0; 5 while (of_irq_parse_one(dev, nr, &irq) == 0) 6 nr++; 7 return nr; 8 }
nr表示的是index,of_irq_parse_one每次成功返回,都表示成功从interrupts属性中解析到了第nr个中断,同时将关于这个中断的信息存放到irq中,struct of_phandle_args的含义如下:
1 #define MAX_PHANDLE_ARGS 16 2 struct of_phandle_args { 3 struct device_node *np; // 用于存放赋值处理这个中断的中断控制器的节点 4 int args_count; // 就是interrupt-controller的#interrupt-cells的值 5 uint32_t args[MAX_PHANDLE_ARGS]; // 用于存放具体描述某一个中断的参数的值 6 };
最后将解析到的中断个数返回。
-
of_irq_to_resource_table
知道interrupts中描述了几个中断后,这个函数开始将这些中断转换为resource,这个是由of_irq_to_resource函数完成。
1 for (i = 0; i < nr_irqs; i++, res++) 2 if (!of_irq_to_resource(dev, i, res)) 3 break;
第二个参数i表示的是index,即interrupts属性中的第i个中断。
1 int of_irq_to_resource(struct device_node *dev, int index, struct resource *r) 2 { 3 int irq = irq_of_parse_and_map(dev, index); // 返回interrupts中第index个hwirq中断映射到的virq 4 if (r && irq) { // 将这个irq封装成resource 5 const char *name = NULL; 6 memset(r, 0, sizeof(*r)); 7 of_property_read_string_index(dev, "interrupt-names", index, 8 &name); // 一般这个"interrupt-names"属性是可选的 9 r->start = r->end = irq; // 全局唯一的virq 10 r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq)); // 这个中断的属性,如上升沿还是下降沿触发 11 r->name = name ? name : of_node_full_name(dev); 12 } 13 return irq; 14 }
所以,分析重点是irq_of_parse_and_map,这个函数会获得pinctrl@11000000节点的interrupts属性的第index个中断的参数,这是通过of_irq_parse_one完成的,然后获得该中断所隶属的interrupt-controller的irq domain,也就是前面GIC注册的那个irq domain,利用该domain的of_xlate函数从前面表示第index个中断的参数中解析出hwirq和中断类型,最后从系统中为该hwriq分配一个全局唯一的virq,并将映射关系存放到中断控制器的irq domain中,也就是gic的irq domain。
下面结合kernel代码分析一下:
1 unsigned int irq_of_parse_and_map(struct device_node *dev, int index) 2 { 3 struct of_phandle_args oirq; 4 of_irq_parse_one(dev, index, &oirq); // 获得interrupts的第index个中断参数,并封装到oirq中 5 return irq_create_of_mapping(&oirq); //返回映射到的virq 6 }
---> irq_create_of_mapping
1 unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data) 2 { 3 struct irq_fwspec fwspec; 4 of_phandle_args_to_fwspec(irq_data, &fwspec); // 将irq_data中的数据转存到fwspec,没必要分析 5 return irq_create_fwspec_mapping(&fwspec); 6 }
---> irq_create_fwspec_mapping
1 unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec) 2 { 3 struct irq_domain *domain; 4 struct irq_data *irq_data; 5 irq_hw_number_t hwirq; 6 unsigned int type = IRQ_TYPE_NONE; 7 int virq; 8 // 根据中断控制器的device_node找到所对应的irq domain,在前面GIC驱动注册irq domian的时候, 9 // 会将irq_domain的fwnode设置为中断控制器的device_node的fwnode成员 10 if (fwspec->fwnode) { 11 domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED); 12 if (!domain) 13 domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY); 14 } else { 15 domain = irq_default_domain; 16 } 17 // 对于GIC的irq domain来说,会调用d->ops->translate(d, fwspec, hwirq, type) 18 // 也就是gic_irq_domain_translate,这个单独分析.对于没有定义translate的irq_domain, 19 // 会调用d->ops->xlate 20 irq_domain_translate(domain, fwspec, &hwirq, &type); 21 ... ... 22 // 从这个irq domain查询看该hwirq之前是否已经映射过,一般情况下都没有 23 virq = irq_find_mapping(domain, hwirq); 24 if (virq) { 25 ... ... 26 return virq; 27 ... ... 28 } 29 if (irq_domain_is_hierarchy(domain)) { // 对于GIC的irq domain这样定义了alloc的domain来说,走这个分支 30 virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec); 31 } else { // 其他没有定义irq_domain->ops->alloc的domain,走这个分支 32 /* Create mapping */ 33 virq = irq_create_mapping(domain, hwirq); 34 } 35 irq_data = irq_get_irq_data(virq); 36 /* Store trigger type */ 37 irqd_set_trigger_type(irq_data, type); 38 return virq; //返回映射到的virq 39 }
看一下gic irq domain的translate的过程:
--->gic_irq_domain_translate
1 static int gic_irq_domain_translate(struct irq_domain *d, 2 struct irq_fwspec *fwspec, 3 unsigned long *hwirq, 4 unsigned int *type) 5 { 6 if (is_of_node(fwspec->fwnode)) { // 走这个分支 7 if (fwspec->param_count < 3) // 检查描述中断的参数个数是否合法 8 return -EINVAL; 9 // 这里加16的目的是跳过SGI中断,因为SGI用于CPU之间通信,不归中断子系统管 10 // GIC支持的中断中从0-15号属于SGI,16-32属于PPI,32-1020属于SPI 11 *hwirq = fwspec->param[1] + 16; 12 // 从这里可以看到,描述GIC中断的三个参数中第一个表示中断种类,0表示的是SPI,非0表示PPI 13 // 这里加16的意思是跳过PPI 14 // 同时我们也知道了,第二个参数表示某种类型的中断(PPI or SPI)中的第几个(从0开始) 15 if (!fwspec->param[0]) 16 *hwirq += 16; 17 // 第三个参数表示的中断的类型,如上升沿、下降沿或者高低电平触发 18 *type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK; 19 return 0; // 成功 20 } 21 ... ... 22 return -EINVAL; 23 }
通过这个函数,我们就获得了fwspec所表示的hwirq和type
接着看一下irq_find_mapping,如果hwirq之前跟virq之间发生过映射,会存放到irq domain中,这个函数就是查询irq domain,以hwirq为索引,寻找virq
---> irq_find_mapping
1 unsigned int irq_find_mapping(struct irq_domain *domain, 2 irq_hw_number_t hwirq) 3 { 4 struct irq_data *data; 5 ... ... 6 if (hwirq < domain->revmap_size) // 如果满足linear irq domain的条件,hwirq作为数字下标 7 return domain->linear_revmap[hwirq]; 8 ... ... 9 data = radix_tree_lookup(&domain->revmap_tree, hwirq); // hwirq作为key 10 return data ? data->irq : 0; 11 }
下面分析virq的分配以及映射,对于GIC irq domain,由于其ops定义了alloc,在注册irq domain的时候会执行domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY
---> irq_domain_alloc_irqs
1 int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base, // 这里的irq_base是-1 2 unsigned int nr_irqs, int node, void *arg, // 这里的nr_irqs是1 3 bool realloc, const struct cpumask *affinity) 4 { 5 int i, ret, virq; 6 ... ... 7 // 下面这个函数会从系统中一个唯一的virq,其实就是全局变量allocated_irqs从低位到高位第一个为0的位的位号 8 // 然后将allocated_irqs的第virq位置为1, 然后会为这个virq分配一个irq_desc, virq会存放到irq_desc的irq_data.irq中 9 // 最后将这个irq_desc存放到irq_desc_tree中,以virq为key,函数irq_to_desc就是以virq为key,查询irq_desc_tree 10 // 迅速定位到irq_desc 11 virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node, 12 affinity); 13 ... ... 14 irq_domain_alloc_irq_data(domain, virq, nr_irqs); 15 irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg); 16 for (i = 0; i < nr_irqs; i++) 17 irq_domain_insert_irq(virq + i); // 将virq跟hwirq的映射关系存放到irq domain中,这样就可以通过hwirq在该irq_domain中快速找到virq 18 return virq; 19 }
----> irq_domain_alloc_irq_data 会根据virq获得对应的irq_desc,然后将domain赋值给irq_desc->irq_data->domain
----> irq_domain_alloc_irqs_recursive 这个函数会调用gic irq domain的domain->ops->alloc,即gic_irq_domain_alloc
1 static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq, 2 unsigned int nr_irqs, void *arg) 3 { 4 int i, ret; 5 irq_hw_number_t hwirq; 6 unsigned int type = IRQ_TYPE_NONE; 7 struct irq_fwspec *fwspec = arg; 8 ret = gic_irq_domain_translate(domain, fwspec, &hwirq, &type); // 参考之前的分析 9 // 这个函数主要做了如下工作: 10 // 根据virq找到对应的irq_desc,将hwirq存放到irq_desc的irq_data.hwirq中 11 // 将irq chip存放到irq_desc的irq_data.chip中 12 // 对于PPI类型的中断(hwirq<32),将irq_desc的handle_irq设置为handle_percpu_devid_irq 13 // 对于SPI类型的中断,将irq_desc的handle_irq设置为handle_fasteoi_irq 14以上是关于基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识的主要内容,如果未能解决你的问题,请参考以下文章
基於tiny4412的Linux內核移植--- 中斷和GPIO學習
基于tiny4412的Linux内核移植 -- MMA7660驱动移植
基于tiny4412的Linux内核移植 -- SD卡驱动移植
基于tiny4412的Linux内核移植 -- MMA7660驱动移植(九-2)