文章目录
- 1 什么是设备树?
- 2 DTS、DTB和DTC
- 3 DTS语法
- 3.1 dtsi头文件
- 3.2 设备节点
- 3.3 标准属性
- 3.4 根节点compatible属性
- 3.5 向节点追加或修改内容
- 4 创建小型模板设备树
- 5 设备树在系统中的体现
- 6 绑定信息文档
- 7 设备树常用OF操作函数
- 7.1 查找节点的OF函数
- 7.2 查找父/子节点的OF函数
- 7.3 提取属性值的OF函数
- 7.4 其他常用的OF函数
系列文章:
Linux驱动开发——字符设备驱动开发
Linux驱动开发——LED驱动开发
Linux驱动开发——新字符设备驱动开发
1 什么是设备树?
设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等,如下图所示:
在以前的内核中,还没有采用设备树,内核源码中有大量的arch/arm/mach-xxx和arch/arm/plat-xxx文件夹,用于存储不同平台的板级信息,但随着芯片产业的发展,加入内核中的板级信息日益庞大,导致内核“虚胖”,所以引入了PowerPC等架构已经采用的设备树。将这些描述板级硬件信息的内容都从Linux中分离,用一个专属的文件格式类描述,这个专属的文件就叫做设备树,文件扩展名为.dts。一个SOC可以出很多不同的班子,不同的板子肯定有相同的部分信息,将这些相同信息提取到一个文件中,其他dts文件引用这个文件,避免重复定义,这个通用文件就是dtsi文件,有点类似于C语言中的头文件。
2 DTS、DTB和DTC
设备树源文件为dts文件,该文件编译之后,成为dtb文件,而编译dts文件的工具是DTC工具。
该工具是以源码的形式存在于内核源码中的,位于kernel/scripts/dtc目录下
可以使用dtc工具编译dts文件:
这里是Android系统内核,首先需要执行defconfig配置:
make ARCH=arm64 rockchip_defconfig android-11.config
然后编译
make ARCH=arm64 dtbs
输出如下:
CALL scripts/checksyscalls.sh
DTC arch/arm64/boot/dts/rockchip/px30-ad-d6-anx6345.dtb
DTC arch/arm64/boot/dts/rockchip/px30-ad-r35-mb-rk618-dual-lvds.dtb
DTC arch/arm64/boot/dts/rockchip/px30-ad-r35-mb-rk618-hdmi.dtb
DTC arch/arm64/boot/dts/rockchip/px30-ad-r35-mb-rk618-hdmi-lvds.dtb
DTC arch/arm64/boot/dts/rockchip/px30-ad-r35-mb-rk618-lvds.dtb
DTC arch/arm64/boot/dts/rockchip/px30-evb-ddr3-lvds-v10.dtb
DTC arch/arm64/boot/dts/rockchip/px30-evb-ddr3-v10.dtb
DTC arch/arm64/boot/dts/rockchip/px30-evb-ddr3-v10-avb.dtb
...
也可以单独编译某一个dts文件
make ARCH=arm64 rockchip/rk3568-atk-evb1-ddr4-v10.dtb
默认内核编译的脚本中会包含源码树文件的编译。
3 DTS语法
3.1 dtsi头文件
dtsi头文件类似于C语言中的头文件,包含一些公共的信息,可以在其他dts文件中导入
#include "rk3568-atk-evb1-ddr4-v10.dtsi"
#include "rk3568-linux.dtsi"
dts文件中除了可以引用dtsi文件,还可以引用.h头文件,甚至也可以引用dts文件。但是遵从编写习惯,还是将dtsi文件作为设备树的头文件,而不使用.h头文件。
一般.dtsi文件中描述的是SOC的内部外设信息,比如cpu架构、主频、外设寄存器地址范围等。
#include <dt-bindings/clock/rk3568-cru.h>
...
#include <dt-bindings/thermal/thermal.h>
#include "rk3568-dram-default-timing.dtsi"
/ {
compatible = "rockchip,rk3568";
interrupt-parent = <&gic>;
#address-cells = <2>;
#size-cells = <2>;
aliases {
csi2dphy0 = &csi2_dphy0;
csi2dphy1 = &csi2_dphy1;
csi2dphy2 = &csi2_dphy2;
dsi0 = &dsi0;
dsi1 = &dsi1;
ethernet0 = &gmac0;
ethernet1 = &gmac1;
gpio0 = &gpio0;
gpio1 = &gpio1;
gpio2 = &gpio2;
gpio3 = &gpio3;
gpio4 = &gpio4;
i2c0 = &i2c0;
...
mmc0 = &sdhci;
...
serial0 = &uart0;
...
spi0 = &spi0;
...
};
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x0>;
enable-method = "psci";
clocks = <&scmi_clk 0>;
operating-points-v2 = <&cpu0_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
#cooling-cells = <2>;
dynamic-power-coefficient = <187>;
};
cpu1: cpu@100 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x100>;
enable-method = "psci";
clocks = <&scmi_clk 0>;
operating-points-v2 = <&cpu0_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
};
cpu2: cpu@200 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x200>;
enable-method = "psci";
clocks = <&scmi_clk 0>;
operating-points-v2 = <&cpu0_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
};
cpu3: cpu@300 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x300>;
enable-method = "psci";
clocks = <&scmi_clk 0>;
operating-points-v2 = <&cpu0_opp_table>;
cpu-idle-states = <&CPU_SLEEP>;
};
idle-states {
entry-method = "psci";
CPU_SLEEP: cpu-sleep {
compatible = "arm,idle-state";
local-timer-stop;
arm,psci-suspend-param = <0x0010000>;
entry-latency-us = <100>;
exit-latency-us = <120>;
min-residency-us = <1000>;
};
};
};
}
以上是rk3568.dtsi文件的节选,包含了cpu、uart、I2c相关的一些设备信息。
3.2 设备节点
- 根节点
设备树采用树形结构描述板子上的设备信息,每个设备都是一个节点,叫做设备节点,每个节点通过一些属性信息来描述节点信息,属性就是键-值对。
/ {
...
cpus {
...
};
}
这里的“/”是根节点,每个设备树文件只有一个根节点。如果引用的两个文件都有根节点,那么两个根节点会进行合并,合并成一个根节点。
- 子节点
aliases {
...
}
cpus {
...
}
以上位于根节点之下的为根节点的子节点,设备树中节点命名格式为:
node-name@unit-address
其中node-name是节点的名字,节点的名字能够清晰的描述节点的功能,比如“uart1”表示UART1这个外设。
unit-address一般表示设备的地址或者寄存器首地址,如果没有,则可以不要
查看cpu节点,显示如下:
cpu0:cpu@0
这里用:分为了两部分,前面是节点的标签,后面是节点定义
- 节点属性
一个节点会包含不同的属性,不同的属性保存不同的内容,属性都是键值对,值可以为空或者任意的字节流,以下是常见的属性数据形式: - 字符串
compatible="rockchip,rk3568";
- 32位无符号整数
reg=<0>;
- 字符串列表
compatible="rockchip,rk3568-evb", "rockchip,rk3568";
3.3 标准属性
以下是节点中常用的一些属性
- 1、compatible属性
compatible属性叫做“兼容性”属性,是一个字符串列表,用于将设备和驱动绑定起来。字符串列表用于选择设备所要使用的驱动程序,格式如下:
"manufacturer,model"
其中manufacture表示厂商,model一般是模块对应的驱动名字。比如rk3568-atk-evb1-v10.dtsi中有一个MIPI摄像头节点,芯片采用SNOY公司的IMX415,属性值如下:
compatible = "sony,imx415";
compatible可以有多个属性值,比如:
compatible = "arm,cortex-a55-pmu", "arm,armv8-pmuv3";
在这样的配置中,在查找设备对应的驱动时,先将第一个兼容值在Linux内核中查找,如果能够找到则使用该驱动文件,如果没有,则查看第二个兼容值,以此类推,直到查找完所有的兼容值。
一般驱动程序文件都会有一个OF匹配列表,用于保存compatible值,如果设备节点的compatible值和OF匹配列表中的任何一个值相等,则表示设备可以使用该驱动。比如文件buildroot系统源码中的imx415.c文件中:
static const struct of_device_id imx415_of_match[] = {
{ .compatible = "sony,imx415" },
{},
};
该数组中只有一个compatible值,当设备树中的哪个节点与该compatible值匹配的时候,那么这个节点就会使用此驱动文件
- 2、model属性
model属性值是一个字符串,一般model描述开发板的名字或者设备模块信息,比如:
model = "Rockchip rk3568 EVB DDR4 V10 Board";
- 3、status属性
表示设备状态,属性值是字符串
值 | 描述 |
---|---|
“okay” | 表示设备可操作 |
“disabled” | 表示设备不可操作,但是未来可以变为可操作的,比如热插拔设备插入后 |
“fail” | 表示设备不可操作,设备检测到了一系列错误,设备也不太可能变成可操作 |
“fail-sss” | 和“fail”相同,后面的sss表示检测到的内容 |
- 4、#address-cells和#size-cells属性
这两个属性都是无符号32位整数,这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。#address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32 位),#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32 位)。
这两个属性表明子节点如何编写reg属性值,reg属性和地址相关,这两个就是描述起始地址和地址长度。reg属性格式为:
reg = <address1 length1 address2 length2 address3 length3……>
每个“address length”组合表示一个地址范围,其中address是起始地址,length是地址长度,#address-cells 表明 address 这个数据所占用的字长,#size-cells 表明 length 这个数据所占用的字长。
- 5、reg属性
(address,length)对,描述设备地址空间资源信息或者设备地址信息,比如某个外设寄存器地址范围信息,或者IIC设备地址等。
uart5: serial@fe690000 {
compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
reg = <0x0 0xfe690000 0x0 0x100>;
...
}
这里0xfe690000描述的是uart5寄存器首地址,长度为0x100
- 6、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 属性值为空值,说明子地址空间和父地址空间完全相同,不需要进行地址转换,对于我们所使用的 RK3568 来说,子地址空间和父地址空间完全相同,因此会在 rk3568.dtsi 中找到大量的值为空的 ranges 属性 - 7、name属性
记录节点名字,已被弃用 - 8、device_type属性
用于描述设备的FCode,但是设备树没有FCode,所以此属性不会使用。此属性只能用于cpu节点或者memory节点。
3.4 根节点compatible属性
每个节点都有compatible属性,根节点也是,根节点的属性内容如下:
/ {
model = "Rockchip RK3568 EVB1 DDR4 V10 Board";
compatible = "rockchip,rk3568-evb1-ddr4-v10", "rockchip,rk3568";
};
rk3568-atk-evb1-ddr4-v10.dtsi中定义的根节点中的compatible包含两个值。设备节点中的compatible用于匹配驱动程序,根节点中的有什么作用呢?这里根节点第一个通常描述设备名称,这里使用的就是“rk3568-evb1-ddr4-v10”这个设备。第二个值描述了设备使用的SOC,这里使用的是rk3568。
Linux内核会根据根节点的compatible属性查看是否支持此设备,如果支持的话就会启动Linux内核。
include/linux/rockchip/cpu.h
static inline bool cpu_is_rk3568(void)
{
if (rockchip_soc_id)
return (rockchip_soc_id & ROCKCHIP_CPU_MASK) == ROCKCHIP_CPU_RK3568;
return of_machine_is_compatible("rockchip,rk3568");
}
这里会判断soc是否为rk3568,根据根节点的compatible属性进行判断。
3.5 向节点追加或修改内容
假设要将一个fxls8471的设备连接到rk3568的i2c5这个端口上,那么就需要向i25节点中添加一个fxls8471子节点
i2c5: i2c@fe5e0000 {
compatible = "rockchip,rk3399-i2c";
reg = <0x0 0xfe5e0000 0x0 0x1000>;
clocks = <&cru CLK_I2C5>, <&cru PCLK_I2C5>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c5m0_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
fxls8471: fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
};
};
这样就添加了一个节点,但是有个问题是,这里添加的文件是rk3568.dtsi文件,而该文件是设备树头文件,所有使用SOC的版本都会引用这个文件,那么对于特定设备的修改缺影响了所有的该SOC的板子,这是不合理的。所以需要向设备追加数据,目前该板子使用的设备树文件为rk3568-atk-evb1-ddr4-v10.dsi,因此可以在这个文件中追加
&i2c5 {
status = "okay";
clock-frequency = <400000>;
fxls8471: fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
};
}
&i2c5表示访问i2c5这个label所对应的节点,label是通过aliases设置的,这样就完成了子节点的追加。同时修改了i2c5这个设备的状态,从disabled改为okay。
4 创建小型模板设备树
/ {
compatible = "rockchip,rk3568-evb1-ddr4-v10","rockchip,rk3568";
cpus {
#address-cells = <2>;
#size-cells = <0>;
cpu0: cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x0>;
};
cpu1: cpu@100 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x100>;
};
cpu2: cpu@200 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x200>;
};
cpu3: cpu@300 {
device_type = "cpu";
compatible = "arm,cortex-a55";
reg = <0x0 0x300>;
};
}
uart2: serial@fe660000 {
compatible = "rockchip,rk3568-uart", "snps,dw-apb-uart";
reg = <0x0 0xfe660000 0x0 0x100>;
};
spi0: spi@fe610000 {
compatible = "rockchip,rk3568-spi","rockchip,rk3066-spi";
reg = <0x0 0xfe610000 0x0 0x1000>;
};
i2c5: i2c@fe5e0000 {
compatible = "rockchip,rk3568-i2c","rockchip,rk3399-i2c";
reg = <0x0 0xfe5e0000 0x0 0x1000>;
};
};
这里创建了一个小型设备树,描述rk3568的cpu、uart2、spi0和i2c5这几个节点
5 设备树在系统中的体现
可以通过根文件系统的/proc/device-tree目录查看设备树,每一个节点都会在这个目录下生成一个文件夹
查看根节点属性
以上为model属性
以上是compatible属性
以上是cpus节点,其下的子节点也会创建目录
以上是aliases节点,目录下有很多文件,每个文件内容就是其节点的真实名字
以上是aliases中的i2c0文件内容
以上是chosen子节点,重点是bootargs参数,是为了uboot向Linux内核传递数据的。
Kernel command line: storagemedia=emmc androidboot.storagemedia=emmc androidboot.mode=normal androidboot.dtb_idx=0 androidboot.dtbo_idx=0 androidboot.verifiedbootstate=orange androidboot.serialno=daf9b9fe169b1e37 console=ttyFIQ0 androidboot.baseband=N/A androidboot.wificountrycode=CN androidboot.veritymode=enforcing androidboot.hardware=rk30board androidboot.console=ttyFIQ0 androidboot.verifiedbootstate=orange firmware_class.path=/vendor/etc/firmware init=/init rootwait ro loop.max_part=7 buildvariant=eng earlycon=uart8250,mmio32,0xfe660000 androidboot.boot_devices=fe310000.sdhci,fe330000.nandc
从Android的内核日志中可以看到kernel command line就是chosen中bootargs中的值。这个是Android中的,这里会比设备树中的chosen定义多一些数据,多出来的这部分是uboot传递给Linux内核的,boot会对将其与设备树中的定义进行组合,并传递给kernel。
6 绑定信息文档
设备树描述设备信息,不同的设备信息不同,反映到设备树中就是属性不同。如果需要添加一个硬件对应的节点,可以通过Linux内核源码中的Documentation/devicetree/bindings目录下的文档进行配置,这些文档称为绑定文档。
比如添加一个i2c文档,可以查阅Documentation/devicetree/bindings/i2c/i2c-rk3x.txt文件:
* Rockchip RK3xxx I2C controller
This driver interfaces with the native I2C controller present in Rockchip
RK3xxx SoCs.
Required properties :
- reg : Offset and length of the register set for the device
- compatible: should be one of the following:
- "rockchip,rv1108-i2c": for rv1108
- "rockchip,rv1126-i2c": for rv1126
- "rockchip,rk3066-i2c": for rk3066
- "rockchip,rk3188-i2c": for rk3188
- "rockchip,rk3228-i2c": for rk3228
- "rockchip,rk3288-i2c": for rk3288
- "rockchip,rk3328-i2c", "rockchip,rk3399-i2c": for rk3328
- "rockchip,rk3399-i2c": for rk3399
- interrupts : interrupt number
- clocks: See ../clock/clock-bindings.txt
- For older hardware (rk3066, rk3188, rk3228, rk3288):
- There is one clock that's used both to derive the functional clock
for the device and as the bus clock.
- For newer hardware (rk3399): specified by name
- "i2c": This is used to derive the functional clock.
- "pclk": This is the bus clock.
Required on RK3066, RK3188 :
- rockchip,grf : the phandle of the syscon node for the general register
file (GRF)
- on those SoCs an alias with the correct I2C bus ID (bit offset in the GRF)
is also required.
Optional properties :
- clock-frequency : SCL frequency to use (in Hz). If omitted, 100kHz is used.
- i2c-scl-rising-time-ns : Number of nanoseconds the SCL signal takes to rise
(t(r) in I2C specification). If not specified this is assumed to be
the maximum the specification allows(1000 ns for Standard-mode,
300 ns for Fast-mode) which might cause slightly slower communication.
- i2c-scl-falling-time-ns : Number of nanoseconds the SCL signal takes to fall
(t(f) in the I2C specification). If not specified this is assumed to
be the maximum the specification allows (300 ns) which might cause
slightly slower communication.
- i2c-sda-falling-time-ns : Number of nanoseconds the SDA signal takes to fall
(t(f) in the I2C specification). If not specified we'll use the SCL
value since they are the same in nearly all cases.
Example:
aliases {
i2c0 = &i2c0;
}
i2c0: i2c@2002d000 {
compatible = "rockchip,rk3188-i2c";
reg = <0x2002d000 0x1000>;
interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
rockchip,grf = <&grf>;
clock-names = "i2c";
clocks = <&cru PCLK_I2C0>;
i2c-scl-rising-time-ns = <800>;
i2c-scl-falling-time-ns = <100>;
};
这里描述了i2c设备节点配置包含的属性,如reg、compatible等,还有一个示例。
7 设备树常用OF操作函数
设备树描述了设备的详细信息,这些信息通过Linux内核提供了一系列的函数来获取,这一系列的函数都有一个统一的前缀“of_”,这些of函数定义在include/linux/of.h文件中
7.1 查找节点的OF函数
Linux内核使用device_node结构体来描述一个节点
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 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)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
查找节点有关的OF函数有5个:
- of_find_node_by_name
extern struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
通过节点名称查找,from表示开始节点,如果为NULL,表示从根节点开始查找整个设备树,name表示要查找的节点名字。
- of_find_node_by_type
extern struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
通过device_type属性来查找指定的节点,from和第一个一样,type表示device_type
- of_find_compatible_node
extern struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compat);
通过device_type和compatible这两个属性查找节点,device_type可以为NULL,表示忽略掉device_type属性。
- of_find_matching_node_and_match
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
extern 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);
通过of_device_id匹配表来查找节点,match表示找到的匹配的of_device_id
- of_find_node_by_path
static inline struct device_node *of_find_node_by_path(const char *path)
{
return of_find_node_opts_by_path(path, NULL);
}
通过路径来查找指定的节点
7.2 查找父/子节点的OF函数
- of_get_parent
extern struct device_node *of_get_parent(const struct device_node *node);
用于获取指定节点的父节点
3. of_get_next_child
extern struct device_node *of_get_next_child(const struct device_node *node,
struct device_node *prev);
node表示父节点,prev表示前一个子节点,也就是从哪一个子节点开始遍历查找对应的子节点,设置为NULL表示从node父节点的第一个子节点开始查找。
7.3 提取属性值的OF函数
Linux内核中使用property表示设备树中的属性
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
};
Linux提供了读取该属性值的OF函数
4. of_find_property
extern struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp);
np表示设备节点,name表示属性的名字,lenp表示属性值的字节数,返回找到的property
5. of_property_count_elems_of_size
extern int of_property_count_elems_of_size(const struct device_node *np,
const char *propname, int elem_size);
该函数用于获取属性中元素的数量,np为设备节点,propname表示属性的名字,elem_size表示元素长度。
6. of_property_read_u32_index
extern int of_property_read_u32_index(const struct device_node *np,
const char *propname,
u32 index, u32 *out_value);
该函数用于从属性中获取指定标号的u32类型数据值。
np表示设备节点,propname表示属性名称,index表示读取的值的标号,out_value表示读取到的值。
返回值0表示成功,负值表示读取失败,-EINVAL 表示属性不存在,-ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太小
7. of_property_read_u8_array、of_property_read_u16_array、of_property_read_u32_array 、of_property_read_u64_array
static inline int of_property_read_u8_array(const struct device_node *np,
const char *propname, u8 *out_values, size_t sz)
static inline int of_property_read_u16_array(const struct device_node *np,
const char *propname, u16 *out_values, size_t sz)
static inline int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values, size_t sz)
static inline int of_property_read_u64_array(const struct device_node *np,
const char *propname,
u64 *out_values, size_t sz)
分别读取属性的u8、u16、u32、u64类型的数组,sz表示要读取数组元素数量。
8. of_property_read_u8、of_property_read_u16、of_property_read_u32、of_property_read_u64
static inline int of_property_read_u8(const struct device_node *np,
const char *propname,
u8 *out_value)
static inline int of_property_read_u16(const struct device_node *np,
const char *propname,
u16 *out_value)
static inline int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
static inline int of_property_read_u64(const struct device_node *np,
const char *propname,
u64 *out_value)
分别读取属性的u8、u16、u32、u64类型的数组
9. of_property_read_string
extern int of_property_read_string(const struct device_node *np,
const char *propname,
const char **out_string);
读取字符串类型的属性值
10. of_n_addr_cells
extern int of_n_addr_cells(struct device_node *np);
用于获取#address-cells属性值
11. of_n_size_cells
extern int of_n_size_cells(struct device_node *np);
用于获取#size-cells属性值
7.4 其他常用的OF函数
- of_device_is_compatible
static inline int of_device_is_compatible(const struct device_node *device,
const char *name)
用于查看节点的compatible属性是否包含name指定的字符串,也就是检查节点的兼容性。
2. of_get_address
static inline const __be32 *of_get_address(struct device_node *dev, int index,
u64 *size, unsigned int *flags)
用于获取地址相关的属性,主要是“reg”或者“assigned-addresses”属性。dev是设备节点,index表示读取的地址标号,如reg中的地址index,size表示地址长度。flags表示IORESOURCE_IO、IORESOURCE_MEM。
3. of_translate_address
extern const __be32 *of_get_address(struct device_node *dev, int index,
u64 *size, unsigned int *flags);
用于将从设备树读取到的物理地址转换为虚拟地址
4. of_address_to_resource
IIC、SPI、GPIO 等这些外设都有对应的寄存器,这些寄存器其实就是一组内存空间,Linux内核使用 resource 结构体来描述一段内存空间, “resource”翻译出来就是“资源”,因此用 resource结构体描述的都是设备资源信息,resource 结构体定义在文件 include/linux/ioport.h 中,定义如下:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
unsigned long desc;
struct resource *parent, *sibling, *child;
ANDROID_KABI_RESERVE(1);
ANDROID_KABI_RESERVE(2);
ANDROID_KABI_RESERVE(3);
ANDROID_KABI_RESERVE(4);
};
对于32位的SOC,resource_size_t是u32类型,其中start表示起始地址,end表示结束地址,name是这个资源的名字,flags是自愿标志位,一般表示资源类型,可选的资源标志定义在include/linux/ioport.h文件中:
#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结构体类型:
extern int of_address_to_resource(struct device_node *dev, int index,
struct resource *r);
- of_iomap
extern void __iomem *of_iomap(struct device_node *device, int index);
该函数用于直接内存映射,之前通过ioremap函数完成物理地址到虚拟地址的映射,采用设备树开发之后通过of_iomap函数来获取内存地址所对应的虚拟地址。of_iomap本质是将reg属性中的地址信息转换为虚拟地址,如果reg属性有多段的话,可以通过index参数来指定映射哪一段内存