linux 设备树【转】

萝らか妹 提交于 2020-03-08 07:41:41

转自:http://blog.csdn.net/chenqianleo/article/details/77779439

[-]

  1. linux 设备树
    1. 为什么要使用设备树Device Tree
    2. 设备树的的组成和结构
      1. 1设备树的组成
        1. 11 DTS和DTSI
        2. 12 DTC
        3. 13 DTB
        4. 14 绑定bingding
        5. 15 Bootloader 使用dtb
      2. 2设备树框架
    3. 设备树语法
      1. 下面这个是rk3399-fpgadts
      2. 1根节点兼容性
      3. 2节点名
      4. 3引用
      5. KEY
        1. 1compatible
        2. 2address
        3. 3interrupts
        4. 4gpio
      6. DTB的加载过程
      7. API调用

linux 设备树


参考地址
http://blog.csdn.net/green1900/article/details/45646095
http://www.cnblogs.com/xiaojiang1025/p/6131381.html
http://blog.csdn.net/21cnbao/article/details/8457546


1.为什么要使用设备树(Device Tree)?

在以前的内核源码中,存在大量对板级细节信息描述的代码,这些代码充斥在/arch/arm/plat-xxx和/arch/arm/mach-xxx目录,对内核而言这些platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data绝大多数纯属垃圾冗余代码。为了解决这一问题,ARM内核版本3.x之后引入了原先在Power PC等其他体系架构已经使用的Flattened Device Tree。DTS不是arm的专利

在使用了设备树后,对于同一SOC的不同主板,只需更换设备树文件.dtb即可实现不同主板的无差异支持,而无需更换内核文件。


2.设备树的的组成和结构

设备树可以描述的信息包括了
1. CPU的数量和类别、
2. 内存基地址和大小、
3. 总线和桥、
4. 外设连接、
5. 中断控制器和中断使用情况、
6. GPIO控制器和GPIO使用情况、
7. Clock控制器和Clock使用情况。
需要注意的是,设备树对于可热插拔的热备不进行具体描述,它只描述用于控制该热插拔设备的控制器

2.1设备树的组成

设备树包含了DTC(device tree compiler) , DTS(device tree resource) 和 DTB(device tree blob),简单来说,dts是源码,dtc是编译器,dtb是生成的可执行文件

2.1.1 DTS和DTSI

.dts和.dtsi是一种ASCII文本的设备树描述,此文本格式非常适合人们阅读,基本上,一个.dts对应一种ARM设备,放在arch/arm/boot/dts目录,由于一个soc对应好多个不同的开发板,每个开发板有一个.dts,所以这些dts势必有共同部分,为了减少代码的屯余,设备树将这些共同部分提炼保存在dtsi中,供不同的dts使用,dtsi文件类似于c语言的头文件

2.1.2 DTC

DTC为编译工具,它可以将.dts文件编译成.dtb文件。DTC的源码位于内核的scripts/dtc目录,内核选中CONFIG_OF,编译内核的时候,主机可执行程序DTC就会被编译出来

2.1.3 DTB

DTB设备由DTC编译后的二进制格式的设备树描述,可以由linux内核解析,uboot这样的bootloader也可以识别.dtb,有两种使用方式,一种是bootloader启动内核过程中会先读取dtb到文件中;第二种是把dtb和zImage打包在一起做成一个印象文件,firefly-3399就是采用这种方式,打包生成了boot.img

2.1.4 绑定(bingding)

对于Device Tree中的结点和属性具体是如何来描述设备的硬件细节的,一般需要文档来进行讲解,文档的后缀名一般为.txt。这些文档位于内核的Documentation/devicetree/bindings目录,其下又分为很多子目录

2.1.5 Bootloader 使用dtb

在Uboot中,可以从NAND、SD或者TFTP等任意介质将.dtb读入内存,假设.dtb放入的内存地址为0x71000000,之后可在Uboot运行命令fdt addr命令设置.dtb的地址,如:
U-Boot> fdt addr 0x71000000
fdt的其他命令就变地可以使用,如fdt resize、fdt print等
对于ARM来讲,可以透过bootz kernel_addr initrd_address dtb_address的命令来启动内核,即dtb_address作为bootz或者bootm的最后一次参数,第一个参数为内核映像的地址,第二个参数为initrd的地址,若不存在initrd,可以用 -代替,第三个就是dtb地址

2.2设备树框架

设备树用树状结构描述设备信息,它有以下几种特性
1. 每个设备树文件都有一个根节点,每个设备都是一个节点。
2. 节点间可以嵌套,形成父子关系,这样就可以方便的描述设备间的关系。
3. 每个设备的属性都用一组key-value对(键值对)来描述。
4. 每个属性的描述用;结束


3. 设备树语法

设备树是一颗树,书上的每个节点由节点和属性组成,属性是键值对

下面这个是rk3399-fpga.dts

#include "rk3399.dtsi"  //包含了公共部分
/ {
        model = "Rockchip RK3399 FPGA Board";
        compatible = "rockchip,fpga", "rockchip,rk3399"; //根节点兼容性分析,下面具体分析
        chosen {
                bootargs = "init=/init console=uart,mmio32,0xff1a0000";
        };
        memory@00000000 { //子节点  memory@00000000节点名
                device_type = "memory";
                reg = <0x0 0x00000000 0x0 0x20000000>;
        };
};
&uart2 { //使用了引用
        status = "okay";
        clocks = <&xin24m>, <&xin24m>;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.1根节点兼容性

compatible = "rockchip,fpga", "rockchip,rk3399";
  • 1
上面是根节点的兼容属性,定义了整个系统(设备级别)的名称,通过这个属性就可以判断出它启动的是什么设备。它的组织形式是&lt;manufacture&gt;&lt;model&gt;,在实际中一般包括两个或两个以上的兼容字符串,上面第一个是"rockchip,fpga",第二个是"rockchip,rk3399",我们来看第二个,manufacture是板子级别的名字,“rockchip”代表的是瑞芯微公司,model是芯片级别的,“rk3399”是瑞芯微公司一个soc的名称

我们从源码中找出rk3399的两个dts,可以看出第一个兼容字符串的model不同,第二个完全相同

rk3399-firefly-linux.dts
compatible = "rockchip,rk3399-firefly-linux", "rockchip,rk3399";
rk3399-fpga.dts
compatible = "rockchip,fpga", "rockchip,rk3399";
  • 1
  • 2
  • 3
  • 4

3.2节点名

理论个节点名只要是长度不超过31个字符的ASCII字符串即可,Linux内核还约定设备名应写成形如[@]的形式,其中name就是设备名,最长可以是31个字符长度。unit_address一般是设备地址,用来唯一标识一个节点
Linux中的设备树还包括几个特殊的节点,比如chosen,chosen节点不描述一个真实设备,而是用于firmware传递一些数据给OS,比如bootloader传递内核启动参数给内核

chosen{
    bootargs = "console=ttySAC2,115200";
    stdout-path=&serial_2;
};
  • 1
  • 2
  • 3
  • 4

3.3引用

当我们找一个节点的时候,我们必须书写完整的节点路径,这样当一个节点嵌套比较深的时候就不是很方便,所以,设备树允许我们用下面的形式为节点标注引用(起别名),借以省去冗长的路径。这样就可以实现类似函数调用的效果

3.KEY

在设备树中,键值对是描述属性的方式,比如,Linux驱动中可以通过设备节点中的”compatible”这个属性查找设备节点
inux设备树语法中定义了一些具有规范意义的属性,包括:compatible, address, interrupt等,这些信息能够在内核初始化找到节点的时候,自动解析生成相应的设备信息。此外,还有一些Linux内核定义好的,一类设备通用的有默认意义的属性,这些属性一般不能被内核自动解析生成相应的设备信息,但是内核已经编写的相应的解析提取函数,常见的有 “mac_addr”,”gpio”,”clock”,”power”。”regulator” 等等。

3.1.compatible

设备节点中对应的节点信息已经被内核构造成struct platform_device。驱动可以通过相应的函数从中提取信息。主要有三种方法提取信息

    1、compatible属性是用来查找节点
    2、通过节点名查找指定节点
    3、节点路径查找指定节点
  • 1
  • 2
  • 3

看一个使用compatible提取属性的例子

#dts
    gpio_demo: gpio_demo {
        status = "okay";
        compatible = "firefly,rk3399-gpio";                  
    };
#驱动代码
static struct of_device_id firefly_match_table[] = {
    { .compatible = "firefly,rk3399-gpio",}, //完全相同
    {}, //最后一个成员一定是空,因为相关的操作API会读取这个数组直到遇到一个空。
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.2address

  • #address-cells,用来描述子节点”reg”属性的地址表中用来描述首地址的cell的数量
  • #size-cells,用来描述子节点”reg”属性的地址表中用来描述地址长度的cell的数量。
        pinctrl: pinctrl {
                compatible = "rockchip,rk3399-pinctrl";
                #address-cells = <0x2>;
                #size-cells = <0x2>;
                gpio0: gpio0@ff720000 {
                        compatible = "rockchip,gpio-bank";
                        reg = <0x0 0xff720000 0x0 0x100>;
                        //前两个数字表示一个地址0x0 0xff720000
                        //后两个数字表示一个地址跨度 0x100
                };
            ...
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3.3interrupts

一个计算机系统中大量设备都是通过中断请求CPU服务的,所以设备节点中就需要在指定中断

  • interrupt-controller 一个空属性用来声明这个node接收中断信号,即这个node是一个中断控制器
  • #interrupt-cells,是中断控制器节点的属性,用来标识这个控制器需要几个单位做中断描述符,用来描述子节点中”interrupts”属性使用了父节点中的interrupts属性的具体的哪个值。一般,如果父节点的该属性的值是3,则子节点的interrupts一个cell的三个32bits整数值分别为:<中断域 中断 触发方式>,如果父节点的该属性是2,则是<中断 触发方式>
  • interrupt-parent,标识此设备节点属于哪一个中断控制器,如果没有设置这个属性,会自动依附父节点的
  • interrupts,一个中断标识符列表,表示每一个中断输出信号

3.4gpio

  • gpio-controller,用来说明该节点描述的是一个gpio控制器
  • #gpio-cells,用来描述gpio使用节点的属性一个cell的内容,即 `属性 = <&引用GPIO节点别名 GPIO标号 工作模式>
firefly-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>;          /* GPIO0_B4 */
firefly-irq-gpio = <&gpio4 29 IRQ_TYPE_EDGE_RISING>;  /* GPIO4_D5 */  
  • 1
  • 2

4.DTB的加载过程

参考地址
http://blog.csdn.net/green1900/article/details/45646095
此处输入图片的描述
http://blog.csdn.net/lichengtongxiazai/article/details/38941913

总的归纳为:

① kernel入口处获取到uboot传过来的.dtb镜像的基地址

② 通过early_init_dt_scan()函数来获取kernel初始化时需要的bootargs和cmd_line等系统引导参数。

③ 调用unflatten_device_tree函数来解析dtb文件,构建一个由device_node结构连接而成的单向链表,并使用全局变量of_allnodes保存这个链表的头指针。

④ 内核调用OF的API接口,获取of_allnodes链表信息来初始化内核其他子系统、设备等。


5.API调用

#来查找在dtb中的根节点
unsigned long __init of_get_flat_dt_root(void)

# 根据deice_node结构的full_name参数,在全局链表of_allnodes中,查找合适的device_node
struct device_node *of_find_node_by_path(const char *path)

#若from=NULL,则在全局链表of_allnodes中根据name查找合适的device_node
struct device_node *of_find_node_by_name(struct device_node *from,const char *name)

#根据设备类型查找相应的device_node
struct device_node *of_find_node_by_type(struct device_node *from,const char *type)

# 根据compatible字符串查找device_node
struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compatible)

#根据节点属性的name查找device_node
struct device_node *of_find_node_with_property(struct device_node *from,const char *prop_name)

#根据compat参数与device node的compatible匹配,返回匹配度
int of_device_is_compatible(const struct device_node *device,const char *compat)

#获得父节点的device node
struct device_node *of_get_parent(const struct device_node *node)

#读取该设备的第index个irq号
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)

#读取该设备的第index个irq号,并填充一个irq资源结构体
int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)

#获取该设备的irq个数
int of_irq_count(struct device_node *dev)
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!