linux 设备树详解

设备树

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

在这里插入图片描述

树的主干就是系统总线,IIC 控制器、GPIO 控制器、SPI 控制器等都是接到系统主线上的分支。DTS 文件的主要功能就是按照上图所示的结构来描述板子上的设备信息。SOC厂商有多种开发板,将这些共同的信息提取出来作为一个通用的文件,其他的.dts 文件直接引用这个通用文件即可,这个通用文件就是.dtsi 文件,类似于 C 语言中的头文件。一般.dts 描述板级信息(也就是开发板上有哪些 IIC 设备、SPI 设备等),.dtsi 描述 SOC 级信息(也就是 SOC 有几个 CPU、主频是多少、各个外设控制器信息等)。

DTS、DTB、DTC之间的关系

DTS 是设备树源码文件,DTB 是将DTS 编译以后得到的二进制文件。编译的工具就是DTC。该工具存放/scripts/dtc文件夹下。文件/scripts/dtc/Makefile下有:

# scripts/dtc makefile

hostprogs-y	:= dtc
always		:= $(hostprogs-y)

dtc-objs	:= dtc.o flattree.o fstree.o data.o livetree.o treesource.o \
		   srcpos.o checks.o util.o
dtc-objs	+= dtc-lexer.lex.o dtc-parser.tab.o

# Source files need to get at the userspace version of libfdt_env.h to compile

说明了DTC 工具依赖于 dtc.cflattree.cfstree.c 等文件,最终编译并链接出 DTC 这个主机文件

在文件arch/arm/boot/dts/Makefile中有IMX6ULLSOC所编译生成的.dtb文件,以后我们需要添加就在该文件中找到对用的芯片,加载在下方即可。

dtb-$(CONFIG_SOC_IMX6ULL) += \
	imx6ull-14x14-ddr3-arm2.dtb \
	imx6ull-14x14-ddr3-arm2-adc.dtb \
	imx6ull-14x14-ddr3-arm2-cs42888.dtb \
	imx6ull-14x14-ddr3-arm2-ecspi.dtb \
	imx6ull-14x14-ddr3-arm2-emmc.dtb \
	imx6ull-14x14-ddr3-arm2-epdc.dtb \
	imx6ull-14x14-ddr3-arm2-flexcan2.dtb \
	imx6ull-14x14-ddr3-arm2-gpmi-weim.dtb \
	imx6ull-14x14-ddr3-arm2-lcdif.dtb \
	imx6ull-14x14-ddr3-arm2-ldo.dtb \
	imx6ull-14x14-ddr3-arm2-qspi.dtb \
	imx6ull-14x14-ddr3-arm2-qspi-all.dtb \
	imx6ull-14x14-ddr3-arm2-tsc.dtb \
	imx6ull-14x14-ddr3-arm2-uart2.dtb \
	imx6ull-14x14-ddr3-arm2-usb.dtb \
	imx6ull-14x14-ddr3-arm2-wm8958.dtb \
	imx6ull-14x14-evk.dtb \
	imx6ull-14x14-evk-btwifi.dtb \
	imx6ull-14x14-evk-emmc.dtb \
	imx6ull-14x14-evk-gpmi-weim.dtb \
	imx6ull-14x14-evk-usb-certi.dtb \
	imx6ull-9x9-evk.dtb \
	imx6ull-9x9-evk-btwifi.dtb \
	imx6ull-9x9-evk-ldo.dtb

当选中 I.MX6ULL 这个 SOC 以后(CONFIG_SOC_IMX6ULL=y),所有使用到I.MX6ULL 这个 SOC 的板子对应的.dts 文件都会被编译为.dtb

DTS语法

我们基本上不会从头到尾重写一个.dts 文件,大多时候是直接在 SOC 厂商提供的.dts文件上进行修改。

dtsi头文件

和 C 语言一样,设备树也支持头文件,设备树的头文件扩展名为.dtsi。可以通过“#include”来引用.h.dtsi .dts 文件。只是,我们在编写设备树头文件的时候最好选择.dtsi 后缀。

#include <dt-bindings/input/input.h>
#include "imx6ull.dtsi"
#include "imx6ull-14x14-evk.dts"

一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UARTIIC 等等。在arch/arm/boot/dts/imx6ull.dtsi中描述cpu的信息:

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

		cpu0: cpu@0 {
			compatible = "arm,cortex-a7";
			device_type = "cpu";
			reg = <0>;
			clock-latency = <61036>; /* two CLK32 periods */
			operating-points = <
				/* kHz	uV */
				696000	1275000
				528000	1175000
				396000	1025000
				198000	950000
			>;
			fsl,soc-operating-points = <
				/* KHz	uV */
				696000  1275000
				528000	1175000
				396000	1175000
				198000	1175000
			>;
			clocks = <&clks IMX6UL_CLK_ARM>,
				 <&clks IMX6UL_CLK_PLL2_BUS>,
				 <&clks IMX6UL_CLK_PLL2_PFD2>,
				 <&clks IMX6UL_CA7_SECONDARY_SEL>,
				 <&clks IMX6UL_CLK_STEP>,
				 <&clks IMX6UL_CLK_PLL1_SW>,
				 <&clks IMX6UL_CLK_PLL1_SYS>,
				 <&clks IMX6UL_PLL1_BYPASS>,
				 <&clks IMX6UL_CLK_PLL1>,
				 <&clks IMX6UL_PLL1_BYPASS_SRC>,
				 <&clks IMX6UL_CLK_OSC>;
			clock-names = "arm", "pll2_bus",  "pll2_pfd2_396m", "secondary_sel", "step",
				      "pll1_sw", "pll1_sys", "pll1_bypass", "pll1", "pll1_bypass_src", "osc";
		};
	};

imx6ull.dtsi 文件中不仅仅描述了 cpu0 这一个节点信息,I.MX6ULL 这颗 SOC 所有的外设都描述的清清楚楚,比如 ecspi1~4uart1~8usbphy1~2i2c1~4 等等。

设备节点

设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。如下imx6ull.dtsi 文件中缩减出来的设备树文件内容:

/ { 	// 根节点,include的文件中也有根节点,不会冲突,这两个“/”根节点的内容会合并成一个根节点。
    	// aliases、cpus 和 intc 是三个子节点
	aliases {
		can0 = &flexcan1;
	};

	cpus {
		#address-cells = <1>;
		#size-cells = <0>;
		// cpu0 也是子节点,只是 cpu0 是 cpus 的子节点
		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>;
	};
  • 节点命名格式分析
1、node-name@unit-address:
    eg: interrupt-controller@00a01000
    “node-name”是节点名字,为 ASCII 字符串,节点名字应该能够清晰的描述出节点的功能。
    “unit-address”一般表示设备的地址或寄存器首地址,
    	如果某个节点没有地址或者寄存器的话“unitaddress”可以不要。eg: aliases
2、label: node-name@unit-address
    eg:cpu0: cpu@0
    引入 label 的目的就是为了方便访问节点,可以直接通过&label 来访问这个节点,
    比如通过&cpu0 就可以访问“cpu@0”这个节点,而不需要输入完整的节点名字。

每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示

  • 字符串
ompatible = "arm,cortex-a7";
// 设置 compatible 属性的值为字符串“arm,cortex-a7”。
  • 32 位无符号整数
reg = <0>;
reg = <0 0x123456 100>;
  • 字符串列表
compatible = "fsl,imx6ull-gpmi-nand", "fsl, imx6ul-gpmi-nand";
// 字符串与字符串之间使用逗号隔开
// 设置属性 compatible 的值为“fsl,imx6ull-gpmi-nand”和“fsl, imx6ul-gpmi-nand”。

标准属性

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

compatible 属性

compatible 属性的值是一个字符串列表,**compatible 属性用于将设备和驱动绑定起来。**字符串列表用于选择设备所要使用的驱动程序。

compatible = "manufacturer,model"
// manufacturer 表示厂商 model 一般是模块对应的驱动名字
compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";
// “fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”和“imx-audio-wm8960”表示驱动模块名字。
// 设备首先使用第一个兼容值在 Linux 内核里面查找,看看能不能找到与之匹配的驱动文件。
// 如果没有找到的话就使用第二个兼容值查。

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

imx-wm8960.c文件中有:

/*
// Struct used for matching a device
struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};
*/
/*of_XXX其中的of表示open firmware 即开放固件*/
static const struct of_device_id imx_wm8960_dt_ids[] = {
    // 在设备树中是"fsl,imx-audio-wm8960"的就是用这个驱动
	{ .compatible = "fsl,imx-audio-wm8960", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids);

static struct platform_driver imx_wm8960_driver = {
	.driver = {
		.name = "imx-wm8960",
		.pm = &snd_soc_pm_ops,
        // 设置这个 platform_driver 所使用的OF 匹配表。
		.of_match_table = imx_wm8960_dt_ids,  
	},
	.probe = imx_wm8960_probe,
	.remove = imx_wm8960_remove,
};
module_platform_driver(imx_wm8960_driver);
model 属性
model = "wm8960-audio";
// model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字什么的。
status 属性

status 属性看名字就知道是和设备状态有关的status 属性值也是字符串,字符串是设备的状态信息。

描述
“okay”表明设备是可操作的
"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 格式:

reg = <address1 length1 address2 length2 address3 length3……>
// 每个“address length”组合表示一个地址范围,其中 address 是起始地址,length 是地址长度,
// #address-cells 表明 address 这个数据所占用的字长,
// #size-cells 表明 length 这个数据所占用的字长
spi4 {
    compatible = "spi-gpio";
    // 说明 spi4 的子节点 reg 属性中起始地址所占用的字长为 1,地址长度所占用的字长为 0。
    #address-cells = <1>;
    #size-cells = <0>;

    gpio_spi: gpio_spi@0 {
        compatible = "fairchild,74hc595";
        // addres=0,没有length的值,相当于设置了起始地址,而没有设置地址长度。
        reg = <0>;
    };
};

aips3: aips-bus@02200000 {
    compatible = "fsl,aips-bus", "simple-bus";
    // 起始地址长度所占用的字长为 1,地址长度所占用的字长也为 1。
    #address-cells = <1>;
    #size-cells = <1>;

    dcp: dcp@02280000 {
        compatible = "fsl,imx6sl-dcp";
        // 相当于设置了起始地址为 0x02280000,地址长度为 0x40000。
        reg = <0x02280000 0x4000>;
    };
};
reg 属性

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

ranges 属性

ranges属性值可以为空或者按照(child-bus-address,parent-bus-address,length)格式编写的数字矩阵,ranges 是一个地址映射/转换表。
child-bus-address: 子总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
parent-bus-address:父总线地址空间的物理地址,由父节点的#address-cells 确定此物理地址所占用的字长。
length:子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长。
如果ranges属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换

soc {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "simple-bus";
    interrupt-parent = <&gpc>;
    ranges;  // 子地址空间和父地址空间完全相同
}
soc {
	compatible = "simple-bus";
	#address-cells = <1>;
	#size-cells = <1>;
    
    // 指定了一个 1024KB(0x00100000)的地址范围,子地址空间的物理起始地址为 0x0
    // 父地址空间的物理起始地址为 0xe0000000。
	ranges = <0x0 0xe0000000 0x00100000>;
	
	serial {
        device_type = "serial";
        compatible = "ns16550";
        // 定义了 serial 设备寄存器的起始地址为 0x4600,寄存器长度为 0x100
        // 经过地址转换, serial 设备可以从 0xe0004600 开始进行读写操作	
        // 0xe0004600=0x4600 + 0xe0000000
        reg = <0x4600 0x100>;
        clock-frequency = <0>;
        interrupts = <0xA 0x8>;
        interrupt-parent = <&ipic>;
    };
};
name 属性

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

device_type 属性

用于描述设备的 FCode,过时了,建议不用。它的值是字符串,用来表示节点的类型。在跟platform_driver匹配时,优先级为中。compatible属性在匹配过程中,优先级最高。

根节点 compatible 属性

每个节点都有 compatible 属性,根节点“/”也不例外。

/ {
	model = "Freescale i.MX6 ULL 14x14 EVK Board";
	compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
}

compatible 有两个值:“fsl,imx6ull-14x14-evk”“fsl,imx6ull”。设备节点的 compatible 属性值是为了匹配 Linux 内核中的驱动程序。根节点的 compatible 属性用于描述我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,第二个值描述了设备所使用的 SOCLinux 内核会通过根节点的 compoatible 属性查看是否支持此设备,如果支持的话设备就会启动 Linux 内核。

使用设备树之前设备匹配方法

在没有使用设备树以前,uboot 会向 Linux 内核传递一个叫做 machine id 的值,machine id 设备 ID,告诉 Linux 内核自己是个什么设备。Linux 内核是支持很多设备的,针对每一个设备(板子),Linux内核都用MACHINE_STARTMACHINE_END来定义一个 machine_desc 结构体来描述这个设备。

// arch/arm/mach-imx/mach-mx35_3ds.c
// 定义了 Freescale MX35PDK 这个设备
MACHINE_START(MX35_3DS, "Freescale MX35PDK")
	/* Maintainer: Freescale Semiconductor, Inc */
	.atag_offset = 0x100,
	.map_io = mx35_map_io,
	.init_early = imx35_init_early,
	.init_irq = mx35_init_irq,
	.init_time	= mx35pdk_timer_init,
	.init_machine = mx35_3ds_init,
	.reserve = mx35_3ds_reserve,
	.restart	= mxc_restart,
MACHINE_END
    
/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
// arch/arm/include/asm/mach/arch.h
#define MACHINE_START(_type,_name)			\
static const struct machine_desc __mach_desc_##_type	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_##_type,		\
	.name		= _name,

#define MACHINE_END				\
};

将上面的代码展开后有:

static const struct machine_desc __mach_desc_MX35_3DS	
 __used	
 /* machine_desc结构体__mach_desc_MX35_3DS储存在.arch.info.init */
 __attribute__((__section__(".arch.info.init"))) = {	
    /*
    	说明了machine id 为 MACH_TYPE_MX35_3DS 
     	定义在 include/generated/mach-types.h 中
    */
	.nr		= MACH_TYPE_MX35_3DS,
    /*板子的名字叫做 "Freescale MX35PDK" */
	.name		= "Freescale MX35PDK",
	.atag_offset = 0x100,
	.map_io = mx35_map_io,
	.init_early = imx35_init_early,
	.init_irq = mx35_init_irq,
	.init_time	= mx35pdk_timer_init,
	.init_machine = mx35_3ds_init,
	.reserve = mx35_3ds_reserve,
	.restart	= mxc_restart,
};

include/generated/mach-types.h中定义了各种machine iduboot 会给 Linux 内核传递 machine id 这个参数。linux将传入的machine id和这里定义的宏比较,如果有,那么linux内核就支持该设备,否则不支持。

/*其中的部分宏定义*/
#define MACH_TYPE_U300                 1627
#define MACH_TYPE_WRT350N_V2           1633
#define MACH_TYPE_OMAP_LDP             1639
#define MACH_TYPE_MX35_3DS             1645 /*Freescale MX35PDK 对应的machine id*/
#define MACH_TYPE_NEUROS_OSD2          1647
#define MACH_TYPE_TRIZEPS4WL           1649
#define MACH_TYPE_TS78XX               1652
使用设备树以后的设备匹配方法

使用设备树后,不在使用上述宏,而是使用宏定义DT_MACHINE_START

// arch/arm/include/asm/mach/arch.h
#define DT_MACHINE_START(_name, _namestr)		\
static const struct machine_desc __mach_desc_##_name	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
 	/*
 		.nr= ~0说明引入设备树以后不会再根据 machine id 
 		来检查Linux 内核是否支持某个设备了
    */
	.nr		= ~0,				\
	.name		= _namestr,

#endif
// arch/arm/mach-imx/mach-imx6ul.c
static const char *imx6ul_dt_compat[] __initconst = {
    /*
    	只要某个设备根节点“/”的 compatible 属性值与
		imx6ul_dt_compat 表中的任何一个值相等,
		那么就表示 Linux 内核支持此设备。
	*/
	"fsl,imx6ul",
	"fsl,imx6ull",
	NULL,
};

DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)")
	.map_io		= imx6ul_map_io,
	.init_irq	= imx6ul_init_irq,
	.init_machine	= imx6ul_init_machine,
	.init_late	= imx6ul_init_late,
	/*.dt_compat 保存着本设备兼容属性*/
	.dt_compat	= imx6ul_dt_compat,
MACHINE_END
  • linux内核匹配过程

Linux 内核调用 start_kernel 函数来启动内核,start_kernel 函数会调用 setup_arch 函数来匹配 machine_descsetup_arch 调用 setup_machine_fdt 函数来获取匹配的 machine_descsetup_machine_fdt 函数通过调用函数 of_flat_dt_match_machine 来获取匹配的 machine_desc

在这里插入图片描述

向节点添加或修改内容

假如我们要在开发板上的i2c1上添加其他的硬件设备。就要修改设备树里面的内容。不能直接修改文件imx6ull.dtsi文件里面的i2c1节点的内容,这样的话,其他未使用该设备的板子也相应的添加了。我们直接在我们板子对应的.dts文件中修改。imx6ull_14x14_evk.dts

// 向 i2c1 节点添加/修改数据,
&i2c1 {
    // i2c1 时钟为 100KHz。
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

	mag3110@0e {
		compatible = "fsl,mag3110";
		reg = <0x0e>;
		position = <2>;
	};

	fxls8471@1e {
		compatible = "fsl,fxls8471";
		reg = <0x1e>;
		position = <0>;
		interrupt-parent = <&gpio5>;
		interrupts = <0 8>;
	};
};

设备树在系统中的体现

Linux 内核启动的时候会解析设备树中各个节点的信息,并且在根文件系统的/proc/device-tree 目录下根据节点名字创建不同文件夹。文件夹显示的是根节点的各种属性和子节点。

在这里插入图片描述

特殊节点

在根节点“/”中有两个特殊的子节点:aliases chosen

  • aliases节点

其中aliases的意思是别名的意思。因此 aliases 节点的主要功能就是定义别名,定义别名的目的就是为了方便访问节点。不过我们一般会在节点命名的时候会加上 label,然后通过&label来访问节点。

  • chosen节点

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

chosen {
    stdout-path = &uart1;
};

属性“stdout-path”,表示标准输出使用 uart1。但是在/proc/device-tree/chosen 目录里面多了 bootargs 属性。uboot 中的 fdt_chosen 函数在设备树的 chosen 节点中加入了 bootargs属性,并且还设置了 bootargs 属性值。

以下是 fdt_chosen 函数调用过程。

在这里插入图片描述

Linux 内核解析 DTB 文件

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

在这里插入图片描述

start_kernel 函数中完成了设备树节点解析的工作,最终实际工作的函数为 unflatten_dt_node

绑定信息文档

设备树是用来描述板子上的设备信息的,不同的设备其信息不同,反映到设备树中就是属性不同。

在Linux 内核源码中有详细的.txt 文档描述了如何添加节点,这些.txt 文档叫做绑定文档,路径为:Linux 源码目录/Documentation/devicetree/bindings。在 I.MX6ULL 这颗 SOCI2C 下添加一个节点,那么就可以查看Documentation/devicetree/bindings/i2c/i2c-imx.txt

* Freescale Inter IC (I2C) and High Speed Inter IC (HS-I2C) for i.MX

Required properties:
- compatible :
  - "fsl,imx1-i2c" for I2C compatible with the one integrated on i.MX1 SoC
  - "fsl,imx21-i2c" for I2C compatible with the one integrated on i.MX21 SoC
  - "fsl,vf610-i2c" for I2C compatible with the one integrated on Vybrid vf610 SoC
- reg : Should contain I2C/HS-I2C registers location and length
- interrupts : Should contain I2C/HS-I2C interrupt
- clocks : Should contain the I2C/HS-I2C clock specifier

Optional properties:
- clock-frequency : Constains desired I2C/HS-I2C bus clock frequency in Hz.
  The absence of the propoerty indicates the default frequency 100 kHz.
- dmas: A list of two dma specifiers, one for each entry in dma-names.
- dma-names: should contain "tx" and "rx".

Examples:

i2c@83fc4000 { /* I2C2 on i.MX51 */
	compatible = "fsl,imx51-i2c", "fsl,imx21-i2c";
	reg = <0x83fc4000 0x4000>;
	interrupts = <63>;
};

i2c@70038000 { /* HS-I2C on i.MX51 */
	compatible = "fsl,imx51-i2c", "fsl,imx21-i2c";
	reg = <0x70038000 0x4000>;
	interrupts = <64>;
	clock-frequency = <400000>;
};

i2c0: i2c@40066000 { /* i2c0 on vf610 */
	compatible = "fsl,vf610-i2c";
	reg = <0x40066000 0x1000>;
	interrupts =<0 71 0x04>;
	dmas = <&edma0 0 50>,
		<&edma0 0 51>;
	dma-names = "rx","tx";
};

设备树常用 OF 操作函数

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

查找节点的 OF 函数

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

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;	/* removed 属性 */
	struct	device_node *parent;	/* 父节点 */
	struct	device_node *child;		/* 子节点 */
	struct	device_node *sibling;
	struct	kobject kobj;
	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
};
  • of_find_node_by_name 函数
// 通过节点名字查找指定的节点
struct device_node *of_find_node_by_name(struct device_node *from,
	const char *name);
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
name:要查找的节点名字。
返回值:找到的节点,如果为 NULL 表示查找失败。
  • of_find_node_by_type 函数
// 通过 device_type属性 查找指定的节点
struct device_node *of_find_node_by_type(struct device_node *from,
   const char *type);
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
type: 要查找的节点对应的 type 字符串,也就是 device_type 属性值
返回值:找到的节点,如果为 NULL 表示查找失败。
  • of_find_compatible_node 函数
// 根据 device_type 和 compatible 这两个属性查找指定的节点
struct device_node *of_find_compatible_node(struct device_node *from,
	const char *type, const char *compat);
from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树
type: 要查找的节点对应的 type 字符串,也就是 device_type 属性值,可以是NULL表是忽略
compat:要查找的节点所对应的 compatible 属性列表
返回值:找到的节点,如果为 NULL 表示查找失败。
  • of_find_matching_node_and_match 函数
struct of_device_id {
	char	name[32];
	char	type[32];
	char	compatible[128];
	const void *data;
};
// 通过 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 表示查找失败
  • of_find_node_by_path 函数
// 通过路径来查找指定的节点
struct device_node *of_find_node_opts_by_path(const char *path,
	const char **opts);
static inline struct device_node *of_find_node_by_path(const char *path)
{
	return of_find_node_opts_by_path(path, NULL);
}
path:带有全路径的节点名,可以使用节点的别名,比如“/backlight”就是 backlight 这个节点的全路径。
返回值:找到的节点,如果为 NULL 表示查找失败

查找父/子节点的 OF 函数

  • of_get_parent 函数
// 获取指定节点的父节点
struct device_node *of_get_parent(const struct device_node *node);
  • of_get_next_child 函数
// 用迭代的方式查找子节点
struct device_node *of_get_next_child(const struct device_node *node,
					     struct device_node *prev);
node:父节点
prev:表从哪一个子节点开始迭代的查找下一个子节点。可以设置为NULL,表示从第一个子节点开始。
返回值:找到的下一个子节点

提取属性值的 OF 函数

Linux 内核中使用结构体 property 表示属性。

struct property {
	char	*name; 	/* 属性名字 */
	int	length;		/* 属性长度 */
	void	*value;	/* 属性值 */
	struct property *next;	/* 下一个属性 */
	unsigned long _flags;
	unsigned int unique_id;
	struct bin_attribute attr;
};
  • of_find_property 函数
// 用于查找指定的属性
struct property *of_find_property(const struct device_node *np,
					 const char *name,
					 int *lenp);
np:设备节点
name: 属性名字。
lenp:属性值的字节数
返回值:找到的属性。
  • 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:元素长度。
返回值:得到的属性元素数量。
  • of_property_read_u32_index 函数
// 用于从属性中获取指定标号的 u32 类型数据值
// 比如某个属性有多个 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 表示属性值列表太小。
  • of_property_read_uX_array 函数
// 分别是读取属性中 u8、u16、u32 和 u64 类型的数组数据
// 比如大多数的 reg 属性都是数组数据,可以使用这 4 个函数一次读取出 reg 属性中的所有数据。
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);
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值,分别为 u8、u16、u32 和 u64。
sz:要读取的数组元素数量。
返回值:0,读取成功,负值,读取失败,
    -EINVAL 表示属性不存在,
    -ENODATA 表示没有要读取的数据,
    -EOVERFLOW 表示属性值列表太小。
  • of_property_read_uX 函数
// 有些属性只有一个整形值,这四个函数就是用于读取这种只有一个整形值的属性
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);
np:设备节点。
proname: 要读取的属性名字。
out_value:读取到的数组值。
返回值:0,读取成功,负值,读取失败,
    -EINVAL 表示属性不存在,
    -ENODATA 表示没有要读取的数据,
    -EOVERFLOW 表示属性值列表太小。
  • 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,读取成功,负值,读取失败。
  • of_n_addr_cells 函数
// 用于获取#address-cells 属性值
int of_n_addr_cells(struct device_node *np);
np:设备节点。
返回值:获取到的#address-cells 属性值。
  • of_n_size_cells 函数
// 数用于获取#size-cells 属性值
int of_n_size_cells(struct device_node *np)
np:设备节点。
返回值:获取到的#size-cells 属性值。

其他常用 OF 函数

  • of_device_is_compatible 函数
// 用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性
static inline int of_device_is_compatible(const struct device_node *device,
					  const char *compat)
device:设备节点。
compat:要查看的字符串。
返回值:0,节点的 compatible 属性中不包含 compat 指定的字符串;
    正数,节点的 compatible属性中包含 compat 指定的字符串。
  • of_get_address 函数
// 数用于获取地址相关属性,主要是“reg”或者“assigned-addresses”属性值
static inline const __be32 *of_get_address(struct device_node *dev, int index,
					u64 *size, unsigned int *flags)
dev:设备节点。
index:要读取的地址标号。
size:地址长度。
flags:参数,比如 IORESOURCE_IO、IORESOURCE_MEM 等
返回值:读取到的地址数据首地址,为 NULL 的话表示读取失败。
  • of_translate_address 函数
// 负责将从设备树读取到的地址转换为物理地址
u64 of_translate_address(struct device_node *np, const __be32 *addr);
dev:设备节点。
in_addr:要转换的地址。
返回值:得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。
  • of_address_to_resource 函数

IIC、SPI、GPIO 等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间,Linux内核使用 resource 结构体来描述一段内存空间,“resource”翻译出来就是“资源”,因此用 resource结构体描述的都是设备资源信息。

// include/linux/ioport.h
struct resource {
	resource_size_t start;	/*  resource_size_t 其实就是u32类型 开始地址 */
	resource_size_t end;	/* 结束地址 */
	const char *name;		/* 资源的名字 */
	unsigned long flags;	/* 资源标志位,用于表示资源类型 */
    /* 常见的标志位 IORESOURCE_MEM 、 IORESOURCE_REG 、IORESOURCE_IRQ */
	struct resource *parent, *sibling, *child;
};
// 将 reg 属性值转换为 resource 结构体类型
int of_address_to_resource(struct device_node *dev, int index,
				  struct resource *r);
dev:设备节点。
index:地址资源标号。
r:得到的 resource 类型的资源值。
返回值:0,成功;负值,失败。
  • of_iomap 函数
// 数用于直接内存映射,以前我们会通过 ioremap 函数来完成物理地址到虚拟地址的映射,
// 采用设备树以后就可以直接通过 of_iomap 函数来获取内存地址所对应的虚拟地址
void __iomem *of_iomap(struct device_node *np, int index)
np:设备节点。
index:reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。
返回值:经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值