• 如何从Linux 的用户空间检查设备树信息?


    前言

    在 Linux 启动过程中,U-Boot / UEFI 将一个“设备树 Blob”(DTB)文件加载到内存中,并将指向它的指针传递给内核。此 DTB 文件描述了 Linux 内核的系统硬件布局,允许将特定于平台的代码移出内核源代码并替换为可以解析 DTB 并根据需要配置系统的通用代码。

    编译设备树

    设备树源 (DTS) 文件是简单的文本文件,可以使用设备树编译器 (DTC) 工具将其编译为二进制设备树 Blob (DTB) 格式。DTC 工具位于/scripts/dtc下的 Linux 内核源代码中,也可通过一些分发包管理器(例如 Ubuntu 上的 APT)进行安装:

    $ sudo apt-get update
    $ sudo apt-get install device-tree-compiler
    
    • 1
    • 2

    使用 DTC 反编译 DTB 是无损的,即可以将 DTB 反编译为 DTS,然后将该 DTS 编译回 DTB 而不会丢失任何信息。反编译:

    $ dtc -I dtb -O dts juno.dtb > juno.dts
    
    • 1

    并重新编译:

    $ dtc -I dts -O dtb juno.dts > juno.dtb
    
    • 1

    设备树源 (DTS) 语法

    注意:通用设备树语法的完整文档可以在DeviceTree.org找到,Linux 绑定文档可以在这里找到 Linux 内核源代码

    设备树源文件是由具有相关属性的节点和子节点组成的树结构。一般语法如下:

    / {
        node1 {
            a-string-property = "a string";
            a-string-list-property = "first string", "second string";
            a-byte-data-property = [0x01 0x02 0x03];
            child-node1 {
                first-child-property;
                second-child-property
                a-string-property = "child string";
            };
            child-node2 {
            };
        };
        node2 {
            an-empty-property;
            a-cell-property = <1 2 3>;
            child-node1 {
            };
        };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • /表示树的根节点
    • node1并且node2是根节点的子节点
    • node1有两个子节点:child-node1child-node2

    每个节点都有一组与之关联的属性:

    • 以空字符结尾的文本字符串用双引号括起来:
     a-string-property = "一个字符串";
    
    • 1
    • 单元格(32 位无符号整数)以空格分隔并用尖括号括起来:
    细胞属性 = <1 2 3>;
    
    • 1
    • 二进制数据以空格分隔并用方括号括起来:
    二进制属性 = [0x01 0x02 0x03]
    • 1
    • 可以使用逗号连接混合表示数据:
    a-mixed-property = "一个字符串", [0x01 0x02 0x03], <1 2 3>;
    
    • 1
    • 逗号也用于创建字符串列表:
    a-string-list = "第一个字符串", "第二个字符串";
    
    • 1

    DTS 示例

    这是 Juno 的 DTS 条目示例,特别是 GIC-400:

    interrupt-controller@2c010000 {
        compatible = "arm,gic-400";
        reg = <0x0 0x2c010000 0x0 0x1000 0x0 0x2c02f000 0x0 0x2000 0x0 0x2c04f000 0x0 0x2000 0x0 0x2c06f000 0x0 0x2000>;
        #address-cells = <0x2>;
        #interrupt-cells = <0x3>;
        #size-cells = <0x2>;
        interrupt-controller;
        interrupts = <0x1 0x9 0x3f04>;
        ranges = <0x0 0x0 0x0 0x2c1c0000 0x0 0x40000>;
        linux,phandle = <0x1>;
        phandle = <0x1>;
        v2m@0 {
            compatible = "arm,gic-v2m-frame";
            msi-controller;
            reg = <0x0 0x0 0x0 0x1000>;
        };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    让我们分解这个条目,以了解如何使用设备树节点来描述真实硬件:

    interrupt-controller@2c010000 {
    
    • 1

    中断控制器位于父节点“总线”上的地址 0x2C010000(稍后会详细介绍)。在这种情况下,父节点是根节点,因此从 CPU 的角度来看,这意味着地址 0x2C010000

    compatible = "arm,gic-400";
    
    • 1

    Linux 内核使用它来决定将哪个设备驱动程序绑定到外围设备。在这种情况下,中断控制器与 ARM GIC-400 驱动程序兼容。

    reg = <0x0 0x2c010000 0x0 0x1000 0x0 0x2c02f000 0x0 0x2000 0x0 0x2c04f000 0x0 0x2000 0x0 0x2c06f000 0x0 0x2000>;
    
    • 1

    reg属性定义了外围设备将响应的地址范围。该属性由许多元组组成,每个元组包含一个区域的基地址和该区域的大小。要解析属性,我们需要了解三件事:

    • 每个单元格值是一个 32 位无符号整数

    • 父节点的#address-cells值定义了多少个单元格值构成一个区域的基地址

    • 父节点的#size-cells值定义了多少个单元格值构成一个区域的长度

    中断控制器节点是根节点的子节点,在 Juno 上有#address-cells = #size-cells= 2。按顺序移动该reg属性并将前 2 个单元格组合为基地址,接下来的 2 个单元格为长度,接下来的 2 个单元格为基地址等给出中断控制器将响应的以下区域:

    64-bit address 0x0000_0000_2C01_0000 with 64-bit length 0x0000_0000_0000_1000
    
    • 1
    64-bit address 0x0000_0000_2C02_F000 with 64-bit length 0x0000_0000_0000_2000
    
    • 1
    64-bit address 0x0000_0000_2C04_F000 with 64-bit length 0x0000_0000_0000_2000
    
    • 1
    64-bit address 0x0000_0000_2C06_F000 with 64-bit length 0x0000_0000_0000_2000
    
    • 1
    interrupt-controller;
    
    • 1

    所有中断控制器节点都必须包含一个空interrupt-controller属性。

    interrupts = <0x1 0x9 0x3f04>;
    
    • 1

    GIC 的中断设备树绑定格式可以 在 Linux 内核文档中找到 :

    • 第一个单元格表示中断类型(0 表示 SPI,1 表示 PPI)
    • 第二个单元格包含许多标志,编码如下:位 [3:0] 定义触发类型和电平标志,其中 4 对应于 Active High Level-Sensitive位 [15:8] 定义 CPU 中断掩码,其中每个位表示中断连接到特定 CPU(位 8 = CPU0,位 9 = CPU1,…,位 15 = CPU7)

    所以这里 GIC 本身将 PPI #9 作为针对 CPU [0…5] 的活动高电平敏感中断,即 Juno 上的所有 CPU,总共有 6 个 CPU。此 PPI(中断 ID 25)对应于 Juno 上的 VGIC 维护中断,是支持虚拟化扩展的 CPU 所必需的。

    ranges = <0x0 0x0 0x0 0x2c1c0000 0x0 0x40000>;
        v2m@0 {
            compatible = "arm,gic-v2m-frame";
            msi-controller;
            reg = <0x0 0x0 0x0 0x1000>;
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    节点中的所有地址都指定为该节点本地的总线地址。range 属性提供了一种将这些总线地址映射到父节点地址空间的机制。

    Juno 的中断控制器节点是v2m地址 0x0 的子节点,位于中断控制器“总线”的本地。该节点用于 GICv2m 扩展,需要从中断控制器“总线”映射到 CPU 的内存角度。

    reg与属性类似,ranges使用#address-cellsand#size-cells来解析 (Local Address, Parent Address, Length) 的元组,其中:

    • 本地地址的宽度由本地节点的#address-cells值决定

    • 父地址的宽度由父节点的#address-cells值决定

    • Length 的宽度由本地节点的#size-cells值决定

    在 Juno 上,这些都是 2,因此范围属性被解释为“将 64 位本地地址 0x0 映射到 64 位长度为 0x40000 的 64 位父地址 0x2C1C0000”。因为v2m节点在本地地址0x0,所以会映射到父节点的地址0x2C1C0000,而父节点是根节点,这就是CPU对内存的看法。

    查找设备树

    您可能需要检查某些 dtb 的值,以进行调试,检查 dtb 在您修改后是否确实得到了更新等。虽然您无法获得已加载的设备树的类似 .dts 文件的视图,但您可以使用 /proc/device-tree 中的条目检查值。

    在 Linux 的命令提示符下,您应该会在 /proc/device-tree 目录中看到类似以下内容:

    root@j7-evm:~# ls /proc/device-tree/
    #address-cells  firmware                l3-cache0
    #size-cells     fixedregulator-evm12v0  main_r5fss_cpsw9g_virt_mac0
    __symbols__     fixedregulator-sd       memory@80000000
    aliases         fixedregulator-vsys3v3  model
    chosen          fixedregulator-vsys5v0  name
    compatible      gpio-keys               pmu
    connector       interconnect@100000     reserved-memory
    cpus            interrupt-parent        serial-number
    dma_buf_phys    ion                     sound@0
    dummy-panel     l2-cache0               timer-cl0-cpu0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    假设您不知道要查找的设备树的绝对路径。/proc/device-tree 中有一个 __symbol__ 目录。它对设备树中的每个符号标签都有。我们可以通过cat 来找到该符号的确切路径。以下是演示使用的示例:

    root@j7-evm:~# cat /proc/device-tree/__symbols__/main_i2c0
    /interconnect@100000/i2c@2000000
    
    root@j7-evm:~# ls /proc/device-tree/interconnect@100000/i2c@2000000/
    #address-cells   clock-names  gpio@20     name       pinctrl-names
    #size-cells      clocks       gpio@22     phandle    power-domains
    clock-frequency  compatible   interrupts  pinctrl-0  reg
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    👇点击下方公众号卡片获取资料👇
  • 相关阅读:
    System Synthesis
    【英语:基础进阶_语法进阶提升】F2.英文时态进阶
    django 的 filter 使用技巧
    在keil中debug分析单片机数据和函数调用过程(c51为例),使用寄存器组导致错误原因分析
    HTML+CSS+JS网页设计期末课程大作业 web前端开发技术 web课程设计 网页规划与设计
    MySQL事务和锁
    4年软件测试工作经验,跳槽之后面试20余家公司的总结
    Jenkins 使用 Description Setter
    HTML5期末作业:明星网站设计与实现——明星薛之谦介绍网页设计7个页面HTML+CSS+JavaScript
    3.3 具有调度功能的响应式设计
  • 原文地址:https://blog.csdn.net/qq_45172832/article/details/126910663