第五课. 中断系统中的设备树
这节课讲解如何在中断系统中使用设备树,也就是用设备树如何描述中断。
中断体系在4.x内核中变化很大,中断体系又跟pinctrl系统密切相关,pinctrl中又涉及GPIO子系统,这样讲下去的话,设备树课程就变成驱动专题了,所以我打算只讲中断体系统,对于pinctrl、gpio等系统留待以后在驱动课程中扩展。
这一课的参考资料如下:
- 基于设备树的TQ2440的中断(1)
- 基于设备树的TQ2440的中断(2)
- 基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
- 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代码分析
本课视频预计分为五节。 其中第01节描述中断概念的引入与处理流程,这节视频来自"韦东山第1期裸板视频加强版", 如果已经理解了中断的概念, 请忽略该节。
目录
第05节_示例_使用设备树描述按键中断
在上节视频里我们体验了怎么在设备树中描述中断,这一节我们来写一个按键驱动程序来看看怎么使用设备树来描述按键驱动程序所使用的引脚和所使用的中断。
这个驱动程序就不现场编写了,毕竟我们主题是讲设备树,而不是讲怎么写驱动程序。
源码路径
我们在以前按键驱动程序的基础上修改按键驱动程序。
按键驱动程序百度云盘路径:
100ask分享的所有文件
009_UBOOT移植_LINUX移植_驱动移植(免费)
源码文档图片.zip
源码文档图片
源码文档
毕业班_文档_图片_源码_bin
毕业班第4课移植驱动到3.4.2内核_文档_图片_源码
drivers_and_test_new
jz2440
7th_buttons_all
该源码已经先下载下来,放在设备树的文件夹里,路径为:
100ask分享的所有文件
018_设备树详解
doc_and_sources_for_device_tree
source_and_images
第5,6课的源码及映像文件(使用了完全版的设备树)
第5课第5节_按键驱动_源码_设备树
000th_origin_code
000th_origin_code
是从前面毕业班视频里直接拷贝过来的;001th_buttons_drv
是用在之前阉割版本的内核,里面没有支持用设备树描述中断;002th_buttons_drv
是本节视频使用的驱动程序,在设备树里可以描述中断了;
将000th
和001th
进行对比:
- Makefile有变化,两个的内核路径不一样,因为编译驱动需要借助内核源码;
- 驱动有少量变量,比如头文件,一些结构体名字,定时器相关的函数,gpio读取函数名字等;
可以看到这两个驱动程序的变化不大,使用的中断号都是和硬件绑定的;
按键中断
在上节我们知道了中断抽象出了三个中断控制器。
根据原理图可知,我们的按键涉及eint0、eint2、eint11、eint19。
其中eint0、eint2接在最顶层的中断控制器,eint11、eint19接在gpf中断控制器。
以前我们在设备树中描述中断时,需要指定这个中断是发给哪一个中断控制器,它属于这个中断中的哪一个中断。即在interrupt-parent
指定中断控制器,在interrupts
指定是该中断控制器中的哪个中断,并且指定中断类型。
现在我们有四个中断,分别属于两个中断控制器,它向两个中断控制器发送信号,就不能使用老方法了,我们需要引入一个新的属性。
参考设备树的官方文档,里面有个中断扩展属性,在里面可以指定多个中断,参考如下:
interrupts-extended = <&pic 0xA 8>, <&gic 0xda>;
比如该示例有两个中断控制器,每个后面紧跟对应得描述内容,描述内容的多少由中断控制器决定。
如下图截取按键中断的描述:
首先是指定中断控制器,让再描述哪一个中断。
对于intc
中断控制器:
第一个表示是发给主控制器还是子控制器;
第二个表示子中断控制器发给主控制器的哪一个;
第三个表示是这个中断控制器里的哪一个中断;
第四个表示中断的触发方式;
对于gpg
中断控制器:
第一个表示是这个中断控制器里的哪一个中断;
第二个表表示中断的触发方式;
可以看到两个不同的中断控制器,它们后面的描述数据的数量是不一样的,这个数量的多少,是在设备树里面#interrupt-cells
里定义的。
至此,对中断属性的解释已经清楚了,我们的驱动程序需要设备号,中断号是一个软件的概念,那么这些中断信息怎么转换成中断号呢?
在设备树的设备节点中描述"中断的硬件信息",表明使用了"哪一个中断控制器里的哪一个中断, 及中断触发方式"。
设备节点会被转换为 platform_device, "中断的硬件信息" 会转换为"中断号", 保存在platform_device的"中断资源"里。
驱动程序从platform_device的"中断资源"取出中断号, 就可以request_irq了。
实验
a. 把"002th_buttons_drv/jz2440_irq.dts" 放入内核 arch/arm/boot/dts目录, 在内核根目录下执行:
make dtbs // 得到 arch/arm/boot/dts/jz2440_irq.dtb
使用上节视频的uImage或这个jz2440_irq.dtb启动内核;
b. 编译、测试驱动:
b.1 把 002th_buttons_drv 上传到ubuntu
b.2 编译驱动:
export PATH=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/work/system/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi/bin
cd 002th_buttons_drv
make // 得到 buttons.ko
b.3 编译测试程序:
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/local/arm/4.3.2/bin
cd 002th_buttons_drv
arm-linux-gcc -o buttons_test buttons_test.c
b.4 测试:
insmod buttons.ko
./buttons_test &
然后按键
第06节_内核对设备树中断信息的处理过程
中断结构
从硬件结构上看, 处理过程分上下两个层面: 中断控制器, 使用中断的设备;
从软件结构上看, 处理过程分左右两个部分: 在设备树中描述信息, 在驱动中处理设备树;
(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之间建立联系:
- 要调用 irq_domain->ops->map, 比如根据hwirq的属性设置virq的中断处理函数(是一个分发函数还是可以直接处理中断)
irq_desc[virq].handle_irq = 常规函数;
- 如果这个hwirq有上一级中断, 假设它的中断号为virq', 还要设置:
irq_desc[virq'].handle_irq = 中断分发函数;
- 要调用 irq_domain->ops->map, 比如根据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 weidongshan@qq.com, 不再设置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), 做必要的硬件设置
c. 驱动程序从platform_device
的"中断资源"取出中断号, 就可以request_irq
了