Linux设备树进阶

一.设备树的中断属性

中断属性:
interrupt-controller:一个空的属性,用来定义该节点是一个接收中断的设备,即是个中断控制器。
#interrupt-cells:一个中断控制器节点的属性,声明了该中断控制器的中断指示符中cell 的个数,类似于#address-cells(决定了interrupt:后面元素的个数)。
interrupt-parent:一个设备节点的属性,指向设备所连接的中断控制器。如果这个设备节点没有该属性,那么这个节点继承父节点的这个属性。
interrupts:一个设备节点的属性,含一个中断指示符的列表,对应于该设备上的每个中断输出信号

二.设备树的时钟节点

时钟(Clock)用于描述硬件设备和系统中的时钟源以及时钟相关的配置和连接关系。时钟在计算机系统中起着至关重要的作用,用于同步和定时各种硬件设备的操作。时钟可以分为两个主要角色:时钟生产者(clock provider)和时钟消费者(clock consumer)。

1.时钟生产者

时钟生产者是负责生成和提供时钟信号的硬件或软件模块。它可以是时钟控制器、PLL、时钟发生器等。
时钟生产者在设备树中以时钟节点的形式表示。
时钟节点的属性:
clock-output-names:定义了输出时钟信号的名字。
clock-cells:它是一个整数值,表示时钟编号的位数。通常情况下,当 clock-cells 为 0 时表示一个时钟,为 1 表示多个时钟。具体示例如下所示:

示例1:单个时钟
osc24m: osc24m {
    compatible = "clock";
    clock-frequency = <24000000>;
    clock-output-names = "osc24m";
    #clock-cells = <O>;
};
示例2:多个时钟
clock: clock {
    #clock-cells = <1>;
    clock-output-names = "clock1", "clock2";

clock-frequency** : **是设备树中用于指定时钟频率的属性。它用于描述时钟节点所提供的时钟信号的频率,使用 Hertz (Hz) 作为单位。对于时钟生产者节点,clock-frequency 属性表示该节点生成的时钟信号的频率。
clock-indices:是指时钟生产者节点(如时钟控制器)所提供的时钟源的编号。通过在时钟消费者节点中使用该 属性,可以明确指定该节点所需的时钟源,并按照特定的顺序进行匹配。一个clock-indices示例如下所示:

scpi_dvfs: clocks-0 {
    #clock-cells = <1>;
    clock-indices = <0>, <1>, <2>;
    clock-output-names = "atlclk", "aplclk", "gpuclk";
};

在这个节点中"atlclk", “aplclk”, "gpuclk"三个时钟源的索引就分别被设置为了0、1、2
assigned-clocks 和 assigned-clock-rates在设备树中用于描述多路时钟的属性,通常一起使用。
assigned-clocks 属性用于标识时钟消费者节点所使用的时钟源。它是一个整数数组,每个元素对应一个时钟编号。通过在时钟消费者节点中使用 assigned-clocks 属性,可以指定该节点所需的时钟源。
assigned-clock-rates 属性用于指定每个时钟源的时钟频率。它是一个整数数组,每个元素对应一个时钟源的频率。时钟频率以 Hz (赫兹) 为单位表示。assigned-clock-rates 属性的元素数量和顺序应与 assigned-clocks 属性中的时钟编号相对应。

cru: clock-controller@fdd20000 {
    #clock-cells = <1>;
    assigned-clocks = <&pmucru CLK_RTC_32K>, <&cru ACLK_RKVDEC_PRE>;
    assigned-clock-rates = <32768>, <300000000>;
};

assigned-clock-parents 属性用于指定时钟消费者节点所使用的时钟源的父时钟源。在时钟消费者节点中使用 assigned-clock-parents 属性,可以明确指定该节点所需的父时钟源,并按照特定的顺序进行匹配。

clock: clock {
    assigned-clocks = <&clkcon 0>, <&pll 2>;
    assigned-clock-parents = <&pll 2>;
    assigned-clock-rates = <115200>, <9600>;
};

上述设备树表示了一个名为 clock 的时钟消费者节点,具有以下属性:
assigned-clocks 属性指定了该节点使用的时钟源,引用了两个时钟源节点:clkcon 0 和 pll 2。
assigned-clock-parents 属性指定了这些时钟源的父时钟源,引用了 pll 2 时钟源节点。
assigned-clock-rates 属性指定了每个时钟源的时钟频率,分别是 115200 和 9600。

2.时钟消费者

时钟消费者是依赖时钟信号的硬件设备或模块。它们通过引用时钟生产者节点提供的时钟源来获取时钟信号。
时钟消费者属性:
clocks:该属性用于指定时钟消费者节点所需的时钟源。它是一个整数数组,每个元素是一个时钟编号,表示时钟消费者需要的一个时钟源。
clock-names:可选属性,用于指定时钟消费者节点所需时钟源的名称。它是一个字符串数组,与 clocks 数组一一对应,用于提供时钟源的描述性名称。

clock: clock {
    clocks = <&cru CLK_VOP>;
    clock-names = "clk_vop";
};

clocks 属性指定了该节点使用的时钟源,引用了 cru 节点中的 CLK_VOP 时钟源。
clock-names 属性指定了时钟源的名称,这里是 “clk_vop”。

三.设备树的CPU节点

1.cpus节点
cpus 节点是一个容器节点,是处理器拓扑结构的顶层节点,其下包含了系统中每个处理器的子节点。每个子节点的名称通常为 cpu@X,其中 X 是处理器的索引号。每个子节点都包含了与处理器相关的属性,例如时钟频率、缓存大小等。
处理器属性:
cpu@X 子节点中的属性可以包括以下信息:
(1)device_type:指示设备类型为处理器(“cpu”)。
(2)reg:指定处理器的地址范围,通常是物理地址或寄存器地址。
(3)compatible:指定处理器的兼容性信息,用于匹配相应的设备驱动程序。
(4)clock-frequency:指定处理器的时钟频率。
(5)cache-size:指定处理器的缓存大小。
2.处理器拓扑关系:
除了处理器的基本属性,cpus 节点还可以包含其他用于描述处理器拓扑关系的节点,以提供更详细的处理器拓扑信息。这些节点可以帮助操作系统和软件了解处理器之间的连接关系、组织结构和特性。
cpu-map 节点:描述处理器的映射关系,通常在多核处理器系统中使用。
socket 节点:描述多处理器系统中的物理插槽或芯片组。
cluster 节点:描述处理器集群,即将多个处理器组织在一起形成的逻辑组。
core 节点:描述处理器核心,即一个物理处理器内的独立执行单元。
thread 节点:描述处理器线程,即一个物理处理器核心内的线程。
这些节点的嵌套关系可以在 cpus 节点下形成一个层次结构,反映了处理器的拓扑结构。
单核CPU示例:

cpus {
    #address-cells = <1>;
    #size-cells = <0>;
 
    cpu0: cpu@0 {
        compatible = "arm,cortex-a7";
        device_type = "cpu";
        // 其他属性...
    };
}

多核CPU示例:

cpus {
    #address-cells = <1>;
    #size-cells = <0>;
 
    cpu0: cpu@0 {
        device_type = "cpu";
        compatible = "arm,cortex-a9";
    };
 
    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a9";
    };
 
    cpu2: cpu@2 {
        device_type = "cpu";
        compatible = "arm,cortex-a9";
    };
 
    cpu3: cpu@3 {
        device_type = "cpu";
        compatible = "arm,cortex-a9";
    };
}

cpu-map 节点是设备树中用于描述大小核架构处理器的映射关系的节点之一。它的父节点必须是 cpus 节点,而子节点可以是一个或多个 cluster 和 socket 节点。通过 cpu-map 节点,可以定义不同核心和集群之间的连接和组织结构。
socket 节点用于描述处理器插槽(socket)之间的映射关系。每个 socket 子节点表示一个处理器插槽,可以使用 cpu-map-mask 属性来指定该插槽使用的核心。通过为每个 socket 子节点指定适当的 cpu-map-mask,可以定义不同插槽中使用的核心。这样,操作系统和软件可以了解到不同插槽之间的核心分配情况。
cluster 节点用于描述核心(cluster)之间的映射关系。每个 cluster 子节点表示一个核心集群,可以使用 cpu-map-mask 属性来指定该集群使用的核心。通过为每个 cluster 子节点指定适当的 cpu-map-mask,可以定义每个集群中使用的核心。这样,操作系统和软件可以了解到不同集群之间的核心分配情况。
通过在 cpu-map 节点中定义 socket 和 cluster 子节点,并为它们指定适当的 cpu-map-mask,可以提供处理器的拓扑结构信息。这对于操作系统和软件来说非常有用,因为它们可以根据这些信息进行任务调度和资源分配的优化,以充分利用大小核架构处理器的性能和能效特性。
一个大小核架构的具体示例如下所示:

cpus {
    #address-cells = <2>;
    #size-cells = <0>;
    cpu-map {
        cluster0 {
            core0 {
                cpu = <&cpu_l0>;
            };
            core1 {
                cpu = <&cpu_l1>;
            };
            core2 {
                cpu = <&cpu_l2>;
            };
            core3 {
                cpu = <&cpu_l3>;
            };
        };
        cluster1 {
            core0 {
                cpu = <&cpu_b0>;
            };
            core1 {
                cpu = <&cpu_b1>;
            };
        };
    };
 
        cpu_l0: cpu@0 {
                device_type = "cpu";
                compatible = "arm,cortex-a53", "arm,armv8";
        };
 
        cpu_l1: cpu@1 {
                device_type = "cpu";
                compatible = "arm,cortex-a53", "arm,armv8";
        };
 
        cpu_l2: cpu@2 {
                device_type = "cpu";
                compatible = "arm,cortex-a53", "arm,armv8";
        };
 
        cpu_l3: cpu@3 {
                device_type = "cpu";
                compatible = "arm,cortex-a53", "arm,armv8";
        };
 
        cpu_b0: cpu@100 {
                device_type = "cpu";
                compatible = "arm,cortex-a72", "arm,armv8";
        };
 
        cpu_b1: cpu@101 {
                device_type = "cpu";
                compatible = "arm,cortex-a72", "arm,armv8";
        };
};

四.设备树的GPIO节点

一段源码如下:

gpio0: gpio@fdd60000 {
    compatible = "rockchip,gpio-bank";
    reg = <0x0 0xfdd60000 0x0 0x100>;
    interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&pmucru PCLK_GPI00>, <&pmucru DBCLK_GPI00>;
    gpio-controller;
    #gpio-cells = <2>;
    gpio-ranges = <&pinctrl 0 0 32>;
    interrupt-controller;
    #interrupt-cells = <2>;
};

gpio-controller属性用于标识一个设备节点作为GPIO控制器。
#gpio-cells属性用于指定GPIO引脚描述符的编码方式, 属性值是一个整数。通常,这个值为2。GPIO引脚描述符是用于标识和配置GPIO引脚的一组值,例如引脚编号、引脚属性等。如果#gpio-cells属性被设置为了2,那么每个引脚描述属性中会有两个整数,如下:

reset-gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_LOW>;

其中RK_PB6 ,定义了RK引脚名。GPIO_ACTIVE_LOW表示设置为低电平。
gpio-ranges属性是设备树中一个用于描述GPIO映范围射的属性。
在设备树中,GPIO控制器的每个引脚都有一个本地编号,用于在控制器内部进行引脚寻址。然而,这些本地编号并不一定与外部引脚的物理编号或其他系统中使用的编号一致。为了解决这个问题,可以使用gpio-ranges属性将本地编号映射到实际的引脚编号。
gpio-ranges属性是一个包含一系列整数值的列表,每个整数值对应于设备树中的一个GPIO控制器。列表中的每个整数值按照特定的顺序提供以下信息:
(1)外部引脚编号的起始值。
(2)GPIO控制器内部本地编号的起始值。
(3)引脚范围的大小(引脚数量)。
在示例中gpio-ranges属性的值为<&pinctrl 0 0 32>,其中<&pinctrl>表示引用了名为pinctrl的引脚控制器节点,0 0 32表示外部引脚从0开始,控制器本地编号从0开始,共映射了32个引脚。
其他属性。一点源码如下:

gpio-controller@00000000 {
    compatible = "foo";
    reg = <0x00000000 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <18>;
    gpio-reserved-ranges = <0 4>, <12 2>;
    gpio-line-names = "MMC-CD", "MMC-WP",
                      "voD eth", "RST eth", "LED R",
                      "LED G", "LED B", "col A",
                      "col B", "col C", "col D",
                      "NMI button", "Row A", "Row B",
                      "Row C", "Row D", "poweroff",
                      "reset";
};

第6行的ngpios 属性指定了 GPIO 控制器所支持的 GPIO 引脚数量。它表示该设备上可用的 GPIO 引脚的总数。在这个例子中,ngpios` 的值为 18,意味着该 GPIO 控制器支持 18 个 GPIO 引脚。
第7行的gpio-reserved-ranges属性定义了保留的GPIO范围。每个范围由两个整数值表示,用尖括号括起来。保留的GPIO范围意味着这些GPIO引脚不可用或已被其他设备或功能保留。在这个例子中,有两个保留范围:<0 4>和<12 2>。<0 4>表示从第0个引脚开始的连续4个引脚被保留,而<12 2>表示从第12个引脚开始的连续2个引脚被保留。
第8行的gpio-line-names 属性定义了GPIO引脚的名称,以逗号分隔。每个名称对应一个 GPIO 引脚。这些名称用于标识和识别每个GPIO引脚的作用或连接的设备。

五.设备树中的pinctrl

1.pinmux介绍

Pinmux(引脚复用)是指在系统中配置和管理引脚功能的过程。在许多现代集成电路中,单个引脚可以具有多个功能,例如作为 GPIO、UART、SPI 或 I2C 等。通过使用引脚复用功能,可以在这些不同的功能之间切换。
引脚复用通过硬件和软件的方式实现。硬件层面,芯片设计会为每个引脚提供多个功能的选择。通过编程设置寄存器或开关,可以选择某个功能来连接引脚。这种硬件层面的配置通常是由引脚控制器(Pin Controller)或引脚复用控制器(Pin Mux Controller)负责管理。
软件层面,操作系统或设备驱动程序需要了解和配置引脚的功能。在设备树中,可以指定引脚的复用功能,将其连接到特定的硬件接口或功能。操作系统或设备驱动程序在启动过程中解析设备树,并根据配置对引脚进行初始化和设置。

2.使用pinctrl设置复用关系

pinctrl(引脚控制)用于描述和配置硬件设备上的引脚功能和连接方式。它是设备树的一部分,用于在启动过程中传递引脚配置信息给操作系统和设备驱动程序,以便正确地初始化和控制引脚。
在设备树中,pinctrl(引脚控制)使用了客户端和服务端的概念来描述引脚控制的关系和配置。
客户端:

node {
    pinctrl-names = "default", "wake up";
    pinctrl-0 = <&pinctrl_hog_1>;
    pinctrl-1 = <&pinctrl_hog_2>;
}

pinctrl-names 属性定义了两个状态名称:default 和 wake up。
pinctrl-0 属性指定了第一个状态 default 对应的引脚配置,引用了 pinctrl_hog_1 节点。
pinctrl-1 属性指定了第二个状态 wake up 对应的引脚配置,引用了 pinctrl_hog_2 节点。
这意味着设备可以处于两个不同的状态之一,每个状态分别使用不同的引脚配置。
服务端:
服务端是设备树中定义引脚配置的部分。它包含引脚组和引脚描述符,为客户端提供引脚配置选择。

其中<3 RK_PB1 4 &pcfg_pull_up>和<3 RK_PB2 4 &pcfg_pull_up>分别表示将GPIO3的PB1引脚设置为功能4,将GPIO3的PB2也设置为功能4,且电器属性都会设置为上拉。

六.DTB文件格式

1.DTB文件介绍

设备树 DTB 格式是设备树数据的平面二进制编码。它用于在软件程序之间交换设备树数据。例如,在启动操作系统时,固件会将 DTB 传递给操作系统内核。
DTB 格式在单个、线性、无指针数据结构中对设备树数据进行编码。它由一个小头部和三个可变大小的部分组成:内存保留块、结构块和字符串块。这些应该以该顺序出现在展平的设备树中。因此,设备树结构作为一个整体,当加载到内存地址时,将类似于下图

2.DTB文件解析

以下面的代码为例,对生成的DTB文件进行解析

/dts-v1/;
 
/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;
 
    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0, 115200";
    };
 
    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;
    };
 
    aliases {
        led1 = "/gpio@22020101";
    };
 
    node1 {
        #address-cells = <1>;
        #size-cells = <1>;
 
        gpio@22020102 {
            reg = <0x20220102 0x40>;
        };
    };
 
    node2 {
        node1-child {
            pinnum = <01234>;
        };
    };
 
    gpio@22020101 {
        compatible = "led";
        reg = <0x20220101 0x40>;
        status = "okay";
    };
};

使用二进制分析软件打开DTB文件并设置大端模式之后如下

2.1 header

devicetree 的头布局由以下 C 结构定义。所有的头字段都是 32 位整数,以大端格式存储。

struct fdt_header {
    uint32_t magic;                 // 设备树头部的魔数
    uint32_t totalsize;             // 设备树文件的总大小
    uint32_t off_dt_struct;         // 设备树结构体(节点数据)相对于文件开头的偏移量
    uint32_t off_dt_strings;        // 设备树字符串表相对于文件开头的偏移量
    uint32_t off_mem_rsvmap;        // 内存保留映射表相对于文件开头的偏移量
    uint32_t version;               // 设备树版本号
    uint32_t last_comp_version;     // 最后一个兼容版本号
    uint32_t boot_cpuid_phys;       // 启动 CPU 的物理 ID
    uint32_t size_dt_strings;       // 设备树字符串表的大小
    uint32_t size_dt_struct;        // 设备树结构体(节点数据)的大小
};

然后来查看二进制文件,其中4个字节表示一个单位,前十个单位分别代表上述的十个字段如下图

2.2内存预留块

内存保留块(Memory Reserved Block)是用于客户端程序的保护和保留物理内存区域的列表。这些保留区域不应被用于一般的内存分配,而是用于保护重要数据结构,以防止客户端程序覆盖这些数据。由于在示例设备树中没有设置内存保留块,所以相应的区域都为0,如下

内存保留块是一个由一组 64 位大端整数对构成的列表。每对整数对应一个保留内存区域,其中包含物理地址和区域的大小(以字节为单位)。这些保留区域应该彼此不重叠。
**格式: **内存保留块中的每个保留区域由一个64位大端整数对表示。每对由以下 C 结构表示:

struct fdt_reserve_entry { 
        uint64_t address; 
        uint64_t size;
};

其中的第一个整数表示保留区域的物理地址,第二个整数表示保留区域的大小(以字节为单位)。

2.3结构块

结构块是设备树中描述设备树节点的部分。它由一系列带有数据的令牌序列组成。
令牌类型:
a. FDT_BEGIN_NODE (0x00000001): FDT_BEGIN_NODE 标记表示一个节点的开始。它后面跟着节点的名称作为额外数据。
b. FDT_END_NODE (0x00000002): FDT_END_NODE 标记表示一个节点的结束。该标记没有额外的数据,紧随其后的是下一个标记。
c. FDT_PROP (0x00000003): FDT_PROP 标记表示设备树中属性的开始。它后面跟着描述属性的额外数据,该数据首先由属性的长度和名称组成,表示为以下 C 结构

struct {
        uint32_t len; 
        uint32_t nameoff;
}

长度表示属性值的字节长度,名称偏移量指向字符串块中存储属性名称的位置。在这个结构之后,属性的值作为字节字符串给出。属性值后可能需要填充零字节以对齐,然后是下一个令牌。
d. FDT_NOP (0x00000004): FDT_NOP 令牌可以被解析设备树的程序忽略。该令牌没有额外的数据,紧随其后的是下一个令牌,可以是任何有效的令牌。
e. FDT_END (0x00000009): FDT_END 标记表示结构块的结束。

2.4字符串块

字符串块用于存储设备树中使用的所有属性名称。它由一系列以空字符结尾的字符串组成,这些字符串在字符串块中简单地连接在一起,具体示例如下

字符串块中的字符串以空字符(\0)作为终止符来连接。这意味着每个字符串都以空字符结尾,并且下一个字符串紧跟在上一个字符串的末尾。这种连接方式使得字符串块中的所有字符串形成一个连续的字符序列。

七.设备树的展开

设备树展开是指将设备树二进制文件解析成内核中的设备节点(device_node)的过程。内核会读取设备树二进制文件的内容,并根据设备树的描述信息,构建设备树数据结构,例如设备节点、中断控制器、寄存器、时钟等。这些设备树数据结构将在内核运行时用于管理和配置硬件资源。
最终设备树二进制文件会被解析成device_node,device_node结构体具体内容如下所示:

struct device_node {
        const char *name;                // 设备节点的名称
        const char *type;                // 设备节点的类型
        phandle phandle;                  // 设备节点的句柄
        const char *full_name;           // 设备节点的完整名称
        struct fwnode_handle fwnode;     // 设备节点的固件节点句柄
 
        struct property *properties;     // 设备节点的属性列表
        struct property *deadprops;      // 已删除的属性列表
        struct device_node *parent;      // 父设备节点指针
        struct device_node *child;       // 子设备节点指针
        struct device_node *sibling;     // 兄弟设备节点指针
#if defined(CONFIG_OF_KOBJ)
        struct kobject kobj;             // 内核对象(用于 sysfs)
#endif
        unsigned long _flags;            // 设备节点的标志位
        void *data;                      // 与设备节点相关的数据指针
#if defined(CONFIG_SPARC)
        const char *path_component_name; // 设备节点的路径组件名称
        unsigned int unique_id;          // 设备节点的唯一标识
        struct of_irq_controller *irq_trans; // 设备节点的中断控制器
#endif
};

每个节点都有一个device_node结构体与之对应。
(1)name:name 字段表示设备节点的名称。设备节点的名称是在设备树中唯一标识该节点的字符串。它通常用于在设备树中引用设备节点。
(2)type:type 字段表示设备节点的类型。设备节点的类型提供了关于设备节点功能和所属设备类别的信息。它可以用于识别设备节点的用途和特性。
(3)properties:properties 字段是指向设备节点属性列表的指针。设备节点的属性包含了与设备节点相关联的配置和参数信息。属性以键值对的形式存在,可以提供设备的特定属性、寄存器地址、中断信息等。property字段同样定义在内核源码的“/include/linux/of.h”文件中,具体内容如下所示:

struct property {
        char *name;                    // 属性的名称
        int length;                    // 属性值的长度(字节数)
        void *value;                   // 属性值的指针
        struct property *next;         // 下一个属性节点指针
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
        unsigned long _flags;          // 属性的标志位
#endif
#if defined(CONFIG_OF_PROMTREE)
        unsigned int unique_id;        // 属性的唯一标识
#endif
#if defined(CONFIG_OF_KOBJ)
        struct bin_attribute attr;     // 内核对象二进制属性
#endif
};

(4)parent:parent 字段指向父设备节点。设备树中的设备节点按照层次结构组织,父设备节点是当前设备节点的直接上级。通过 parent 字段,可以在设备树中遍历设备节点的父子关系。
(5)child:child 字段指向子设备节点。在设备树中,一个设备节点可以拥有多个子设备节点。通过 child 字段,可以遍历设备节点的所有子设备节点。
(6)sibling:sibling 字段指向兄弟设备节点。在设备树中,同一级别的兄弟设备节点共享相同的父设备节点。通过 sibling 字段,可以在同级设备节点之间进行遍历

八.device_node展开成platform_device

device_node这时候还并不能跟内核中的platform_driver进行对接,而为了让操作系统能够识别和管理设备,需要将设备节点转换为平台设备。
转换规则:
根节点下包含 compatible 属性的子节点,对于每个子节点,会创建一个对应的 platform_device。
包含 compatible 属性,且值为 “simple-bus”、“simple-mfd” 或 “isa” 的节点以及它们的子节点。如果子节点包含 compatible 属性值则会创建一个对应的platform_device。
检查节点的 compatible 属性是否包含 “arm” 或 “primecell”。如果是,则不将该节点转换为 platform_device,而是将其识别为 AMBA 设备。

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

查找节点

1、of_find_node_by_name 函数

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

函数参数和返回值含义如下:
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。
name:要查找的节点名字。
返回值:找到的节点,如果为 NULL 表示查找失败。
2、of_find_compatible_node 函数
of_find_compatible_node 函数根据 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 表示查找失败
3、of_find_node_by_path 函数
of_find_node_by_path 函数通过路径来查找指定的节点,函数原型如下

inline struct device_node *of_find_node_by_path(const char *path)

函数参数和返回值含义如下:
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个
节点的全路径。
返回值:找到的节点,如果为 NULL 表示查找失败

查找父/子节点

1、of_get_parent 函数
of_get_parent 函数用于获取指定节点的父节点(如果有父节点的话),函数原型如下:

struct device_node *of_get_parent(const struct device_node *node)

函数参数和返回值含义如下:
node:要查找的父节点的节点。
返回值:找到的父节点。
2、of_get_next_child 函数
of_get_next_child 函数用迭代的方式查找子节点,函数原型如下:

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

函数参数和返回值含义如下:
node:父节点。
prev:前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
返回值:找到的下一个子节点。

提取属性

1、of_find_property 函数
of_find_property 函数用于查找指定的属性,函数原型如下:

property *of_find_property(const struct device_node *np,
                                const char *name, int *lenp)

函数参数和返回值含义如下:
np:设备节点。
name: 属性名字。
lenp:属性值的字节数
返回值:找到的属性。
2、of_property_count_elems_of_size 函数
of_property_count_elems_of_size 函数用于获取属性中元素的数量,比如 reg 属性值是一个数组,那么使用此函数可以获取到这个数组的大小,此函数原型如下:

int of_property_count_elems_of_size(const struct device_node *np,
                                    const char *propname,int elem_size)

函数参数和返回值含义如下:
np:设备节点。
proname: 需要统计元素数量的属性名字。
elem_size:元素大小。 (设备树中,数字是u32类型,传参可以是sizeof(u32))
返回值:得到的属性元素数量。
3、of_property_read_u32_index 函数
of_property_read_u32_index 函数用于从属性中获取指定标号的 u32 类型数据值(无符号 32位),比如某个属性有多个 u32 类型的值,那么就可以使用此函数来获取指定标号的数据值,此函数原型如下:

int of_property_read_u32_index(const struct device_node *np,
                            const char *propname, u32 index, u32 *out_value)

函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
index:要读取的值标号。
out_value:读取到的值
返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
4、 of_property_read_u8_array 函数
of_property_read_u16_array 函数
of_property_read_u32_array 函数
of_property_read_u64_array 函数
这 4 个函数分别是读取属性中 u8、u16、u32 和 u64 类型的数组数据,比如大多数的 reg 属性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据。这四个函数的原型如下:

函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值,分别为 u8、u16、u32 和 u64。
sz:要读取的数组元素数量。
返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
5、of_property_read_u8 函数
of_property_read_u16 函数
of_property_read_u32 函数
of_property_read_u64 函数
有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性,分别用于读取 u8、u16、u32 和 u64 类型属性值,函数原型如下:

函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值。
返回值:0,读取成功,负值,读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小。
6、of_property_read_string 函数
of_property_read_string 函数用于读取属性中字符串值,函数原型如下:

int of_property_read_string(struct device_node *np,
                const char *propname, const char **out_string)

函数参数和返回值含义如下:
np:设备节点。
proname: 要读取的属性名字。
out_string:读取到的字符串值。
返回值:0,读取成功,负值,读取失败。
7、of_n_addr_cells 函数
of_n_addr_cells 函数用于获取#address-cells 属性值,函数原型如下:

int of_n_addr_cells(struct device_node *np)

函数参数和返回值含义如下:
np:设备节点。
返回值:获取到的#address-cells 属性值。
8、of_n_size_cells 函数
of_size_cells 函数用于获取#size-cells 属性值,函数原型如下:

int of_n_size_cells(struct device_node *np)

函数参数和返回值含义如下:
np:设备节点。
返回值:获取到的#size-cells 属性值。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嵌入式小李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值