设备树的历史背景
背景:
在早期的嵌入式系统中,硬件配置信息通常硬编码在内核源码中,这导致了内核代码的冗长和难以维护。
为了解决这个问题,设备树(Device Tree)被引入,使得硬件描述与内核代码分离,从而提高了内核的可移植性和可维护性。
引入时间:
设备树最初在PowerPC架构中引入,后来被ARM架构广泛采用,并逐渐成为Linux内核的标准配置方式。
设备树的基本概念
c
节点(Node):
设备树中的每个节点代表一个硬件设备或一个硬件组件。
节点可以包含子节点,形成树状结构。
属性(Property):
节点中的键值对,用于描述硬件设备的特性或配置。
属性可以是字符串、整数、布尔值、数组等。
路径(Path):
设备树中的节点路径表示节点在树中的位置。
例如,/cpus/cpu@0 表示根节点下的 cpus 节点下的 cpu@0 节点。
设备树的常用节点和属性
常用节点:
/:根节点,表示整个系统。
/cpus:包含所有CPU节点的父节点。
/memory:描述系统内存配置。
/chosen:用于传递启动参数,如 bootargs。
/soc:描述片上系统(SoC)的硬件配置。
/aliases:定义设备树中的别名,方便引用。
常用属性:
compatible:描述设备的兼容性,通常用于匹配驱动程序。
reg:描述设备的寄存器地址和大小。
interrupts:描述设备的中断信息。
clock-frequency:描述时钟频率。
status:描述设备的状态,如 "okay" 或 "disabled"。
设备树的编译与使用
c
编译设备树:
使用 dtc(Device Tree Compiler)工具将DTS文件编译为DTB文件。
dtc -I dts -O dtb -o my_device_tree.dtb my_device_tree.dts
加载设备树:
在启动Linux内核时,可以通过引导加载程序(如U-Boot)将DTB文件传递给内核。
bootm <kernel_image> - <dtb_file>
内核解析设备树:
内核在启动时会解析DTB文件,并根据设备树中的描述配置硬件设备。
内核驱动程序可以通过设备树节点和属性来识别和配置硬件设备。
设备树与驱动程序
驱动程序与设备树的交互:
驱动程序可以通过设备树节点和属性来获取硬件信息,并进行相应的初始化和配置。
例如,驱动程序可以通过 of_get_property() 函数获取设备树中的属性值。
设备树与设备模型的关系:
Linux内核使用设备模型(Device Model)来管理硬件设备。
设备树节点会被转换为设备模型中的设备节点(Device Node),驱动程序通过这些设备节点与硬件交互。
如何书写设备树
设备树的基本语法:
节点定义:使用大括号 {} 定义节点。
属性定义:使用键值对的形式定义属性,键和值之间用等号 = 连接。
注释:使用 // 或 /* */ 进行注释。
dts
/dts-v1/; // 声明设备树文件的版本为v1
/ {
// 根节点,表示整个系统
model = "My Custom Board"; // 描述系统的型号
compatible = "my,custom-board"; // 描述系统的兼容性,用于匹配驱动程序
cpus {
// 定义CPU节点
#address-cells = <1>; // 定义地址单元的数量,用于描述寄存器地址
#size-cells = <0>; // 定义大小单元的数量,用于描述寄存器大小
cpu@0 {
// 定义第一个CPU节点
compatible = "arm,cortex-a9"; // 描述CPU的兼容性
reg = <0>; // 描述CPU的寄存器地址
};
cpu@1 {
// 定义第二个CPU节点
compatible = "arm,cortex-a9"; // 描述CPU的兼容性
reg = <1>; // 描述CPU的寄存器地址
};
};
memory@80000000 {
// 定义内存节点
device_type = "memory"; // 描述设备的类型为内存
reg = <0x80000000 0x10000000>; // 描述内存的起始地址和大小(256MB)
};
chosen {
// 定义chosen节点,用于传递启动参数
bootargs = "console=ttyS0,115200"; // 设置启动参数,配置串口终端
};
soc {
// 定义SoC节点,表示片上系统
compatible = "simple-bus"; // 描述SoC的兼容性
#address-cells = <1>; // 定义地址单元的数量
#size-cells = <1>; // 定义大小单元的数量
serial@101f1000 {
// 定义串口节点
compatible = "ns16550a"; // 描述串口的兼容性
reg = <0x101f1000 0x100>; // 描述串口的寄存器地址和大小
interrupts = <1 1>; // 描述串口的中断信息
clock-frequency = <1843200>; // 描述串口的时钟频率
};
ethernet@10108000 {
// 定义以太网节点
compatible = "smc91x"; // 描述以太网的兼容性
reg = <0x10108000 0x1000>; // 描述以太网的寄存器地址和大小
interrupts = <2 1>; // 描述以太网的中断信息
};
};
aliases {
// 定义别名节点,方便引用
serial0 = "/soc/serial@101f1000"; // 定义串口0的别名
ethernet0 = "/soc/ethernet@10108000"; // 定义以太网0的别名
};
};
示例
my_device_tree.dts
dts
/dts-v1/; // 声明设备树文件的版本为v1
/ {
// 根节点,表示整个系统
model = "My Custom Board"; // 描述系统的型号
compatible = "my,custom-board"; // 描述系统的兼容性,用于匹配驱动程序
cpus {
// 定义CPU节点
#address-cells = <1>; // 定义地址单元的数量,用于描述寄存器地址
#size-cells = <0>; // 定义大小单元的数量,用于描述寄存器大小
cpu@0 {
// 定义第一个CPU节点
compatible = "arm,cortex-a9"; // 描述CPU的兼容性
reg = <0>; // 描述CPU的寄存器地址
};
};
memory@80000000 {
// 定义内存节点
device_type = "memory"; // 描述设备的类型为内存
reg = <0x80000000 0x10000000>; // 描述内存的起始地址和大小(256MB)
};
chosen {
// 定义chosen节点,用于传递启动参数
bootargs = "console=ttyS0,115200"; // 设置启动参数,配置串口终端
};
my_custom_device {
// 定义自定义设备节点
compatible = "my,custom-device"; // 描述自定义设备的兼容性
status = "okay"; // 描述设备的状态,表示设备可用
my_property = "Hello, Device Tree!"; // 自定义字符串属性
my_int_property = <42>; // 自定义整数属性
my_array_property = <0x11 0x22 0x33>; // 自定义数组属性
};
};
my_custom_device_module.c
c
#include <linux/module.h> // 包含内核模块相关的头文件
#include <linux/of.h> // 包含设备树相关的头文件
#include <linux/of_device.h> // 包含设备树设备相关的头文件
MODULE_LICENSE("GPL"); // 声明模块的许可证为GPL
MODULE_AUTHOR("Your Name"); // 声明模块的作者
MODULE_DESCRIPTION("A simple module to read custom device tree properties"); // 描述模块的功能
// 设备探测函数,当设备匹配时调用
static int my_custom_device_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node; // 获取设备树节点
const char *my_property; // 定义字符串属性变量
u32 my_int_property; // 定义整数属性变量
u32 my_array_property[3]; // 定义数组属性变量
int ret; // 定义返回值变量
// 获取字符串属性
ret = of_property_read_string(node, "my_property", &my_property);
if (ret) {
pr_err("Failed to read my_property\n"); // 打印错误信息
return ret; // 返回错误码
}
pr_info("my_property: %s\n", my_property); // 打印字符串属性值
// 获取整数属性
ret = of_property_read_u32(node, "my_int_property", &my_int_property);
if (ret) {
pr_err("Failed to read my_int_property\n"); // 打印错误信息
return ret; // 返回错误码
}
pr_info("my_int_property: %u\n", my_int_property); // 打印整数属性值
// 获取数组属性
ret = of_property_read_u32_array(node, "my_array_property", my_array_property, 3);
if (ret) {
pr_err("Failed to read my_array_property\n"); // 打印错误信息
return ret; // 返回错误码
}
pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]); // 打印数组属性值
return 0; // 返回成功
}
// 设备移除函数,当设备移除时调用
static int my_custom_device_remove(struct platform_device *pdev)
{
return 0; // 返回成功
}
// 设备树匹配表,用于匹配设备树节点
static const struct of_device_id my_custom_device_of_match[] = {
{ .compatible = "my,custom-device", }, // 匹配自定义设备节点
{ /* sentinel */ } // 结束标记
};
MODULE_DEVICE_TABLE(of, my_custom_device_of_match); // 声明设备树匹配表
// 平台驱动结构体
static struct platform_driver my_custom_device_driver = {
.probe = my_custom_device_probe, // 设置探测函数
.remove = my_custom_device_remove, // 设置移除函数
.driver = {
.name = "my_custom_device", // 设置驱动名称
.of_match_table = my_custom_device_of_match, // 设置设备树匹配表
},
};
// 注册平台驱动
module_platform_driver(my_custom_device_driver);
device_node 结构体
device_node 结构体是设备树节点的核心数据结构,表示设备树中的一个节点。
c
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; // 已删除的属性链表
struct device_node *parent; // 父节点
struct device_node *child; // 子节点
struct device_node *sibling; // 兄弟节点
struct kobject kobj; // 内核对象
void *data; // 私有数据
};
设备树操作API
c
2.1 of_find_node_by_path()
根据路径查找设备树节点。
struct device_node *of_find_node_by_path(const char *path);
path:设备树节点的路径。
返回值:找到的设备树节点指针,如果未找到则返回 NULL。
示例:
struct device_node *node = of_find_node_by_path("/my_custom_device");
if (node) {
pr_info("Found node: %s\n", node->full_name);
} else {
pr_info("Node not found\n");
}
2.2 of_find_node_by_name()
根据节点名称查找设备树节点。
struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。
name:节点名称。
返回值:找到的设备树节点指针,如果未找到则返回 NULL。
示例:
struct device_node *node = of_find_node_by_name(NULL, "my_custom_device");
if (node) {
pr_info("Found node: %s\n", node->full_name);
} else {
pr_info("Node not found\n");
}
2.3 of_find_node_by_type()
根据节点类型查找设备树节点。
struct device_node *of_find_node_by_type(struct device_node *from, const char *type);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。
type:节点类型。
返回值:找到的设备树节点指针,如果未找到则返回 NULL。
示例:
struct device_node *node = of_find_node_by_type(NULL, "memory");
if (node) {
pr_info("Found node: %s\n", node->full_name);
} else {
pr_info("Node not found\n");
}
2.4 of_find_compatible_node()
根据 compatible 属性查找设备树节点。
struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible);
from:从哪个节点开始查找(可选,传 NULL 表示从根节点开始)。
type:节点类型(可选)。
compatible:compatible 属性值。
返回值:找到的设备树节点指针,如果未找到则返回 NULL。
示例:
struct device_node *node = of_find_compatible_node(NULL, NULL, "my,custom-device");
if (node) {
pr_info("Found node: %s\n", node->full_name);
} else {
pr_info("Node not found\n");
}
2.5 of_get_property()
获取设备树节点的属性值。
const void *of_get_property(const struct device_node *np, const char *name, int *lenp);
np:设备树节点指针。
name:属性名称。
lenp:属性值的长度(可选)。
返回值:属性值的指针,如果未找到则返回 NULL。
示例:
const char *prop_value;
int prop_len;
prop_value = of_get_property(node, "my_property", &prop_len);
if (prop_value) {
pr_info("my_property: %s\n", prop_value);
} else {
pr_info("my_property not found\n");
}
2.6 of_property_read_string()
读取字符串类型的属性值。
int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string);
np:设备树节点指针。
propname:属性名称。
out_string:输出字符串指针。
返回值:0表示成功,负数表示失败。
示例:
const char *my_property;
if (of_property_read_string(node, "my_property", &my_property) == 0) {
pr_info("my_property: %s\n", my_property);
} else {
pr_info("my_property not found\n");
}
2.7 of_property_read_u32()
读取32位整数类型的属性值。
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
np:设备树节点指针。
propname:属性名称。
out_value:输出整数值。
返回值:0表示成功,负数表示失败。
示例:
u32 my_int_property;
if (of_property_read_u32(node, "my_int_property", &my_int_property) == 0) {
pr_info("my_int_property: %u\n", my_int_property);
} else {
pr_info("my_int_property not found\n");
}
2.8 of_property_read_u32_array()
读取32位整数数组类型的属性值。
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz);
np:设备树节点指针。
propname:属性名称。
out_values:输出整数数组。
sz:数组大小。
返回值:0表示成功,负数表示失败。
示例:
u32 my_array_property[3];
if (of_property_read_u32_array(node, "my_array_property", my_array_property, 3) == 0) {
pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]);
} else {
pr_info("my_array_property not found\n");
}
2.9 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:设备树节点指针。
propname:属性名称。
elem_size:元素大小(字节)。
返回值:元素数量,负数表示失败。
示例:
int count = of_property_count_elems_of_size(node, "my_array_property", sizeof(u32));
if (count > 0) {
pr_info("my_array_property has %d elements\n", count);
} else {
pr_info("my_array_property not found\n");
}
2.10 of_property_read_variable_u32_array()
读取可变长度的32位整数数组类型的属性值。
int of_property_read_variable_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz_min, size_t sz_max);
np:设备树节点指针。
propname:属性名称。
out_values:输出整数数组。
sz_min:最小数组大小。
sz_max:最大数组大小。
返回值:实际读取的元素数量,负数表示失败。
示例:
u32 my_array_property[3];
int count = of_property_read_variable_u32_array(node, "my_array_property", my_array_property, 1, 3);
if (count > 0) {
pr_info("my_array_property: 0x%x 0x%x 0x%x\n", my_array_property[0], my_array_property[1], my_array_property[2]);
} else {
pr_info("my_array_property not found\n");
}
设备树绑定API
c
设备树绑定API用于将设备树节点与驱动程序绑定。以下是一些常用的API函数:
3.1 of_device_is_compatible()
检查设备树节点是否与指定的 compatible 属性匹配。
int of_device_is_compatible(const struct device_node *device, const char *compat);
device:设备树节点指针。
compat:compatible 属性值。
返回值:非零表示匹配,零表示不匹配。
示例:
if (of_device_is_compatible(node, "my,custom-device")) {
pr_info("Node is compatible with 'my,custom-device'\n");
} else {
pr_info("Node is not compatible with 'my,custom-device'\n");
}
3.2 of_device_is_available()
检查设备树节点是否可用(即 status 属性是否为 "okay")。
int of_device_is_available(const struct device_node *device);
device:设备树节点指针。
返回值:非零表示可用,零表示不可用。
示例:
if (of_device_is_available(node)) {
pr_info("Node is available\n");
} else {
pr_info("Node is not available\n");
}
3.3 of_platform_populate()
递归地为设备树节点创建平台设备。
int of_platform_populate(struct device_node *root, const struct of_device_id *matches, const struct of_dev_auxdata *lookup, struct device *parent);
root:设备树根节点指针。
matches:设备树匹配表。
lookup:设备树辅助数据(可选)。
parent:父设备指针(可选)。
返回值:0表示成功,负数表示失败。
示例:
static const struct of_device_id my_custom_device_of_match[] = {
{ .compatible = "my,custom-device", },
{ /* sentinel */ }
};
int ret = of_platform_populate(NULL, my_custom_device_of_match, NULL, NULL);
if (ret) {
pr_err("Failed to populate platform devices\n");
}
在设备树中,别名(Aliases)和引用(References)
c
1. 别名(Aliases)
别名是一种方便的方式,用于给设备树中的节点或路径指定一个易于使用的名称。在设备树中,别名通常用于简化设备的访问,使得访问特定设备的代码更直观。
定义方式:别名在设备树的顶部通过aliases节点进行声明。例如:
aliases {
spi0 = "spi@1";
i2c0 = "i2c@0";
serial0 = "uart@0";
};
在这个例子中,您可以使用spi0来引用名为spi@1的设备节点。
使用场景:别名通常用于驱动程序中,以便通过别名快速访问设备。例如,在驱动程序中,您可以使用of_alias_get_id函数依据别名获取设备的ID。
2. 引用(References)
引用用于在设备树中引用其他节点或属性,主要通过节点路径来实现。
引用可以帮助减少冗余,特别是在多个节点需要共享同一设备或配置时。
定义方式:设备树中的节点可以通过路径引用。例如:
my_device: my_device_node {
compatible = "my,device";
reg = <0x1000 0x100>;
};
another_device: another_device_node {
compatible = "another,device";
depends_on = <&my_device>; // 远程引用
};
在这个例子中,another_device_node中的depends_on属性用来引用了my_device_node。
使用场景:引用可以用来指定设备之间的依赖关系,或者在一个设备中引用其他设备的资源。使用引用可以避免重复定义相同的设备配置,有助于提高设备树结构的可读性和可维护性。
compatible 字段
设备树中一个重要的属性,它用于指定设备的兼容性字符串,帮助操作系统识别硬件和对应的驱动程序。
什么是 compatible 属性?
compatible 属性的作用是告诉操作系统这个设备支持的功能和
它的驱动程序的匹配信息。它包含一个或多个字符串,
描述了不同的兼容设备类或设备名称。
该属性通常是一个字符串数组,包含一个或多个值,
描述设备的不同兼容性,开发者可以在设备树源文件中指定。
示例
一些设备树中的节点可能如下所示:
c
my_device: my_device@0 {
compatible = "vendor,my_device", "vendor,generic_device";
reg = <0x0 0x10000 0x0 0x1000>; // 寄存器地址
interrupts = <1>; // 中断号
};
解释
my_device@0:节点的名称,通常包含设备的名称和地址。
compatible:这个节点指定了它的兼容性为vendor,my_device和vendor,generic_device。这表明这个设备可以被这两个驱动程序支持。
reg 和 interrupts 是与硬件设备相关的其他重要信息。
在驱动程序中的作用
c
在涉及 Linux 内核驱动的开发时,设备树的 compatible 属性用于驱动程序的识别和绑定。通常在驱动代码中,也对 compatible 属性进行匹配,以确定是否能够处理特定的设备。
驱动代码示例
在驱动程序中,可能会看到如下代码片段:
static const struct of_device_id my_device_ids[] = {
{ .compatible = "vendor,my_device" },
{ .compatible = "vendor,generic_device" },
{},
};
MODULE_DEVICE_TABLE(of, my_device_ids);
of_device_id:用于定义与设备树中的 compatible 字符串匹配的结构体数组。
MODULE_DEVICE_TABLE:用于将模块与设备树的兼容性代码进行连结,这样,当设备在启动时,内核可以根据设备树中的信息找到相应的驱动程序进行加载。
ompatible 属性相关的API
c
1. of_device_get_match_data
功能: 根据设备树节点中的 compatible 属性,返回与之匹配的驱动数据。
const void *of_device_get_match_data(struct device *dev);
参数:
dev:指向设备结构的指针,通常在驱动程序中获取。
返回值: 返回指向匹配数据的指针,如果没有匹配,返回 NULL。
2. of_matchDevice
功能: 检查设备树节点与给定的设备 ID 列表的匹配。
const struct of_device_id *of_match_device(const struct of_device_id *matches, struct device *dev);
参数:
matches:设备ID匹配表。
dev:指向要匹配的设备结构的指针。
返回值: 返回匹配的 of_device_id 结构指针,如果没有匹配,返回 NULL。
3. of_device_id
功能: 定义设备与驱动匹配的结构。通常用于在驱动程序中声明支持哪些设备。
struct of_device_id {
const char *compatible; // 设备的兼容性字符串
const void *data; // 可选的私有数据,用于驱动程序
};
使用示例: 将其用于驱动程序中,定义支持的设备。
static const struct of_device_id my_device_ids[] = {
{ .compatible = "vendor,my_device" },
{ .compatible = "vendor,generic_device" },
{},
};
4. of_register_driver
功能: 注册一个与设备树相连接的驱动程序。
int of_register_driver(struct of_driver *drv);
参数:
drv:指向 of_driver 结构体的指针,包含驱动的描述信息。
5. of_driver
功能: 描述一个设备树驱动的结构。
struct of_driver {
struct module *owner; // 驱动模块的所有者
struct of_device_id *ids; // 支持的设备ID列表
int (*probe)(struct device *dev); // 驱动的探测函数
void (*remove)(struct device *dev); // 驱动的移除函数
};
6. of_get_child_by_name
功能: 根据名称获取设备树节点的子节点。
struct device_node *of_get_child_by_name(const struct device_node *np, const char *name);
参数:
np:父节点。
name:要匹配的子节点的名称。
返回值: 返回匹配的子节点指针,如果未找到,则返回 NULL。
7. of_get_parent
功能: 获取设备树节点的父节点。
struct device_node *of_get_parent(const struct device_node *np);
参数:
np:设备树节点。
返回值: 返回父节点指针,如果没有父节点则返回 NULL。