小白带你探索Linux设备树1_框架篇V1

0. 说在前面的话

学习过程中的总结记录,感悟比较浅显,经不起推敲,欢迎批评指正!!!

文章主体是以imx8qm-mek评估板为实例进行介绍设备树。

1. 什么是设备树

1.1 设备树的定义

官方定义:

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设备树出现之前的电路板硬件的细节被硬编码到内核中了,导致内核代码臃肿难以维护,因此将内核与那些臃肿的硬件代码解耦,方便维护。

1.2 设备树的使用

首先看下设备树如何被被加载到内核当中的过程
在这里插入图片描述
首先将上图涉及到的dts、dtb、dtc简单说明下。

  • dts:device tree source 设备树源文件,也就是我们进行编辑的文件,用于增删相关的硬件细节,可以看做C之类的代码,并且支持部分C语法,后面会详谈;
  • dtb:device tree blob 可以视为二进制版本的dts,内核可以解析;
  • dtc:device tree compiler 设备树编译器,dtb文件就是通过dtc将dts文件编译而成的。个人感觉可以类比C语言的编译。

上图可以总结为:利用dtc将dts文件编译为可被内核解析的dtb文件,通过bootloader的引导,kernel会将dtb文件加载到内存,并进行解析。
在没引进设备树之前,当每次更改设备,都需要重新编译内核,而现在仅仅需要编译设备树即可,省时省力。

2. ARM Linux设备树的起源

2.1 忍无可忍

设备树出现的原因,还得从2011年3月17日Linus Torvalds的那封信说起,下面是信件的截图:
Linus 那封对于ARM垃圾代码忍无可忍的信件
因ARM带来大量的板级细节代码,导致内核中充斥着大量的垃圾代码,最后Linus忍无可忍了,于是乎这就成了ARM Linux设备树诞生的契机。

2.2 事件背景

每次正式的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(一):背景介绍,我就不在这里班门弄斧了。

3. DT的文件组织结构

3.1 dts目录简介

dts文件一般位于./arch/xxx/boot/dts目录中,首先映入眼帘的是各大SOC厂商的目录
在这里插入图片描述
我们这里选择freescale也就是现在的NXP作为分析对象
在这里插入图片描述

3.2 dts分析文件下载

如果大家没有内核源代码的话,可以使用如下命令将我们的作案工具单独下载下来,不用clone整个内核源代码,耗时又费力!!!

git clone https://github.com/sazczmh/imx8qm_linux_dts.git

如果clone不了的话,也可以从我的百度云盘下载
链接: https://pan.baidu.com/s/15lg0u54RMNjaMkge5e3E4g 密码: nsnv

clone后的代码如下
在这里插入图片描述

3.3 完整dts文件的组成

从上图可以看到给大家的仓库中,不仅有.dts后缀的文件还有.dtsi后缀的文件,还记得我们之前提到过的dts支持部分C语法吗?这里的.dtsi类似C语言的头文件。

为了模块化dts文件,驱动工程师将电路板公用的部分提炼为各个.dtsi文件,减少代码的重复,并且易于移植。下图就是头文件包含示例:
在这里插入图片描述
从图中也可以明显的注意到,该文件还包含了.h文件,是不是有点C的赶脚。

下面我们简单分析下仓库里那么多dts、dtsi的组织,在分析之前需要明白以下几个点:

  1. 仓库里设备树文件不是一块评估板的设备树,是同一芯片imx8qm的多个评估板的设备树;
  2. 同一块评估板因为用途不同,有多种设备树进行个性化配置;
  3. dtsi文件是提炼出来的公用的模块文件,为的是减少代码重复,易于移植。

明白了以上几个点之后,让我们着手分析吧

首先抛弃dtsi文件仅仅分析dts文件
在这里插入图片描述
从图中可以明显的看到imx8qm的评估板种类有三种(我猜的…,大概是的),咱们的重点放在mek-Multisensory Enablement Kit系列,关于这几个文件的解释可以查看官网的release note,相关截图如下。
在这里插入图片描述
从上图可以明显的看出来,mek那么多设备树分别有不同的特性。下面我们就仅用fsl-imx8qm-mek.dts来进行示例分析。

如下是fsl-imx8qm-mek.dts的层次结构
在这里插入图片描述
从上图也可以看出一个完整的设备树文件是由1个dts文件+n个dtsi文件组成的。

4. DT的语法组织框架

4.1 DT的基本语法

/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语法组织框架吧。

  • 唯一的根节点: “/” ,多个根节点会合并;
  • 根节点会包含一系列子节点,抽象框架里面有两个子节点,它们分别是"node1" 和 “node2”;
  • 子节点"node1“又包含一系列子节点,这个抽象框架里有两个子节点,它们分别是: “child-node1” and “child-node2”;
  • 各个节点又包含一系列属性。

属性是简单的关键词和其对应的值所组成,与关键词所对应的值可以为空或者任意的数据属性,下面简单说说设备树源文件几个常见的属性例子:

  • 文本字符串数据属性以双引号的形式展示出来:
    • a-string-property = "A string";
  • ‘Cells’ 属性是32 bit 无符号整数,并且由尖括号来定位:
    • 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";

说过基本的语法,接下来让我们开始实战。

4.2 DT的简化框架

当了解基本的DT语法之后,首先展示一副简化的DT框架,让大家对DT有个简单的认识
在这里插入图片描述

https://www.nxp.com/docs/en/application-note/AN5125.pdf

4.3 根节点的model与compatible

在这里插入图片描述

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

    • fsl,imx8qm-mek为板子级别的名字,代表imx8qm的MEK评估板;
    • fsl,imx8qm为芯片级别的名字,代表imx8qm这款SOC。

这里面要提一点根节点仅有一个,但是观察dtsi文件发现,存在多个根节点,dtc会将多个根节点进行合并,类似下图。
在这里插入图片描述

https://bootlin.com/pub/conferences/2014/elc/petazzoni-device-tree-dummies/

4.4 节点名字

下面集中列举几个节点名字,先有个感性的认识。

  • sata: sata@5f020000
  • memory@80000000
  • reserved-memory

节点名字具有如下格式

[label: ]<name>[@<unit-address>]
  • name是由简单的ascii字符串,最长31字符。通常情况下name的命名是说明这个设备节点的种类,例如sata、memory。
  • unit-address指的是该设备节点的地址,会在reg属性中具体指定,节点名字中可以不包含,但通常情况下都加上个unit-address。
  • label: 相当于该设备节点的指针,方便其他设备节点引用,或者形成层次性的结构。
    • 方便其他设备引用
      对于gic: interrupt-controller@51a00000来说, 设备节点通过&gic来引用该设备节点
      在这里插入图片描述
    • 形成层次性的结构
      对于sata: sata@5f020000,该设备节点在fsl-imx8qm-device.dtsi中进行定义了
      在这里插入图片描述
      然后又在fsl-imx8qm-mek.dtsi中进行进一步配置
      在这里插入图片描述

4.5 reg属性

先来看个reg属性实例
在这里插入图片描述
首先reg的第一个地址,对应着unit-address,reg格式的定义由父节点的如下属性所控制。

  • #address-cells
  • #size-cells

reg属性的语法如下

  • reg = <address1 length1 [address2 length2] [address3 length3] ... >

sata父节点的reg属性的限制如下
在这里插入图片描述
另外再提一下,reg是根据该soc的RM中描述,下面举个例子
在这里插入图片描述
最后为了驱动中方便查询,通常给利用reg-name属性给reg起个别名,sata的reg-name如下

  • reg-names = "ctl", "phy";

4.6 interrupts属性

sata的中断属性如下

  • interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>;

该属性由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

4.7 其他属性

clocks、power-domain、status…等属性都是如法炮制,我就不在这里多解释了。

4.8 特殊节点

  • aliases节点:某些节点的快捷访问方式?官方是这样解释的,但我是没看出来…。
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 节点:该节点不代表一个真实设备,通常用于固件和操作系统传递参数的桥梁,就像boot参数。而且其parent node必须是名字是“/”的根节点。
chosen {
		bootargs = "console=ttyLP0,115200 earlycon=lpuart32,0x5a060000,115200";
		stdout-path = &lpuart0;
	};

5. 总结

本文以imx8qm-mek评估板为实例进行介绍DT,让大家对DT的定义、ARM Linux DT的起源、具体评估板DT的文件组织结构、DT的语法组织框架有个简单的认识。本篇博客并没有事无巨细的介绍DT,仅仅是点到为止,而且小编也是刚刚接触设备树,没有太多的经验,如发现错误,还望指出来,大家一起交流共同进步。

过段时间会推出本篇博客的续作小白带你探索Linux设备树2_API篇V1,进行总结一个driver如何使用设备树。

参考文献

主要是国外官方的权威资料

  1. https://elinux.org/Device_Tree_Reference
  2. https://elinux.org/Device_Tree_Usage
  3. https://bootlin.com/pub/conferences/2014/elc/petazzoni-device-tree-dummies/
  4. https://www.nxp.com/docs/en/application-note/AN5125.pdf
  5. http://www.wowotech.net/device_model/dt_basic_concept.html


原创不易,切勿剽窃!

在这里插入图片描述

欢迎大家关注我创建的微信公众号——小白仓库
原创经验资料分享:包含但不仅限于FPGA、ARM、RISC-V、Linux、LabVIEW等软硬件开发,另外分享生活中的趣事以及感悟。目的是建立一个平台记录学习过的知识,并分享出来自认为有用的与感兴趣的道友相互交流进步。

猜你喜欢

转载自blog.csdn.net/qq_35712169/article/details/104208420
v1