[X]关闭

设备树概念归纳

文档创建者:ぉ沙皮狗的忧伤
浏览次数:6942
最后更新:2019-11-14
本帖最后由 ぉ沙皮狗的忧伤 于 2019-11-15 16:25 编辑

背景:
      2011年3月份Linux创始人Linus Torvalds提倡ARM平台应该参考其他平台PowerPC的设备树机制描述硬件。之前ARM平台采用旧的机制在kernel/arch/arm/plat-xxx目录和kernel/arch/arm/mach-xxx目录下用代码描述硬件,如注册platform设备,声明设备的resource等。这些代码都是用来描述芯片平台及板级差异的,对于内核来讲都是垃圾代码。



一、什么是设备树
    设备树(Device tree)是一套从软件角度来看描述硬件属性的规则,例如CPU的数量和类别,内存的基地址和大小,总线和桥,外设连接,中断控制器和中断使用情况等等


    存放路径:
       一般放置在内核的"arch/arm/boot/dts/"目录内


         实现过程:
                   dtc  -I  dts -O dtb -o  $(shell pwd)/devicetree.dtb  $(shell pwd)/dts/system-top.dts, 这个文件可以通过dtc命令编译成二进制的.dtb文件供内核驱动使用。


二、设备树的作用
     实现驱动代码与设备信息相分离

        设备树出现之前:
              所有关于设备的具体信息都要写在驱动里,一旦外围设备变化,驱动代码就要重写。

        设备树出现之后:
              驱动代码只负责处理驱动的逻辑,而关于设备的具体信息存放到设备树文件中,如果只是硬件接口信息的变化而没有驱动逻辑的变化,驱动开发者只需要修改设备树文件信息,不需要改写驱动代码。




三、DTS、DTB和DTC的描述   DT(Device tree)设备树
   FDT(Flattened Device tree)展开设备树,并且把它做成一个树状的结构
   OF(Open Firmware)开放式固件
   DTSI(Device tree source include)相当于DTS的头文件
   DTS(Device tree syntax,另一种说法是Device tree source)是设备树源文件,
为了方便阅读及修改,采用文本格式。
    DTC(Device tree compiler)是一个小工具,负责将DTS转换成DTB(Device treeblob)。
    DTB是DTS的二进制形式,供机器使用。
    使用中,我们首先根据硬件修改DTS文件,然后在编译的时候通过DTC工具将DTS文件转
换成DTB文件,然后将DTB文件烧写到机器上(如emmc,磁盘等存储介质)
    Uboot在启动内核前将DTB 件读到内存中,跳转到内核执行的同时将DTB起始地址
传给内核内核通过起始地址就可以根据DTB的结构解析整个设备树


四、编译设备树———DTC(device tree compiler)
1、将.dts文本文件通过DTC工具编译成.dtb二进制文件
dtc所在的目录kernel/scripts/dtc/

dts所在的目录kernel/arch/arm/boot/dts/


查看dts目录下的Makefile,当我们增加自己的设备树文件可以在kernel/arch/arm/boot/dts/目录下,相应的平台中添加自己的设备树名字,当某种SOC被选中后,那些.dtb文件会被编译出来


在kernel/arch/arm/configs/目录下xilinx_zynq_defconfig文件中


通过make xilinx_zynq_defconfig就能使得CONFIG_ARCH_AYNQ得到定义,make dtbs就能把dts目录下的zynq SOC平台相关的.dts文件通过DTC工具编译成.dtb文件





uboot加载设备树
在uboot启动时会去环境变量中获取设备树相应的参数,根据image去找到相应的设备树文件取得到相应的位置,解析出来展开.dtb文件。在kernel/include/configs/zynq_mz7x.h中包含这些信息

qdevtree_addr = 0xD20000是设备树存在qspi_flash中的地址
devicetree_size=0x020000是设备树的长度
devicetree_image=devicetree.dtb是设备树的名字
devicetree_load_address=0x2000000是设备树加载到内存里面的地址



我们可以把设备树看做一颗二叉树,allnodes就是树干或者称/根节点,根节点里面就会有很多很多子节点,子节点中又包含了子子节点,节点里面包含了设备信息,例如地址,中断,时钟等等等



每个设备信息包含在设备节点中,从哪里体现出来?当内核启动时在/proc/device-tree中,例如gpio-keys就是我自己写的设备节点






采用内核提供的api接口,去读取设备的硬件描述信息,不需要我们去遍历设备树


设备树的核心就是硬件描述信息


五、dts和dts

  zynq-7000.dts       私有部分,有很多.dts文件,私有部分存放的是各自不同的地方
  zynq-7000-org.dtsi    公共部分,
  zynq-myd.dts        私有部分,有很多.dts文件,私有部分存放的是各自不同的地方
  zynq-myd-xylon.dtsi   公共部分
  
       基于同样的软件分层设计的思想,由于一个SoC可能对应多个machine,如果每个machine的设备树都写成一个完全独立的.dts文件,那么势必相当一些.dts文件有重复的部分,为了解决这个问题,Linux设备树目录把一个SoC公用的部分或者多个machine共同的部分提炼为相应的.dtsi文件这样每个.dts就只有自己差异的部分,公有的部分只需要"include"相应的.dtsi文件, 这样就是整个设备树的管理更加有序。




设备树语法
-----------------------------------------
/ {
    node1 {
        a-string-property =“A string”;
        a-string-list-property =“first string”,“second string”;
        a-byte-data-property = [01 23 34 56];

        child-node1 {
            //boolean,first-child-property定义为true,不定义为false.
            first-child-boolean-property;
            second-child-cell-property = <1>;
            a-string-property =“Hello,world”;
        };
        child-node2 {

        };
    };
    node2 {
        an-empty-property;
        a-cell-property = <1 2 3 4>;
    };
};
————————————————
节点每个节点必须有一个名字,节点的描述形式"<名字>[@<设备地址>]"
   名字:由字符串组成,描述是能体现出它描述的设备
   设备地址:主要是用来区分
   树中每个表示一个设备的节点都需要一个compatible属性

    - 单个根节点:“ / ”
    - 几个子节点:“ node1 ”和“ node2 ”
    - node1的子节点:“ child-node1 ”和“ child-node2 ” ,跟节点的子子节点
    - 一堆散落在树上的属性。
    小结:我们可以把设备树形象的看做一颗树,单根节点可以看做树干,子节点看做树杈,子子节点看做树杈的树杈,属性就是树叶

属性
节点中包含的就是属性
    a-string-property =“A string”;
    a-string-list-property =“first string”,“second string”;
    ..........

    a-byte-data-property = [01 23 34 56];    //数组,[]中括号中二进制数,数据长度都是都是偶数位,总长度是4个字节,数据是长度
    a-cell-property = <1 2 3 4>;         //数组,<>尖括号中表示32位无符号整数,数据是独立的


根节点
   根节点:“ / ”


compatible属性
    compatible(兼容性)  
    compatible = "acme,coyotes-revenge";
    作用:用来做匹配,一般通过后面的名字,去查找
   
reg属性
ret = <address length [address length] [address length] .....>
       address为地址位置
       length为长度


#address-cells和#address-size属性

#address-cells = <2>
#address-size = <2>
ret = <address length [address length] [address length]>
       address为地址位置
       length为长度
cells=2的意思是用来表示ret占用多少个位置来描述这个地址


中断属性      interrupt-control一个空的属性定义节点作为一个接收中断信号的设备
      interrupt-cells这是一个中断控制器的节点属性,它声明了该中断控制器的中断指示符中的cell个数
      interrupt-parent中断的信息描述可以从父类那里继承下来
      interrupt中断指示符的类表

      interrupts = <0 20 2>;  0表示共享外围中断 20表示中断号为20+32 = 52,2表示触发方式为下降沿触发
      interrupt-parent = <&intc>;从intc父类那里继承,这个外设是接在这个中断控制器上的
如果你想获取设备的信息,无非就是把整个节点拿到,获取节点中的属性,这个节点结构体struct device_node,通过of api接口去获取



属性编写的套路
   
  • 第一种是抄类似的dts,比如平台是ZYNQ,这类相近的dts或者去dtsi里面去
  • 第二种是查询内核中的文档,比如kernel/Documentation/devicetree/bindings/gpio/gpio-zynq.txt就描述了zynq平台的GPIO属性设置方法;


内核(驱动)与节点的匹配
        首先,内核必须要知道dtb文件的地址,这由U-boot来告诉内核,谁来告诉U-boot dtb文件的地址?是从环境变量里面获取的,只要内核知晓了dtb文件的地址,那么驱动就可以通过一些API任意获取设备树的内部信息


  • 对于3.x版本之后的内核,platform、i2c、spi等设备不再需要在mach-xxx中注册,驱动程序将直接和设备树里的设备节点进行配对,是通过设备节点中的compatible(兼容性)来与设备节点进行配对的,驱动中的of_match_table 中的compatible 值和设备节点中的compatible 相匹配,那么probe函数才会被触发。


  • i2c和spi驱动还支持一种“别名匹配”的机制,就以pcf8523为例,假设某程序员在设备树中的pcf8523设备节点中写了compatible = "pcf8523";,显然相对于驱动id_table中的"nxp,pcf8523",他遗漏了nxp字段,但是驱动却仍然可以匹配上,因为别名匹配对compatible中字符串里第二个字段敏感


查看现象





不足之处多多指点






































本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

发表评论已发布 2

菲尼克兔

发表于 2019-11-13 21:45:21 | 显示全部楼层

楼主总结的真不错,打紧学习一下

ぉ沙皮狗的忧伤

发表于 2019-11-14 10:42:03 | 显示全部楼层

菲尼克兔 发表于 2019-11-13 21:45
楼主总结的真不错,打紧学习一下

还在更新中
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则