首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

device_node:解压设备树与生成内核设备节点树的流程概述

  • 25-03-04 14:02
  • 2778
  • 11806
blog.csdn.net

本专栏往期内容

总线:

  1. 驱动中的device和device_driver结构体-CSDN博客
  2. bus总线的相关结构体和注册逻辑-CSDN博客
  3. bus中设备驱动的probe触发逻辑和device、driver的添加逻辑-CSDN博客
  4. platform bus平台总线详解-CSDN博客

设备树:

  1. 设备树语法规则讲解-CSDN博客
  2. 基于设备树的嵌入式系统硬件平台识别与参数传递流程解析-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
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

设备树最后就抽象成这样一个结构体,是不是觉得在哪里见过,就是在 平台总线驱动模型 中的struct device的成员之一

  1. struct fwnode_handle fwnode;
    • 这个成员用于支持更通用的设备模型,可以与 fwnode 结构结合,以支持非设备树的设备节点。这种设计使得内核能够灵活地处理不同类型的设备描述信息,不仅仅局限于设备树。
  2. struct property *properties;
    • 这个指针指向当前设备节点的属性列表。每个设备节点可以有多个属性,每个属性包含一个键值对,用于描述设备的特征。例如,一个设备节点可能有“compatible”属性来标识设备的兼容性,或“reg”属性来描述设备的寄存器地址。
  3. struct property *deadprops;
    • 这个指针指向已标记为删除的属性列表。当需要删除某个属性时,内核并不会立即从内存中释放它,而是将其移入 deadprops 列表。这样做的好处是避免频繁的内存分配和释放,提升性能。在某些情况下,可能会需要恢复这些被删除的属性。
  4. struct device_node *parent;
    • 这个指针指向当前设备节点的父节点。设备节点形成一棵树结构,每个节点可能有一个父节点,这有助于管理节点之间的层级关系。
  5. struct device_node *child;
    • 这个指针指向当前节点的第一个子节点。设备树是一个树状结构,每个节点可以有多个子节点,而这个指针帮助快速访问第一个子节点。
  6. struct device_node *sibling;
    • 这个指针指向当前节点的下一个兄弟节点。通过这个指针,可以在同级别的设备节点之间进行遍历。
  7. struct kobject kobj;
    • kobject 是内核对象的基础结构,提供了一些通用功能,如名称、引用计数和 sysfs 接口等。每个设备节点都可以通过 kobj 与内核的对象模型集成,使得设备节点可以通过 sysfs 进行用户空间访问。
  8. unsigned long _flags;
    • 这个成员用于存储设备节点的状态标志。可以包含各种与设备节点状态相关的信息,如是否已经被标记为“已分离”(detached),或是否已注册等。
  9. void *data;
    • 这个指针可以用于存储与设备节点相关的特定数据。由于设备树本身并不定义任何具体数据结构,这为开发人员提供了灵活性,可以根据需要在此存储额外信息。

条件编译部分

  1. #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;
}
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

内核函数 __unflatten_device_tree 主要用于将设备树二进制文件(DTB)“解压”成内核使用的设备节点结构,并将其组织成设备树的结构。具体来说,它完成了以下几个重要步骤:

函数功能概述

  1. 解析并校验 DTB 文件头部:检查设备树的有效性,确保提供的设备树格式正确。
  2. 分配内存:根据设备树中的结构,确定需要多少内存用于保存所有设备节点和属性,并一次性分配足够的内存。
  3. 解压设备树:扫描 DTB,实际将设备节点和属性解析出来,并生成内核中使用的 struct device_node 结构,形成一个内核中的设备树。
  4. 标记分离(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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 参数说明:
    • 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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 第二步:通过 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;
  • 1
  • 2
  • 3
  • 4
  • 第三步:调用 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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 第四步:将计算出的 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));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 第五步:输出调试信息,显示分配内存的地址。
    • 然后进行第二次扫描,这次调用 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 第六步:如果 detached 标志为真,并且 mynodes 指针有效,则设置设备树的 “detached” 标志,表示这棵设备树与内核的其余部分没有直接关联(分离状态)。
    • 最后,函数返回分配的内存地址,完成设备树的解压。

运行逻辑

  1. 设备树验证:函数首先检查设备树的头部,确保 blob 有效。
  2. 计算内存需求:第一次扫描设备树,确定解压后所需的内存大小。
  3. 内存分配:为整个设备树分配足够的内存,一次性为所有节点、属性等结构分配空间。
  4. 设备树解压:第二次扫描设备树,解析每个节点和属性,生成内核使用的设备树结构,并保存在分配的内存中。
  5. 溢出检查:通过末尾的 0xdeadbeef 标记检查是否发生了内存溢出。
  6. 分离状态:如果设备树被标记为分离,设置相应标志。

通过这两个扫描,设备树的二进制数据被转换为内核可以使用的设备节点树,同时保存到全局链表中,供内核的设备驱动子系统使用。

unflatten_device_tree函数的主要功能就是扫描DTB,将device node被组织成:

1、global list。全局变量struct device_node *of_allnodes就是指向设备树的global list

2、tree。

这些功能主要是在__unflatten_device_tree函数中实现

注:本文转载自blog.csdn.net的憧憬一下的文章"https://blog.csdn.net/caiji0169/article/details/142833041"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top