【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
硬件中断号是写在芯片手册当中的, 它会明确告诉你这个硬件中断属于哪一个中断控制器管理, 以及哪一个或哪几个GPIO与这个硬件中断是硬件连接的.
中断不仅是软件的概念, 在硬件中断的概念中, 它是实打实的硬件, 我们嵌入式软件中, 驱动层的中断是需要硬件的支撑才可以实现中断的.
何为软件中断号呢? 当硬件中断在被我们软件工程师通过代码所调用时, 我们在代码中不会继续使用硬件中断号(虽然多数情况下硬件中断号和软件中断号是一样的), 这是由Linux系统所决定的, Linux帮我们做了许多操作, 使我们不需要关心硬件中断是如何与我们的软件中断绑定起来的, 我们只需要知道使用这个软件中断号, 如同使用硬件中断号一样!
有些博文会把软件中断号说成硬件中断号, 应该是博主口误, 请注意区分.
以前, 对于每一个硬件中断(hwirq)都预先确定了它的中断号(virq), 这些中断号一般都写在一个头文件中. 比如: arch/arm/mach-s3c24xx/include/mach/irqs.h.
/* main cpu interrupts */
#define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */
#define IRQ_EINT1 S3C2410_IRQ(1)
#define IRQ_EINT2 S3C2410_IRQ(2)
...
/* interrupts generated from the external interrupts sources */
#define IRQ_EINT0_2412 S3C2410_IRQ(32)
#define IRQ_EINT1_2412 S3C2410_IRQ(33)
...
在使用request_irq(virq, handler…)时, virq的来源如上图所示, 已经被硬编码为代码中的一员, 我们只需要确定我们期望监视的那一个中断属于irqs.h文件中的哪一个宏即可. (具体结合芯片手册和代码进行查看, 宏的名称一般芯片手册中的中断名称接近或相同)
优点:
直接明了, 系统启动时就已经把软件中断号(virq)给分配好了, 我们在使用request_irq(virq, handler…)时不需要考虑怎么分配一个软件中断号, 直接拿过来使用即可. 如request_irq(IRQ_EINT2, handler…)
缺点:
代码冗余, 针对每一个平台/板子的都需要添加一个mach-xxx文件夹, 且里面的中断也是一个一个添加上去(对开发它的人来说是个灾难), 而那些中断可能一辈子都不会被用到, 这也是为什么设备树会被推出而且申请中断号的流程也不一样的原因.
/ {
model = "PLATFORM CHIP DEMO Board";
compatible = "PLATFORM,CHIP";
memory {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
// 在设备树中表明, 要使用哪个中断.
button@0x120F8000 { // 自己添加的一个按键结点
compatible = "ybk_btn"; // 用于与驱动匹配
reg = <0x120f8000 0x1000>; // 该按键的GPIO地址及属性
interrupt-parent = <&gic>; // 该按键对应的硬件中断属于哪一个中断控制器
/**
* 具体含义由中断控制器来解释. 用多少个u32描述也是由中断控制器来指定.
* 下面是我的平台的解释
* 0, 属于该中断控制器的第0个子中断控制器
* 23, 属于第0个子中断控制器中的第23个中断位
* 4, 中断触发方式的选择, 即该GPIO什么情况下会触发中断
* 1 = low-to-high edge triggered
* 2 = high-to-low edge triggered
* 4 = active high level-sensitive
* 8 = active low level-sensitive
*/
interrupts = <0 23 4>;
};
};
如上图所示, Linux的软件中断号将不会直接在mach-xxx/include/mach/irqs.h中给出, 当你需要时, 你要自己通过一些操作来获得.
其中的button@0x120F8000结点是我自己添加的, 里面我指定了这个按键属于哪一个中断控制器, 以及属于哪一个中断源, 触发中断的条件是什么. 当我们需要得到virq时, 在驱动层的probe函数中添加如下代码:
int btn_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct resource *res = NULL;
int i, ret;
for(i=0; i<sizeof(pins_desc)/sizeof(pins_desc[0]); i++) {
res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if(res) {
pins_desc[i].irq = res->start;
} else {
printk("get irq failed!\n");
return -EINVAL;
}
}
printk("btn_probe, found btn\n");
...............................
return 0;
}
我们把设备树编入了内核, 在驱动代码中调用platform_get_irq(pdev, 0);或者platform_get_resource(pdev, IORESOURCE_IRQ, 0);即可得到我们所期待的virq!
在使用时, 执行request_irq(virq, handler): 内核根据virq可以知道对应的硬件中断, 然后去设置、使能中断等.
发生硬件中断时: 内核读取硬件信息, 确定hwirq, 反算出virq, 然后调用irq_desc[virq].handle_irq, 最终调用到对应的handler(处理函数).
怎么根据hwirq计算出virq?
硬件上有多个intc(中断控制器), 对于同一个hwirq数值, 会对应不同的virq. 所以在讲hwirq时,应该强调"是哪一个intc的hwirq", 在描述hwirq转换为virq时, 引入一个概念: irq_domain(域), 在这个域里hwirq转换为某一个virq.
使用子中断EINT4的过程:
// step1, 为父中断(intc, 4)设置irq_desc:
找空闲项, virq=4, 保存起来: intc's irq_domain.linear_revmap[4] = 4
设置irq_desc[4].handle_irq = s3c_irq_demux
// step2, 为子中断eint(subintc, 4)设置irq_desc:
找空闲项, virq=5, 保存起来: subintc's irq_domain.linear_revmap[4] = 5
// step3, 驱动程序调用了 request_irq(5, my_handler)
会把my_handler保存到irq_desc[5].action链表中.
// step4, 发生了中断:
内核读取intc, 得到hwirq=4, virq=intc's irq_domain.linear_revmap[4] = 4
调用irq_desc[4].handle_irq, 即s3c_irq_demux
// step5, s3c_irq_demux:
读取subintc, 得到hwirq=4, virq=subintc's irq_domain.linear_revmap[4] = 5
调用irq_desc[5].handle_irq, 它会调用action链表中保存的my_handler
每一个中断控制器都有一个irq_domain结构体;
interrupt-controller中断控制器使用interrupt-controller(空值)来描述自己.