linux驱动开发-设备树

设备树的历史背景

背景:

在早期的嵌入式系统中,硬件配置信息通常硬编码在内核源码中,这导致了内核代码的冗长和难以维护。

为了解决这个问题,设备树(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。
相关推荐
tan77º10 分钟前
【项目】分布式Json-RPC框架 - 项目介绍与前置知识准备
linux·网络·分布式·网络协议·tcp/ip·rpc·json
sukalot3 小时前
window显示驱动开发—在混合系统中使用跨适配器资源
数据库·驱动开发·音视频
正在努力的小河3 小时前
Linux设备树简介
linux·运维·服务器
荣光波比3 小时前
Linux(十一)——LVM磁盘配额整理
linux·运维·云计算
LLLLYYYRRRRRTT3 小时前
WordPress (LNMP 架构) 一键部署 Playbook
linux·架构·ansible·mariadb
轻松Ai享生活4 小时前
crash 进程分析流程图
linux
大路谈数字化5 小时前
Centos中内存CPU硬盘的查询
linux·运维·centos
luoqice6 小时前
linux下查看 UDP Server 端口的启用情况
linux
倔强的石头_7 小时前
【Linux指南】动静态库与链接机制:从原理到实践
linux
赏点剩饭7787 小时前
linux中的hostpath卷、nfs卷以及静态持久卷的区别
linux·运维·服务器