Linux设备树,DTS、DTB浅析

文章目录

  • [1 什么是设备树](#1 什么是设备树)
    • [1.1 设备树的核心作用](#1.1 设备树的核心作用)
  • [2 DTS、DTB及DTC](#2 DTS、DTB及DTC)
  • [3 DTS语法](#3 DTS语法)
    • [3.1 .dtsi头文件](#3.1 .dtsi头文件)
    • [3.2 设备节点](#3.2 设备节点)
    • [3.3 标准属性](#3.3 标准属性)
    • [3.4 compatible属性](#3.4 compatible属性)
    • [3.5 向节点追加或修改内容](#3.5 向节点追加或修改内容)
  • [4 设备树在linux系统中的体现](#4 设备树在linux系统中的体现)
    • [4.1 特殊节点](#4.1 特殊节点)
      • [4.1.1 aliases子节点](#4.1.1 aliases子节点)
      • [4.1.2 chosen子节点](#4.1.2 chosen子节点)
  • [5 linux内核解析DTB文件](#5 linux内核解析DTB文件)
  • [6 绑定信息文档](#6 绑定信息文档)
  • [7 设备树常用OF操作函数](#7 设备树常用OF操作函数)
    • [7.1 查找节点的OF函数](#7.1 查找节点的OF函数)
    • [7.2 查找父/子节点的OF函数](#7.2 查找父/子节点的OF函数)
    • [7.3 提取属性值的OF函数](#7.3 提取属性值的OF函数)
    • [7.4 其他常用的OF函数](#7.4 其他常用的OF函数)

1 什么是设备树

设备树(Device Tree),描述设备设备树的文件叫做DTS(Device Tree Source),DTS文件采用了树形结构来描述板机设备,也就是开发板信息,比如CPU数量、内存基地址、IIC接口上接了那些设备、SPI 接口上接了那些设备等。

在下图片中,树的主干就是系统总线,IIC控制器、SPI控制器等都是接到系统主线的分支上的。通过DTS这个文件描述设备信息是有相关的语法规则的,并且在Linux内核中只有3.x版本以后的才支持设备树。

1.1 设备树的核心作用

  1. 硬件抽象与解耦:设备树将硬件描述(如CPU、内存、外设等)与内核代码分离,使内核无需针对特定硬件进行硬编码。硬件变更时,只需修改设备树文件,无需重新编译内核。
  2. 支持多硬件平台:同一内核镜像可通过加载不同的设备树文件,适配多种硬件配置(如开发板、量产设备),显著提升内核的可移植性。
  3. 动态硬件配置:内核在启动时解析设备树,动态初始化硬件设备,避免静态编译导致的代码冗余。

2 DTS、DTB及DTC

设备树源文件扩展名为.dts,我们在开发板中使用的一直都是.dtb文件,DTS是设备树源码文件,DTB是将DTS编译后得到的二进制文件,使用DTC工具将DTS源码文件编译为DTB设备树文件。设备树文件(.dtb)就是在UBOOT通过bootz或者bootm命令向Linux内核中传递的二进制设备树文件(.dtb)。

3 DTS语法

3.1 .dtsi头文件

与C和C++语言一样,设备树也支持头文件,后缀名为.dtsi,设备树头文件中可以使用#include来引用.h、.dtsi和.dts文件。

一般.dtsi文件用于描述SOC的内部外设信息,比如CPU架构、主频、外设寄存器地址范围,如UART、IIC等等,如果一个系列里有多个SOC就会把相同外设信息提炼到一个.dtsi文件里,这样为了减少代码的冗余,以stm32mp151.dtsistm32mp153.dtsistm32mp157.dtsi为例。

stm32mp151.dtsi文件内容(部分):

c 复制代码
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
 * Copyright (C) STMicroelectronics 2017 - All Rights Reserved
 * Author: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
 */
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/clock/stm32mp1-clks.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/reset/stm32mp1-resets.h>
#include <dt-bindings/thermal/thermal.h>

/ {
	#address-cells = <1>;
	#size-cells = <1>;

	cpus {
		#address-cells = <1>;
		#size-cells = <0>;

		cpu0: cpu@0 {
			compatible = "arm,cortex-a7";
			device_type = "cpu";
			reg = <0>;
			clocks = <&scmi0_clk CK_SCMI0_MPU>;
			clock-names = "cpu";
			operating-points-v2 = <&cpu0_opp_table>;
			nvmem-cells = <&part_number_otp>;
			nvmem-cell-names = "part_number";
			#cooling-cells = <2>;
		};
	};

	cpu0_opp_table: cpu0-opp-table {
		compatible = "operating-points-v2";
		opp-shared;
	};

	arm-pmu {
		compatible = "arm,cortex-a7-pmu";
		interrupts = <GIC_SPI 200 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-affinity = <&cpu0>;
		interrupt-parent = <&intc>;
	};
	
	// ......
	
};

stm32mp153.dtsi文件内容:

c 复制代码
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
 * Copyright (C) STMicroelectronics 2019 - All Rights Reserved
 * Author: Alexandre Torgue <alexandre.torgue@st.com> for STMicroelectronics.
 */

#include "stm32mp151.dtsi"

/ {
	cpus {
		cpu1: cpu@1 {
			compatible = "arm,cortex-a7";
			device_type = "cpu";
			reg = <1>;
			clocks = <&scmi0_clk CK_SCMI0_MPU>;
			clock-names = "cpu";
			operating-points-v2 = <&cpu0_opp_table>;
		};
	};

	arm-pmu {
		interrupts = <GIC_SPI 200 IRQ_TYPE_LEVEL_HIGH>,
			     <GIC_SPI 201 IRQ_TYPE_LEVEL_HIGH>;
		interrupt-affinity = <&cpu0>, <&cpu1>;
	};

	soc {
		m_can1: can@4400e000 {
			compatible = "bosch,m_can";
			reg = <0x4400e000 0x400>, <0x44011000 0x1400>;
			reg-names = "m_can", "message_ram";
			interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>;
			interrupt-names = "int0", "int1";
			clocks = <&scmi0_clk CK_SCMI0_HSE>, <&rcc FDCAN_K>;
			clock-names = "hclk", "cclk";
			bosch,mram-cfg = <0x0 0 0 32 0 0 2 2>;
			status = "disabled";
		};

		m_can2: can@4400f000 {
			compatible = "bosch,m_can";
			reg = <0x4400f000 0x400>, <0x44011000 0x2800>;
			reg-names = "m_can", "message_ram";
			interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>;
			interrupt-names = "int0", "int1";
			clocks = <&scmi0_clk CK_SCMI0_HSE>, <&rcc FDCAN_K>;
			clock-names = "hclk", "cclk";
			bosch,mram-cfg = <0x1400 0 0 32 0 0 2 2>;
			status = "disabled";
		};
	};
};

stm32mp157.dtsi文件内容:

c 复制代码
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
 * Copyright (C) STMicroelectronics 2019 - All Rights Reserved
 * Author: Alexandre Torgue <alexandre.torgue@st.com> for STMicroelectronics.
 */

#include "stm32mp153.dtsi"

/ {
	soc {
		gpu: gpu@59000000 {
			compatible = "vivante,gc";
			reg = <0x59000000 0x800>;
			interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
			clocks = <&rcc GPU>, <&rcc GPU_K>;
			clock-names = "bus" ,"core";
			resets = <&rcc GPU_R>;
			status = "disabled";
		};

		dsi: dsi@5a000000 {
			compatible = "st,stm32-dsi";
			reg = <0x5a000000 0x800>;
			phy-dsi-supply = <&reg18>;
			clocks = <&rcc DSI_K>, <&scmi0_clk CK_SCMI0_HSE>, <&rcc DSI_PX>;
			clock-names = "pclk", "ref", "px_clk";
			resets = <&rcc DSI_R>;
			reset-names = "apb";
			status = "disabled";
		};
	};
};

上述设备树头文件中,157的使用#include "stm32mp153.dtsi"包含了153的,在153的头文件使用#include "stm32mp151.dtsi"的包含了151的,151中使用了基础头文件定义了151的外设信息。151就类似于是一个父类,而153和157是151的子类。基础上得到的"派生类"。因此 ST就把最基本的外设资源都写在stm32mp151.dtsi文件里。 stm32mp151.dtsi就是描述151、153和157共有的外设信息的。

3.2 设备节点

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

c 复制代码
/ {
	aliases {
		can0 = &flexcan1; 
	}; 
	 cpus {
	  	#address-cells = <1>; 
	  	#size-cells = <0>;  
	  	cpu0: cpu@0 { 
		  	compatible = "arm,cortex-a7"; 
		  	device_type = "cpu"; 
		  	reg = <0>; 
	  	}; 
	}; 
	intc: interrupt-controller@00a01000 { 
		compatible = "arm,cortex-a7-gic"; 
		#interrupt-cells = <3>; 
		interrupt-controller; 
		reg = <0x00a01000 0x1000>, 
		<0x00a02000 0x100>; 
	}; 
} 
  • /是根节点,每个设备树文件只有一个根节点,如果工程中有两个或者多个文件都有一个/根节点,那么这些文件中的根节点的内容会合并成一个根节点;
  • aliases、cpus和intc是三个子节点,在设备树中节点命名格式为node-name@unit-address
    • node-name是节点的名字,为ASCII字符串,节点名字应该能够清晰的辨别出节点的功能,比如uart1就表示这个节点是UART1外设。unit-address一般表示设备的地址或寄存器首地址,如果某个节点没有地址或者寄存器的话unit-address可以不要,比如cpu@0interrupt-controller@00a01000
    • cpu0:cpu@0并不是node-name@unit-address这样的格式,而是用:隔开成了两部分,:前面是节点标签,:后面是节点名字,格式为lable:node-name@unit-address,引入label的目的就是为了方便访问节点,可以直接通过&label来访问这个节点,比如通过&cpu0就可以访问cpu@0这个节点,而不需要输入完整的节点名字
  • 上述代码中的cpu0也是一个节点,只是cpu0是cpus的子节点。每个节点都有不同的属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。

设备树中常用的数据形式如下所示:

数据形式 实现方式 详细描述
字符串 compatible = "arm,cortex-a7"; 设置compatible属性的值为字符串arm,cortex-a7
32位无符号整数 reg=<0> 设置reg的值也可以设置为一组值reg=<0 0x123456 100>;
字符串列表 compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand"; 字符串与字符串之间用隔开

3.3 标准属性

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

  • compatible属性

    compatible属性也叫做"兼容性"属性,这是一个非常重要的属性!compatible属性的值是一个字符串 列表,compatibel属性用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,compatible属性格式如下:

    d 复制代码
    "manufacturer,model"

    其中的manufacturer表示厂商,model一般是模块对应驱动的名字。

    d 复制代码
    compatible = "cirrus,cs42l51";  

    其中 cirrus表示厂商是 Cirrus Logic,cs42l51表示驱动模块名字。 compatible也可以多个属性值。比如:

    d 复制代码
    compatible = "cirrus,my_cs42l51","cirrus,cs42l51";

    这样设备就有两个属性值,这个设备首先使用第一个兼容值在Linux内核里面查找,看看能不能找到与之匹配的驱动文件,如果没有找到的话就使用第二个兼容值查,以此类推,直到查找完compatible属性中的所有值。

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

    d 复制代码
    static const struct of_device_id ahci_of_match[] = {
        { .compatible = "mediatek,mtk-ahci", },
        {},

};

```

数组ahci_of_match就是mtk-ahci这个驱动文件的匹配表,此匹配表只有一个匹配值mediatek,mtk-ahci。如果在设备树中有哪个节点的compatible属性值与此相等,那么这个节点就会使用此驱动文件。

  • model属性

    model属性值也是一个字符串,一般model属性描述开发板的名字或者设备模块信息,比如名字什么的,比如:

    d 复制代码
    model = "STMicroelectronics STM32MP157C-DK2 Discovery Board";
  • status属性

    status属性看名字就知道是和设备状态有关的,status属性值也是字符串,字符串是设备的状态信息,可选的状态如表所示:

描述
okay 表明设备是可操作的
disabled 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于disabled的具体含义还要看设备绑定文档
fail 表明设备不可操作,设备检测到了一系列的错误,而且设备也大可能变得可操作
fail-sss 含义和fail相同,后面的sss部分是检测到的错误内容
  • #address-cells#size-cells属性

    这两个属性的值都是无符号32位整形,#address-cells和#size-cells这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells属性值决定了子节点reg属性中地址信息所占用的字长(32位),#size-cells属性值决定了子节点reg属性中长度信息所占的字长(32位)。#address-cells和#size-cells表明了子节点应该如何编写reg属性值,一般reg属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度,reg属性的格式为:

    d 复制代码
    reg = <address1 length1 address2 length2 address3 length3............>

    每个"address length"组合表示一个地址范围,其中address是起始地址,length是地址长度,#address-cells表明address这个数据所占用的字长,,#size-cells表明length这个数据所占用的字长,比如:

c 复制代码
spi4 { 
	compatible = "spi-gpio"; 
	#address-cells = <1>;    // 说明了spi4的子节点reg属性中起始地址所占用的字长为1
	#size-cells = <0>;       // 地址长度所占用的字长为0
	gpio_spi: gpio_spi@0 {   // reg属性值为0,因为父节点中设置了相关的值,继承父节点起始地址,没有设置地址长度
		compatible = "fairchild,74hc595"; 
		reg = <0>; 
	}; 
 }; 
aips3: aips-bus@02200000 { 
	compatible = "fsl,aips-bus", "simple-bus"; 
	#address-cells = <1>; 
	#size-cells = <1>; 
	dcp: dcp@02280000 { 
		compatible = "fsl,imx6sl-dcp"; 
		reg = <0x02280000 0x4000>; #address=0X02280000,length=0X4000
	 }; 
};
  • reg属性

    reg属性值一般是(address,length)。一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息

  • ranges属性

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

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

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

  • name属性

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

  • device_type属性

    device_type属性值为字符串,IEEE1275会用到此属性,用于描述设备的FCode,但是设备树没有FCode,所以此属性也被抛弃了。此属性只能用于cpu节点或者memory节点。Stm32mp151.dtsi的cpu0节点用到了此属性,内容如下所示:

c 复制代码
	cpus {
		#address-cells = <1>;
		#size-cells = <0>;

		cpu0: cpu@0 {
			compatible = "arm,cortex-a7";
			device_type = "cpu";
		};
	};

3.4 compatible属性

每个节点都有 compatible属性,根节点/也不例外,设备节点的compatible属性值是为了匹配Linux内核中的驱动程序。

c 复制代码
/ {
	model = "STMicroelectronics STM32MP157D eval daughter";
	compatible = "st,stm32mp157d-atk", "st,stm32mp157";
	// ......
};

可以看出,compatible有两个值:"st,stm32mp157d-atk"和"st,stm32mp157"。通过根节点的compatible属性可以知道我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,比如这里使用的是"stm32mp157d-atk"这个设备,第二个值描述了设备所使用的SOC,比如这里使用的是stm32mp157"这颗SOC。Linux内核会通过根节点的compoatible属性查看是否支持此设备,如果支持的话设备就会启动Linux内核。

当Linux内核引入设备树以后使用DT_MACHINE_START。来检查Linux内核是否支持这个设备。在Linux内核中通过start_kernel函数启动内核,然后start_kernel函数会调用setup_arch函数来匹配machine_desc,然后再调用setup_machine_fdt函数进一步获取匹配的machine_desc,这个函数的参数就是atags的首地址(也就是uboot传递给Linux内核的dtb文件首地址),setup_machine_fdt函数返回值就是找到的最匹配的machine_desc。然后通过of_get_flat_dt_root获取设备树的根节点。

3.5 向节点追加或修改内容

使用&label的形式来访问节点,不再基础文件中追加或修改内容,在额外的新文件中编写要追加或修改的内容。例如:

d 复制代码
&i2c1 {
	// 要追加或者修改的内容
};

4 设备树在linux系统中的体现

Linux内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree目录下根据节点名字来创建不同文件夹,目录下是根节点"/"的所有属性和子节点。文件的层次结构和设备树文件的层次结构一一对应。

4.1 特殊节点

4.1.1 aliases子节点

d 复制代码
    aliases {

        serial0 = &uart4;

    };

单词aliases的意思是"别名",因此aliases节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上label,然后通过&label来访问节点,这样也很方便,而且设备树里面大量的使用&label的形式来访问节点。

4.1.2 chosen子节点

d 复制代码
	chosen {
		stdout-path = "serial0:115200n8";
	};

chosen并不是一个真实的设备,chosen节点主要是为了uboot向Linux内核传递数据,重点是bootargs参数。一般.dts文件中chosen节点通常为空或者内容很少。

5 linux内核解析DTB文件

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

6 绑定信息文档

路径:Linux源码目录/Documentation/devicetree/bindings/

根据设备查找对应文件夹中的文件即可。

7 设备树常用OF操作函数

Linux内核给我们提供了一系列的函数来获取设备树中的节点或者属性信息,这一系列的函数都有一个统一的前缀"of_",所以在很多资料里面也被叫做OF函数。这些OF函数原型都定义在include/linux/of.h文件中。

7.1 查找节点的OF函数

设备都是以节点的形式挂到设备树上,要想获取这个设备的其他属性信息,需要先获取设备节点,Linux内核使用device_node结构体来描述一个节点,此结构体定义在文件include/linux/of.h中,定义如下:

c 复制代码
struct device_node {
	const char *name;               /* 节点名字 */
	phandle phandle;
	const char *full_name;          /* 节点全名 */
	struct fwnode_handle fwnode;

	struct	property *properties;   /* 属性 */
	struct	property *deadprops;	/* removed properties */
	struct	device_node *parent;    /* 父节点 */
	struct	device_node *child;     /* 子节点 */
	struct	device_node *sibling;
#if defined(CONFIG_OF_KOBJ)
	struct	kobject kobj;
#endif
	unsigned long _flags;
	void	*data;
#if defined(CONFIG_SPARC)
	unsigned int unique_id;
	struct of_irq_controller *irq_trans;
#endif
};
c 复制代码
///
/// \brief of_find_node_by_name     函数通过节点名字查找指定的节点
/// \param from                     开始查找的节点,如果为 NULL表示从根节点开始查找整个设备树
/// \param name                     要查找的节点名字
/// \return                         找到的节点,如果为 NULL表示查找失败
///
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);

///
/// \brief of_find_node_by_type     函数通过 device_type属性查找指定的节点
/// \param from                     开始查找的节点,如果为 NULL表示从根节点开始查找整个设备树
/// \param type                     要查找的节点对应的 type字符串,也就是 device_type属性值
/// \return                         找到的节点,如果为 NULL表示查找失败
///
struct device_node *of_find_node_by_type(struct device_node *from, const char *type);

///
/// \brief of_find_compatible_node      根据 device_type和 compatible这两个属性查找指定的节点
/// \param from                         开始查找的节点,如果为 NULL表示从根节点开始查找整个设备树
/// \param type                         要查找的节点对应的 type字符串,也就是 device_type属性值,可以为 NULL,表示忽略掉 device_type属性
/// \param compatible                   要查找的节点所对应的 compatible属性列表
/// \return                             找到的节点,如果为 NULL表示查找失败
///
struct device_node *of_find_compatible_node(struct device_node *from,
                                            const char *type,
                                            const char *compatible);

///
/// \brief of_find_matching_node_and_match  通过 of_device_id匹配表来查找指定的节点
/// \param from                             开始查找的节点,如果为 NULL表示从根节点开始查找整个设备树
/// \param matches                          of_device_id匹配表,也就是在此匹配表里面查找节点
/// \param match                            找到的匹配的 of_device_id
/// \return                                 找到的节点,如果为 NULL表示查找失败
///
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);

///
/// \brief of_find_node_by_path     通过路径来查找指定的节点
/// \param path                     带有全路径的节点名,可以使用节点的别名,比如 "/backlight"就是 backlight这个节点的全路径
/// \return                         找到的节点,如果为 NULL表示查找失败
///
struct device_node *of_find_node_by_path(const char *path);

7.2 查找父/子节点的OF函数

c 复制代码
///
/// \brief of_get_parent        用于获取指定节点的父节点 (如果有父节点的话 )
/// \param node                 要查找的父节点的节点
/// \return                     找到的父节点
///
struct device_node *of_get_parent(const struct device_node *node);

///
/// \brief of_get_next_child    用迭代的查找子节点
/// \param node                 父节点
/// \param prev                 前一个子节点,也就是从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始
/// \return                     找到的下一个子节点
///
struct device_node *of_get_next_child(const struct device_node *node,
                         struct device_node *prev);

7.3 提取属性值的OF函数

节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,Linux内核中使用结构体property表示属性,此结构体同样定义在文件include/linux/of.h中,内容如下:

c 复制代码
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
};
c 复制代码
///
/// \brief of_find_property     用于查找指定的属性
/// \param np                   设备节点
/// \param name                 属性名字
/// \param lenp                 属性值的字节数
/// \return                     找到的属性
///
struct property *of_find_property(const struct device_node *np,
                     const char *name,
                     int *lenp);
///
/// \brief of_property_count_elems_of_size  用于获取属性中元素的数量
/// \param np                               设备节点
/// \param propname                         需要统计元素数量的属性名字
/// \param elem_size                        元素长度
/// \return                                 得到的属性元素数量
///
int of_property_count_elems_of_size(const struct device_node *np,
                const char *propname, int elem_size);
///
/// \brief of_property_read_u32_index   函数用于从属性中获取指定标号的 u32类型数据值 (无符号 32位 )
/// \param np                           设备节点
/// \param propname                     要读取的属性名字
/// \param index                        要读取的值标号
/// \param out_value                    读取到的值
/// \return                             0读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小
///
int of_property_read_u32_index(const struct device_node *np,
                       const char *propname,
                       u32 index, u32 *out_value);

///
/// \brief of_property_read_u8_array          
/// \brief of_property_read_u16_array
/// \brief of_property_read_u32_array
/// \brief of_property_read_u64_array         4个函数分别是读取属性中 u8、 u16、 u32和 u64类型的数组数据
/// \param np                                 设备节点
/// \param propname                           要读取的属性名字
/// \param out_values                         读取到的数组值,分别为u8、 u16、 u32和 u64
/// \param sz                                 要读取的数组元素的数量
/// \return                                   0,读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小
///
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);
					     
///
/// \brief of_property_read_u8
/// \brief of_property_read_u16
/// \brief of_property_read_u32
/// \brief of_property_read_u64     四个函数用于读取这种只有一个整形值的属性
/// \param np                       设备节点
/// \param propname                 要读取的属性名字
/// \param out_value                读取到的数组值
/// \return                         0,读取成功,负值,读取失败,-EINVAL表示属性不存在,-ENODATA表示没有要读取的数据,-EOVERFLOW表示属性值列表太小
///
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);
                       
///
/// \brief of_property_read_string  用于读取属性中字符串值
/// \param np                       设备节点
/// \param propname                 要读取的属性名字
/// \param out_string               读取到的字符串值
/// \return                         0,读取成功;负值,读取失败
///
int of_property_read_string(const struct device_node *np,
                   const char *propname,
                   const char **out_string);
///
/// \brief of_n_addr_cells  获取 #address-cells 属性值
/// \param np               设备节点
/// \return                 获取到的 #address-cells 属性值
///
int of_n_addr_cells(struct device_node *np);

///
/// \brief of_n_size_cells  获取 #size-cells 属性值
/// \param np               设备节点
/// \return                 获取到的 #size-cells 属性值
///
int of_n_size_cells(struct device_node *np);

7.4 其他常用的OF函数

c 复制代码
///
/// \brief of_device_is_compatible  函数用于查看节点的 compatible属性是否有包含 compat指定的字符串,也就是检查设备节点的兼容性
/// \param device                   设备节点
/// \param compat                   要查看的字符串
/// \return                         0,节点的 compatible属性中不包含 compat指定的字符串;正数,节点的 compatible属性中包含 compat指定的字符串
///
int of_device_is_compatible(const struct device_node *device,
                      const char *compat);

///
/// \brief of_get_address   获取地址相关属性
/// \param dev              设备节点
/// \param index            要读取的地址标号
/// \param size             地址长度
/// \param flags            参数,比如 IORESOURCE_IO、 IORESOURCE_MEM等
/// \return                 读取到的地址数据首地址,为 NULL的话表示读取失败
///
const __be32 *of_get_address(struct device_node *dev, int index,
               u64 *size, unsigned int *flags);

///
/// \brief of_translate_address     函数负责将从设备树读取到的地址转换为物理地址
/// \param np                       设备节点
/// \param addr                     要转换的地址
/// \return                         得到的物理地址,如果为 OF_BAD_ADDR的话表示转换失败
///
u64 of_translate_address(struct device_node *np, const __be32 *addr);

IIC、SPI、GPIO等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间,Linux内核使用resource结构体来描述一段内存空间,"resource"翻译出来就是"资源",因此用resource结构体描述的都是设备资源信息,resource结构体定义在文件include/linux/ioport.h中,定义如下:

c 复制代码
struct resource {
	resource_size_t start;
	resource_size_t end;
	const char *name;
	unsigned long flags;
	unsigned long desc;
	struct resource *parent, *sibling, *child;
};

对于32位的SOC来说,resource_size_t是u32类型的。其中start表示开始地址,end表示结束地址,name是这个资源的名字,flags是资源标志位,一般表示资源类型,可选的资源标志定义在文件include/linux/ioport.h中,如下所示:

c 复制代码
/*
 * IO resources have these defined flags.
 *
 * PCI devices expose these flags to userspace in the "resource" sysfs file,
 * so don't move them.
 */
#define IORESOURCE_BITS		0x000000ff	/* Bus-specific bits */

#define IORESOURCE_TYPE_BITS	0x00001f00	/* Resource type */
#define IORESOURCE_IO		0x00000100	/* PCI/ISA I/O ports */
#define IORESOURCE_MEM		0x00000200
#define IORESOURCE_REG		0x00000300	/* Register offsets */
#define IORESOURCE_IRQ		0x00000400
#define IORESOURCE_DMA		0x00000800
#define IORESOURCE_BUS		0x00001000

#define IORESOURCE_PREFETCH	0x00002000	/* No side effects */
#define IORESOURCE_READONLY	0x00004000
#define IORESOURCE_CACHEABLE	0x00008000
#define IORESOURCE_RANGELENGTH	0x00010000
#define IORESOURCE_SHADOWABLE	0x00020000

#define IORESOURCE_SIZEALIGN	0x00040000	/* size indicates alignment */
#define IORESOURCE_STARTALIGN	0x00080000	/* start field is alignment */

#define IORESOURCE_MEM_64	0x00100000
#define IORESOURCE_WINDOW	0x00200000	/* forwarded by bridge */
#define IORESOURCE_MUXED	0x00400000	/* Resource is software muxed */

#define IORESOURCE_EXT_TYPE_BITS 0x01000000	/* Resource extended types */
#define IORESOURCE_SYSRAM	0x01000000	/* System RAM (modifier) */

#define IORESOURCE_EXCLUSIVE	0x08000000	/* Userland may not map this resource */

#define IORESOURCE_DISABLED	0x10000000
#define IORESOURCE_UNSET	0x20000000	/* No address assigned yet */
#define IORESOURCE_AUTO		0x40000000
#define IORESOURCE_BUSY		0x80000000	/* Driver has marked this resource busy */

最常见的资源标志就是IORESOURCE_MEM、IORESOURCE_REG和IORESOURCE_IRQ等。接下来我们回到of_address_to_resource函数,此函数看名字像是从设备树里面提取资源值,但是本质上就是将reg属性值,然后将其转换为resource结构体类型。

c 复制代码
///
/// \brief of_address_to_resource   将 reg属性值,然后将其转换为 resource结构体类型
/// \param dev                      设备节点
/// \param index                    地址资源标号
/// \param r                        得到的resource类型的资源之
/// \return                         0,成功;负值,失败
///
int of_address_to_resource(struct device_node *dev, int index,
                  struct resource *r);

of_iomap函数用于直接内存映射,以前会通过ioremap函数来完成物理地址到虚拟地址的映射,采用设备树以后就可以直接通过of_iomap函数来获取内存地址所对应的虚拟地址,不需要使用ioremap函数了。当然了,也可以使用ioremap函数来完成物理地址到虚拟地址的内存映射,只是在采用设备树以后,大部分的驱动都使用of_iomap函数了。of_iomap函数本质上也是将reg属性中地址信息转换为虚拟地址,如果reg属性有多段的话,可以通过index参数指定要完成内存映射的是哪一段。

c 复制代码
///
/// \brief of_iomap     将reg属性中地址信息转换为虚拟地址,如果 reg属性有多段的话,
///                     可以通过 index参数指定要完成内存映射的是哪一段
/// \param device       设备节点
/// \param index        reg属性中要完成内存映射的段,如果reg属性只有一段的话index就设置为0
/// \return             经过内存映射后的虚拟内存首地址,如果为 NULL的话表示内存映射失败
///
void __iomem *of_iomap(struct device_node *device, int index);
相关推荐
虾..2 小时前
Linux 五种IO模型
linux·服务器·数据库
KillerNoBlood2 小时前
OpenClaw笔记
linux·网络·笔记
吹牛不交税2 小时前
vben admin框架vue项目发布部署到linux的docker中
linux·运维·docker
聆风吟º2 小时前
【C标准库】深入理解C语言memcpy函数:用法、原理与避坑指南
c语言·开发语言·memcpy·库函数
凤年徐2 小时前
Linux 基础指令与权限管理完全指南
linux·运维·chrome
C++ 老炮儿的技术栈2 小时前
现代 C++(C++11 及以后)的移动语义
linux·c语言·开发语言·c++·github
LJianK12 小时前
《Java 数据分组的四种姿势:从 for 循环到 Stream API》
java·linux·服务器
草原上唱山歌3 小时前
如何理解C语言中的指针?
c语言·开发语言·数据结构
林九生3 小时前
【Claude Code】Claude Code 接入阿里云百炼 Coding Plan 完整配置教程(Linux版)
linux·阿里云·云计算