学习过程中的总结记录,感悟比较浅显,经不起推敲,欢迎批评指正!!!
文章主体是以imx8qm-mek评估板为实例进行介绍设备树。
官方定义:
The primary purpose of Device Tree in Linux is to provide a way to
describe non-discoverable hardware. This information was previously
hard coded in source code.
ARM设备树出现之前的电路板硬件的细节被硬编码到内核中了,导致内核代码臃肿难以维护,因此将内核与那些臃肿的硬件代码解耦,方便维护。
首先看下设备树如何被被加载到内核当中的过程
首先将上图涉及到的dts、dtb、dtc简单说明下。
上图可以总结为:利用dtc将dts文件编译为可被内核解析的dtb文件,通过bootloader的引导,kernel会将dtb文件加载到内存,并进行解析。
在没引进设备树之前,当每次更改设备,都需要重新编译内核,而现在仅仅需要编译设备树即可,省时省力。
设备树出现的原因,还得从2011年3月17日Linus Torvalds的那封信说起,下面是信件的截图:
因ARM带来大量的板级细节代码,导致内核中充斥着大量的垃圾代码,最后Linus忍无可忍了,于是乎这就成了ARM Linux设备树诞生的契机。
每次正式的linux kernel release之后都会有两周的merge
window,在这个窗口期间,kernel各个部分的维护者都会提交各自的patch,将自己测试稳定的代码请求并入kernel main
line。每到这个时候,Linus就会比较繁忙,他需要从各个内核维护者的分支上取得最新代码并merge到自己的kernel source
tree中。Tony Lindgren,内核OMAP development
tree的维护者,发送了一个邮件给Linus,请求提交OMAP平台代码修改,并给出了一些细节描述:
- 1、简单介绍本次改动
- 2、关于如何解决merge conficts。有些git mergetool就可以处理,不能处理的,给出了详细介绍和解决方案
一切都很平常,也给出了足够的信息,然而,正是这个pull request引发了一场针对ARM
linux的内核代码的争论。我相信Linus一定是对ARM相关的代码早就不爽了,ARM的merge工作量较大倒在其次,主要是他认为ARM很多的代码都是垃圾,代码里面有若干愚蠢的table,而多个人在维护这个table,从而导致了冲突。因此,在处理完OMAP的pull
request之后(Linus并非针对OMAP平台,只是Tony Lindgren撞在枪口上了),他发出了怒吼:
对事件背景感兴趣的详见wowo写的Device Tree(一):背景介绍,我就不在这里班门弄斧了。
dts文件一般位于./arch/xxx/boot/dts目录中,首先映入眼帘的是各大SOC厂商的目录
我们这里选择freescale也就是现在的NXP作为分析对象
如果大家没有内核源代码的话,可以使用如下命令将我们的作案工具单独下载下来,不用clone整个内核源代码,耗时又费力!!!
git clone https://github.com/sazczmh/imx8qm_linux_dts.git
如果clone不了的话,也可以从我的百度云盘下载
链接: https://pan.baidu.com/s/15lg0u54RMNjaMkge5e3E4g 密码: nsnv
从上图可以看到给大家的仓库中,不仅有.dts后缀的文件还有.dtsi后缀的文件,还记得我们之前提到过的dts支持部分C语法吗?这里的.dtsi类似C语言的头文件。
为了模块化dts文件,驱动工程师将电路板公用的部分提炼为各个.dtsi文件,减少代码的重复,并且易于移植。下图就是头文件包含示例:
从图中也可以明显的注意到,该文件还包含了.h文件,是不是有点C的赶脚。
下面我们简单分析下仓库里那么多dts、dtsi的组织,在分析之前需要明白以下几个点:
明白了以上几个点之后,让我们着手分析吧
首先抛弃dtsi文件仅仅分析dts文件
从图中可以明显的看到imx8qm的评估板种类有三种(我猜的…,大概是的),咱们的重点放在mek-Multisensory Enablement Kit系列,关于这几个文件的解释可以查看官网的release note,相关截图如下。
从上图可以明显的看出来,mek那么多设备树分别有不同的特性。下面我们就仅用fsl-imx8qm-mek.dts来进行示例分析。
如下是fsl-imx8qm-mek.dts的层次结构
从上图也可以看出一个完整的设备树文件是由1个dts文件+n个dtsi文件组成的。
/dts-v1/;
/ {
node1 {
a-string-property = "A string";
a-string-list-property = "first string", "second string";
// hex is implied in byte arrays. no '0x' prefix is required
a-byte-data-property = [01 23 34 56];
child-node1 {
first-child-property;
second-child-property = <1>;
a-string-property = "Hello, world";
};
child-node2 {
};
};
node2 {
an-empty-property;
a-cell-property = <1 2 3 4>; /* each number (cell) is a uint32 */
child-node1 {
};
};
};
该抽象框架引自https://elinux.org/Device_Tree_Usage
说实话,小白我第一次遇到这个语法图的时候,真的是一脸懵逼,这个语法图太抽象了,和真实的设备树文件匹配不上,真实的设备树文件如下:
如果这时候大家还没有厌烦我的唠叨的话,就且听我慢慢道来dts语法组织框架吧。
属性是简单的关键词和其对应的值所组成,与关键词所对应的值可以为空或者任意的数据属性,下面简单说说设备树源文件几个常见的属性例子:
a-string-property = "A string";
cell-property = <0xbeef 123 0xabcd1234>;
binary-property = [0x01 0x23 0x45 0x67];
mixed-property = "a string", [0x01 0x23 0x45 0x67], <0x12345678>;
string-list = "red fish", "blue fish";
说过基本的语法,接下来让我们开始实战。
当了解基本的DT语法之后,首先展示一副简化的DT框架,让大家对DT有个简单的认识
https://www.nxp.com/docs/en/application-note/AN5125.pdf
model属性指明了该评估板的设备生产商(感觉主要指的芯片生产商)、该评估板(也可指芯片系列)的名字,一般而言,该属性的数值为“manufacturer,model”,本示例当中
model = "Freescale i.MX8QM MEK";
指明了该评估板的生产商为Freescale,评估板的名字为i.MX8QM MEK。
compatible属性是上文所提到的字符串列表属性,这些属性用于匹配machine type的,可以据此判断启动的设备。
string-list = "red fish", "blue fish";
根节点的compatible属性一般包括两个以上的兼容性字符串,本例中为
compatible = "fsl,imx8qm-mek", "fsl,imx8qm";
这里面要提一点根节点仅有一个,但是观察dtsi文件发现,存在多个根节点,dtc会将多个根节点进行合并,类似下图。
https://bootlin.com/pub/conferences/2014/elc/petazzoni-device-tree-dummies/
下面集中列举几个节点名字,先有个感性的认识。
sata: sata@5f020000
memory@80000000
reserved-memory
节点名字具有如下格式
[label: ]<name>[@<unit-address>]
先来看个reg属性实例
首先reg的第一个地址,对应着unit-address,reg格式的定义由父节点的如下属性所控制。
#address-cells
#size-cells
reg属性的语法如下
reg =
sata父节点的reg属性的限制如下
另外再提一下,reg是根据该soc的RM中描述,下面举个例子
最后为了驱动中方便查询,通常给利用reg-name属性给reg起个别名,sata的reg-name如下
reg-names = "ctl", "phy";
sata的中断属性如下
interrupts = ;
该属性由interrupt-parent属性所限定,如果该节点没有指定interrupt-parent,那么将有父节点的interrupt-parent所限定,sata的父节点的相应属性为。
interrupt-parent = <&gic>;
gic节点的定义为
gic: interrupt-controller@51a00000 {
compatible = "arm,gic-v3";
reg = <0x0 0x51a00000 0 0x10000>, /* GIC Dist */
<0x0 0x51b00000 0 0xC0000>, /* GICR */
<0x0 0x52000000 0 0x2000>, /* GICC */
<0x0 0x52010000 0 0x1000>, /* GICH */
<0x0 0x52020000 0 0x20000>; /* GICV */
#interrupt-cells = <3>;
interrupt-controller;
interrupts = <GIC_PPI 9
(GIC_CPU_MASK_SIMPLE(6) | IRQ_TYPE_LEVEL_HIGH)>;
interrupt-parent = <&gic>;
};
注意如下几个宏定义均在相应.h文件中进行声明。
GIC_SPI
IRQ_TYPE_LEVEL_HIGH
clocks、power-domain、status…等属性都是如法炮制,我就不在这里多解释了。
aliases {
csi0 = &mipi_csi_0;
csi1 = &mipi_csi_1;
dpu0 = &dpu1;
dpu1 = &dpu2;
ethernet0 = &fec1;
ethernet1 = &fec2;
dsi_phy0 = &mipi_dsi_phy1;
dsi_phy1 = &mipi_dsi_phy2;
mipi_dsi0 = &mipi_dsi1;
mipi_dsi1 = &mipi_dsi2;
ldb0 = &ldb1;
ldb1 = &ldb2;
isi0 = &isi_0;
isi1 = &isi_1;
isi2 = &isi_2;
isi3 = &isi_3;
isi4 = &isi_4;
isi5 = &isi_5;
isi6 = &isi_6;
isi7 = &isi_7;
serial0 = &lpuart0;
serial1 = &lpuart1;
serial2 = &lpuart2;
serial3 = &lpuart3;
serial4 = &lpuart4;
mmc0 = &usdhc1;
mmc1 = &usdhc2;
mmc2 = &usdhc3;
usbphy0 = &usbphy1;
can0 = &flexcan1;
can1 = &flexcan2;
can2 = &flexcan3;
i2c0 = &i2c_rpbus_0;
i2c1 = &i2c_rpbus_1;
};
chosen {
bootargs = "console=ttyLP0,115200 earlycon=lpuart32,0x5a060000,115200";
stdout-path = &lpuart0;
};
本文以imx8qm-mek评估板为实例进行介绍DT,让大家对DT的定义、ARM Linux DT的起源、具体评估板DT的文件组织结构、DT的语法组织框架有个简单的认识。本篇博客并没有事无巨细的介绍DT,仅仅是点到为止,而且小编也是刚刚接触设备树,没有太多的经验,如发现错误,还望指出来,大家一起交流共同进步。
过段时间会推出本篇博客的续作小白带你探索Linux设备树2_API篇V1,进行总结一个driver如何使用设备树。
主要是国外官方的权威资料
- https://elinux.org/Device_Tree_Reference
- https://elinux.org/Device_Tree_Usage
- https://bootlin.com/pub/conferences/2014/elc/petazzoni-device-tree-dummies/
- https://www.nxp.com/docs/en/application-note/AN5125.pdf
- http://www.wowotech.net/device_model/dt_basic_concept.html
原创不易,切勿剽窃!
欢迎大家关注我创建的微信公众号——小白仓库
原创经验资料分享:包含但不仅限于FPGA、ARM、RISC-V、Linux、LabVIEW等软硬件开发,另外分享生活中的趣事以及感悟。目的是建立一个平台记录学习过的知识,并分享出来自认为有用的与感兴趣的道友相互交流进步。