设备树
描述设备树的文件叫做 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.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_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
架构、主频、外设寄存器地址范围,比如 UART
、IIC
等等。在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~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: 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
属性用于描述我们所使用的设备,一般第一个值描述了所使用的硬件设备名字,第二个值描述了设备所使用的 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 id
,uboot
会给 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_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";
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
这颗 SOC
的 I2C
下添加一个节点,那么就可以查看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 的话表示内存映射失败。