在上一节中断系统中的设备树(一)__Linux对中断处理的框架分析提过irq_desc[]下标是中断号virq(虚拟中断号),虚拟中断号和硬件中号是一一对应的,所以根据硬件中断确定是哪个中断发生。以前,对于每一个硬件中断(hwirq)都预先确定它的中断号(virq),这些中断号一般都写在一个头文件里arch\arm\mach-s3c24xx\include\mach\irqs.h
我们可以对上图进行简单的解析:
- 在总中断控制器中可以催算出下面的公式:
virq = hwirq + 1 hwirq = virq - 1
- 在子中断控制器中可以催算出下面的公式:
virq =hwirq' + 36 hwirq' = virq - 36
- 在外部子中断控制器中可以催算出下面的公式:
virq =hwirq' + 48
hwirq' = virq - 48
根据上面的图片和公式我们可以知道,对于不同的中断控制器里面的硬件中断号,转化为虚拟中断号的公式是不一样的,所以在说硬件中断号时需要说明是哪个中断控制器里面的硬件中断号。
irq_domain的引入
在描述hwirq转换为virq时, 引入一个概念: irq_domain, 域, 在这个域里hwirq转换为virq,显然irq_domain和中断控制器是一一对应的,每一个中断控制器对应一个irq_domain,先提出一个问题:怎样使用irq_domain把硬件中断号转化为软件中断号? -- 这个问题以后再说。
以前中断号(virq)跟硬件密切相关: 对于只有少说中断控制器来说这种方法是比较好的,给出硬件中断号预先确定好它的虚拟中断号,但是当中断控制器数量变多时,实际的中断号就有可能成百上千或者更多,这样操作就会比较麻烦了。
解决办法:virq和hwirq之间的联系取消掉, hwirq仅仅是一个标号而已:也就是虚拟中断号和硬件的固定联系取消掉,当我们需要使用某一个硬件中断时,在irq_desc[]查找一个空缺的项,这个空缺的项的下标就是虚拟中断号,然后再找到的irq_desc空缺的项里面 放中断处理函数就可以了。
如何在irq_desc[]中查找空闲项呢?在linux内核里面定义有一个位图allocated_irqs(它里面的每一位都对应irq_desc[]中的每一项) ,当allocated_irqs变量某一位为1时,表示irq_desc[]对应的 那一项被占用了,那么我们怎样查找空余项呢?
答:当硬件ID=2时,我们就从allocated_irqs变量里面的bit2开始查找(比较bit2、bit3),查看是哪一位为0,找到之后就找到对应的空闲项了,虚拟中断号保存在irq_domain.linear_revmap[]中, irq_domain 结构体的定义如下所示:
struct irq_domain {
struct list_head link;
const char *name;
const struct irq_domain_ops *ops;
void *host_data;
unsigned int flags;
unsigned int mapcount;
/* Optional data */
struct fwnode_handle *fwnode;
enum irq_domain_bus_token bus_token;
struct irq_domain_chip_generic *gc;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file;
#endif
/* reverse map data. The linear map gets appended to the irq_domain */
irq_hw_number_t hwirq_max;
unsigned int revmap_direct_max_irq;
unsigned int revmap_size;
struct radix_tree_root revmap_tree;
struct mutex revmap_tree_mutex;
unsigned int linear_revmap[]; //保存hwirq对应的virq,譬如:linear_revmap[hwirq] = virq
};
当中断发生时,CPU会读取中断控制器获得硬件中断号,然后找到中断控制器所对应的irq_domain(每一个中断控制器对应一个irq_domain),从irq_domain.linear_revmap[]中根据hwirq(硬件中断号)得到virq(虚拟中断号),根据虚拟中断号 得到irq_desc[]数组对应的irq_desc项。
譬如:使用子中断EINT4的过程:
- 使用父中断(intc,4)设置irq_desc: a.找空闲项,virq=4,保存起来:intc's irq_domain.linear_revmap[4] = 4; b.设置irq_desc[4].handle_irq = s3c_irq_demux;
- 为子中断eint4(subintc,4)设置irq_desc: a.找空闲项,virq=5,保存起来:subintc's irq_domain.linear_revmap[4] = 5;
- 驱动程序request_irq(5.my_handler):会把my_handler保存在irq_desc[5].action链表中;
- 发生了中断,内核读取intc,得到hwirq=4,virq = intc's irq_domain.liner_revmap[4] = 4调用irq_desc[4].handle_irq,即s3c_irq_demux;
- s3c_irq_demux: 读取subintc,得到
hwirq=4,virq = subintc's
irq_domain.liner_remap[4] = 5
,调用irq_desc[5].handle_irq,它会调用action链表中保存的my_handler。
每一个中断控制器都有一个irq_domain.linera_revmap
用于保存\获取虚拟中断号,调用irq_desc.handle_irq ==》s3c_irq_demux这个分发函数会读取下级中断控制器,得到子中断控制器的4号中断,再次读取子中断控制器对应的irq_domain.liner_revmap[4] = 5对应的虚拟中断号是5,那么就会调用第五个irq_desc,执行handle_irq。(提示:在irq_domain.linear_revmap[]
大部分数组项都是空闲的。)
兼容老的固定中断号
也要保存的硬件中断号和虚拟中断号之间的对应关系,在irq_domain也有liner_revmap[hwirq] = 预先设置号的virq,所以在老的固定中断号的版本中我们写驱动程序直接 request_irq(virq,....)。(提示:老的固定中断号的版本中,中断号是通过宏方式进行定义的,所以直接使用中断号进行注册。)
- 之前我们在老的固定中断号的版本中,写中断相关的驱动程序时,直接调用request_irq(virq,...)申请中断就可以了,在request_irq函数内部会根据virq(虚拟中断号)去设置、是能中断等操作。
- 现在当我们使用新版本的中断号时,怎么使用中断,现在不可以直接使用request_irq(virq,...) 因为现在虚拟中断还没有和硬件中断号挂钩,所以需要先在设备树中表明要使用哪个中断(intc,hwirq) ==> virq(提示:linux内核再解析设备树时就会把该中断和某个虚拟中断号挂钩,然后才能使用request_irq(virq,...));
问题:我们怎么把设备树中上面提到的信息(intc,hwirq)转化为某个irq_domain中的硬件中断号?
答:需要使用irq_domain操作:
- irq_domain.ops->xlate:解析设备树得到hwirq和irq_type(中断触发方式) 然后就可以根据hwirq获得virq(虚拟中断号),知道virq之后linux驱动程序才才可以 调用request_irq(virq,...);
- irq_domain.ops->map:把hwirq和virq建立起联系,如果hwirq是子中断号还要去设置主中断 最终要可以根据virq找到hwirq对应的硬件;