科技服务网站建设内容,九江网站开发公司,投资建设网站,襄樊北京网站建设设备树
描述设备树的文件叫做 DTS(Device Tree Source)#xff0c;这个 DTS 文件采用树形结构描述板级设备#xff0c;也就是开发板上的设备信息#xff0c;比如CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等。 树的主干就是系统总线#x…设备树
描述设备树的文件叫做 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 makefilehostprogs-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.c、flattree.c、fstree.c 等文件最终编译并链接出 DTC 这个主机文件
在文件arch/arm/boot/dts/Makefile中有IMX6ULL的SOC所编译生成的.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_IMX6ULLy)所有使用到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 架构、主频、外设寄存器地址范围比如 UART、IIC 等等。在arch/arm/boot/dts/imx6ull.dtsi中描述cpu的信息
cpus {#address-cells 1;#size-cells 0;cpu0: cpu0 {compatible arm,cortex-a7;device_type cpu;reg 0;clock-latency 61036; /* two CLK32 periods */operating-points /* kHz uV */696000 1275000528000 1175000396000 1025000198000 950000;fsl,soc-operating-points /* KHz uV */696000 1275000528000 1175000396000 1175000198000 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~4、uart1~8、usbphy1~2、i2c1~4 等等。
设备节点
设备树是采用树形结构来描述板子上的设备信息的文件每个设备都是一个节点叫做设备节点每个节点都通过一些属性信息来描述节点信息属性就是键—值对。如下imx6ull.dtsi 文件中缩减出来的设备树文件内容
/ { // 根节点include的文件中也有根节点不会冲突这两个“/”根节点的内容会合并成一个根节点。// aliases、cpus 和 intc 是三个子节点aliases {can0 flexcan1;};cpus {#address-cells 1;#size-cells 0;// cpu0 也是子节点只是 cpu0 是 cpus 的子节点cpu0: cpu0 {compatible arm,cortex-a7;device_type cpu;reg 0;};};intc: interrupt-controller00a01000 {compatible arm,cortex-a7-gic;#interrupt-cells 3;interrupt-controller;reg 0x00a01000 0x1000,0x00a02000 0x100;};节点命名格式分析
1、node-nameunit-addresseg: interrupt-controller00a01000“node-name”是节点名字为 ASCII 字符串节点名字应该能够清晰的描述出节点的功能。“unit-address”一般表示设备的地址或寄存器首地址如果某个节点没有地址或者寄存器的话“unitaddress”可以不要。eg: aliases
2、label: node-nameunit-addresseg:cpu0: cpu0引入 label 的目的就是为了方便访问节点可以直接通过label 来访问这个节点比如通过cpu0 就可以访问“cpu0”这个节点而不需要输入完整的节点名字。每个节点都有不同属性不同的属性又有不同的内容属性都是键值对值可以为空或任意的字节流。设备树源码中常用的几种数据形式如下所示
字符串
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_spi0 {compatible fairchild,74hc595;// addres0没有length的值相当于设置了起始地址而没有设置地址长度。reg 0;};
};aips3: aips-bus02200000 {compatible fsl,aips-bus, simple-bus;// 起始地址长度所占用的字长为 1地址长度所占用的字长也为 1。#address-cells 1;#size-cells 1;dcp: dcp02280000 {compatible fsl,imx6sl-dcp;// 相当于设置了起始地址为 0x02280000地址长度为 0x40000。reg 0x02280000 0x4000;};
};reg 属性
reg 属性的值一般是(addresslength)对。用于描述设备地址空间资源信息一般都是某个外设的寄存器地址范围信息。
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 开始进行读写操作 // 0xe00046000x4600 0xe0000000reg 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 属性用于描述我们所使用的设备一般第一个值描述了所使用的硬件设备名字第二个值描述了设备所使用的 SOC。Linux 内核会通过根节点的 compoatible 属性查看是否支持此设备如果支持的话设备就会启动 Linux 内核。
使用设备树之前设备匹配方法
在没有使用设备树以前uboot 会向 Linux 内核传递一个叫做 machine id 的值machine id 设备 ID告诉 Linux 内核自己是个什么设备。Linux 内核是支持很多设备的针对每一个设备(板子)Linux内核都用MACHINE_START和MACHINE_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_ENDlinux内核匹配过程
Linux 内核调用 start_kernel 函数来启动内核start_kernel 函数会调用 setup_arch 函数来匹配 machine_desc。setup_arch 调用 setup_machine_fdt 函数来获取匹配的 machine_desc。setup_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;mag31100e {compatible fsl,mag3110;reg 0x0e;position 2;};fxls84711e {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 这颗 SOC 的 I2C 下添加一个节点那么就可以查看Documentation/devicetree/bindings/i2c/i2c-imx.txt
* Freescale Inter IC (I2C) and High Speed Inter IC (HS-I2C) for i.MXRequired 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 specifierOptional 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:i2c83fc4000 { /* I2C2 on i.MX51 */compatible fsl,imx51-i2c, fsl,imx21-i2c;reg 0x83fc4000 0x4000;interrupts 63;
};i2c70038000 { /* HS-I2C on i.MX51 */compatible fsl,imx51-i2c, fsl,imx21-i2c;reg 0x70038000 0x4000;interrupts 64;clock-frequency 400000;
};i2c0: i2c40066000 { /* 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 表示从根节点开始查找整个设备树
matchesof_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设备节点。
indexreg 属性中要完成内存映射的段如果 reg 属性只有一段的话 index 就设置为 0。
返回值经过内存映射后的虚拟内存首地址如果为 NULL 的话表示内存映射失败。