zl程序教程

您现在的位置是:首页 >  系统

当前栏目

Linux设备树详解

Linux设备 详解
2023-09-14 09:13:24 时间

设备树小故事

设备树(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

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 };
  • 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

编译完成以后得到 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

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

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

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

参考资料

  • 正点原子 I.MX6U 嵌入式 Linux 驱动开发指南
👇点击下方公众号卡片获取资料👇