1. 什么是设备树
设备树的本质也是操作寄存器,只不过寄存器的相关信息放在了设备树中,配置寄存器时需要使用OF函数从设备树中读取寄存器数据后再进行配置
Linux 3.x之前是没有设备树的,Linux通过内核源码中arch/arm/mach-xxx和arch/arm/plat-xxx文件夹里的板级描述文件来描述ARM架构中的板级信息。 随着ARM硬件种类增多,与板子相关的设备文件也越来越多,导致内核越来越大,而实际上这些硬件板级信息与内核并无相关关系
2011年,Linus发现该问题后,引入了PowerPC等架构已经采用的设备树机制,将板级信息从内核中分离开来,用一个专属的文件格式(.dts文件)来描述。 设备树的作用就是描述硬件平台的硬件资源,它可被bootloader传递到内核,内核可以从设备树中获取硬件信息。 设备树描述硬件资源时有两个特点:
- 以树状结构描述硬件资源
- 可以像头文件那样,一个设备树文件可以引用另一个设备树文件,实现代码重用
DTS、DTSI、DTB、DTC文件的区别及定义:
DTC工具源码在Linux内核的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
......
由上可见DTC工具依赖于dtc.c, flattree.c, fstree.c等文件,最终编译并链接出DTC这个主机文件。 若要编译DTS文件,只需要进入到Linux源码根目录下,然后执行如下命令:
make all #编译Linux源码中的所有东西,包括zImage、.ko驱动模块以及设备树
make dtbs #只是编译设备树
基于ARM架构的SOC有很多,一种SOC又可制作出多款板子,每个板子都有对应的DTS文件,那么如何确定编译哪一个DTS文件呢? 以I.MX6ULL芯片的板子为例,打开arch/arm/boot/dts/Makefile,有如下内容:
dtb-$(CONFIG_SOC_IMX6UL) += \\
imx6ul-14x14-ddr3-arm2.dtb \\
imx6ul-14x14-ddr3-arm2-emmc.dtb \\
......
dtb-$(CONFIG_SOC_IMX6ULL) += \\
imx6ull-14x14-ddr3-arm2.dtb \\
imx6ull-14x14-ddr3-arm2-adc.dtb \\
......
选中I.MX6ULL后(即CONFIG_SOC_IMX6ULL=y),所有使用该SOC的板子对应的.dts文件都会被编译为.dtb。 若新做一个板子,只需要新建一个对应的.dts文件,再将对应的.dtb文件名添加到 dtb-$(CONFIG_SOC_IMX6ULL)下,这样在编译设备树时就会将对应的.dts编译为二进制的.dtb文件
2. 设备树语法介绍
2.1 设备树代码分析
关于i. MX6ULL已有的设备树文件,大致有以下几种
imx6ull-14x14-evk-emmc.dts文件:在/arch/arm/boot/dts/文件夹中,描述了emmc板子的usdhc信息。 该文件通过头文件的形式包含了另一个设备树文件
#include "imx6ull-14x14-evk.dts"
&usdhc2 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc2_8bit>;
pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
bus-width = <8>;
non-removable;
status = "okay";
};
imx6ull-14x14-evk.dts文件:在/arch/arm/boot/dts/文件夹中,包含了根节点、子节点及其他追加内容
#include
#include "imx6ull.dtsi"
/ {
model = "Freescale i.MX6 ULL 14x14 EVK Board";
compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory {
reg = <0x80000000 0x20000000>;
};
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x14000000>;
linux,cma-default;
};
};
backlight {
compatible = "pwm-backlight";
pwms = <&pwm1 0 5000000>;
brightness-levels = <0 4 8 16 32 64 128 255>;
default-brightness-level = <6>;
status = "okay";
};
pxp_v4l2 {
compatible = "fsl,imx6ul-pxp-v4l2", "fsl,imx6sx-pxp-v4l2", "fsl,imx6sl-pxp-v4l2";
status = "okay";
};
regulators {
compatible = "simple-bus";
......
};
......
};
&cpu0 {
arm-supply = <®_arm>;
soc-supply = <®_soc>;
dc-supply = <®_gpio_dvfs>;
};
&clks {
assigned-clocks = <&clks IMX6UL_CLK_PLL4_AUDIO_DIV>;
assigned-clock-rates = <786432000>;
};
......
&wdog1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_wdog>;
fsl,wdog_b;
};
imx6ull.dtsi文件:是设备树的头文件,其格式与设备树基本相同
#include
#include "imx6dl-pinfunc.h"
#include "imx6qdl.dtsi"
/ {
aliases {
i2c3 = &i2c4;
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu0: cpu@0 {
compatible = "arm,cortex-a9";
device_type = "cpu";
......
};
cpu@1 {
compatible = "arm,cortex-a9";
device_type = "cpu";
reg = <1>;
next-level-cache = <&L2>;
};
};
reserved-memory {
......
};
soc {
......
ocram: sram@00905000 {
compatible = "mmio-sram";
reg = <0x00905000 0x1B000>;
clocks = <&clks IMX6QDL_CLK_OCRAM>;
};
......
};
};
......
&vpu_fsl {
iramsize = <0>;
};
2.2 DTS语法介绍
设备树采用树形结构来描述板子上的设备信息,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键-值对
node-name@unit-address{
属性1 = ...
属性2 = ...
子节点...
}
节点名称:node-name用于指定节点名称,应使用字母开头,并能描述设备类别(根节点用斜杠表示)
单元地址:@unit-address用于指定单元地址,@为分隔符,后面是实际的单元地址,它的值与节点reg属性的第一个地址一致,若没有reg属性值,则可以省略单元地址
节点属性:节点的大括号{ }中包含的内容是节点属性, 一个节点可以包含多个属性信息,设备树最主要的内容就是编写节点的属性。 属性包括自定义属性和标准属性
- model属性:用于指定设备的制造商和型号,多个字符串使用“,”分隔开
- compatible属性:由一个或多个字符串组成,是用来查找节点的方法之一
- status属性:用于指示设备的操作状态,通过status可以禁用或启用设备
- reg属性:描述设备资源在其父总线定义的地址空间内的地址,通常用于表示一块寄存器的起始地址和长度
- #address-cells 和 #size-cells:这两个属性同时存在,前者决定了子节点reg属性中地址信息所占用的字长,后者决定了长度信息所占的字长
- ranges属性:是一个地址映射/转换表,由子地址、父地址和地址空间长度这三部分组成
- child-bus-address:子总线地址空间的物理地址, 由父节点的#address-cells 确定此物理地址所占用的字长
- parent-bus-address:父总线地址空间的物理地址,同样由父节点的#address-cells 确定此物理地址所占用的字长
- length:子地址空间的长度,由父节点的#size-cells 确定此地址长度所占用的字长
特殊节点:aliases子节点、chosen子节点
//为其他节点起一个别名
aliases {
i2c3 = &i2c4;
};
//该节点位于根节点下,它不代表实际硬件,主要用于给内核传递参数
//下面代码表示系统标准输出stdout使用串口uart1
chosen {
stdout-path = &uart1;
};
3. 设备树OF函数
内核提供了一系列函数用于从设备节点获取节点中定义的属性,这些函数以of_开头,称为OF函数。 在编写设备树版的驱动时,在进行硬件配置方面,就是要用这些OF函数,将寄存器地址等信息从设备树文件中获取出来,然后进行配置
3.1 查找节点的OF函数
设备都是以节点的形式挂到设备树上的,因此要获取这个设备的其他属性信息,必须先获取到这个设备的节点。 内核使用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 *compatible)
//from:开始查找的节点,NULL表示从根节点开始查找整个设备树
//type:要查找的节点对应的device_type属性值,为NULL时表示忽略掉该属性
//compatible:要查找的节点所对应的compatible属性列表
//返回值:找到的节点,NULL表示查找失败
of_find_matching_node_and_match:通过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:通过路径来查找指定的节点
inline struct device_node *of_find_node_by_path(const char *path)
//path:带有全路径的节点名
//返回值:找到的节点,NULL表示查找失败
3.2 查找父/子节点的OF函数
of_get_parent:用于查找父节点
struct device_node *of_get_parent(const struct device_node *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,表示从第一个子节点开始
//返回值: 找到的下一个子节点
3.3 提取属性值的OF函数
节点的属性信息里面保存了驱动所需要的内容,因此对于属性值的提取非常重要,内核中使用结构体property表示属性,定义在include/linux/of.h中,内容如下:
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:查找指定的属性
property *of_find_property(const struct device_node *np, const char *name, int *lenp)
//np:设备节点
//name:属性名字
//lenp:属性值的字节数
//返回值:找到的属性
of_property_count_elems_of_size:获取属性中元素的数量
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类型数据值
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_u8_array:读取属性中u8类型的数组数据(类似的还有u16、u32 和 u64)
int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz)
//np:设备节点
//proname:要读取的属性名字
//out_value:读取到的数组值
//sz:要读取的数组元素数量
//返回值:0 读取成功,负值,读取失败,-EINVAL 表示属性不存在,
// -ENODATA 表示没有要读取的数据,-EOVERFLOW 表示属性值列表太
of_property_read_u8:读取只有一个整形值的属性(类似的还有u16、u32和u64)
int of_property_read_u8(const struct device_node *np, const char *propname, u8 *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属性值
3.4 其他常用OF函数
of_device_is_compatible:查看节点的compatible属性是否有包含compat指定的字符串,也就是检查设备节点的兼容性
int of_device_is_compatible(const struct device_node *device, const char *compat)
//device:设备节点
//compat:要查看的字符串
//返回值: 0,节点的 compatible 属性中不包含 compat 指定的字符串;
// 正数,节点的 compatible 属性中包含 compat 指定的字符串
of_get_address:获取地址相关属性
const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags)
//dev:设备节点
//index:要读取的地址标号
//size:地址长度
//flags:参数,比如IORESOURCE_IO、IORESOURCE_MEM等
//返回值:读取到的地址数据首地址,表示读取失败
of_translate_address:将设备树读取到的地址转换为物理地址
u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)
//dev:设备节点
//in_addr:要转换的地址
//返回值:得到的物理地址,为OF_BAD_ADDR表示转换失败
of_address_to_resource:将reg属性值,转换为resource结构体类型
int of_address_to_resource(struct device_node *dev, int index, struct resource *r)
//dev:设备节点
//index:地址资源标号
//r:得到的 resource 类型的资源值
//返回值: 0,成功;负值,失败
of_iomap:用于直接内存映射
void __iomem *of_iomap(struct device_node *np, int index)
//np:设备节点
//index:reg属性中要完成内存映射的段,如果reg属性只有一段的话index就设置为0
//返回值:经过内存映射后的虚拟内存首地址,NULL表示内存映射失败
-
ARM
+关注
关注
134文章
9054浏览量
366827 -
寄存器
+关注
关注
31文章
5322浏览量
120018 -
源码
+关注
关注
8文章
633浏览量
29140 -
Linu
+关注
关注
0文章
26浏览量
19809 -
设备树
+关注
关注
0文章
38浏览量
3110
发布评论请先 登录
相关推荐
评论