内容详见《【正点原子】I.MX6U嵌入式Linux驱动开发指南》四十三章
开发板:imx6ull mini
虚拟机:VMware17
ubuntu:ubuntu20.04
一、什么是设备树
视频:第6.1讲 Linux设备树详解-什么是设备树?_哔哩哔哩_bilibili
对应《指南》43.1部分
uboot启动时需要内核zImage 和 .dtb文件,其中dtb便是由设备树文件dts(Device Tree Source)转换而来。dts文件用树形结构来描述板级信息,即开发板上的设备信息,如CPU数量、 内存基地址、IIC接了哪些设备等等。
bootcmd命令中,80800000就是zImage在RAM中的存放起始地址,83000000就是设备树在RAM中的存储地址。
tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000
没有设备树以前,设备的板级信息等都在.c文件中进行配置,最后会被编码到内核里面,又臭又长。因此引入了设备树,用一个专用的文件格式.dts来描述板级信息,将这些玩意与linux分离。
如果不同的板子有相同的信息,就可以将这部分提取出来作为一个通用文件.dtsi,其他的.dts文件直接引用就可以。
一般.dts描述板级信息,.dtsi描述SOC级信息。
二、DTS文件
视频:第6.2讲 Linux设备树详解-DTS文件以及组织形式_哔哩哔哩_bilibili
对应《指南》43.2~3部分
2.1 编译为dtb
命令:
# 进入到Linux源码根目录下
make all # 全部编译,包括zImage、.ko文件等等
make dtbs # 将当前内核里所有.dts文件编译为.dtb
make XXX.dtb # 将指定的dts文件编译为dtb
示例:
cd …………/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/
make dtbs
cd arch/arm/boot/dts/
ls *.dtb # 此时应当可以看到一大堆的dts文件
2.2 dts语法
打开两个文件:linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/boot/dts下的imx6ull-alientek-emmc.dts和imx6ull.dtsi。
打开两个文档:(4、参考资料)中的Devicetree SpecificationV0.2.pdf和Power_ePAPR_APPROVED_v1.12.pdf
/dts-v1/;
首先在imx6ull-alientek-emmc.dts开头可以看到:
/dts-v1/;
(解释在power_ePAPR_APPROVED_v1.12.pdf的96页:)
The /dts-v1/; shall be present to identify the file as a version 1 DTS (dts files without this tag will be treated by dtc as being in the obsolete version 0, which uses a different format for integers in addition to other small but incompatible changes).
/dts-v1/; 必须写上,将文件标识为版本1 DTS(没有此标签的DTS文件将被dtc视为过时的版本0,而旧格式在整数的表示方式等方面与版本1不兼容)
节点命名规则
(Power_ePAPR_APPROVED_v1.12.pdf 15页)
《指南》43.3.2部分
节点名完整写法为:node-name@unit-address。其中node-name为设备名,unit-address一般是外设寄存器的起始地址,但也不一定,比如下面的例子。需要具体分析。
// imx6ull.dtsi 1096行
i2c4: i2c@021f8000 { // i2c为name,021f8000为i2c4寄存器起始绝对地址…………
};// imx6ull-alientek-emmc.dts 245行
&i2c1 {mag3110@0e { // 这里mag3110并不是IMX6ULL的外设,而是一个IIC外设,// 因此0e也不是外设寄存器起始地址,而是一个IIC地址…………};
};// imx6ull.dtsi 94行
intc: interrupt-controller@00a01000 { //↑这里有个冒号,冒号前面是一个标签label,后面才是节点名// 这样可以通过 &label 来访问该节点,如&intc就可以访问interrupt-controller@00a01000节点// 比如上面的&i2c1,就可以在imx6ull.dtsi 939行找到“i2c1: i2c@021a0000”// &定义的节点内容如果没有则追加,如果已存在则替换,比如&i2c1里的内容就会替换原本i2c1: i2c@021a0000的内容…………
};
层级结构
Power_ePAPR_APPROVED_v1.12.pdf 14页
“/”表示设备树文件的根节点,每个设备树文件只有一个根节点。
imx6ull.dtsi和imx6ull-alientek-emmc.dts这两个文件都有一个“/”根节点,这两个“/”根节点的内容会合并成一个根节点。
2.3 设备树在系统中的体现
第6.4讲 Linux设备树详解-设备树在根文件系统中的体现以及添加自定义节点_哔哩哔哩_bilibili
系统启动后可以在根文件系统里看到设备树的节点信息。
内核启动时会解析设备树文件,并在/proc/device-tree目录下生成相应的设备树节点文件,存放设备树信息。cd到该路径下使用ls命令,可以看到很到诸如chosen、memory、reserved-memory、backlight等文件。
这与imx6ull-alientek-emmc.dts中的结构一致:
cat查看model的内容:
这与imx6ull-alientek-emmc.dts中model的属性一致:
cd到soc/aips-bus@02100000/i2c@021a0000下,可以看到这些文件:
但是imx6ull.dtsi的939行下只能看到:
clock-frequency、fxls8471@1e、mag3110@0e、name、printrl-0、printrl-names是从哪来的?用&追加的。在imx6ull-alientek-emmc.dts的245行可以找到:
其中还修改了status,使用cat status可以看到status被覆盖为了okay。
2.4 其他特殊节点
第6.5讲 Linux设备树详解-设备树特殊节点_哔哩哔哩_bilibili
《指南》43.6部分
2.4.1 aliases
在imx6ull.dtsi的开头可以看到:
/ {aliases {can0 = &flexcan1;can1 = &flexcan2;…………};
};
同时,/soc/下定义了flexcan1:
/ {soc {flexcan1: can@02090000{……}……};……
};
那么,can0 = &flexcan1; 等价于 can0 = &{/soc/can@02090000}; 或 can0 = "/soc/can@02090000";
aliases节点的主要功能就是定义别名,以方便访问节点。不过一般会在节点命名的时候会加上label,然后通过&label来访问节点。
2.4.2 chosen
主要目的是将uboot里的bootargs环境变量的值传给linux内核作为命令行参数cmd line
在串口cd /proc/device-tree/chosen,可以看到bootargs、name、stdout-path三个。
但是imx6ull-alientek-emmc.dts中的chosen节点只定义了stdout-path一个:
chosen {stdout-path = &uart1;};
所以bootargs是从来的?(name先不管)当执行bootcmd中的bootz 80800000 - 83000000这句时,bootz层层调用,最终fdt_chosen函数读取bootargs的值并传给/chosen节点,供内核使用:
在alientek_uboot/common/fdt_common下可以找到fdt_chosen:
int fdt_chosen(void *fdt){int nodeoffset;int err;char *str; /* used to set string properties */err = fdt_check_header(fdt);if (err < 0) {printf("fdt_chosen: %s\n", fdt_strerror(err));return err;}/* find or create "/chosen" node. 如果存在子节点/chosen,则返回其偏移nodeoffset,否则创建并返回nodeoffset*/nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen"); // fdt为指向设备树文件的指针,0表示根节点, chosen为要查找的子节点名称if (nodeoffset < 0) // 失败return nodeoffset;str = getenv("bootargs"); // 从uboot的环境变量中读取bootargs的值if (str) { // 如果bootargs不为空err = fdt_setprop(fdt, nodeoffset, "bootargs", str, // 通过fdt_setprop向/chosen节点添加或修改bootargs属性strlen(str) + 1);if (err < 0) { // 失败printf("WARNING: could not set bootargs %s.\n",fdt_strerror(err));return err;}}return fdt_fixup_stdout(fdt, nodeoffset);
}
2.4 修改节点
2.4.1添加自定义节点
为方便查看,直接在imx6ull-alientek-emmc.dts的根节点下添加自定义节点:
// 自定义节点。添加到imx6ull-alientek-emmc.dts的148行mytestnode: mytest@0101 {};
# VSCODE终端
cd .../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek # 到内核根目录下
make dtbs # 编译
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb .../tftpboot/ -f # 复制到自己存放zImage和dtb文件的目录下,如果忘了哪个路径就用vi /etc/default/tftpd-hpa命令看看# 串口
reboot # 重启开发板ls /proc/device-tree/ # 此时应当能看到mytest@0101
(打错字了↓)
2.4.2 使用&进行追加
在imx6ull-alientek-emmc.dts文件最末尾添加:
&intc{mytestnode{};
};
// 追加给imx6ull.dtsi 94行的intc
# VSCODE终端
cd .../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek # 到内核根目录下
make dtbs # 编译
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dtb .../tftpboot/ -f # 复制到自己存放zImage和dtb文件的目录下,如果忘了哪个路径就用vi /etc/default/tftpd-hpa命令看看# 串口
reboot # 重启开发板cd /proc/device-tree/interrupt-controller@00a01000/
ls # 此时能看到mytestnode