5.3中断系统中的设备树——中断号的演变与irq_domain

通过上一节我们知道,在内核中有一个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。

这里的每一个宏,就是一个虚拟中断号。

使用时:

  1. 执行 request_irq(virq, my_handler) :内核根据virq可以知道对应的硬件中断号,然后去设置、使能中断等;
  2. 发生硬件中断时,内核读取硬件信息,确定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_domainlinear_revmap数组中,也就是 linear_revmap[hwirq] = virq

对于本例,hwirq=2,virq=2,所以就是linear_revmap[2] = 2。

这样,后续发生2号中断时:

  1. 首先根据中断向量进入到指定地址执行中断处理流程,将会调用到C语言的中断处理函数
  2. 然后,在中断处理函数中读取中断控制器,得到硬件中断号
  3. 之后,再根据这个中断控制器,得到对应的irq_domain
  4. 通过irq_domain的linear_revmap数组以及硬件中断号,就可以得到一个virq
  5. 最后,在irq_desc数组中,根据virq,找到对应的那一项,把其中的handle_irq拿出来执行。

假设要使用子中断控制器(subintc)的n号中断, 它发生时会导致父中断控制器(intc)的m号中断:

  1. 设备树表明要使用<subintc n>,subintc表示要使用<intc m>
  2. 解析设备树时,会为<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
  3. 设置驱动程序 request_irq(virq', my_handler);
  4. 发生硬件中断时,内核读取intc硬件信息, 确定hwirq = m, 确定 virq =  irq_domain.linear_revmap[m];
    然后调用 irq_desc[m].handle_irq, 即demux_func
  5. demux_func:读取sub intc硬件信息, 确定hwirq = n, 确定 virq' =  sub irq_domain.linear_revmap[n];
  6. 然后调用 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函数对设备树进行解析,获得对应的hwirqirq_type中断触发方法)。

然后,再把hwirq映射得到virq,之后驱动程序才能来设置和使用中断。

还有一个map函数,用来建立hwirq和virq之间的映射关系的,比如,若配置的是子中断,那么map函数还要去设置父中断。

xlatemap都是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 的更详细的说明,会在后面的文章中说明。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值