设备树浅析

1. 简介:

设备树:device tree,即设备构成的树

dts:device tree source,采用树形结构描述板级设备,即开发板上的设备信息,如cpu数量、内存基地址、iic接口上接了哪些设备、spi接口上……

dtb:将dts编译以后得到的二进制文件,uboot通过bootz或者bootm命令可以像linux内核传递这个二进制的.dtb

dtc:设备树编译工具,将dts编译成dtb,dtc工具源码在linux内核的scripts/dtc目录下,其makefile中说明依赖于dtc.c、flattree.c、fstree.c等文件,最终编译并链接出dtc这个主机文件,如果要编译dts文件的话只需要进入linux源码根目录下,然后执行make all(源码中所有东西)或者make dtbs(仅设备树文件)

2. dts语法:

该种文件常常时直接在soc厂商提供的.dts文件上进行修改,dts是一种asic文本文件,修改方便

2.1. dtsi头文件:

.dts文件是可以直接引用.dtsi和.h文件的,可以使用#include来引用.h或者.dtsi头文件

一般.dtsi文件用于描述soc的内部外设信息,比如cpu架构、主频、外设寄存器地址范围

2.2. 设备节点:

设备树采用树形结构来描述板子上的设备信息,每个设备都是一个节点,即设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键值对

2.2.1. 根节点:

“/”是根节点,每个设备树文件只有一个根节点,当两个dts文件各自都有一个“/”根节点之后,其实两个“/”根节点的内容会合并成一个根节点的

2.2.2. 子节点:

如cpu举例图,语法结构是:

2.2.3. node-name@unit-address

node-name是节点名字,为ascii字符串,节点名字应该能够清晰的描述节点的功能,比如“uart1”就表示这个节点是uart1外设

unit-address一般表示设备的地址或者寄存器首地址,如果某个节点没有地址或者寄存器的话,unit-address可以不要

当然还有其他格式,比如:

2.2.4. label:node-name@unit-address

label是为了方便访问节点,可以直接通过@label来访问这个节点

子节点也可以有自己的子节点

2.2.5. 其他数据结构:

字符串:“”

32位无符号整数:<>

字符串列表:“”,“”

2.3. 标准属性:

节点是由一堆的属性组成,节点都是具体的设备,不同的设备需要的属性不同,用户可以自定义属性,当然了,很多属性是标准属性,linux下的很多外设驱动都会使用这些标准属性

2.3.1. compatible:

兼容性属性,其值是一个字符串列表,用于将设备和驱动绑起来,字符串列表用于选择设备所要使用的驱动程序,格式如下:

“manufacturers,model”

manufacturers表示厂商,model一般是模块对应的驱动名字

假设属性值有两个,那么这个设备首先使用第一个兼容值在linux内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有则使用第二个兼容值

一般驱动文件都会有一个OF匹配表,保存着一些compatible值,如果设备节点的compatible属性值和OF匹配表中的任何一个值相等,那么就表示设备可以使用这个驱动

比如在[model].c里面就会这样定义:(model在这里是imx_wm8960)这就是OF匹配表

2.3.2. model:

这个属性值也是一个字符串,用来描述设备模块信息,就是名字啥的

2.3.3. status:

也是字符串,是设备状态信息:

2.3.4. #address-cells和#size-cells:

这俩都是无符号32位整形,二者可以用在任何拥有子节点的设备中,用以描述子节点的地址信息,前者决定子节点reg属性中地址信息所占用的字长(32位),后者决定了子节点reg属性中长度信息所占的字长(32位)

2.3.5. reg:

一般是(address,length)对,用以描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围

2.3.6. ranges:

可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,是一个地址映射/转换表,range属性每个项目由子弟之、附地址和地址空间长度三部分组成:

child-bus-address:子总线地址空间的物理地址,由父节点的#address-cells确定此物理地址所占用的字长

parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells确定此物理地址所占用的字长

length:子弟之空间长度,由父节点的#size-dells确定此地址长度所占用的字长

如果ranges属性值为空,说明子地址空间和附地址空间完全相同,不需要进行地址转换

2.3.7. name:

字符串,用于记录节点名字,多被弃用,不必管,只一些老设备需要

2.3.8. device_type:

字符串,ieee1275会用到这个属性,用以描述设备FCode,设备树没得FCode就不管了

2.4. 根节点compatible属性:

根节点的该属性结构和其他节点的一样,第一个值描述了所使用的硬件设备名字,第二个值描述设备所使用的soc,linux内核会通过根节点的该属性查看是否支持此设备,如果支持的话设备就会启动linux内核

2.4.1. 使用设备树之前的匹配方法:

没有设备树的啥时候,uboot会向linux内核传递一个叫做machine id的值,即设备ID,高速linux内核自己是什么设备,看看linux是否支持

linux内核是支持很多设备的,针对每一个设备(板子),linux内核都用MACHINE_START和MACHINE_END来定义一个machine_desc结构体来描述这个设备

2.4.2. 使用设备树之后的设备匹配方法:

有了设备树之后,不用上面的宏了,而是换了DT_MACHINE_START:

他的结构跟上面的差不多,只是.nr不一样了,此处该值为~0,即引入设备树之后不会再根据machine id来检查linux内核是否支持某个设备了

这就是匹配表了

对于如何匹配这个表(根据设备树根节点的compatible属性来匹配出对应的machine_desc),是这样的:

linux内核调用start_kernel函数来启动内核,start_kernel函数会调用setup_arch函数来匹配machine_desc,举个例子:

调用setup_machine_fdt函数来获取匹配的machine_desc,参数就是atags的首部地址,也就是uboot传递给linux内核的dtb文件首地址,其返回值就是找到最匹配的machine_desc,定义如下:

再调用of_flat_dt_match_machine(),来获取machine_desc

整个流程如下所示:

2.5. 向节点追加或修改内容:

产品的开发会涉及频繁需求变更,比如iic换了,那么一旦硬件改了,我们就要改设备树文件

如果新加了一个硬件,那么最简单的就是在原本该类硬件的节点下直接添加一个子节点

如果是追加或者修改呢,可以公国&label来访问节点,即需要写个引用,&谁就代表要引用谁,不会影响到其他使用此设备树的板子

3. 创建小型模板设备树:

一般就是在soc厂商提供的.dts上面修改,主要做的是:

3.1. 添加根节点:

在根节点下添加兼容表

3.2. 添加cpus节点和soc节点:

/ {

compatible = "fsl,imx6ull-alientek-evk", "fsl,imx6ull";

cpus {

#address-cells = <1>;

#size-cells = <0>;

//CPU0节点

cpu0: cpu@0 {

compatible = "arm,cortex-a7";

device_type = "cpu";

reg = <0>;

};

};

//soc节点

soc {

#address-cells = <1>;

#size-cells = <1>;

compatible = "simple-bus";

ranges;

}

}

3.3. 添加存储器节点和其他外设节点:

存储器节点可以这样搞:

外设节点涉及到层级关系的,需要用子节点关系来搞

3.4. 设备树在系统中的体现:

在文件系统中

/proc/device-tree/  或  /sys/firmware/devicetree/base/下都会有设备树的影子(根节点“/”的所有属性和其子节点)

如果想查看根节点的子节点的子节点的话,那就进入对应的目录就可以查看了

3.5. 特殊节点:

根节点中有两个特殊子节点,一个是aliases,一个是chosen

3.5.1. aliases子节点:

意思就是“别名”,也就是在这里就是起个名字,当然了,一般在节点命名的时候会加上label的,然后通过&label来访问节点,这里一个意思

3.5.2. chosen子节点:

这不是一个真实的设备,chosen节点主要是为了uboot向linux内核传递数据,重点是bootargs参数,内容就像这样子:

如果用了这个,那就可以找到目录下有bootargs这些了,bootargs里面是参数传递的依据。

在内核代码中也有fdt_chosen函数,大概流程如下:

4. linux内核解释dtb文件

linux内核在启动的时候会解析dtb文件,然后再/proc/device-tree目录下生成相应的设备树节点文件,过程如下所示:

5. 绑定信息文档:

在设备树中添加一个硬件对应的节点需要查相关的说明,在linux内核源码中就有详细的.txt文档描述如何添加节点,即绑定文档,路径为/Documentation/devicetree/bindings,如果看不懂,就位fae吧

6. 设备树常用的OF操作函数:

设备树描述了设备的详细信息,包括数字类型、字符串类型、数组类型的,我们再编写驱动的时候需要获取到这些写信息,就通过一系列的“of_”前缀的函数来获取设备树中的节点或者属性信息,即OF函数,当然了,所匹配的逻辑上的表就是OF表

6.1. 查找节点的of函数:

设备都是以设备节点的形式“挂”到设备树上,通过获取到这个设备的节点来获取这个设备的其他属性

 折叠源码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

/*

    用途      :通过节点名字查找指定节点

    @from       :开始查找节点,如果为NULL表示从根节点开始查找整个设备树

    @name       :要查找的节点名字

    return      :找到的节点,如果null表示查找失败

*/

struct device_node *of_find_node_by_name(struct device_node *from, const char *name);

/*

    用途      :通过device_type属性查找指定节点

    @frome      :开始查找的节点,如果为NULL表示从根节点开始查找整个设备树

    @type       :要查找的节点对应的type字符串,即device_type属性值

    return      :找到的节点,如果null表示查找失败

*/

struct device_node *of_find_node_by_type(struct device_node *from, const char *type);

/*

    用途      :根据 device_type和 compatible这两个属性查找指定的节点

    @from       :开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。

    @type       :要查找的节点对应的type字符串,即device_type属性值

    @compatible :要查找的节点对应的compatible属性列表

    return      :找到的节点,如果null表示查找失败

*/

struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);

/*

    用途      :通过of_device_id匹配表来查找指定节点

    @from       :开始查找的节点,如果为NULL表示从根节点开始查找整个设备树

    @matches    :of_device_id匹配表,即在此匹配表里面查找节点

    @match      :找到匹配的of_devide_id

    return      :找到的节点,如果null表示查找失败

*/

struct of_find_matching_node_and_match(struct device_node *from, const struct_of_device_id *matches, const struct of_device_id **match);

/*

    用途      :通过路径来查找指定的节点

    path        :带有全路径的节点名,可以使用节点的别名,比如"/backlight"就是backlight这个节点的全路径

    return      :找到的节点,如果null表示查找失败

*/

inline struct device_node *of_find_node_by_path(const char *path);

6.2. 查找父/子节点的of函数:

 折叠源码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

/*

    用途      :获取指定节点的父节点(如果有父节点)

    @node       :要查找父节点的节点

    return      :找到的父节点

*/

struct device_node *of_get_parrent(const struct device_node *node);

/*

    用途      :通过device_type属性查找指定节点

    @node       :要查找父节点的节点

    @prev       :前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点,可以设置为NULL,表示从第一个子节点开始

    return      :找到的下一个子节点

*/

struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev);

6.3. 提取属性值的of函数:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值