系列文章目录
Linux中断子系统【1】- 中断控制器GIC驱动分析
Linux中断子系统【2】- Linux内核软中断softirq和小任务tasklet分析
Linux中断子系统【3】- Linux内核request_irq源码分析
Linux中断子系统【4】- Linux中断子系统之中断映射(初始化中断控制器)
Linux中断子系统【5】- 中断控制器及驱动分析(图像化分析)
Linux中断子系统【6】- Linux 内核笔记之高层中断处理(中断入口->中断服务函数)
Linux中断子系统【7】- irq_desc数据结构分析
Linux中断子系统【8】- linux-3.4.6内核中断流程分析
Linux中断子系统【1】- linux级联中断控制器的处理流程
文章目录
- 系列文章目录
- 前言
- 一、一级中断控制器处理流程
- 二、多级中断控制器处理流程
- 三、GIC中的重要函数和结构体
- 总结
前言
分析Linux内核中断驱动的系列文章
参考:
GIC驱动程序分析
一、一级中断控制器处理流程
对于irq_desc
,内核有两种分配方法:
- 一次分配完所有
iqr_desc
- 按需分配(用到某个中断才分配它的
irq_desc
)。
现在的内核基本使用第二种方法。
- 假设
GIC
可以向CPU
发出16 ~ 1019
号中断,这些数字被称为hwirq
。0 ~ 15
用于Process
之间通信,比较特殊 - 假设要使用
UART
模块,它发出的中断连接到GIC
的32
号中断,分配的irq_desc
序号为16
在GIC domain
中会记录(32,16)
- 那么注册中断时就是:
request_irq(16, ...)
,把处理程序注册进irqaction
- 发生
UART
中断时- 程序从
GIC
中读取寄存器知道发生了32
号中断,通过GIC irq_domain
可以知道virq为16
- 调用
irq_desc[16]
中的handlerA
函数,它的作用是调用action
链表中用户注册的函数
- 程序从
二、多级中断控制器处理流程
- 假设
GPIO
模块下有4
个引脚,都可以产生中断,都连接到GIC
的33
号中断 GPIO
也可以看作一个中断控制器,对于它的4
个中断- 对于
GPIO
模块中0 ~ 3
这四个hwirq
,一般都会一下子分配四个irq_desc - 假设这
4
个irq_desc
的序号为100 ~ 103
,在GPIO domain
中记录(0, 100) (1,101) (2,102) (3,103)
- 对于
KEY
,注册中断时就是:request_irq(102, ...)
- 程序从
GIC
中读取寄存器知道发生了33
号中断,通过GIC irq_domain
可以知道virq
为16
- 调用
irq_desc[16]
中的handleB
函数 handleB
读取GPIO
寄存器,确定是GPIO
里2
号引脚发生中断- 通过
GPIO irq_domain
可以知道virq
为102
- 调用
irq_desc[102]
中的handleA
函数,它的作用是调用action
链表中用户注册的函数
- 程序从
程序先是通过request_irq
注册处理函数,和分配虚拟中断号,使得硬件中断号
和虚拟中断号产
生联系,并存储在irq_domain
中。
当硬件触发时,CPU
通过硬件中断号,判断哪个中断触发了,通过关联的软件中断号
,找到对应的处理函数。
处理函数时,需要把对应的中断屏蔽住,处理完后再恢复。
有时候另一些情况时,像GPIO会有多个中断号,那么每个不同的中断号也会有不同的软件中断号,对应的处理函数也不一样。
以 platform_get_irq(pdev, 1);
为例,获取中断号之后要注册中断服务函数Linux中断子系统【3】- Linux内核request_irq源码分析
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {[0 ... NR_IRQS-1] = {.handle_irq = handle_bad_irq,.depth = 1,.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),}
};gpio2: gpio@020a0000 {compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";reg = <0x020a0000 0x4000>;interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>;gpio-controller;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <2>;};/*最终目的通过临时创建一个irq_desc,并和中断控制器(GIC)的域中的某一个硬件中断源产生映射关系,将来硬件中断来临就会执行相应的中断服务函数以便reques_irq根据虚拟中断号在irq_desc_tree中找到相对应的irq_desc,并注册中断服务函数。最终完成了:virq-->irq_desc(radix_tree_insert(&irq_desc_tree, virq, desc))以及: hwirq->irq_data (radix_tree_insert(&domain->revmap_tree, hwirq, data)而 irq_data->irq中保存者virq,这样硬件中断来临会先取出irq-data,并执行相对应的virq->action.*/int platform_get_irq(struct platform_device *dev, unsigned int num)-->return of_irq_get(dev->dev.of_node, num);-->of_irq_parse_one(dev, index, &oirq)/*struct of_phandle_args oirq*/-->irq_find_host(oirq.np);{struct irq_domain *irq_find_host(struct device_node *node){struct irq_domain *h = NULL;list_for_each_entry(h, &irq_domain_list, link) {rc = (h->of_node != NULL) && (h->of_node == node);if (rc) {return h}}}}-->return irq_create_of_mapping(&oirq);/*找到GIC/或者某一个中断控制器的节点,来创建SPI终端号与irq_desc的映射*/{irq_hw_number_t hwirq;xlate(&hwirq);/*找到硬件中断源68(0~1024)<GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>*/if (irq_domain_is_hierarchy(domain)) {/*查看是否是层级中断控制器 domain->flags & IRQ_DOMAIN_FLAG_HIERARCHY;*//** If we've already configured this interrupt,* don't do it again, or hell will break loose.*/virq = irq_find_mapping(domain, hwirq);if (virq)return virq;virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);{virq = __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false);}}return virq;/*返回虚拟中断号*/}--> __irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false)--> irq_domain_alloc_descs(irq_base, nr_irqs, 0, node)--> static inline int alloc_descs(unsigned int start, unsigned int cnt, int node,struct module *owner)/*返回再gic域上的空irq_desc序列编号*//* Allocate a virtual interrupt number *//*virq = irq_domain_alloc_descs(-1, 1, hwirq,of_node_to_nid(domain->of_node));{data->hwirq = hwirq/*实际走了这个不想改了*/}*/--> alloc_desc(start + i, node, owner);/*desc->irq_data.irq = irq;*/--> irq_insert_desc(start + i, desc);{radix_tree_insert(&irq_desc_tree, start + i, desc);/*虚拟终端号*/}-->irq_domain_alloc_irq_data(domain, virq, nr_irqs)/*创建struct irq_data *irq_data*/-->irq_domain_insert_irq(virq + i);/*直到这里virq与hwirq正式映射了*/{struct irq_data *data = irq_get_irq_data(virq)/*irq_data->hwirq = hwirq;*//*int irq_domain_associate(struct irq_domain *domain, unsigned int virq,irq_hw_number_t hwirq)*/irq_hw_number_t hwirq = data->hwirq;radix_tree_insert(&domain->revmap_tree, hwirq, data);/*硬件中断号与data相匹配*/}
由Linux中断子系统【3】- Linux内核request_irq源码分析可知层级中断使用dem_requeset_irq
即可,那么链式中断服务注册函数 irq_set_chained_handler(port->irq, mx3_gpio_irq_handler); irq_set_handler_data(port->irq, port);
与其区别是什么?
- 区别是:
requeset_irq
在申请irq_desc
同时会使用action
的链表功能添加多个中断服务函数
而irq_set_chained_handler
因为是链式只能注册一个中断服务函数,在函数中判断最终的二级中断号
static inline void irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
-->void __irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,const char *name
--> struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);/*根据终端号寻找对应的irq_desc*/
-->static inline struct irq_desc *irq_get_desc_buslock(unsigned int irq, unsigned long *flags, unsigned int check)--> return __irq_get_desc_lock(irq, flags, true, check);--> struct irq_desc *__irq_get_desc_lock(unsigned int irq, unsigned long *flags, bool bus,unsigned int check){struct irq_desc *desc = irq_to_desc(irq); return desc}-->struct irq_desc *irq_to_desc(unsigned int irq){return radix_tree_lookup(&irq_desc_tree, irq);}
三、GIC中的重要函数和结构体
沿着中断的处理流程,GIC涉及这4个重要部分:
CPU
从异常向量表中调用handle_arch_irq
,这个函数指针是由GIC
驱动设置的,GIC
才知道怎么判断发生的是那个GIC
中断- 从
GIC
获取hwirq
后,要转换为virq
:需要有GIC Domain
- 调用
irq_desc[virq].handle_irq
函数:这也应该由GIC
驱动提供 - 处理中断时,要
屏蔽中断
、清除中断
等:这些函数保存在irq_chip
里,由GIC驱动提供
从硬件上看,GIC功能:
可以使能、屏蔽中断
发生中断时,可以从GIC里判断是哪个中断
在内核里,使用gic_chip_data
结构体表示GIC,gic_chip_data
里有:
irq_chip
:中断使能、屏蔽、清除,放在irq_chip
中的各个函数里实现
// drivers/irqchip/irq-gic.c
static const struct irq_chip gic_chip = {.irq_mask = gic_mask_irq,.irq_unmask = gic_unmask_irq,.irq_eoi = gic_eoi_irq,.irq_set_type = gic_set_type,.irq_get_irqchip_state = gic_irq_get_irqchip_state,.irq_set_irqchip_state = gic_irq_set_irqchip_state,.flags = IRQCHIP_SET_TYPE_MASKED |IRQCHIP_SKIP_SET_WAKE |IRQCHIP_MASK_ON_SUSPEND,
};
irq_domain
:
申请中断
在设备树里指定hwirq
、flag
,可以使用irq_domain
的函数来解析设备树
根据hwirq
可以分配virq
,把(hwirq, virq)
存入irq_domain
中
发生中断时,从GIC
读取hwirq
,可以通过irq_domain
找到virq
,再找到处理函数
所以GIC
用gic_chip_data
来表示,gic_chip_data
中重要的成员是:irq_chip、irq_domain
总结
以上都在说中断号映射。
中断号分为硬件中断号(HW ID)和软件中断号(IRQ number)。
这里有两个中断控制器,处理完毕进入 CPU
。外设和中断控制器连接在一起,外设给中断控制器的是硬件中断号,如果中断控制器有级联,那么硬件中断号在不同的中断控制器中可能会重复。但是到了 CPU
以后,我们需要对不同中断控制器给过来的硬件中断号进行翻译,翻译成在软件中唯一的软件中断号,叫 irq number
。
注意,我们在设备树中配置的是硬件中断号
,在软件中申请中断 request_irq
使用的是软件中断号
,所以申请之前要先从设备树获取硬件中断号,然后使用 irq_of_parse_and_map
进行翻译再使用
-
硬件中断号与软件中断号
在
Linux kernel
中我们使用下面两个ID
来标识一个来自外设的中断:1、
IRQ number
。CPU需要为每个外设中断编号,我们称之 IRQ Number。这个 IRQ number 是一个虚拟的 interrupt ID
,和硬件无关,仅仅是被 CPU 用来标识个外设中断。2、
HW interrupt ID
。对于interrupt controller
而言,它收集了多个外设的interrupt request line
并向上传递,因此,interrupt controller
需要对外设中进行编码。Interrupt controller
用HW interrupt ID
来标识外设的中断。在interrupt controller
级联的情况下,仅仅用HW interrupt ID
已经不能唯一标识一 个外设中断,还需要知道该HW interrupt ID
所属的interrupt controller (HW interrupt ID
在不同的Interrupt controller
上是会重复编码的) .这样,CPU和
interrupt controller
在标识中断上就有了一些不同的概念,但是,对于驱动工程师而言,我们和 CPU 视角是一样的,我们只希望得到一个 IRQ number,而不关心具体是哪个 interrupt controller 上的那个 HW interrupt ID。这样一个好处是在中断相关的硬件发生变化的时候, 驱动软件不需要修改。因此,linux kemel
中的中断子系统需要提供一个将HW interrupt ID
映射到IRQ number
上来的机制。最早的系统中,中断系统比较简单,可以认为静态的继续映射,可是随着系统的日趋复杂,就不得不采用一种新的方式来管理中断了,那就促使了
irq domain
的产生。 -
映射方式
映射方式有三种:
线性映射:irq_domain_add_linear,
树映射:irq_domain_create_tree
不映射:irq_domain_add_nomap
线性映射:维护固定大小的表,索引是硬件中断号(设备树配置的是硬件中断号) ,如果硬件中断最大数量固定,并且数值不大,可以选择线性映射。
树映射:硬件中断号可能很大,可以选择树映射。
不映射:硬件中断号就是 irq number。
打开
CONFIG_SPARSE_IRQ
宏 (中断编号不连续) 中断描述符以 radix tree 来组织,在初始化时进行动态分配,然后再插入 radix-tree中。关闭CONFIG_SPARSE_IRQ
宏(中断编号连续) ,中断描述符以数组的形式组织,并且已经分配好。 -
映射过程
创建中断映射表流程如下