本专栏往期内容
总线:
- 驱动中的device和device_driver结构体-CSDN博客
- bus总线的相关结构体和注册逻辑-CSDN博客
- bus中设备驱动的probe触发逻辑和device、driver的添加逻辑-CSDN博客
- platform bus平台总线详解-CSDN博客
设备树:
前言
本章主要内容:在 Linux 内核的初始化过程中,设备树二进制文件(DTB)被解压为设备节点树,用于内核设备驱动的管理。通过内核函数 __unflatten_device_tree
,DTB 被解析成内核的 struct device_node
结构。这一流程分为两个主要步骤:首先通过扫描 DTB 来确定所需内存大小,接着分配内存并进行实际的解压操作,生成设备节点树,并保存在全局链表和树结构中。解压后的设备节点被存储为全局链表 of_allnodes
,同时形成层次化的父子节点关系树,供内核使用。这一过程确保设备驱动能够正确识别和使用硬件设备的配置信息。
注:以下的代码皆摘自于linux 4.9.88版本的内核源码,不同版本可能有所出入。
1. struct device_node
struct device_node {
const char *name; ----------------------device node name
const char *type; -----------------------对应device_type的属性
phandle phandle; -----------------------对应该节点的phandle属性
const char *full_name; ----------------从“/”开始的,表示该node的full path
struct fwnode_handle fwnode; ----------------这个成员用于支持更通用的设备模型,可以与 fwnode 结构结合,以支持非设备树的设备节点。这种设计使得内核能够灵活地处理不同类型的设备描述信息,不仅仅局限于设备树。
struct property *properties; -------------该节点的属性列表
struct property *deadprops; ----------如果需要删除某些属性,kernel并非真的删除,而是挂入到deadprops的列表
struct device_node *parent;
struct device_node *child;
struct device_node *sibling; ------parent、child以及sibling将所有的device node连接起来
struct kobject kobj;------kobject 是内核对象的基础结构,提供了一些通用功能,如名称、引用计数和 sysfs 接口等。每个设备节点都可以通过 kobj 与内核的对象模型集成,使得设备节点可以通过 sysfs 进行用户空间访问。
unsigned long _flags; ------这个成员用于存储设备节点的状态标志。可以包含各种与设备节点状态相关的信息,如是否已经被标记为“已分离”(detached),或是否已注册等。
void *data; ------这个指针可以用于存储与设备节点相关的特定数据。由于设备树本身并不定义任何具体数据结构,这为开发人员提供了灵活性,可以根据需要在此存储额外信息。
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id; ------用于为设备节点分配一个唯一的标识符,以帮助识别和管理不同设备节点。
struct of_irq_controller *irq_trans; ------这个指针用于表示与中断控制器相关的设备节点信息。中断处理在硬件设备中至关重要,而这个成员用于帮助管理设备节点与中断之间的关系。
#endif
};
设备树最后就抽象成这样一个结构体,是不是觉得在哪里见过,就是在 平台总线驱动模型 中的struct device的成员之一
struct fwnode_handle fwnode;
- 这个成员用于支持更通用的设备模型,可以与
fwnode
结构结合,以支持非设备树的设备节点。这种设计使得内核能够灵活地处理不同类型的设备描述信息,不仅仅局限于设备树。
- 这个成员用于支持更通用的设备模型,可以与
struct property *properties;
- 这个指针指向当前设备节点的属性列表。每个设备节点可以有多个属性,每个属性包含一个键值对,用于描述设备的特征。例如,一个设备节点可能有“compatible”属性来标识设备的兼容性,或“reg”属性来描述设备的寄存器地址。
struct property *deadprops;
- 这个指针指向已标记为删除的属性列表。当需要删除某个属性时,内核并不会立即从内存中释放它,而是将其移入
deadprops
列表。这样做的好处是避免频繁的内存分配和释放,提升性能。在某些情况下,可能会需要恢复这些被删除的属性。
- 这个指针指向已标记为删除的属性列表。当需要删除某个属性时,内核并不会立即从内存中释放它,而是将其移入
struct device_node *parent;
- 这个指针指向当前设备节点的父节点。设备节点形成一棵树结构,每个节点可能有一个父节点,这有助于管理节点之间的层级关系。
struct device_node *child;
- 这个指针指向当前节点的第一个子节点。设备树是一个树状结构,每个节点可以有多个子节点,而这个指针帮助快速访问第一个子节点。
struct device_node *sibling;
- 这个指针指向当前节点的下一个兄弟节点。通过这个指针,可以在同级别的设备节点之间进行遍历。
struct kobject kobj;
kobject
是内核对象的基础结构,提供了一些通用功能,如名称、引用计数和 sysfs 接口等。每个设备节点都可以通过kobj
与内核的对象模型集成,使得设备节点可以通过 sysfs 进行用户空间访问。
unsigned long _flags;
- 这个成员用于存储设备节点的状态标志。可以包含各种与设备节点状态相关的信息,如是否已经被标记为“已分离”(detached),或是否已注册等。
void *data;
- 这个指针可以用于存储与设备节点相关的特定数据。由于设备树本身并不定义任何具体数据结构,这为开发人员提供了灵活性,可以根据需要在此存储额外信息。
条件编译部分
#if defined(CONFIG_SPARC)
- 这个条件编译部分意味着以下成员仅在特定架构(如 SPARC)下可用。具体成员如下:
const char *path_component_name;
- 该成员表示路径组件的名称,在某些架构中可能需要。
unsigned int unique_id;
- 用于为设备节点分配一个唯一的标识符,以帮助识别和管理不同设备节点。
struct of_irq_controller *irq_trans;
- 这个指针用于表示与中断控制器相关的设备节点信息。中断处理在硬件设备中至关重要,而这个成员用于帮助管理设备节点与中断之间的关系。
2. 初始化流程:device_node的提取
在系统初始化的过程中,会将DTB转换成节点是device_node的树状结构。具体的代码位于setup_arch的unflatten_device_tree中。
// Linux-4.9.88\arch\arm\kernel\setup.c
void __init setup_arch(char **cmdline_p)
{
.....
unflatten_device_tree(); // \Linux-4.9.88\drivers\of\fdt.C,继续往下看
.....
}
//--------------------------------分割线----------------------------------------
/**
* unflatten_device_tree - create tree of device_nodes from flat blob
*
* unflattens the device-tree passed by the firmware, creating the
* tree of struct device_node. It also fills the "name" and "type"
* pointers of the nodes so the normal device-tree walking functions
* can be used.
*/
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false); //继续往下看
/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
of_alias_scan(early_init_dt_alloc_memory_arch);
}
//--------------------------------分割线----------------------------------------
/**
* __unflatten_device_tree - create tree of device_nodes from flat blob
*
* unflattens a device-tree, creating the
* tree of struct device_node. It also fills the "name" and "type"
* pointers of the nodes so the normal device-tree walking functions
* can be used.
* @blob: The blob to expand
* @dad: Parent device node
* @mynodes: The device_node tree created by the call
* @dt_alloc: An allocator that provides a virtual address to memory
* for the resulting tree
*
* Returns NULL on failure or the memory chunk containing the unflattened
* device tree on success.
*/
static void *__unflatten_device_tree(const void *blob,
struct device_node *dad,
struct device_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align),
bool detached)
{
int size;
void *mem;
pr_debug(" -> unflatten_device_tree()\n");
if (!blob) {
pr_debug("No device tree pointer\n");
return NULL;
}
pr_debug("Unflattening device tree:\n");
pr_debug("magic: %08x\n", fdt_magic(blob));
pr_debug("size: %08x\n", fdt_totalsize(blob));
pr_debug("version: %08x\n", fdt_version(blob));
if (fdt_check_header(blob)) {
pr_err("Invalid device tree blob header\n");
return NULL;
}
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
if (size < 0)
return NULL;
size = ALIGN(size, 4);
pr_debug(" size is %d, allocating...\n", size);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
if (!mem)
return NULL;
memset(mem, 0, size);
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
pr_debug(" unflattening %p...\n", mem);
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes);
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warning("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));
if (detached && mynodes) {
of_node_set_flag(*mynodes, OF_DETACHED);
pr_debug("unflattened tree is detached\n");
}
pr_debug(" <- unflatten_device_tree()\n");
return mem;
}
内核函数 __unflatten_device_tree
主要用于将设备树二进制文件(DTB)“解压”成内核使用的设备节点结构,并将其组织成设备树的结构。具体来说,它完成了以下几个重要步骤:
函数功能概述
- 解析并校验 DTB 文件头部:检查设备树的有效性,确保提供的设备树格式正确。
- 分配内存:根据设备树中的结构,确定需要多少内存用于保存所有设备节点和属性,并一次性分配足够的内存。
- 解压设备树:扫描 DTB,实际将设备节点和属性解析出来,并生成内核中使用的
struct device_node
结构,形成一个内核中的设备树。 - 标记分离(detached)状态:如果设备树被标记为“分离”,则设置相应的标志。
详细代码分析
Linux-4.9.88\Linux-4.9.88\drivers\of\fdt.c:
static void *__unflatten_device_tree(const void *blob,
struct device_node *dad,
struct device_node **mynodes,
void *(*dt_alloc)(u64 size, u64 align),
bool detached)
{
int size;
void *mem;
pr_debug(" -> unflatten_device_tree()\n");
if (!blob) {
pr_debug("No device tree pointer\n");
return NULL;
}
- 参数说明:
blob
:指向设备树二进制文件(DTB)的指针。dad
:父节点的指针,即要解压出的设备树的根节点。可以为空。mynodes
:指向解压后设备节点的全局链表。dt_alloc
:内存分配函数,用于为设备节点和属性分配内存。detached
:如果为true
,则表示设备树是“分离”的,不会直接与内核的其余部分关联。
- 第一步:函数首先检查
blob
是否为空,如果为空则返回,因为这表示没有设备树可供处理。
pr_debug("Unflattening device tree:\n");
pr_debug("magic: %08x\n", fdt_magic(blob));
pr_debug("size: %08x\n", fdt_totalsize(blob));
pr_debug("version: %08x\n", fdt_version(blob));
if (fdt_check_header(blob)) {
pr_err("Invalid device tree blob header\n");
return NULL;
}
- 第二步:通过
fdt_magic(blob)
等函数输出设备树头部信息,如魔数(magic)、大小(size)和版本(version)。fdt_check_header(blob)
:检查设备树头部是否有效。如果无效,函数返回NULL
,表示设备树解析失败。
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
if (size < 0)
return NULL;
- 第三步:调用
unflatten_dt_nodes
扫描设备树。这是第一遍扫描,主要目的是计算出完整解压设备树所需的内存大小,存储在size
变量中。- 如果
size
小于 0,表示扫描失败,则直接返回NULL
。
- 如果
size = ALIGN(size, 4);
pr_debug(" size is %d, allocating...\n", size);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
if (!mem)
return NULL;
memset(mem, 0, size);
*(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef);
- 第四步:将计算出的
size
值对齐到 4 字节(通常是为了保证内存对齐,提高访问效率)。- 接着调用
dt_alloc
分配内存,大小为size + 4
字节。加上4字节是为了在内存末尾设置一个标记(0xdeadbeef
),用于后面校验是否发生内存溢出。 memset(mem, 0, size)
将分配的内存初始化为 0。
- 接着调用
pr_debug(" unflattening %p...\n", mem);
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes);
if (be32_to_cpup(mem + size) != 0xdeadbeef)
pr_warning("End of tree marker overwritten: %08x\n",
be32_to_cpup(mem + size));
unflatten_dt_nodes具体如下:
// Linux-4.9.88\Linux-4.9.88\drivers\of\fdt.c:
static int unflatten_dt_nodes(const void *blob,
void *mem,
struct device_node *dad,
struct device_node **nodepp)
{
struct device_node *root;
int offset = 0, depth = 0, initial_depth = 0;
#define FDT_MAX_DEPTH 64
unsigned int fpsizes[FDT_MAX_DEPTH];
struct device_node *nps[FDT_MAX_DEPTH];
void *base = mem;
bool dryrun = !base;
if (nodepp)
*nodepp = NULL;
/*
* We're unflattening device sub-tree if @dad is valid. There are
* possibly multiple nodes in the first level of depth. We need
* set @depth to 1 to make fdt_next_node() happy as it bails
* immediately when negative @depth is found. Otherwise, the device
* nodes except the first one won't be unflattened successfully.
*/
if (dad)
depth = initial_depth = 1;
root = dad;
fpsizes[depth] = dad ? strlen(of_node_full_name(dad)) : 0;
nps[depth] = dad;
for (offset = 0;
offset >= 0 && depth >= initial_depth;
offset = fdt_next_node(blob, offset, &depth)) {
if (WARN_ON_ONCE(depth >= FDT_MAX_DEPTH))
continue;
fpsizes[depth+1] = populate_node(blob, offset, &mem,
nps[depth],
fpsizes[depth],
&nps[depth+1], dryrun);
if (!fpsizes[depth+1])
return mem - base;
if (!dryrun && nodepp && !*nodepp)
*nodepp = nps[depth+1];
if (!dryrun && !root)
root = nps[depth+1];
}
if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
pr_err("Error %d processing FDT\n", offset);
return -EINVAL;
}
/*
* Reverse the child list. Some drivers assumes node order matches .dts
* node order
*/
if (!dryrun)
reverse_nodes(root);
return mem - base;
}
- 第五步:输出调试信息,显示分配内存的地址。
- 然后进行第二次扫描,这次调用
unflatten_dt_nodes
实际解析设备树,并将其解压到刚刚分配的内存中。这个函数会填充**struct device_node**
结构,构建完整的设备树。 - 解压完成后,检查之前设置的
0xdeadbeef
标记,如果被覆盖,表示在解析过程中发生了内存溢出,输出警告信息。
- 然后进行第二次扫描,这次调用
if (detached && mynodes) {
of_node_set_flag(*mynodes, OF_DETACHED);
pr_debug("unflattened tree is detached\n");
}
pr_debug(" <- unflatten_device_tree()\n");
return mem;
}
- 第六步:如果
detached
标志为真,并且mynodes
指针有效,则设置设备树的 “detached” 标志,表示这棵设备树与内核的其余部分没有直接关联(分离状态)。- 最后,函数返回分配的内存地址,完成设备树的解压。
运行逻辑
- 设备树验证:函数首先检查设备树的头部,确保
blob
有效。 - 计算内存需求:第一次扫描设备树,确定解压后所需的内存大小。
- 内存分配:为整个设备树分配足够的内存,一次性为所有节点、属性等结构分配空间。
- 设备树解压:第二次扫描设备树,解析每个节点和属性,生成内核使用的设备树结构,并保存在分配的内存中。
- 溢出检查:通过末尾的
0xdeadbeef
标记检查是否发生了内存溢出。 - 分离状态:如果设备树被标记为分离,设置相应标志。
通过这两个扫描,设备树的二进制数据被转换为内核可以使用的设备节点树,同时保存到全局链表中,供内核的设备驱动子系统使用。
unflatten_device_tree函数的主要功能就是扫描DTB,将device node被组织成:
1、global list。全局变量struct device_node *of_allnodes就是指向设备树的global list
2、tree。
这些功能主要是在__unflatten_device_tree函数中实现