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。
相关推荐
酒酿小圆子~4 小时前
Linux之vim模式下全选命令
linux·运维·vim
落非5 小时前
ubuntu的dns设置问题
linux·运维·ubuntu
Peter_chq5 小时前
【计算机网络】HTTPS协议
linux·c语言·开发语言·网络·c++·后端·网络协议
吱呜猪6875 小时前
nfs服务器
linux·运维·服务器
Karoku0668 小时前
【企业级分布式系统】ZooKeeper集群
linux·运维·数据库·分布式·zookeeper·云原生
杨俊杰-YJ9 小时前
Linux脚本练习
linux·运维·服务器
aduzhe9 小时前
在 Ubuntu 上配置防火墙以开放特定端口
linux·运维·ubuntu
2401_840192279 小时前
k8s更新
linux·运维·服务器
别NULL10 小时前
《现代网络技术》读书笔记:SDN应用平面
linux·网络·sdn
hhj123k10 小时前
服务器作业4
linux·运维·服务器