title: 设备树中的中断
date: 2019/4/29 17:38:38
toc: true
---
TODO
基于设备树的TQ2440的中断(1)
https://www.cnblogs.com/pengdonglin137/p/6847685.html
基于设备树的TQ2440的中断(2)
https://www.cnblogs.com/pengdonglin137/p/6848851.html
基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
http://www.cnblogs.com/pengdonglin137/p/6349209.html
Linux kernel的中断子系统之(一):综述
Linux kernel的中断子系统之(二):IRQ Domain介绍
linux kernel的中断子系统之(三):IRQ number和中断描述符
linux kernel的中断子系统之(四):High level irq event handler
Linux kernel中断子系统之(五):驱动申请中断API
Linux kernel的中断子系统之(六):ARM中断处理过程
linux kernel的中断子系统之(七):GIC代码分析
http://www.wowotech.net/irq_subsystem/interrupt_subsystem_architecture.html
中断概述
这里就比较复杂了,暂时不去做深入的学习,以后再来挖坑学习,太难了 哈哈.这里放一个框图大概理解下基本流程
详细流程看窝窝科技
以前的内核中断描述符的数组序号与硬件是对应的,后来出了个虚拟中断号,不再强调一定是线性关系.
中断入口
2440的中断向量表是这样的(参考uboot)
.globl _start
0---> _start: b reset
4---> ldr pc, _undefined_instruction
8---> ldr pc, _software_interrupt
c---> ldr pc, _prefetch_abort
16--> ldr pc, _data_abort
20--> ldr pc, _not_used
24--> ldr pc, _irq //发生中断时,CPU跳到这个地址执行该指令
ldr pc, _fiq
内核的向量表是这么定义的,在arch\arm\kernel\entry-armv.S
.section .vectors, "ax", %progbits
.L__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, .L__vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq
W(b) vector_fiq
我们可以看到中断是跳转到vector_irq
,这个东西是个宏定义
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4 // 相当于 vector_irq: ...,
// 它会根据SPSR寄存器的值,
// 判断被中断时CPU是处于USR状态还是SVC状态,
// 然后调用下面的__irq_usr或__irq_svc
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
@ 下面是宏
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.....
__irq_usr/__irq_svc
作用是保存现场,调用irq_handler
,恢复现场irq_handler
将会调用C函数handle_arch_irq
.macro irq_handler #ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER ldr r1, =handle_arch_irq mov r0, sp badr lr, 9997f ldr pc, [r1] #else arch_irq_handler_default #endif 9997: .endm
也就是调用
handle_arch_irq
来处理中断
第一个C函数handle_arch_irq
这个函数是在哪里被设置的?
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
return 0;
}
搜索下可以看到在s3c24xx_init_intc
中设置了s3c24xx_handle_irq
s3c24xx_init_intc
s3c24xx_clear_intc(intc);
intc->domain = irq_domain_add_legacy(np, irq_num, irq_start,
0, &s3c24xx_irq_ops,
intc);
set_handle_irq(s3c24xx_handle_irq);
具体的s3c24xx_handle_irq
会调用s3c24xx_handle_intc
/*
* Array holding pointers to the global controller structs
* [0] ... main_intc
* [1] ... sub_intc
* [2] ... main_intc2 on s3c2416
*/
static struct s3c_irq_intc *s3c_intc[3];
这里有子中断的概念在硬件上,初始化如下
s3c2410_init_irq
s3c_intc[0] = s3c24xx_init_intc(NULL, &init_s3c2410base[0], NULL,0x4a000000);
// s3c24xx_init_intc 的第三个参数是 parent 也就是表示 s3c_intc[1] 是 s3c_intc[0] 的一个子控制器
s3c_intc[1] = s3c24xx_init_intc(NULL, &init_s3c2410subint[0],s3c_intc[0], 0x4a000018);
// s3c24xx_init_intc 里面会设置如下结构体
struct s3c_irq_intc {
void __iomem *reg_pending;
void __iomem *reg_intpnd;
void __iomem *reg_mask;
struct irq_domain *domain;
struct s3c_irq_intc *parent;
struct s3c_irq_data *irqs;
};
s3c24xx_handle_intc
// 1. 读取 reg_intpnd 这个应该就是顶级中断控制器的 pend 标志
readl_relaxed
// 2. 计算出硬件中断号
irq_domain_get_of_node
offset = readl_relaxed(intc->reg_intpnd + 4);
offset = __ffs(pnd);
handle_domain_irq(domain,hwirq,regs)
__handle_domain_irq
// 找到虚拟中断号
irq = irq_find_mapping(domain, hwirq);
// 根据虚拟中断号,找到描述符所在,执行 irq_flow_handler_t handle_irq
generic_handle_irq
// 这里就是找到中断描述符的结构
desc = irq_to_desc(irq)
// 执行 irq_flow_handler_t handle_irq
generic_handle_irq_desc((desc))
流程小结
handle_arch_irq:
a. 读 int controller, 得到hwirq
b. 根据hwirq得到virq
c. 调用 irq_desc[virq].handle_irq
如果该中断没有子中断, irq_desc[virq].handle_irq的操作:
a. 取出irq_desc[virq].action链表中的每一个handler, 执行它
b. 使用irq_desc[virq].irq_data.chip的函数清中断
如果该中断是由子中断产生, irq_desc[virq].handle_irq的操作:
a. 读 sub int controller, 得到hwirq'
b. 根据hwirq'得到virq
c. 调用 irq_desc[virq].handle_irq
下面老师的这个图很形象具体了
中断号的演变
虚拟中断号与实际的硬件中断源一一对应,以前我们是定死的提前写好的,需要保证所有虚拟中断号不重合,2440
定义在这里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)
#define IRQ_EINT3 S3C2410_IRQ(3)
#define IRQ_EINT4t7 S3C2410_IRQ(4) /* 20 */
.....
// 子中断号
#define S3C2410_IRQSUB(x) S3C2410_IRQ((x)+58)
#define IRQ_S3CUART_RX0 S3C2410_IRQSUB(0) /* 74 */
#define IRQ_S3CUART_TX0 S3C2410_IRQSUB(1)
#define IRQ_S3CUART_ERR0 S3C2410_IRQSUB(2)
那么我们是怎么通过硬件中断号,反算出虚拟中断号?
- 对于不同中断控制器里面的硬件中断号,它们的转化公式是不同的
- 根中断控制器和子中断控制器的公式是不一样的,称之为域
rq_domain
缺陷
- 当中断控制器数量变多时,有成百上千,这种虚拟中断号和硬件中断号一一对应的方式就很麻烦
- 所以之后虚拟中断号后来只是表示序号,可以自由分配
查找空余项
- 内核中使用一个位图数组
allocated_irqs
标记是否为空闲,1表示占用了 - 从中断号开始依次查找,直到找到最后空闲项.
IRQ domain
Linux使用IRQ domain来描述一个中断控制器(IRQ Controller)所管理的中断源.
// \include\linux\irqdomain.h
struct irq_domain {
linear_revmap[] //linear_revmap[2] 存储着这个控制器硬件中断2号对应的虚拟中断号
例子
使用子中断EINT4的过程
- 初始化为注册,首先注册主中断控制器,这里硬件中断号为4,我们去查找第4项空,存进去
linear_revmap[4]=4
- 为子中断控制器注册虚拟中断号,这里硬件中断号是4,先查找4,发现被占用,则使用5.存到
sub/linear_revmap[4]=5
- 驱动程序
request_irq(5.my_handler)
,会把my_handler
保存在irq_desc[5].action
链表中 - 中断发生时,cpu先读取顶级的中断控制器,在顶级中断控制器的
irq_domain
找到linear_revmap[4]
找到虚拟中断号是4,去执行中断描述符数组的第4项,这里会调用中断分发函数s3c_irq_demux
s3c_irq_demux
又去读取下一级别的sub irq_domain
,找到了linear_revmap[4]=5
.然后去执行中断描述符数组的第5项- 所以也就是说,在
irq_domain.linear_revmap[]
大部分数组项都是空闲的
兼容老的固定中断号
很明显的要先设置好
linear_revmap
,也就是一般就是硬件中断号鱼虚拟中断号以前写驱动程序直接
request_irq(virq,....),
中断号是通过宏方式进行定义的,所以直接使用中断号进行注册现在需要先在设备树表明使用哪个中断,内核会把这个中断号和某一个虚拟中断号挂钩,这些信息会转换成(intc,hwirq) ==> virq 这时才可以 request_irq
.liner_revmap[4] = 5 .xlate (解析设备树,得到hwirq,irq_type) .map(hwirq,virq) (map就是建立联系的作用,若是子中断,去设置父中断)
设备树描述中断
具体的中断
interrupt-parent
属于哪个中断控制器interrupts
中断号和触发方式,这个里面的成员具体怎么表述需要去看具体中断控制器的描述
中断控制器的描述
interrupt-controller
属性名表示是中断控制器#interrupt-cells
,表明下一级的设备要用多少个32位的数据来描述这个中断
interrupt-controller@4a000000 {
compatible = "samsung,s3c2410-irq";
reg = <0x4a000000 0x100>;
interrupt-controller; //---表示是中断控制器
#interrupt-cells = <0x4>;
phandle = <0x1>;
};
2440的表述
interrupt-controller@4a000000 {
compatible = "samsung,s3c2410-irq";
reg = <0x4a000000 0x100>;
interrupt-controller;
#interrupt-cells = <0x4>;
phandle = <0x1>;
};
gpf {
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
phandle = <0x6>;
};
gpg {
gpio-controller;
#gpio-cells = <0x2>;
interrupt-controller;
#interrupt-cells = <0x2>;
};
srom-cs4@20000000 {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
reg = <0x20000000 0x8000000>;
ranges;
ethernet@20000000 {
compatible = "davicom,dm9000";
reg = <0x20000000 0x2 0x20000004 0x2>;
interrupt-parent = <&gpf>; /*使用gpf中断控制器*/
// interrupts 这个数据的解析是由 gpf中断控制器 的cell 表达的
interrupts = <7 IRQ_TYPE_EDGE_RISING>;/*使用gpf控制器中的第七号中断,IRQ_TYPE_EDGE_RISING为中断触发方式*/
local-mac-address = [00 00 de ad be ef];
davicom,no-eeprom;
};
};
在这里并没有用单独的软件结构去描述硬件中的子中断控制器,而是使用interrupt-controller@4a000000
中的一个32位(#interrupt-cells = <0x4>; 总共有4个32位
)来表示是否为子中断控制器