如何从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

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

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

并重新编译:

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

设备树源 (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 {
        };
    };
};
  • /表示树的根节点
  • node1并且node2是根节点的子节点
  • node1有两个子节点:child-node1child-node2

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

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

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>;
    };
};

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

interrupt-controller@2c010000 {

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

compatible = "arm,gic-400";

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

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

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
64-bit address 0x0000_0000_2C02_F000 with 64-bit length 0x0000_0000_0000_2000
64-bit address 0x0000_0000_2C04_F000 with 64-bit length 0x0000_0000_0000_2000
64-bit address 0x0000_0000_2C06_F000 with 64-bit length 0x0000_0000_0000_2000
interrupt-controller;

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

interrupts = <0x1 0x9 0x3f04>;

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>;
    };

节点中的所有地址都指定为该节点本地的总线地址。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

假设您不知道要查找的设备树的绝对路径。/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
👇点击下方公众号卡片获取资料👇
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值