linux 设备树详解

news2024/11/26 2:26:38

设备树

描述设备树的文件叫做 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 的话表示内存映射失败。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/513711.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【服务器数据恢复】Linux系统下OA+oracle的数据恢复案例

服务器数据恢复环境&#xff1a; 某公司一台服务器中组建一组raid5磁盘阵列&#xff1b; 上层操作系统为linux redhat&#xff0c;部署OA系统&#xff0c;后端数据库为oracle。 服务器故障&初检&#xff1a; raid5中有2块磁盘先后掉线&#xff0c;服务器崩溃。oracle已经不…

springboot使用ECharts、ECharts html中文乱码、直接引用CDN资源文件和引用本地资源文件哪个好

springboot使用ECharts、动态地引用版本 1.添加依赖2.创建图表引入ECharts文件方式直接引用CDN资源文件和引用本地资源文件哪个好 3.映射4.添加配置加载ECharts资源5.测试访问6.升级版本号问题7.ECharts html中文乱码 如果想在Spring Boot应用程序中使用ECharts&#xff0c;则可…

多目标检测:基于Yolo优化的多目标检测(附论文下载)

关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;ComputerVisionGzq 学习群&#xff5c;扫码在主页获取加入方式 计算机视觉研究院专栏 作者&#xff1a;Edison_G 为了解决目标检测任务中小目标检测精度低、误检、漏检率高等问题&#xff0c;有研究者提出了一种新…

前端实战项目:网易云静态页面——主页面右侧部分

文章目录 前言main部分结构布局用户登陆右侧列表header的封装歌手列表主播列表申请按钮 总代码 前言 项目持续更新中&#xff5e; 网易云静态页面——导航栏 网易云静态页面——轮播图 Flex布局详解 所用到文件及文件夹 header&#xff1a;是对某些标题样式的封装 main&…

软考A计划-重点考点-专题十一(系统工程知识)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

2023蓝桥杯真题c++省A

[蓝桥杯 2023 省 A] 填空问题 比赛的时候&#xff0c;脑袋要清晰一点&#xff0c;当时写 幸运数 这道题都感觉没在用脑子思考&#xff0c;花了特别多时间 A. 幸运数 小蓝认为如果一个数含有偶数个数位&#xff0c;并且前面一半的数位之和等于后面一半的数位之和&#xff0c;…

小家电类产品出口欧美国家/亚马逊平台认证要求请知悉!

小家电类产品CE认证 欧盟&#xff1a; “CE”标志是一种安全认证标志&#xff0c;被视为制造商打开并进入欧洲市场的护照。CE代表欧洲统一&#xff08;CONFORMITE EUROPEENNE&#xff09;。 根据欧盟的法律&#xff0c;生产商和进口商负责验证其产品符合相关的欧盟指令的要求…

哪款洗地机适合家用?家用洗地机型号分享

洗地机采用多种清洁方式&#xff0c;如湿拖、干拖、热水清洗等&#xff0c;可针对不同使用场合和地面类型进行清洁。而且洗地机无需手工操作&#xff0c;智能感应地面脏污&#xff0c;自动适应地面清洁程度&#xff0c;保证了清洁效率和效果。本文将为大家推荐几款性价比较高、…

三位一体,铸就无敌铁军!海陆空协同,开启集群新篇章!

在机器人领域&#xff0c;多机器人系统的研究一直是一大热点&#xff0c;众多高校与研究所逐步投入到机器人集群系统的研究当中&#xff0c;其中无人机编队表演、无人车群园区运输、无人船集群水域监测等集群应用更是进入了大众的视野。但对多机器人集群系统的需求却远不止于此…

「企业应用架构」应用架构行为准则

应用架构行为准则 应用程序架构是企业解决方案架构&#xff08;ESA&#xff09;的一个子集&#xff08;图1&#xff09;。应用程序架构既是一个过程&#xff08;架构和设计&#xff09;又是一个东西&#xff08;可交付成果——架构的内容&#xff09;。应用程序架构帮助组织规划…

Elasticsearch数据库

目录 1. 什么是ElasticSearch1.1 概念及特点1.2 ElasticSearch适用场景概述 2. 安装ElasticSearch2.1 下载安装包2.2 环境说明2.3 创建es的用户2.4 创建es存储位置2.5 安装es2.5 修改配置文件2.6 系统优化2.7 安装jdk环境2.8 切换es用户启动数据库2.9 systemctl管理2.10 访问 3…

听劝,不要什么都不懂就自学网络安全【黑客】

一、网络安全学习的误区 1.不要试图以编程为基础去学习网络安全 不要以编程为基础再开始学习网络安全&#xff0c;一般来说&#xff0c;学习编程不但学习周期长&#xff0c;且过渡到网络安全用到编程的用到的编程的关键点不多。一般人如果想要把编程学好再开始学习网络安全往…

SSL 证书安装使用中遇到的常见问题

为了实现网站HTTPS加密保护及身份的可信认证&#xff0c;防止传输数据的泄露或篡改&#xff0c;SSL证书已被各政企网站广泛应用。然而在部署和使用SSL证书的过程中&#xff0c;我们经常会遇到一些措手不及的问题&#xff0c;一旦处理不当&#xff0c;就会让网站面临信息被泄漏、…

Linux知识点 -- 常见指令及权限理解

Linux知识点 – 常见指令及权限理解 文章目录 Linux知识点 -- 常见指令及权限理解一、Linux下基本指令1.ls指令 - 列文件或目录信息2.pwd命令 - 显示用户当前所在目录3.cd指令 - 改变工作目录4.touch指令 - 更改文件时间或新建文件5.mkdir指令 - 创建目录 / tree - 以树状形式显…

Hbase入门篇02---数据模型和HBase Shell的基本使用

Hbase入门篇02---数据模型和基本使用 HBase数据模型表行列单元格 &#xff08;cell&#xff09;概念模型 shell命令行进行CRUD操作表的CRUD数据的CRUD数据批量导入计数操作大量数据的计数统计扫描操作limit限制返回条数返回指定列返回指定行键对应的数据 过滤器HBase中的过滤器…

【云原生进阶之PaaS中间件】第一章Redis-1.2数据类型

1 Redis 数据类型 Redis支持五种数据类型&#xff1a;string&#xff08;字符串&#xff09;&#xff0c;hash&#xff08;哈希&#xff09;&#xff0c;list&#xff08;列表&#xff09;&#xff0c;set&#xff08;集合&#xff09;及zset(sorted set&#xff1a;有序集合)。…

XML配置方式使用Spring MVC:实战练习

文章目录 任务1、设置项目首页 - index.jsp1、修改web.xml文件2、创建首页文件3、修改登录控制器4、启动服务器&#xff0c;查看效果 任务2、首页添加登录链接&#xff0c;单击跳转到登录页面1、修改首页文件2、修改登录控制器3、启动服务器&#xff0c;查看效果 任务3、利用Sp…

预测性维护无线振动监测方案QA合集

一、虹科无线振动监测方案 虹科无线振动监测方案具有高安全性、高可靠性、全自动诊断的优势&#xff0c;广泛应用于各种旋转设备的故障诊断。虹科无线振动监测方案包括Accel 310高分辨率无线振动系统&#xff0c;用户能够实现每小时获取标量数据或每日诊断监控机器状态。借助先…

PostgreSQL(五)JDBC连接串常用参数

目录 1.单机 PostgreSQL 连接串2.集群PostgreSQL 连接串 PostgreSQL JDBC 官方驱动下载地址&#xff1a; https://jdbc.postgresql.org/download/ PostgreSQL JDBC 官方参数说明文档&#xff1a; https://jdbc.postgresql.org/documentation/use/ 驱动类&#xff1a; driver-…

yarn切换element-plus版本

yarn的安装和卸载 npm install -g yarn npm uninstall yarn -g //yarn卸载 本机的element-plus版本 "element-plus": "2.0.1", 想要切换的element-plus版本 由于我需要用到树型选择&#xff0c;所以需要升级到2.1.8 用npm卸载element-plus时报如下错误…