往期内容
总线:
- 驱动中的device和device_driver结构体-CSDN博客
- bus总线的相关结构体和注册逻辑-CSDN博客
- bus中设备驱动的probe触发逻辑和device、driver的添加逻辑-CSDN博客
- platform bus平台总线详解-CSDN博客
设备树:
前言
本章主要讲解:在 Linux 内核中,设备树(Device Tree)通过将硬件描述信息从内核代码中分离出来,简化了平台设备的管理。通过解析设备树中的设备节点,内核生成相应的 device_node
结构,并使用函数如 of_platform_populate()
将这些设备节点注册为平台设备(platform_device
),挂载到总线的设备链表上。总线机制会根据设备的属性匹配驱动程序,并调用驱动的 probe
函数对设备进行管理。与此相对,早期的设备管理依赖静态表格,而设备树则提供了动态方式,不再依赖硬编码。
注:以下的代码皆摘自于linux 4.9.88版本的内核源码,不同版本可能有所出入。
1. device_node如何并入linux kernel的设备驱动模型
在linux kernel引入统一设备模型之后,bus、driver和device形成了设备模型中的铁三角。要将设备树(Device Tree)与 Linux 内核的设备驱动模型整合,需要通过一个过程,将设备树中的设备节点转换为相应的设备结构(如 platform_device),并注册到内核的设备模型中。那设备树中的设备节点如何挂入设备模型,并由驱动程序进行管理?
在早期的 ARM Linux 设备驱动模型中,很多设备是通过静态表格(如 platform_device 的静态数组)定义的。这些静态表格描述了设备的资源信息(如寄存器基地址、中断号等),然后通过 platform_add_devices 将它们注册到内核中(例如:一般代码中会定义一个static struct platform_device *xxx_devices的静态数组,在初始化的时候调用platform_add_devices。)。
设备树的引入有效地解决了这种过多的静态表格问题,使得设备信息device可以动态地从设备树中解析出来,而不需要在内核代码中定义大量静态表格(,也就是不需要我们自己去调用platform_add_devices,具体代码体现在/drivers/of/platform.c中),如何去解析设备树这一点咱们在 上面(device_node的提取)和中已经讲过,剩下的就是如何将提取到的device_node化为platform_device:
- 解析设备树:
- 内核在启动时会解析设备树,将设备树中定义的硬件设备信息转换为内核中使用的
device_node
结构(device_node在之前的章节已经讲解过了)。设备树中的每个节点对应一个设备,描述了它的资源、属性和依赖关系。
- 将设备节点挂到总线的设备链表:
- 设备树中的设备节点会根据其类型和属性,转换为内核设备结构(如
platform_device
)。这些设备会通过相应的注册函数(如platform_device_register
)挂载到总线的设备链表上。
例如,of_platform_populate
函数用于将设备树中的节点(device_node)转换为平台设备 (platform_device
),并将它们挂载到平台总线 (platform_bus
) 上。这个函数会遍历设备树,并为每个合适的设备节点创建并注册一个 platform_device
。
Linux-4.9.88\drivers\of\platform.c:
of_platform_default_populate_init-->of_platform_default_populate-->
of_platform_populate-->
/**
* of_platform_populate() - Populate platform_devices from device tree data
* @root: parent of the first level to probe or NULL for the root of the tree
* @matches: match table, NULL to use the default
* @lookup: auxdata table for matching id and platform_data with device nodes
* @parent: parent to hook devices from, NULL for toplevel
*
* Similar to of_platform_bus_probe(), this function walks the device tree
* and creates devices from nodes. It differs in that it follows the modern
* convention of requiring all device nodes to have a 'compatible' property,
* and it is suitable for creating devices which are children of the root
* node (of_platform_bus_probe will only create children of the root which
* are selected by the @matches argument).
*
* New board support should be using this function instead of
* of_platform_bus_probe().
*
* Returns 0 on success, < 0 on failure.
*/
int of_platform_populate(struct device_node *root,
const struct of_device_id *matches,
const struct of_dev_auxdata *lookup,
struct device *parent)
{
struct device_node *child;
int rc = 0;
root = root ? of_node_get(root) : of_find_node_by_path("/"); >>>>>>> 使用 of_find_node_by_path("/") 查找设备树的根节点
if (!root)
return -EINVAL;
pr_debug("%s()\n", __func__);
pr_debug(" starting at: %s\n", root->full_name);
for_each_child_of_node(root, child) {
>>>>> 宏遍历 root 的每一个子节点。每个子节点表示设备树中的一个设备节点
rc = of_platform_bus_create(child, matches, lookup, parent, true);
>>>>>>> 对每一个子节点调用 of_platform_bus_create() 函数,将设备节点转换为 platform_device,并注册到平台总线 (platform_bus) 上
if (rc) {
of_node_put(child);
break;
}
}
of_node_set_flag(root, OF_POPULATED_BUS);
//>>>>>在所有子节点被处理后,of_platform_populate 会标记该设备节点为 OF_POPULATED_BUS。这表示这个总线已经被成功地填充了(即设备树的节点已经被转换并注册为设备)
of_node_put(root);
return rc;
}
- 驱动与设备的匹配:
- 当设备挂载到总线的设备链表后,总线机制会通过
match
函数将设备与合适的驱动程序匹配。总线的match
函数会根据设备的属性(如兼容性字符串compatible
)找到与设备对应的驱动程序。 - 一旦匹配成功,驱动程序的
probe
函数就会被调用,用于初始化设备并开始管理硬件。
注
当然,也不是全部的节点都是挂在bus上的,比如下面三个节点:
- cpus 节点:用于描述处理器的信息,内核根据该节点信息初始化 CPU,但这些信息不会挂载到某个总线的设备链表中。
- memory 节点:内存节点用于描述物理内存布局,内核直接处理该节点并据此进行内存管理,而不会注册为一个设备。
- chosen 节点:用于传递启动参数(如命令行参数、根文件系统路径等),不涉及设备驱动的注册和匹配。
以memory节点为例子,这部分的处理可以参考setup_arch->setup_machine_fd->early_init_dt_scan_nodes->of_scan_flat_dt->early_init_dt_scan_memory
Linux-4.9.88\Linux-4.9.88\drivers\of\fdt.c:
/**
* early_init_dt_scan_memory - Look for an parse memory nodes
*/
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
int depth, void *data)
//node: 当前扫描的设备节点的地址。
//uname: 当前节点的名称。
//depth: 当前节点的深度(树的层级)。
//data: 额外的数据(这里未使用)
{
const char *type = of_get_flat_dt_prop(node, "device_type", NULL); ——————获取当前节点的 device_type 属性,以确定该节点是否是内存节点
const __be32 *reg, *endp;
int l;
/* We are scanning "memory" nodes only */
if (type == NULL) {
/*
* The longtrail doesn't have a device_type on the
* /memory node, so look for the node called /memory@0.
*/
if (!IS_ENABLED(CONFIG_PPC32) || depth != 1 || strcmp(uname, "memory@0") != 0)
return 0;
} else if (strcmp(type, "memory") != 0)
return 0;
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l); ——————函数获取节点的内存属性
if (reg == NULL)
reg = of_get_flat_dt_prop(node, "reg", &l);
if (reg == NULL)
return 0;
endp = reg + (l / sizeof(__be32));
pr_debug("memory scan node %s, reg size %d,\n", uname, l);
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
————————遍历内存节点
u64 base, size;
base = dt_mem_next_cell(dt_root_addr_cells, ®);————————内存的起始地址
size = dt_mem_next_cell(dt_root_size_cells, ®);————————大小
if (size == 0)
continue;
pr_debug(" - %llx , %llx\n", (unsigned long long)base,
(unsigned long long)size);
early_init_dt_add_memory_arch(base, size); ——————将内存信息添加到系统架构中
}
return 0;
}
函数通过解析设备树中的 memory 节点,提取出内存的基地址和大小
然后调用 early_init_dt_add_memory_arch 函数将这些内存区域添加到内核的内存管理系统中。
2. drivers/base/platform.c和drivers/of/platform.c
drivers/base/platform.c:这个文件处理 平台设备(platform devices) 和 平台驱动(platform drivers) 的通用实现,与设备树无关。
- 负责管理 平台总线(platform bus)、平台设备 和 平台驱动。
- 提供了一些核心的 平台设备注册 和 解绑接口。
- 在 非即插即用设备(如嵌入式设备)中,平台设备是一种非常常见的设备类型,通常通过静态定义的方式被添加到系统中。
- 主要职责 是处理平台设备的生命周期管理,如注册、移除、匹配(match)驱动程序等。
platform_device_register(): 注册一个平台设备。
platform_driver_register(): 注册一个平台驱动程序。
platform_match(): 匹配平台设备与驱动。
平台设备模型允许开发者通过 struct platform_device
和 struct platform_driver
定义设备和驱动程序,分别对应于硬件设备和管理设备的驱动程序。drivers/base/platform.c
适用于 不依赖于设备树 的系统,或者那些通过直接编程静态平台设备的系统。典型的例子包括不使用设备树的嵌入式系统或者通过代码硬编码设备和资源的平台设备。
drivers/of/platform.c:这个文件处理的是基于 设备树(Device Tree, DT) 的平台设备及驱动的实现,与设备树相关联。
- 处理设备树中的设备节点,将它们 动态转换 为平台设备,并注册到平台总线上。
- 解析设备树中的设备节点(通常以
compatible
字段来描述),并生成相应的platform_device
。 - 使用设备树来配置和描述设备的硬件信息,如寄存器地址、中断号、时钟等资源。
- 主要职责 是处理与 设备树 相关的设备节点到平台设备的映射,并自动注册到内核的设备模型中。
of_platform_bus_probe(): 解析设备树,扫描指定总线的子节点,注册这些设备到总线上。
of_platform_device_create(): 根据设备树的节点创建一个平台设备。
of_platform_populate(): 扫描设备树的根节点,将所有与某个总线相关的设备节点创建为 platform_device,并注册到总线上。
设备树通常包含嵌入式系统中设备的详细描述,包括硬件资源和属性。drivers/of/platform.c
的任务就是将这些描述从设备树转换成内核中的 platform_device
,这样驱动程序就可以通过 platform_driver
来匹配和管理它们。drivers/of/platform.c
适用于 基于设备树 的系统,特别是现代的 ARM 平台和其他依赖设备树来描述硬件布局的系统。在这些系统中,硬件的详细信息通常由设备树提供,而不是通过静态代码硬编码在内核中。
对于drivers/base/platform.c中的platform_device_register(),其实用的不多,更多的是用form_driver_register()来注册一个驱动,至于platfrom_device其实更多的是交由drivers/of/platform.c中的of_platform_populate去创建,毕竟现在对于硬件资源的指定更多依赖于设备树,很少自己去.c文件去指定然后手动注册。