通过上一节我们知道,在内核中有一个irq_desc数组,数组里面的每一项对应一个中断,数组的下标就是对应中断的虚拟中断号(virq)。
假设只有一个中断控制器,有32个中断,那么中断和irq_desc数组可以一一对应,每一个数组项对应一个中断。
因为第0项一般不用,所以是从第1项开始,一一对应。
此时虚拟中断号和硬件中断号的对应关系为:virq = hwirq + 1。
如果再加一个中断控制器sub_intc,它也会发出中断,并且sub_intc发出的中断会触发上一级的中断控制器的n号中断。
也就是说,sub_intc的0,1,2,3号中断,都会触发上一级中断控制器的n号中断。
根据上一节的说明,sub_intc的0,1,2,3号中断,在irq_desc数组中也会有对应的单独项和它们一一对应。
假设irq_desc数组项中的第36,37,38,39项,分别对应sub_intc的0,1,2,3号中断。
sub_intc的硬件中断号称为hwirq',那么就可以得到hwirq'和virq的转换公式。
virq = hwirq' + 36
也就是说,不论是intc还是sub_intc,都可以根据硬件中断号获得对应的虚拟中断号,并且这些中断号对应的数组项,并不重合。
再增加一个外部中断控制器external intc,让系统更复杂一点。
与sub_intc类似,external intc对应intc的m号中断,我们让external intc的0号中断对应数组项的第48项。
那么,也可以得到一个硬件中断号和虚拟中断之间的转换公式:virq = hwirq'' + 48。
以前, 对于每一个硬件中断(hwirq)都预先确定它的中断号(virq),这些中断号一般都写在一个头文件里, 比如:arch\arm\mach-s3c24xx\include\mach\irqs.h。
这里的每一个宏,就是一个虚拟中断号。
使用时:
- 执行 request_irq(virq, my_handler) :内核根据virq可以知道对应的硬件中断号,然后去设置、使能中断等;
- 发生硬件中断时,内核读取硬件信息,确定hwirq,反算出virq,然后调用 irq_desc[virq].handle_irq,最终会用到 my_handler;
问:前面说了三个中断控制器,intc,sub_intc,external intc,在这三个中断控制器中,不同的硬件中断号对应的虚拟中断号是不同。
但是,intc,sub_intc,external intc都有各自的0号,1号中断等,内核怎么根据这些硬件中断号,推算出对应的虚拟中断号?
答:需要引入了一个新的概念——域(irq_domain),intc,sub_intc,external intc分别有自己的域(irq_domain)。
在不同的域(irq_domain)中,相同的硬件中断号(hwirq)对应的虚拟中断号(virq)是不同的。
所以,在描述hwirq时,应该注意“是哪个域的hwirq”。
那么,怎么使用域将硬件中断号,转化为虚拟中断号?稍后再说。
之前,virq和硬件的对应关系是固定的,比如virq 38固定对应串口3的接收中断,virq 56固定对应GPIO外部中断等。
但现在的趋势是,virq跟硬件无关,仅仅是一个标号(中断描述数组的标号)而已。
问:为什么会变成这样呢?
答:如果只有几个中断,那么可以事先确定好中断号,并且只要几个宏就可以让中断和数组项一一对应。
但是如果有成百上千个中断,就需要成百上千个宏,并且这些数组项要各自独立互不影响,工作量就变得多得多。(想想要定义上千个宏,很恐怖的。。。)
为了避免这种复杂的情况,就将硬件中断号和虚拟中断号之间固定的关系取消掉,它们依旧是一一对应,但是对应关系不再固定了。
当需要使用某个硬件中断(hwirq)时,来查找irq_desc数组,在数组中查找到一个空余项,这个空余项的下标就是这个硬件中断号对应的virq。
我们在这个空余项中存放对应的处理函数就可以了。
假设,要使用inc的INT2。
那么,先要在 irq_desc 数组中,找到一个空余项。
问:怎么查找空余项呢?
答:最笨的方法,就是从下标0开始依次查找。这当然也是一种方法,但是效率可能不好,这种方法的时间复杂度应该是O(n)。
内核使用的是另一种方法。在内核中定义了一个位图,用来记录哪些空余项被使用了。
这个位图其实就是一个数组——allocated_irqs。
allocated_irqs的bit0对应下标0,bitn对应下标n。当某一位等于1时,表示这一项被占用了。
那么,比如硬件ID为2,那么就从bit2开始,bit2,bit3依次查找,直到找到空余项。
这样做的效率应该是比从左到右一个一个找要快。
假设,要设置2号中断,并且allocated_irqs的bit2为0,那么它的virq就等于2。
问:以后处理2号中断时,我们可以从中断控制器里面获得hwirq为2,但是怎么知道对应的virq呢?
答:这就需要在设置中断时,将中断的virq保存下来了。
事实上,这个virq保存在对应的irq_domain里面。
@linear_revmap: Linear table of hwirq->virq reverse mappings
struct irq_domain {
......
unsigned int linear_revmap[];
};
irq_domain里面有一个数组linear_revmap,叫做反向映射数组。
为什么叫反向映射数组呢?
以前,我们使用中断时,是在驱动程序里面执行 request_irq,通过virq找到对应的hwirq。
现在呢,反过来,使用hwirq找到virq。
把hwirq对应的virq,保存在对应的irq_domain的linear_revmap数组中,也就是 linear_revmap[hwirq] = virq。
对于本例,hwirq=2,virq=2,所以就是linear_revmap[2] = 2。
这样,后续发生2号中断时:
- 首先根据中断向量进入到指定地址执行中断处理流程,将会调用到C语言的中断处理函数。
- 然后,在中断处理函数中读取中断控制器,得到硬件中断号。
- 之后,再根据这个中断控制器,得到对应的irq_domain。
- 通过irq_domain的linear_revmap数组以及硬件中断号,就可以得到一个virq。
- 最后,在irq_desc数组中,根据virq,找到对应的那一项,把其中的handle_irq拿出来执行。
假设要使用子中断控制器(subintc)的n号中断, 它发生时会导致父中断控制器(intc)的m号中断:
- 设备树表明要使用<subintc n>,subintc表示要使用<intc m>
- 解析设备树时,会为<subintc n>找到空闲项 irq_desc[virq'],
sub irq_domain.linear_revmap[n] = virq';
会为<intc m> 找到空闲项 irq_desc[virq],
irq_domain.linear_revmap[m] = virq;
并且设置它的handle_irq为某个分析函数demux_func - 设置驱动程序 request_irq(virq', my_handler);
- 发生硬件中断时,内核读取intc硬件信息, 确定hwirq = m, 确定 virq = irq_domain.linear_revmap[m];
然后调用 irq_desc[m].handle_irq, 即demux_func - demux_func:读取sub intc硬件信息, 确定hwirq = n, 确定 virq' = sub irq_domain.linear_revmap[n];
- 然后调用 irq_desc[n].handle_irq, 即my_handler。
在旧的中断配置方法中,irq_domain也有linear_revmap成员,只是它的linear_revmap数组都预先设置好了,只有新的中断配置方法中,linear_revmap才是设置了才不为空(0)。
也就是说,新旧配置方法是兼容的,只是配置方法略有不同。
旧的配置方法是通过宏固定配置,可以直接通过 request_irq 函数配置中断(因为virq是已知的,固定的),而新的配置方法在一开始并不知道virq,需要先在设备树中表明要使用哪个中断(hwirq),然后程序会将这个hwirq和某个virq绑定,确定virq后,才可以调用request_irq函数设置中断。
那么,要怎么在设备树中表明要使用哪个中断?这个下节再说明。
在设备树中表明要使用的中断信息后,会通过xlate函数对设备树进行解析,获得对应的hwirq和irq_type(中断触发方法)。
然后,再把hwirq映射得到virq,之后驱动程序才能来设置和使用中断。
还有一个map函数,用来建立hwirq和virq之间的映射关系的,比如,若配置的是子中断,那么map函数还要去设置父中断。
xlate和map都是irq_domain.ops的成员,他们都是函数指针。
struct irq_domain {
......
const struct irq_domain_ops *ops;
......
};
struct irq_domain_ops {
int (*match)(struct irq_domain *d, struct device_node *node,
enum irq_domain_bus_token bus_token);
int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
enum irq_domain_bus_token bus_token);
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
void (*unmap)(struct irq_domain *d, unsigned int virq);
int (*xlate)(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type);
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
/* extended V2 interfaces to support hierarchy irq_domains */
int (*alloc)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg);
void (*free)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs);
int (*activate)(struct irq_domain *d, struct irq_data *irqd, bool reserve);
void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *out_hwirq, unsigned int *out_type);
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
void (*debug_show)(struct seq_file *m, struct irq_domain *d,
struct irq_data *irqd, int ind);
#endif
};
关于 xlate 和 map 的更详细的说明,会在后面的文章中说明。