• Linux设备树详解


    设备树小故事

    设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、 IIC 接口上接了哪些设备、 SPI 接口上接了哪些设备等等。

    在这里插入图片描述

    在没有使用设备树的时候,有关板级的硬件信息都被硬编码在内核中,这就导致了内核中描述板级硬件的代码过于庞大,不利于阅读。

    随着智能手机的发展,每年新出的 ARM 架构芯片少说都在数十、数百款, Linux 内核下板级信息文件将会成指数级增长!这些板级信息文件都会被硬编码进 Linux 内核中,导致 Linux 内核**“虚胖”**。

    当 Linux 之父 linus 看到 ARM 社区向 Linux 内核添加了大量无用、冗余的板级信息文件,不禁的发出了一句This whole ARM thing is a fucking pain in the ass

    从此以后 ARM 社区就引入了 PowerPC 等架构已经采用的设备树(Flattened Device Tree)

    这个就是设备树的由来,简而言之就是, Linux 内核中 ARM 架构下有太多的冗余的垃圾板级信息文件,导致 linus 震怒,然后 ARM 社区引入了设备树。

    设备树文件

    设备树源文件扩展名为.dts,另外DTS是设备树源码文件, DTB 是将DTS 编译以后得到的二进制文件。

    对于设备树文件的编译我们可以使用DTC 工具对其进行编译,DTC工具源码在Linux内核的scripts/dtc目录下。

    DTC 工具依赖于 dtc.cflattree.cfstree.c 等文件,最终编译并链接出 DTC
    个主机文件。

    如果要编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命
    令:

    make dtbs
    
    • 1

    Linux设备树文件在内核源码中的 arch/arm/mach-xxx 文件夹和arch/arm/plat-xxx 文件夹中

    使用设备树

    我们想要使用设备树首先需要修改设备树文件,设备树文件的位置我们上面已经所过了,大家可以去找一下:
    在这里插入图片描述

    修改设备树文件

    我们想要使用设备树首先需要修改设备树文件,我们需要首先找到我们需要修改的设备树文件,如果不知道对应模块的设备树文件在哪个位置的话我上面有讲到,设备树文件都在内核源码中的 arch/arm/mach-xxx 文件夹和arch/arm/plat-xxx 文件夹中。

    在文件夹中找到我们芯片或者开发板所对应的DTS文件,例如我使用的开发板是野火公司的I.MX6U开发板,所以我们需要首先找到这个开发板所对应的设备树,我们所有的设备都需要在这个设备树文件中进行描述。如果没有可能是内核版本太老所致。

    找到我们需要修改的设备树文件后打开对应的.dts 文件,在根节“/”下创建一个名为“alphaled”的子节点,打开 imx6ull-alientek-emmc.dts 文件,
    在根节点“/”最后面输入如下所示内容:

    1 alphaled {
    2 	#address-cells = <1>;
    3 	#size-cells = <1>;
    4 	compatible = "atkalpha-led";
    5 	status = "okay";
    6 	reg = < 0X020C406C 0X04 /* CCM_CCGR1_BASE */
    7 			0X020E0068 0X04 /* SW_MUX_GPIO1_IO03_BASE */
    8 			0X020E02F4 0X04 /* SW_PAD_GPIO1_IO03_BASE */
    9 			0X0209C000 0X04 /* GPIO1_DR_BASE */
    10 			0X0209C004 0X04 >; /* GPIO1_GDIR_BASE */
    11 };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 2、 3 行:属性#address-cells#size-cells 都为 1,表示 reg 属性中起始地址占用一个字长(cell),地址长度也占用一个字长(cell)。
    • 4 行:属性 compatbile 设置 alphaled 节点兼容性为“atkalpha-led”
    • 5 行:属性 status 设置状态为“okay”
    • 6~10 行: reg 属性,非常重要! reg 属性设置了驱动里面所要使用的寄存器物理地址,比如第 6 行:“0X020C406C 0X04”表示 I.MX6ULLCCM_CCGR1 寄存器,其中寄存器首地址为 0X020C406C,长度为 4 个字节。

    编译设备树

    设备树文件修改之后我们需要首先进行编译以下,正如我们编译C语言一样,DTS文件也是需要编译的,我们使用以下命令对设备树文件DTS进行编译。

    make dtbs
    
    • 1

    编译完成以后得到 imx6ull-alientek-emmc.dtb,使用新的 imx6ull-alientek-emmc.dtb 启动Linux 内核。 Linux 启动成功以后进入到/proc/device-tree/目录中查看是否有“alphaled”这个节点,结果如图:在这里插入图片描述

    异常处理

    如果没有“alphaled”节点的话请重点下面两点:

    • 检查设备树修改是否成功,也就是 alphaled 节点是否为根节点“/”的子节点。
    • 检查是否使用新的设备树启动的 Linux 内核。

    可以进入到图 44.3.1 中的 alphaled 目录中,查看一下都有哪些属性文件,结果如图 所示:
    在这里插入图片描述
    大家可以查看一下 compatiblestatus 等属性值是否和我们设置的一致。如果不一致修改过来即可。

    编写驱动文件

    在编写驱动文件过程中我们只需要关注一下设备树的改动,其他例如申请设备号啥的和原来是一样的,我们现在不需要关注,如果你对字符设备驱动框架的理解还不是很深,你可以看我写的这篇文章:

    回归主题,我们现在开始修改设备树相关代码,我们首先在驱动入口处添加以下代码:

    /* 获取设备树中的属性数据 */
    /* 1、获取设备节点: alphaled */
    dtsled.nd = of_find_node_by_path("/alphaled");
    if(dtsled.nd == NULL) {
    	printk("alphaled node not find!\r\n");
    	return -EINVAL;
    } else {
    	printk("alphaled node find!\r\n");
    }
    
    /* 2、获取 compatible 属性内容 */
    proper = of_find_property(dtsled.nd, "compatible", NULL);
    if(proper == NULL) {
    	printk("compatible property find failed\r\n");
    } else {
    	printk("compatible = %s\r\n", (char*)proper->value);
    }
    
    /* 3、获取 status 属性内容 */
    ret = of_property_read_string(dtsled.nd, "status", &str);
    if(ret < 0){
    	printk("status read failed!\r\n");
    } else {
    	printk("status = %s\r\n",str);
    }
    
    /* 4、获取 reg 属性内容 */
    ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
    if(ret < 0) {
    	printk("reg property read failed!\r\n");
    } else {
    	u8 i = 0;
    	printk("reg data:\r\n");
    	for(i = 0; i < 10; i++)
    		printk("%#X ", regdata[i]);
    	printk("\r\n");
    }
    
    
    /* 初始化 LED */
    #if 0
    	/* 1、寄存器地址映射 */
    	IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
    	SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
    	SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
    	GPIO1_DR = ioremap(regdata[6], regdata[7]);
    	GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
    #else
    	IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
    	SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
    	SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
    	GPIO1_DR = of_iomap(dtsled.nd, 3);
    	GPIO1_GDIR = of_iomap(dtsled.nd, 4);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    看到上面的代码是不是感觉加入设备树之后代码结构变得好复杂啊!还要获取那么多设备树属性和数据,这不是妥妥的给自己找不爽吗?

    其实设备树的加入主要是为了减少这些板级信息被写入Linux内核中,因为如果不使用设备树的话我们每一个开发板都需要加入到Linux内核中,否者你就无法使用该款开发板,基于此,设备树就此诞生了,让我们每一个开发板都有一棵属于自己的树,板上的硬件相当于树上的分支,这样Linux在启动的时候就会先去找你所对应的设备树,相当于读到了你板子的信息,你就可以使用设备树了!

    其实设备树的知识远不止于此,我目前也是囫囵吞枣,也有很多不明白的地方,大家可以多读几篇文章,看看大佬们怎么总结的。

    参考资料

    • 正点原子 I.MX6U 嵌入式 Linux 驱动开发指南
    👇点击下方公众号卡片获取资料👇
  • 相关阅读:
    【LeetCode刷题】1两数之和
    如何让vivado仿真结果显示具体数值
    vue3 vite2 封装 SVG 图标组件 - 基于 vite 创建 vue3 全家桶项目续篇
    用Rust手把手编写一个Proxy(代理), UDP绑定篇
    C语言实现单链表
    瑞吉外卖git
    初识设计模式-策略模式-去掉别扭的if,满足开闭原则
    GBASE 8s的数据导入和导出
    Mybatis-Plus的一些优雅用法
    OpenHarmony鸿蒙南向开发案例:【智能猫眼(基于3518开发板)】
  • 原文地址:https://blog.csdn.net/qq_45172832/article/details/126035793