目录
- 设备树中的中断
- TODO
- 中断概述
- 中断入口
- 第一个C函数handle_arch_irq
- 流程小结
- 中断号的演变
- 查找空余项
- IRQ domain
- 例子
- 兼容老的固定中断号
- 设备树描述中断
- 2440的表述
- 2440中使用设备树
- 内核处理中断
- 中断结构
- 中断相关代码调用关系
title: 设备树中的中断
date: 2019/4/29 17:38:38
toc: true
---
设备树中的中断
原文 100ask
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位
)来表示是否为子中断控制器,看下章节
2440中使用设备树
具体的使用看代码就明白了
新增一个匹配条件
of_match_table
struct platform_driver buttons_drv = { .probe = buttons_probe, .remove = buttons_remove, .driver = { .name = "mybuttons", .of_match_table = of_match_buttons, /* 能支持哪些来自于dts的platform_device */ } }; static const struct of_device_id of_match_buttons[] = { { .compatible = "jz2440_button", .data = NULL }, { /* sentinel */ } };
probe
中获取资源方式,这里获取硬件中断号,以及pin的信息,这里已经转换了虚拟中断static int buttons_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *dp_node = dev->of_node; struct resource *res; int i; for (i = 0; i < sizeof(pins_desc)/sizeof(pins_desc[0]); i++) { /* 根据platform_device的资源进行获得中断号,触发类型 */ res = platform_get_resource(pdev, IORESOURCE_IRQ, i); if (res) { pins_desc[i].irq = res->start; printk("get irq %d\n", pins_desc[i].irq); } else { printk("can not get irq res for eint0\n"); return -1; } pins_desc[i].pin = of_get_named_gpio(dp_node, "eint-pins", i); printk("pins_desc[%d].pin = %d\n", i, pins_desc[i].pin); } return sixth_drv_init(); }
打开设备时申请中断,这里的虚拟中断在资源获取时已经转换了
static int sixth_drv_open(struct inode *inode, struct file *file) { ret = request_irq(pins_desc[0].irq, buttons_irq, 0, "S2", &pins_desc[0]); .... }
内核处理中断
中断结构
从硬件结构上看, 处理过程分上下两个层面: 中断控制器, 使用中断的设备;
从软件结构上看, 处理过程分左右两个部分: 在设备树中描述信息, 在驱动中处理设备树;
(1) 中断控制器
这又分为root irq controller, gpf/gpg irq controller
a. root irq controller
a.1 在设备树中的描述
a.2 在内核中的驱动
b. 对于S3C2440, 还有: gpf/gpg irq controller
b.1 在设备树中的描述(在pinctrl节点里)
b.2 在内核中的驱动 (在pinctrl驱动中)
(2) 设备的中断
a.1 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式")
a.2 在内核中的驱动 (在platform_driver.probe中获得IRQ资源, 即中断号)
irq_domain是核心:
a. 每一个中断控制器都有一个irq_domain
b. 对设备中断信息的解析,
b.1 需要调用 irq_domain->ops->xlate
(即从设备树中获得hwirq, type)
b.2 获取未使用的virq, 保存: irq_domain->linear_revmap[hwirq] = virq;
b.3 在hwirq和virq之间建立联系
中断相关代码调用关系
s3c2440设备树中断相关代码调用关系:
(1) 上述处理过程如何触发?
a. 内核启动时初始化中断的入口:
start_kernel // init/main.c
init_IRQ();
if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
irqchip_init(); // 一般使用它
else
machine_desc->init_irq();
b. 设备树中的中断控制器的处理入口:
irqchip_init // drivers/irqchip/irqchip.c
of_irq_init(__irqchip_of_table); // 对设备树文件中每一个中断控制器节点, 调用对应的处理函数
为每一个符合的"interrupt-controller"节点,
分配一个of_intc_desc结构体, desc->irq_init_cb = match->data; // = IRQCHIP_DECLARE中传入的函数
并调用处理函数
(先调用root irq controller对应的函数, 再调用子控制器的函数, 再调用更下一级控制器的函数...)
(2) root irq controller的驱动调用过程
a. 为root irq controller定义处理函数:
IRQCHIP_DECLARE(s3c2410_irq, "samsung,s3c2410-irq", s3c2410_init_intc_of); //drivers/irqchip/irq-s3c24xx.c
其中:
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__##table##_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
展开为:
static const struct of_device_id __of_table_s3c2410_irq \
__used __section("__irqchip_of_table") \
= { .compatible = "samsung,s3c2410-irq", \
.data = s3c2410_init_intc_of }
它定义了一个of_device_id
结构体, 段属性为__irqchip_of_table
, 在编译内核时这些段被放在__irqchip_of_table
地址处。
即__irqchip_of_table
起始地址处,放置了一个或多个 of_device_id,
它含有compatible
成员;
设备树中的设备节点含有compatible
属性,
如果双方的compatible
相同, 并且设备节点含有interrupt-controller
属性,则调用of_device_id
中的函数来处理该设备节点。
所以IRQCHIP_DECLARE是用来声明设备树中的中断控制器的处理函数。
b. root irq controller处理函数的执行过程:
s3c2410_init_intc_of // drivers/irqchip/irq-s3c24xx.c
// 初始化中断控制器: intc, subintc
s3c_init_intc_of(np, interrupt_parent, s3c2410_ctrl, ARRAY_SIZE(s3c2410_ctrl));
// 为中断控制器创建irq_domain
domain = irq_domain_add_linear(np, num_ctrl * 32,
&s3c24xx_irq_ops_of, NULL);
intc->domain = domain;
// 设置handle_arch_irq, 即中断处理的C语言总入口函数
set_handle_irq(s3c24xx_handle_irq);
(3) pinctrl系统中gpf/gpg irq controller的驱动调用过程
a. pinctrl系统的驱动程序:
a.1 源代码: drivers/pinctrl/samsung/pinctrl-samsung.c
static struct platform_driver samsung_pinctrl_driver = {
.probe = samsung_pinctrl_probe,
.driver = {
.name = "samsung-pinctrl",
.of_match_table = samsung_pinctrl_dt_match, // 含有 { .compatible = "samsung,s3c2440-pinctrl", .data = &s3c2440_of_data },
.suppress_bind_attrs = true,
.pm = &samsung_pinctrl_pm_ops,
},
};
a.2 设备树中:
pinctrl@56000000 {
reg = <0x56000000 0x1000>;
compatible = "samsung,s3c2440-pinctrl"; // 据此找到驱动
a.3 驱动中的操作:
samsung_pinctrl_probe // drivers/pinctrl/samsung/pinctrl-samsung.c
最终会调用到 s3c24xx_eint_init // drivers/pinctrl/samsung/pinctrl-s3c24xx.c
// eint0,1,2,3的处理函数在处理root irq controller时已经设置;
// 设置eint4_7, eint8_23的处理函数(它们是分发函数)
for (i = 0; i < NUM_EINT_IRQ; ++i) {
unsigned int irq;
if (handlers[i]) /* add by [email protected], 不再设置eint0,1,2,3的处理函数 */
{
irq = irq_of_parse_and_map(eint_np, i);
if (!irq) {
dev_err(dev, "failed to get wakeup EINT IRQ %d\n", i);
return -ENXIO;
}
eint_data->parents[i] = irq;
irq_set_chained_handler_and_data(irq, handlers[i], eint_data);
}
}
// 为GPF、GPG设置irq_domain
for (i = 0; i < d->nr_banks; ++i, ++bank) {
ops = (bank->eint_offset == 0) ? &s3c24xx_gpf_irq_ops
: &s3c24xx_gpg_irq_ops;
bank->irq_domain = irq_domain_add_linear(bank->of_node, bank->nr_pins, ops, ddata);
}
(4) 使用中断的驱动调用过程:
a. 在设备节点中描述(表明使用"哪一个中断控制器里的哪一个中断, 及中断触发方式"),比如:
buttons {
compatible = "jz2440_button";
eint-pins = <&gpf 0 0>, <&gpf 2 0>, <&gpg 3 0>, <&gpg 11 0>;
interrupts-extended = <&intc 0 0 0 3>,
<&intc 0 0 2 3>,
<&gpg 3 3>,
<&gpg 11 3>;
};
b. 设备节点会被转换为 platform_device, "中断的硬件信息" 会转换为"中断号", 保存在platform_device的"中断资源"里
第3课第05节_device_node转换为platform_device, 讲解了设备树中设备节点转换为 platform_device 的过程;
我们只关心里面对中断信息的处理:
of_device_alloc (drivers/of/platform.c)
dev = platform_device_alloc("", PLATFORM_DEVID_NONE); // 分配 platform_device
num_irq = of_irq_count(np); // 计算中断数
of_irq_to_resource_table(np, res, num_irq) // drivers/of/irq.c, 根据设备节点中的中断信息, 构造中断资源
of_irq_to_resource
int irq = of_irq_get(dev, index); // 获得virq, 中断号
rc = of_irq_parse_one(dev, index, &oirq); // drivers/of/irq.c, 解析设备树中的中断信息, 保存在of_phandle_args结构体中
domain = irq_find_host(oirq.np); // 查找irq_domain, 每一个中断控制器都对应一个irq_domain
irq_create_of_mapping(&oirq); // kernel/irq/irqdomain.c, 创建virq和中断信息的映射
irq_create_fwspec_mapping(&fwspec);
irq_create_fwspec_mapping(&fwspec);
irq_domain_translate(domain, fwspec, &hwirq, &type) // 调用irq_domain->ops->xlate, 把设备节点里的中断信息解析为hwirq, type
virq = irq_find_mapping(domain, hwirq); // 看看这个hwirq是否已经映射, 如果virq非0就直接返回
virq = irq_create_mapping(domain, hwirq); // 否则创建映射
virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL); // 返回未占用的virq
irq_domain_associate(domain, virq, hwirq) // 调用irq_domain->ops->map(domain, virq, hwirq), 做必要的硬件设置