转载自https://zhuanlan.zhihu.com/p/85353687,作者兰新宇
上文提到,每个IRQ同时有"irq"和"hwirq"两个编号。这里"hwirq"是硬件中断号,或者说是物理中断号,也就是芯片手册上写的那个号码,而"irq"是软件使用的中断号,或者说是逻辑/虚拟中断号。
说起"物理"和"虚拟"这两个词,你有没有想到Linux内存管理中所使用的虚拟地址和物理地址。可是虚拟地址机制是为配合多进程而产生的,中断号就是个线性编号啊,有必要搞这么复杂么?在只有一个中断控制器的系统中,确实是不用的,软件直接使用硬件定义的中断号就行。
如果芯片中有多个中断控制器,假设它们各自都对应16个IRQ,这16个IRQ的编号都是从0到15,那CPU收到中断后,软件单凭中断号是分不清这个中断到底是从哪个中断控制器派发过来的,它需要的是中断控制器自身的编号加上IRQ的编号。
1. irq_domain
为此,我们需要一种将中断控制器local的物理中断号转换成Linux全局的虚拟中断号的机制,而接下来要介绍的struct irq_domain就承担着这个转换的角色。
struct irq_domain {
const char *name;
const struct irq_domain_ops *ops;
irq_hw_number_t hwirq_max;
struct radix_tree_root revmap_tree;
unsigned int revmap_size;
unsigned int linear_revmap[];
unsigned int revmap_direct_max_irq;
...
};
所谓"虚拟",其实就是说和具体的硬件连接没有关系了,仅仅是一个数字而已。我们把从虚拟地址到物理地址的转换称为映射(mapping),把从物理地址到虚拟地址的转换称为逆向映射(reverse mapping)。同样地,将虚拟中断号转换成物理中断号的过程是映射,将物理中断号转换成虚拟中断号的过程则是逆向映射。
如果做个类比的话,那么IRQ就对应page frame的概念,因而描述IRQ的irq_desc可以对应描述page frame的struct page,"hwirq"和"irq"则分别可以对应物理页面号PPN和虚拟页面号VPN。
CPU使用的是虚拟地址,所以大多数时候我们都是需要通过虚拟地址去获得物理地址,进而真正地访问物理内存,逆向映射一般仅用在内存回收的时候。但在中断处理中,中断是从CPU外部来的,因而首先有的是物理中断号,大多数时候我们要做的是根据映射规则获得虚拟中断号,即逆向映射,这就是"irq_domain"结构体中"revmap"的由来。
前面的文章在介绍page cache的逆向映射时,提到其使用的数据结构在早期是糅合了radix tree的PST,现在则是interval tree。中断编号的复杂度与内存地址毕竟不可同日而语,对于中断号的逆向映射,使用radix tree就足够了,物理中断号hwirq作为查找和添加"revmap_tree"节点的key,虚拟中断号irq作为节点的value。
Radix tree其实是一种稀疏的多级数组,当IRQ的总数减少时,它所需节点的总数也减少,就可以退化成单级数组,也就是线性映射(linear),hwirq作为"linear_revmap[]"数组的下标,irq作为该数组元素的值。
可见,系统可能的最大IRQ的总数(用"hwirq_max"表示)是决定采用哪种数据结构的依据,其分界线一般为256,大于256采用radix tree映射,小于256则采用线性映射。在大多数系统中,IRQ的总数都不会超过256,所以实际应用以线性映射居多。
线性映射的优点是查找时间固定,但要求数组大小必须等于"hwirq_max"的大小,假设"hwirq_max"的值是64,即便现在只有0和63两个中断,中间暂未使用的部分也需要连续占满。Radix tree映射的优缺点与之正好相反。
我们之所以要进行映射是因为hwirq是由物理连线决定的,无法改变,但是像PowerPC架构所使用的MPIC(Multi-Processor Interrupt Controller),其功能很强,可以通过寄存器配置hwirq,这样只需要将hwirq直接配置成我们所需要的编号就可以了,不再需要映射的过程。此时,不再有物理中断号和虚拟中断号的概念,"irq"直接等于"hwirq"。
Radix tree映射和线性映射的映射关系是由软件建立的,类似于内存管理中的页表,而MPIC这种映射关系是由硬件建立的,类似于内存管理中的TLB。
虽然radix tree、线性映射和硬件映射不会同时存在,但它们在"irq_domain"中是定义在一起的,"revmap_tree"只对radix tree有意义,revmap_size和linear_revmap[]只对线性映射有意义,revmap_direct_max_irq则只对硬件映射有意义。
2. irq_domain_ops
具体的映射过程是由irq_domain"中的"irq_domain_ops"域所定义的函数来完成的:
struct irq_domain_ops {
int (*match)(struct irq_domain *d, struct device_node *node,
enum irq_domain_bus_token bus_token);
int (*xlate)(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type);
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
void (*unmap)(struct irq_domain *d, unsigned int virq);
...
}
其中,"match"是判断dts中由"node"描述的中断控制器是否和由"d"指向的irq_domain相匹配。
"xlate"代表translate,但它并不是用来将物理中断号"翻译"成虚拟中断号的,那是"map"干的事("xlate"和"map"在英文里的语义比较像,确实容易产生歧义)。"xlate"的真正作用是根据dts的信息生成"hwirq",比如ARM里SPI(Shared Peripheral Interrupt)类型的中断是从0开始编号的,但由于中断控制器GIC的前面32个中断号另有他用,因而SPI中断实际的"hwirq"应该是dts中的编号加上32。
在"xlate"的函数参数中,"node"代表dts中对应的设备节点,"out_hwirq"是翻译后形成的硬件中断号。
引入irq_domain的概念后,操作系统使用的IRQ号与具体的硬件连线的关系被解耦,实现了对底层中断控制器细节的隐藏。
小结一下,本文和上文主要是介绍Linux中有关中断控制器和中断源的数据结构,它们之间的关系概况起来大概是这样的:
从下文开始,将介绍Linux对中断的具体处理流程。
评论 (0)