Linux设备树(1)

warren
2024-08-04 / 0 评论 / 4 阅读 / 正在检测是否收录...

什么是设备树?

个人理解 设备树就是一个采用数形结构描述板级设备信息的文件。
比如在开发板上搞好了一个工程。但是产品的硬件与开发板有引脚差异,则只需要修改设备树文件即可。其他地方就可以不用动了。

DTS、DTB、DTC关系

  • DTS:设备树源码文件
  • DTB:DTS编译后的二进制文件
  • DTC:将DTC编译为DTB的工具
    设备数文件存放在Linux源代码/arch/arm/boot/dts文件夹
    编译dts文件,需要进入Linux源码根目录下,执行命令

    //全部编译(包括zImage、.ko驱动模块、设备树)
    make all
    //只编译设备树
    make dtbs

    lzfb1uxf.png

    DTS语法

    头文件

    和C语言一样,设备树也支持头文件。
    头文件一般分为三种,.h文件、.dtsi文件、.dts文件。它们都通过 #include 来引用。
    一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范
    围,比如 UART、 IIC 等等

    设备节点

    板子上的每一个设备都是一个节点,叫做设备节点
    每一个节点都通过一些属性信息来描述节点信息
    属性信息就是键-值
    例如
    lzfczm02.png
    第一行的 / 指的是根节点。一个设备数文件只允许有一个根节点,两个设备树文件都有根节点且一个包含一个,则根节点会合并。
    第2行、第6行、第17行,是节点的3个子节点,在设备树中节点命令格式如下

    //node_name:节点名字
    //unit-address:设备的地址或寄存器首地址(如果某个节点没有地址或者寄存器的话“unit-address”可以不要)
    node_name@unit-address

    例如第6行的cpus和第17行的interrupt-controller@00a01000
    在第10行有cpu0: cpu@0的描述,是因为引入了 节点标签 。目的为了方便访问节点。可以直接通过&label 来访问这个节点,而不是node-name@unit-address。格式如下

    label: node-name@unit-address

    节点搞完了,然后就是节点里面的属性了。每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对。
    值可以为空或任意的字节流,常用的数据形式如下

    //1.字符串
    device_type = "cpu";
    
    //2.32位无符号整数 或 32位无符号整数组
    reg = <0>
    reg = <0x00a01000 0x1000>
    
    //3.字符串列表
    compatible = "arm,cortex-a7";

    标准属性

    1.compatible属性
    compatible 属性也叫做“兼容性”属性。compatible 属性的值是一个字符串列表,用于将设备和驱动绑定起来。compatible 属性的值格式如下所示:

    //manufacturer:厂商
    //model:驱动名字
    "manufacturer,model"
    eg:
    compatible = "fsl,imx6ul-evk-wm8960

    它的作用就是让驱动来匹配,是否能使用该节点中的参数。
    比如compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";就代表这个节点中的参数适用与飞思卡尔的imx6ul-evk-wm8960驱动和飞思卡尔的imx-audio-wm8960驱动。
    那么有个问题,驱动怎么知道自己应该使用那个参数?
    答案是驱动代码在编写时会有一个OF匹配表,如果设备节点的 compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动。OF匹配表如下所示:

    static const struct of_device_id imx_wm8960_dt_ids[] = {
    { .compatible = "fsl,imx-audio-wm8960", },
    { /* sentinel */ }
    };

    2.model属性
    model 属性值也是一个字符串,一般 model 属性描述设备模块信息,用于指定设备的制造商和型号,推荐使用“制造商, 型号”的格式。从软件的层面讲model属性仅仅表示一个名字而已,没有更多的作用。

    eg: model = "Alientek i.MX6ULL Board";

    3.status属性
    status 属性是设备的状态信息,其属性值也是字符串,可选的状态如下('okay'(运行中)、'disabled'(待启用)、'fail'(严重故障)和'fail-sss'(特定错误))
    lzjsbzah.png

    4.reg属性
    reg 属性的值一般是(address, length)对,用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。结构如下:

    reg = <address1 length1 address2 length2 address3 length3……>
    eg: reg = <0x60000000, 0x100, 0x00002000, 0x80>

    一般来说,地址长度可能不准确,我们一般使用的都是起始地址信息。

    5.#address-cells 和#size-cells 属性
    这两个属性可以用在任何 拥有子节点的设备中 ,用于描述 子节点的地址信息
    属性#address-cells告诉你我们的地址是32为还是64位。 #address-cells = <1>;就是说你的地址是32位,#address-cells = <2>;就是说你的地址是64位。属性#size-cells属性:表示地址大小范围。和#address-cells一样。#size-cells值一般为1,还没见过为2的情况。

    例如:
    对于32位的地址#address-cells = <1>:
    reg =  <0x02000000 0x10000>这种情况我们很常见了就是 0x02000000~0x02000000+0x10000地址空间
    对于64位的地址#address-cells = <2>:
    reg =  <0x10000000 0x02000000 0x10000>这种情况我们64位的地址就是高32位是0x10000000,低32位是0x02000000的64位地址
    所以他代表的地址空间就是:0x1000000002000000 + 0x10000的地址空间

    6.ranges 属性
    ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵。
    child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占位宽。
    parent-bus-address: 父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占位宽。
    length: 子地址空间的长度,由父节点的#size-cells 确定此地址长度所占位宽。
    例如:

    #address-cells = <1>;
    #size-cells = <1>;
    ranges = <0x0 0xe0000000 0x00100000>;
    reg = <0x4600 0x100>;

    ranges属性值指定了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0,父地址空间的物理起始地址为 0xe0000000。
    reg 属性定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100。经过地址转换, serial 设备可以从 0xe0004600 开始进行读写操作,0xe0004600=0x4600+0xe0000000。

    7.name属性
    name 属性值为字符串, name 属性用于记录节点名字, name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。(个人认为和label一样)。

    8.device_type属性
    device_type 属性值为字符串,用于描述设备的 FCode,但是设备树没有 FCode,所以此属性也被抛弃了。
    此属性只能用于 cpu 节点或者 memory 节点。在cpu节点中有device_type = "cpu";

    根节点compatible属性

    根节点的compatible属性一般有两个值,
    第一个值描述了所使用的硬件设备名字(eg:imx6ull-14x14-evk)
    第二个值描述了设备所使用的 SOC(eg:imx6ull)
    它的作用就让Linux内核查看是否支持此设备,如果支持的话设备就会启动Linux内核。
    匹配方式分为两种(老版本 和 新版本)
    1.老版本
    uboot 会给 Linux 内核传递 machine id 这个参数,Linux 内核会检查这个 machine
    id,其实就是将 machine id 与 MACH_TYPE_XXX 宏进行对比,看看有没有相等的,如果相等的话就表示 Linux 内核支持这个设备,如果不支持的话那么这个设备就没法启动 Linux 内核。
    个人理解: uboot传输了一个特定的字符串给Linux,Linux通过宏将该字符串转换为Linux内部特定的宏,再将该宏与.h中的支持列表一一对比。
    2.新版本
    machine_desc 结构体中有个.dt_compat 成员变量,此成员变量保存着本设备兼容属性。只要某个设备(板子)根节点“ /”的 compatible 属性值与xxx_dt_compat 表中的任何一个值相等,那么就表示 Linux 内核支持此设备
    个人理解:uboot将设备树根节点的compatible 属性值传输给Linux。同时Linux内有个兼容表,将传输过来的属性值与兼容表进行一一对比。

    向节点追加内容 或 修改节点内容

    其实向节点追加或修改节点内容的操作是一样的。
    必须我们要向设备中增加一个IIC接口的MPU6050外设,我们找到IIC节点在imx6ull.dtsi文件中,但是我们不能在imx6ull.dtsi文件中修改。因为imx6ull.dtsi是一个头文件,这个头文件被很多个板子设备树文件所调用。如果在它里面修改了。那么所有使用imx6ull这个片子的设备都会被加一个MPU6050外设。
    正确的做法就是:打开alientek这个板子专属的设备树文件(.dts),找到要添加设备的父节点的标号(比如i2c1)。在专属的设备树文件追加下列内容:

    &i2c1{
    /*要追加或修改的内容*/
    
    /*节点不存在 = 新增节点*/
    /*节点存在,内容不存在 = 新增内容*/
    /*节点存在,内容存在 = 修改内容*/
    };
0

评论 (0)

取消