Linux设备树

1,000 阅读10分钟

一、语法

1.节点命名格式

  1. node-name@unit-address

node-name:节点名字,为ASCII字符串,节点名字应该能清晰描述节点的功能。
unit-address:一般表示设备的地址或寄存器的首地址,如果某个节点没有这些,那么unit-addree可以省略。

  1. label:node-name@unit-address

引入label是为了方便调用,可以使用&label来访问这个节点,而不需要输入完整的节点名。

2.属性的值数据类型

  1. 字符串
compatible = "str"
  1. 32位无符号整数
reg = <0 0x123456 100> #一组u32
  1. 字符串列表
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";

3.标准属性

  1. compatible

compatible属性用于将设备和驱动绑定起来,其属性值是字符串列表类型。 特殊的是,根节点的该属性是用于

"manufacturer,model" #compatible格式
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960"; #实例
  1. model属性

model属性值用于描述设备模块,比如名字等等。

model = "wm8960-audio"  
  1. status属性

"okay":设备可操作
"disabled":设备不可操作,但在未来可以变得可操作,如热插拔等。 "fail":设备不可操作,设备检测到一系列错误。
"fail-sss":同上,sss是检测到错误的内容。

  1. reg属性

reg属性用来描述设备的起始地址和长度

reg<address, length> #reg的一般格式,address占用字长则由下面的属性决定
  1. #address-cells 和#size-cells属性

这两个属性用在含子节点的设备中,用来描述子节点的reg属性。
#address-cells决定了子节点reg属性中地址信息所占用字长。
#size-cells决定了子节点reg属性中长度信息所占的字长。

# 例1
#address-cells = <1>;
#size-cells = <1>;
...
reg = <0x02280000 0x4000>; 地址0x02280000 长度0x4000

#例2
#address-cells = <2>;
#size-cells = <2>;
...
reg = <0x90000000 00000000 0x800000 00000000>; 地址0x90000000 00000000 长度0x800000 00000000
  1. ranges属性

ranges是一个地址映射表。

ranges = <child-bus-address,parent-bus-address,lenth>
child-bus-address: 父总线地址空间物理地址,由#address-cells确定此物理地址所占用的子长。
parent-bus-address: 子总线地址空间物理地址,由#address-cells确定此物理地址占用的字长。
lenth: 子地址空间的长度,由#size-cells确定此地址长度所占用的字长。
#实例1 父地址空间和子地址空间完全相同
soc {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "simple-bus";
    interrupt-parent = <&gpc>;
    ranges;
......
    }
    
#实例2 父地址空间和子地址空间不同
soc {
    compatible = "simple-bus";
    #address-cells = <1>;
    #size-cells = <1>;
    ranges = <0x0 0xe0000000 0x00100000>;#此属性值指定了一个1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为0x0,映射父地址空间的物理起始地址为 0xe0000000。

    serial {
        device_type = "serial";
        compatible = "ns16550";
        reg = <0x4600 0x100>; #serial是串口设备节点,reg属性定义了serial设备寄存器的起始地址为0x4600,寄存器长度为 0x100。经过地址转换,serial设备可以从0xe0004600开始进行读写操作,0xe0004600=0x4600+0xe0000000。
    ...
    };
};
  1. name属性

name属性值为字符串,name 属性用于记录节点名字,name 属性已经被弃用,不推荐使用name 属性,一些老的设备树文件可能会使用此属性。

  1. device_type属性

device_type属性值为字符串,只用于cpu节点和memory节点。

#实例
cpu0: cpu@0 {
    compatible = "arm,cortex-a7";
    device_type = "cpu";
    reg = <0>;
......
};

二、创建小型设备树流程

1.要求

(1)I.MX6ULL 这个 Cortex-A7 架构的 32 位 CPU。
(2)I.MX6ULL 内部 ocram,起始地址 0x00900000,大小为 128KB(0x20000)。
(3)I.MX6ULL 内部 aips1 域下的 ecspi1 外设控制器,寄存器起始地址为 0x02008000,大小为 0x4000。
(4) I.MX6ULL 内部 aips2 域下的 usbotg1外设控制器,寄存器起始地址为0x02184000,大小为 0x4000。
(5)I.MX6ULL 内部 aips3 域下的 rngb 外设控制器,寄存器起始地址为 0x02284000,大小为 0x4000。

2.设备树实现

/{
    compatible = "fsl,imx6ull-ht", "fsl,imx6ull";
    #描述cpu
    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        
        cpu0:cpu@0 {
            compatible = "arm,cortex-a7"; #兼容架构
            device_type = "cpu"; 
            reg = <0>;
        };
    }
    #描述SOC内部外设    
    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "simple-bus"
        ranges;
        
        ocram: sram@00900000 {
            compatible = "fsl,lpm-sram";
            reg = <0x00900000 0x20000>;
        };
        
        aips1: aips-bus@02000000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02000000 0x100000>;
            ranges;
            
            ecspi1: ecspi@0200800 {
                #address-cells = <1>;
                #size-cells = <0>;
                compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
                reg = <0x02008000 0x4000>;
                status = "disabled";
            }
        }
        
        aips2: aips-bus@02100000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02100000 0x100000>;
            ranges;
            
            usbotg1: usb@02184000 {
                compatible = "fsl,imx6ul-usb", "fsl,imx27-usb";
                reg = <0x02184000 0x200>;
                status = "disabled";
            }            
        }     
        
        aips3: aips-bus@02200000 {
            compatible = "fsl,aips-bus", "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            reg = <0x02200000 0x100000>;
            ranges;
            
            rngb: rngb@02284000 {
                compatible = "fsl,imx6sl-rng", "fsl,imx-rng", "imxrng";
                reg = <0x02284000 0x4000>;
            }  
        }
    }
}

3.设备树在根文件系统中的体现

/proc/device-tree/和/sys/firmware/devicetree/base/ 目录下体现了根节点的所有属性和子节点。

4.设备树下特殊节点aliases\chosen解析

  1. aliases

给其他节点起一个别名,用于访问。

  1. chosen

该节点主要是为了uboot向linux内核传递数据,重点是bootargs参数

三、设备树中常用的OF操作函数

1.查找节点的of函数

(1) 通过节点名字查找指定的节点
struct device_node *of_find_node_by_name(struct device_node *from, 
                                         const char *name);
/*
 * from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
 * name:要查找的节点名字。
 * 返回值:找到的节点,如果为 NULL表示查找失败。
 */
(2) 通过device_type属性查找指定的节点
struct device_node *of_find_node_by_type(struct device_node *from, 
                                         const char *type)
/*
 * from:开始查找的节点,如果为NULL表示从根节点开始查找整个设备树。
 * type:要查找的节点对应的 type 字符串,也就是 device_type 属性值。
 * 返回值:找到的节点,如果为 NULL表示查找失败。
 */
(3) 通过device_type和compatible这两个属性查找指定的节点
struct device_node *of_find_compatible_node(struct device_node *from, 
                                            const char *type,
                                            const char *compatible)
/*
 * from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
 * type:要查找的节点对应的 type 字符串,也就是 device_type 属性值,可以为 NULL,表示
 * 忽略掉 device_type 属性。
 * compatible: 要查找的节点所对应的 compatible 属性列表。
 * 返回值: 找到的节点,如果为 NULL 表示查找失败
 */ 
(4) 通过 of_device_id 匹配表来查找指定的节点
struct device_node *of_find_matching_node_and_match(struct device_node *from,
                                                    const struct of_device_id *matches,
                                                    const struct of_device_id **match)
/*
 * from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
 * matches:of_device_id 匹配表,也就是在此匹配表里面查找节点。
 * match:找到的匹配的 of_device_id。
 * 返回值:找到的节点,如果为 NULL 表示查找失败
 */
(5) 通过路径来查找指定的节点
inline struct device_node *of_find_node_by_path(const char *path)
/*
 * path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是backlight这个节点的路径。
 * 返回值:找到的节点如果为NULL表示查找失败。
 */

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

(1) 查找父节点
struct device_node *of_get_parent(const struct device_node *node) 
/*
 * node:要查找的父节点的节点。
 * 返回值:找到的父节点。
 */
(2) 查子父节点
struct device_node *of_get_next_child(const struct device_node *node,
                                      struct device_node *prev)
/*
 * node:父节点。
 * prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
 * 返回值: 找到的下一个子节点。
 */

3.提取属性值的of函数

(1) 根据节点属性名字查找
property *of_find_property(const struct device_node *np,
                           const char *name,
                           int *lenp)
(2) 获取属性中元素的数量
int of_property_count_elems_of_size(const struct device_node *np,
                                    const char *propname,
                                    int elem_size)
(3) 从属性中获取指定标号的 u32 类型数据值
int of_property_read_u32_index(const struct device_node *np,
                               const char *propname,
                               u32 index,
                               u32 *out_value)
(4) 这4个函数分别是读取属性中 u8、 u16、 u32 和 u64 类型的数组数据
int of_property_read_u8_array(const struct device_node *np,
                              const char *propname,
                              u8 *out_values,
                              size_t sz)
int of_property_read_u16_array(const struct device_node *np,
                               const char *propname,
                               u16 *out_values,
                               size_t sz)
int of_property_read_u32_array(const struct device_node *np,
                               const char *propname,
                               u32 *out_values,
                               size_t sz)
int of_property_read_u64_array(const struct device_node *np,
                               const char *propname,
                               u64 *out_values,
                               size_t sz)
(5) 有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性。
int of_property_read_u8(const struct device_node *np,
                        const char *propname,
                        u8 *out_value)
int of_property_read_u16(const struct device_node *np,
                         const char *propname,
                         u16 *out_value)
int of_property_read_u32(const struct device_node *np,
                         const char *propname,
                         u32 *out_value)
int of_property_read_u64(const struct device_node *np,
                         const char *propname,
                         u64 *out_value)
(6) 读取属性中字符串值
int of_property_read_string(struct device_node *np,
                            const char *propname,
                            const char **out_string)
(7) 获取#address-cells属性值
int of_n_addr_cells(struct device_node *np)
(8) 获取获取#size-cells属性值
int of_n_size_cells(struct device_node *np)

4.其他常用的OF函数

(1)查看节点的compatible属性是否有包含compat指定的字符串,也就是检查设备节点的兼容性
int of_device_is_compatible(const struct device_node *device,
                            const char *compat)
(2)获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值
const __be32 *of_get_address(struct device_node *dev,
                             int index,
                             u64 *size,
                             unsigned int *flags)
(3) 将从设备树读取到的地址转换为物理地址
u64 of_translate_address(struct device_node *dev,
                         const __be32 *in_addr)
(4) inux内核使用 resource结构体来描述一段内存空间,本质上就是提取reg属性值,然后将其转换为 resource 结构体类型。
int of_address_to_resource(struct device_node *dev,
                           int index,
                           struct resource *r)
(5) of_iomap 函数用于直接内存映射
void __iomem *of_iomap(struct device_node *np,
                       int index)

四、设备树反编译及其在文件系统中的体现

1. 设备树的反汇编

#在linux内核源码中
./scripts/dtc/dtc -I dtb -O dts -o output.dts arch/arm64/boot/dts/xxx.dtb
生成的output.dts就是设备树
当然也可以重新编译成dtb
./scripts/dtc/dtc -I dts -O dtb -o xxx.dtb output.dts

2. 删除设备树属性的方式(不改变源文件)

 1 / {
 2     ... ...
 3     demo1: demo1 {
 4         compatible = "demo1";
 5         property1 = <1>;
 6         property2;
 7         property3 = <2>;
 8         property4;
 9     };
10 };
11 
12 &demo1 {
13     /delete-property/property2;
14     /delete-property/property3;
15 };

3. 设备树在文件系统的体现

(1) /sys/firmware/fdt:原始dtb文件
(2) /sys/firmware/devicetree:以目录结构程现的dtb文件,根节点对应base目录,每一个节点对应一个目录, 每一个属性对应一个文件。
(3) /sys/devices/platform:系统中所有的platform_device,有来自设备树的,也有来有.c文件中注册的对于来自设备树的platform_device,可以进入/sys/devices/platform/<设备名>/of_node查看它的设备树属性。
(4) /proc/device-tree 是链接文件,指向 /sys/firmware/devicetree/base。