Linux设备树详解(一) 基础知识

Linux设备树详解(一) 基础知识 Debug 2023-08-10 11:15:37 1048

    关于设备树,之前就经过详细的系统培训,但是本着会用就行的原则,对各个知识点都没有进行系统的总结。都是用到哪里学哪里,时间长了,基本也忘记了。所以对于后期知识各个知识点进行总结,本章主要讨论一下内容,能看懂和修改对应模块的dts文件。

  • dts的引入
  • dts的基本语法和基本格式介绍
  • dts的组成,各个模块使用方法

1. dts为何物

    在传统Linux内核中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxxarch/arm/mach-xxx,比如板上的platform设备resourcei2c_board_infospi_board_info以及各种硬件的platform_data,这些板级细节代码对内核来讲只不过是垃圾代码。而采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。导致ARM的merge工作量较大,其次ARM很多的代码都是垃圾,代码里面有若干愚蠢的table,而多个人在维护这个table,从而导致了冲突。最后linux发出

Gaah.Guys, this whole ARM thing is a f*cking pain in the ass.

    之后经过一些讨论,对ARM平台的相关code做出如下相关规范调整,这个也正是引入DTS的原因。

  1. 核心代码仍然保存在arch/arm目录下
  2. SoC core architecture code保存在arch/arm目录下
  3. SOC的周边外设模块的驱动保存在drivers目录下
  4. OC的特定代码在arch/arm/mach-xxx目录下
  5. SOC board specific的代码被移除,由DeviceTree机制来负责传递硬件拓扑和硬件资源信息。

    本质上,Device Tree改变了原来用code方式将HW配置信息嵌入到内核代码的方法,改用bootloader传递一个DB的形式。对于嵌入式系统,在系统启动阶段,bootloader会加载内核并将控制权转交给内核,此外,还需要把上述的三个参数信息传递给kernel,以便kernel可以有较大的灵活性。在linux kernel中,Device Tree的设计目标就是如此。

在devie tree中,可描述的信息包括:

  • U的数量和类别
  • 内存基地址和大小
  • 总线和桥
  • 外设连接
  • 中断控制器和中断的使用情况
  • GPIO控制器和GPIO使用情况
  • clock控制器和clock使用情况它基本就是一棵电路板上的CPU、总线、设备组成的树,Bootloader会将          这棵树传递给内核,然后内核来识别这棵树,并根据它展开出Linux内核中的platform_device、i2c_client、spi_device等设备,而这些设备用到的内存、IRQ等资源,也被传递给内核,内核会将这些资源绑定给展开的相应设备。

2. DTS基本知识

2.1 dts

    硬件的相应信息都会写在.dts为后缀的文件中,每一款硬件可以单独写一份xxxx.dts,一般在Linux源码中存在大量的dts文件,对于arm架构可以在arch/arm/boot/dts找到相应的dts,一个dts文件对应一个ARM的machie。

2.2 dtsi

    值得一提的是,对于一些相同的dts配置可以抽象到dtsi文件中,然后类似于C语言的方式可以include到dts文件中,对于同一个节点的设置情况,dts中的配置会覆盖dtsi中的配置。

2.3 dtc

    dtc是编译dts的工具,可以在Ubuntu系统上通过指令apt-get install device-tree-compiler安装dtc工具,不过在内核源码scripts/dtc路径下已经包含了dtc工具;

2.4 dtb

    dtb(Device Tree Blob),dts经过dtc编译之后会得到dtb文件,dtb通过Bootloader引导程序加载到内核。所以Bootloader需要支持设备树才行;Kernel也需要加入设备树的支持;

易百纳社区

3. DTS结构

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

    device tree的基本单元是node。这些node被组织成树状结构,除了root node,每个node都只有一个parent。一个device tree文件中只能有一个root node。每个node中包含了若干的property/value来描述该node的一些特性。每个node用节点名字(node name)标识,节点名字的格式是node-name@unit-address。如果该node没有reg属性(后面会描述这个property),那么该节点名字中必须不能包括@和unit-address。unit-address的具体格式是和设备挂在那个bus上相关。例如对于cpu,其unit-address就是从0开始编址,以此加一。而具体的设备,例如以太网控制器,其unit-address就是寄存器地址。root node的node name是确定的,必须是“/”。也就是说设备树源文件的结构为

  • root节点”/”;
  • root节点下面含一系列子节点,“node1” and “node2”
  • 节点node1和下又含有一系列子节点,“child-node1” and “child-node2”
  • 各个节点都有一系列属性

4. DTS语法介绍

    了解了基本的device tree的结构后,我们总要把这些结构体现在device tree source code上来。在linux kernel中,扩展名是dts的文件就是描述硬件信息的device tree source file,在dts文件中,一个node被定义成:

[label:] node-name[@unit-address] {
   [properties definitions]
   [child nodes]
} 

“[]”表示option,因此可以定义一个只有node name的空节点,label方便在dts文件中引用 基本数据类型:

text string(以null结束),以双引号括起来,如:

string-property = “a string”;

cells 是32位无符号整形数,以尖括号括起来,如:

cell-property = <0xbeef 123 0xabcd1234>;

binary data 以方括号括起来,如:

binary-property = [0x01 0x23 0x45 0x67];

不同类型数据可以在同一个属性中存在,以逗号分格,如:

mixed-property = “a string”, [0x01 0x23 0x45 0x67], <0x12345678>;

多个字符串组成的列表也使用逗号分格,如:

string-list = “red fish”,“blue fish”;

5. dts的组成

5.1 标准属性

5.1.1 compatible

    每一个dts文件都是由一个root的根节点组成,内核通过根节点“/”的兼容性即可判断它启动的是什么设备,其代码结构如下:

/ {
    model = "Spreadtrum SC9830A-5 V1.0.0 Smartphone Board";
    
    compatible = "sprd,sp9830a-5h10-ga1", "sprd,sc9830";
    
    chosen {
        bootargs = "earlycon=sprd_serial,0x70100000,115200n8 loglevel=8 console=ttyS1,115200n8 init=/init root=/dev/ram0 rw androidboot.hardware=sc9830";
        linux,initrd-start = <0x85500000>;
        linux,initrd-end = <0x855a3212>;
    };
};
  1. model属性值是,它指定制造商的设备型号。推荐的格式是:“manufacturer,model”,其中manufacturer是一个字符串描述制造商的名称,而型号指定型号。
  2. compatible属性值是,指定了系统的名称,是一个字符串列表,它包含了一个“<制造商>,<型号>”形式的字符串。重要的是要指定一个确切的设备,并且包括制造商的名字,以避免命名空间冲突。
  3. chosen 节点不代表一个真正的设备,但功能与在固件和操作系统间传递数据的地点一样,如根参数,取代以前bootloader的启动参数,控制台的输入输出参数等。

5.1.2 #address-cells和#size-cells

  • #address-cells = <1>: 基地址、片选号等绝对起始地址所占字长,单位uint32
  • #size-cells = <1>: 长度所占字长,单位uint32
soc {
    #address-cells = <1>;
    #size-cells = <1>;
    serial {
        compatible = "ns16550";
        reg = <0x4600 0x100>;
        clock-frequency = <0>;
        interrupts = <0xA 0x8>;
        interrupt-parent = <&ipic>;
    };
};

5.1.3 CPU addressing

    在讨论寻址时,CPU节点代表了最简单的情况。每个CPU都分配有一个唯一的ID,并且没有与CPU ID相关联的大小。

cpus {
    #address-cells = <1>;
    #size-cells = <0>;
    cpu@0 {
        compatible = "arm,cortex-a9";
        reg = <0>;
    };
    cpu@1 {
        compatible = "arm,cortex-a9";
        reg = <1>;
    };
};

    在cpus节点,#address-cells被设置成了1,#size-cells被设置成了0。这是说子reg值是单独的uint32,它用无大小字段表示地址。在此情况下,这两个cpu分配到的地址为0和1。Cpu节点的#size-cells是0因为每个cpu只分配到了一个单独的地址。

    你仍然需要注意reg值班需要与节点名的值相匹配。按照惯例,如果一个节点有一个reg属性,那么这个节点名称必须包括unit-address,这是reg属性的第一个address值。

5.1.4 Memory Mapped Devices

    与在cpu节点中单独的address值不同,内存映射设备被分配了一系列将要响应的地址,因此不仅需要包含内存的基地址而且还需要映射地址的长度,因此需要使用#size-cells用来表示在每个子reg元组中长度字段的大小。在以下示例中,每个address值为1 cell(32 bits),每个长度值也是1 cell,这在32 bit系统是比较典型的。64 bit设备也许会为#address-cells和#size-cells使用数值2,在device tree中获取64 bit addressing。

/dts-v1/;

/ {
    #address-cells = <1>;
    #size-cells = <1>;
    ...
    serial@101f0000 {
        compatible = "arm,pl011";
        reg = <0x101f0000 0x1000 >;
    };

    serial@101f2000 {
        compatible = "arm,pl011";
        reg = <0x101f2000 0x1000 >;
    };

    gpio@101f3000 {
        compatible = "arm,pl061";
        reg = <0x101f3000 0x1000
               0x101f4000 0x0010>;
    };

    interrupt-controller@10140000 {
        compatible = "arm,pl190";
        reg = <0x10140000 0x1000 >;
    };

    spi@10115000 {
        compatible = "arm,pl022";
        reg = <0x10115000 0x1000 >;
    };
    ...
};

5.1.5 Non Memory Mapped Devices

    处理器总线的其它设备为非内存映射设备。他们有地址范围,但不能被CPU直接寻址。母设备的驱动程序将代替CPU进行间接访问。以i2c设备为例,每个设备都分配了一个地址,但没有长度或范围与之相匹配。这与CPU地址分配很相似。

i2c@1,0 {
     compatible = "acme,a1234-i2c-bus";
     #address-cells = <1>;
     #size-cells = <0>;
     reg = <1 0 0x1000>;
     rtc@58 {
         compatible = "maxim,ds1338";
         reg = <58>;
     };
 };

5.1.6 Ranges (Address Translation)

    我们已经讨论过如何向设备分配地址,但此时这些地址只是本地设备节点,还没有说明如何从那些地址里映射到cpu可以使用的地址。根节点经常描述地址空间的CPU视图。根节点的子节点已经使用了CPU的address domain,所以不需要任何明确的映射。例如,serial@101f0000设备被直接分配了地址0x101f0000。

    根节点的非直接子节点是无法使用CPU的address domain的。为了在deivce tree获取内存映射地址必须指定如何从一个域名将地址转换到另一个。Ranges属性就用于此目的。以下是添加了ranges属性的device tree示例。

/dts-v1/;
/ {
    compatible = "acme,coyotes-revenge";
    #address-cells = <1>;
    #size-cells = <1>;
    ...
    external-bus {
        #address-cells = <2>
        #size-cells = <1>;
        ranges = <0 0  0x10100000   0x10000     // Chipselect 1, Ethernet
                  1 0  0x10160000   0x10000     // Chipselect 2, i2c controller
                  2 0  0x30000000   0x1000000>; // Chipselect 3, NOR Flash

        ethernet@0,0 {
            compatible = "smc,smc91c111";
            reg = <0 0 0x1000>;
        };

        i2c@1,0 {
            compatible = "acme,a1234-i2c-bus";
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <1 0 0x1000>;
            rtc@58 {
                compatible = "maxim,ds1338";
                reg = <58>;
            };
        };

        flash@2,0 {
            compatible = "samsung,k8f1315ebm", "cfi-flash";
            reg = <2 0 0x4000000>;
        };
    };
};

    Ranges是一个地址转换列表。每个输入ranges表格的是包含子地址的元组,母地址和子地址空间的范围大小。每个字段的大小都由获取的子地址的#address-cells值,母地址的#address-cell值和子地址的#size-cells值而定。以外部总线为例,子地址是2 cells,母地址是1 cell,大小也为1 cell。转换三个ranges:

  • Offset 0 from chip select 0 is mapped to address range 0x10100000…0x1010ffff
  • Offset 0 from chip select 1 is mapped to address range 0x10160000…0x1016ffff
  • Offset 0 from chip select 2 is mapped to address range 0x30000000…0x30ffffff

    例如上面的总线是有片选的,就需要描述片选及片选的偏移量,在说明地址时,还需要说明地址映射范围

5.1.7 status

    device tree中的status标识了设备的状态,使用status可以去禁止设备或者启用设备,看下设备树规范中的status可选值

易百纳社区

5.2 中断映射

    与遵循树的自然结构而进行的地址转换不同,机器上的任何设备都可以发起和终止中断信号。另外地址的编址也不同于中断信号,前者是设备树的自然表示,而后者者表现为独立于设备树结构的节点之间的链接。下图显示了设备的自然结构以及每个节点在逻辑中断树中的位置。

易百纳社区

上图包括以下部分:

  • open-pic中断控制器是中断树的根
  • 中断树根有三个子设备,它们将中断直接路由到open-pic
  • 存在三个中断域; 一个以开放式pic节点为根,一个在PCI主桥节点,一个在GPIO Controller节点上
  • 有两个nexus节点; 一个位于PCI主桥,一个位于GPIO控制器。

易百纳社区

下面显示了具有PCI总线控制器和采样中断的设备片段

soc {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    open-pic {
        clock-frequency = <0>;
        interrupt-controller;
        #address-cells = <0>;
        #interrupt-cells = <2>;
    };
    pci {
        #interrupt-cells = <1>;
        #size-cells = <2>;
        #address-cells = <3>;
        interrupt-map-mask = <0xf800 0 0 7>;
        interrupt-map = <
            / * IDSEL 0x11 - PCI slot 1* /
            0x8800 0 0 1 &open-pic 2 1 / * INTA* /
            0x8800 0 0 2 &open-pic 3 1 / * INTB* /
            0x8800 0 0 3 &open-pic 4 1 / * INTC* /
            0x8800 0 0 4 &open-pic 1 1 / * INTD* /
            / * IDSEL 0x12 - PCI slot 2* /
            0x9000 0 0 1 &open-pic 3 1 / * INTA* /
            0x9000 0 0 2 &open-pic 4 1 / * INTB* /
            0x9000 0 0 3 &open-pic 1 1 / * INTC* /
            0x9000 0 0 4 &open-pic 2 1 / * INTD* /
        >;
    };
};

5.3 特殊节点

    aliases节点为了解决节点路径名过长的问题,引入了节点别名的概念,可以引用到一个全路径的节点。如/external-bus/ethernet@0,0,但当用户想知道具体内容的时候显得太累赘,“哪个设备是eth0?”

aliases {
    ethernet0 = ð0;
    serial0 = &serial0;
};

当为设备分配一个标识符的时候,操作系统更倾向于使用aliases。

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Debug
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Debug
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区