RK3568驱动指南|第七期-第63章 dtb展开成device_node实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】824412014(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第七期__设备树)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第63章 dtb展开成device_node实验

在上个小节中我们讲解了设备树deb的文件格式,那deb文件是怎样传递给内核的呢,那就进入到本小节的学习吧。

63.1 dtb展开流程

dtb展开流程图如下(图 63-1)所示:

图 63-1

接下来将会根据上图对deb的展开流程进行详细的讲解:

(1)设备树源文件编写:根据之前的章节中讲解的设备树的基本语法和相关知识编写符合规范的设备树。

(2)设备树编译:设备树源文件经过设备树编译器(dtc)进行编译,生成设备树二进制文件(.dtb)。设备树编译器会检查源文件的语法和语义,并将其转换为二进制格式,以便内核能够解析和使用。

(3)boot.img镜像生成:boot.img是一个包含内核镜像、设备树二进制文件和其他一些资源文件的镜像文件(目前只是适用于瑞芯微的soc上,其他厂商的soc需要具体问题具体分析)。在生成boot.img时,通常会将内核镜像、设备树二进制文件和其他一些资源文件打包在一起。这个过程可以使用特定的工具或脚本完成。

(4)U-Boot加载:U-Boot(Universal Bootloader)是一种常用的开源引导加载程序,用于引导嵌入式系统。在系统启动过程中,U-Boot会将boot.img中的内核和设备树的二进制文件加载到系统内存的特定地址。

(5)内核初始化:U-Boot将内核和设备树的二进制文件加载到系统内存的特定地址后,控制权会转交给内核。在内核初始化的过程中,会解析设备树二进制文件,将其展开为内核可以识别的数据结构,以便内核能够正确地初始化和管理硬件资源。

(6)设备树展开:设备树展开是指将设备树二进制文件解析成内核中的设备节点(device_node)的过程。内核会读取设备树二进制文件的内容,并根据设备树的描述信息,构建设备树数据结构,例如设备节点、中断控制器、寄存器、时钟等。这些设备树数据结构将在内核运行时用于管理和配置硬件资源。

而本章节要讲解的重点就在上面的第6步“设备树的展开”,最终设备树二进制文件会被解析成device_node,device_node结构体定义在内核源码的“/include/linux/of.h”文件中,具体内容如下所示:

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;     // 兄弟设备节点指针
#if defined(CONFIG_OF_KOBJ)
	struct kobject kobj;             // 内核对象(用于 sysfs)
#endif
	unsigned long _flags;            // 设备节点的标志位
	void *data;                      // 与设备节点相关的数据指针
#if defined(CONFIG_SPARC)
	const char *path_component_name; // 设备节点的路径组件名称
	unsigned int unique_id;          // 设备节点的唯一标识
	struct of_irq_controller *irq_trans; // 设备节点的中断控制器
#endif
};

然后对该结构体的重要参数进行讲解:

(1)name:name 字段表示设备节点的名称。设备节点的名称是在设备树中唯一标识该节点的字符串。它通常用于在设备树中引用设备节点。

(2)type:type 字段表示设备节点的类型。设备节点的类型提供了关于设备节点功能和所属设备类别的信息。它可以用于识别设备节点的用途和特性。

(3)properties:properties 字段是指向设备节点属性列表的指针。设备节点的属性包含了与设备节点相关联的配置和参数信息。属性以键值对的形式存在,可以提供设备的特定属性、寄存器地址、中断信息等。property字段同样定义在内核源码的“/include/linux/of.h”文件中,具体内容如下所示:

struct property {
	char *name;                    // 属性的名称
	int length;                    // 属性值的长度(字节数)
	void *value;                   // 属性值的指针
	struct property *next;         // 下一个属性节点指针
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
	unsigned long _flags;          // 属性的标志位
#endif
#if defined(CONFIG_OF_PROMTREE)
	unsigned int unique_id;        // 属性的唯一标识
#endif
#if defined(CONFIG_OF_KOBJ)
	struct bin_attribute attr;     // 内核对象二进制属性
#endif
};

(4)parent:parent 字段指向父设备节点。设备树中的设备节点按照层次结构组织,父设备节点是当前设备节点的直接上级。通过 parent 字段,可以在设备树中遍历设备节点的父子关系。

(5)child:child 字段指向子设备节点。在设备树中,一个设备节点可以拥有多个子设备节点。通过 child 字段,可以遍历设备节点的所有子设备节点。

(6)sibling:sibling 字段指向兄弟设备节点。在设备树中,同一级别的兄弟设备节点共享相同的父设备节点。通过 sibling 字段,可以在同级设备节点之间进行遍历。

至此,关于device_node的结构体讲解就完成了,虽然我们现在知道了,dtb文件最终会展开成device_node这一可以让内核识别的格式,那更具体的实现流程是怎样的呢,让我们进入下一小节的学习吧。

63.2 dtb解析过程源码分析

首先来到源码目录下的“/init/main.c”文件,找到其中的start_kernel 函数,start_kernel 函数是 Linux 内核启动的入口点,它是Linux内核的核心函数之一,负责完成内核的初始化和启动过程,具体内容如下所示:

asmlinkage __visible void __init start_kernel(void)
{
    char *command_line;
    char *after_dashes;

    set_task_stack_end_magic(&init_task);    // 设置任务栈的魔数
    smp_setup_processor_id();    // 设置处理器ID
    debug_objects_early_init();    // 初始化调试对象
    cgroup_init_early();    // 初始化cgroup(控制组)

    local_irq_disable();    // 禁用本地中断
    early_boot_irqs_disabled = true;     // 标记早期引导期间中断已禁用

    /*
     * 中断仍然被禁用。进行必要的设置,然后启用它们。
     */
    boot_cpu_init();    // 初始化引导CPU
    page_address_init();    // 设置页地址
    pr_notice("%s", linux_banner);    // 打印Linux内核版本信息
    setup_arch(&command_line);    // 架构相关的初始化
    mm_init_cpumask(&init_mm);    // 初始化内存管理的cpumask(CPU掩码)
    setup_command_line(command_line);    // 设置命令行参数
    setup_nr_cpu_ids();    // 设置CPU个数
    setup_per_cpu_areas();    // 设置每个CPU的区域
    smp_prepare_boot_cpu();    // 准备启动CPU(架构特定的启动CPU钩子)
    boot_cpu_hotplug_init();    // 初始化热插拔的引导CPU

    build_all_zonelists(NULL);    // 构建所有内存区域列表
    page_alloc_init();    // 初始化页面分配器
	........
}

其中跟设备树相关的函数为第20行的 setup_arch(&command_line);该函数定义在内核源码的“/arch/arm64/kernel/setup.c”文件中,具体内容如下所示:

void __init setup_arch(char **cmdline_p)
{
	init_mm.start_code = (unsigned long) _text;
	init_mm.end_code   = (unsigned long) _etext;
	init_mm.end_data   = (unsigned long) _edata;
	init_mm.brk	   = (unsigned long) _end;

	*cmdline_p = boot_command_line;

	early_fixmap_init();    // 初始化 early fixmap
	early_ioremap_init();    // 初始化 early ioremap

	setup_machine_fdt(__fdt_pointer);    // 设置机器的 FDT(平台设备树)

	// 初始化静态密钥,早期可能会被 cpufeature 代码和早期参数启用
	jump_label_init();
	parse_early_param();

	// 在启动可能的早期控制台后,解除屏蔽异步中断和 FIQ(一旦我们可以报告发生的系统错误)
	local_daif_restore(DAIF_PROCCTX_NOIRQ);

	// 在这个阶段,TTBR0仅用于身份映射。将其指向零页面,以避免做出猜测性的新条目获取。
	cpu_uninstall_idmap();

	xen_early_init();    // Xen 平台的早期初始化
	efi_init();    // EFI 平台的初始化
	arm64_memblock_init();    // ARM64 内存块的初始化

	paging_init();    // 分页初始化

	acpi_table_upgrade();    // ACPI 表的升级

	// 解析 ACPI 表以进行可能的引导时配置
	acpi_boot_table_init();

	if (acpi_disabled)
		unflatten_device_tree();    // 展开设备树

	bootmem_init();    // 引导内存的初始化
	............
}

在setup_arch函数中与设备树相关的函数分别为第13行的setup_machine_fdt(__fdt_pointer)和第37行的unflatten_device_tree(),接下来将对上述两个函数进行详细的介绍

63.2.1 setup_machine_fdt(__fdt_pointer)

setup_machine_fdt(__fdt_pointer)中的__fdt_pointer是dtb二进制文件加载到内存的地址,该地址由bootloader启动kernel时透过x0寄存器传递过来的,具体的汇编代码在内核源码目录下的“/arch/arm64/kernel/head.S”文件中,具体内容如下所示:

preserve_boot_args:
	mov	x21, x0				// x21=FDT

__primary_switched:
	str_l	x21, __fdt_pointer, x5		// Save FDT pointer

第2行: 将寄存器 x0 的值复制到寄存器 x21。x0 寄存器中保存了一个指针,该指针指向设备树(Device Tree)。

第4行: 将寄存器 x21 的值存储到内存地址 __fdt_pointer 中。

然后来看setup_machine_fdt函数,该函数定义在内核源码的“/arch/arm64/kernel/setup.c”文件中,具体内容如下所示:

// 初始化设置机器的设备树
static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
    int size;
    // 将设备树物理地址映射到内核虚拟地址空间
    void *dt_virt = fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL);
    const char *name;

    // 如果映射成功
    if (dt_virt) {
        // 保留设备树占用的内存区域
        memblock_reserve(dt_phys, size);
    }

    // 如果设备树映射失败或者设备树解析失败
    if (!dt_virt || !early_init_dt_scan(dt_virt)) {
        // 输出错误信息
		pr_crit("\n"
			"Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
			"The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
			"\nPlease check your bootloader.",
			&dt_phys, dt_virt);
        // 无限循环,等待系统崩溃
        while (true)
            cpu_relax();
    }

    // 早期修复完成,将设备树映射为只读模式
    fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);

    // 获取设备树的机器名
    name = of_flat_dt_get_machine_name();
    // 如果设备树没有机器名,则返回
    if (!name)
        return;
	    pr_info("Machine model: %s\n", name); // 输出机器型号信息
    dump_stack_set_arch_desc("%s (DT)", name); // 设置栈转储的架构描述为机器型号
}

此函数用于在内核启动过程中设置机器的设备树。在此函数中,将执行以下步骤:

1.使用 fixmap_remap_fdt() 将设备树映射到内核虚拟地址空间中的 fixmap 区域。

2.如果映射成功,则使用 memblock_reserve() 保留设备树占用的物理内存区域。

3.检查设备树的有效性和完整性,通过调用early_init_dt_scan()进行早期扫描。 如果设备树无效或扫描失败,则会输出错误信息并进入死循环。

4.早期修复已完成,现在将设备树映射为只读,通过调用 fixmap_remap_fdt() 实现。

5.获取设备树中的机器模型名称,通过调用 of_flat_dt_get_machine_name()。

6.如果机器模型名称存在,则输出机器模型的信息,并通过 dump_stack_set_arch_desc() 设置堆栈描述信息。

其中上面的第3步调用的early_init_dt_scan() 需要详细的讲解一下,该函数定义在内核源码的“drivers/of/fdt.c”目录下,具体内容如下所示:

bool __init early_init_dt_scan(void *params)
{
    bool status;

    // 验证设备树的兼容性和完整性
    status = early_init_dt_verify(params);
    if (!status)
        return false;

    // 扫描设备树节点
    early_init_dt_scan_nodes();
    return true;
}

首先,调用 early_init_dt_verify() 函数对设备树进行兼容性和完整性验证。该函数可能会检查设备树中的一致性标记、版本信息以及必需的节点和属性是否存在。如果验证失败,函数会返回 false。该函数的具体内容如下所示:

bool __init early_init_dt_verify(void *params)
{
    // 验证传入的参数是否为空
    if (!params)
        return false;

    // 检查设备树头部的有效性
    // 如果设备树头部无效,返回 false
    if (fdt_check_header(params))
        return false;

    // 设置指向设备树的指针为传入的参数
    initial_boot_params = params;

    // 计算设备树的 CRC32 校验值
    // 并将结果保存在全局变量 of_fdt_crc32 中
    of_fdt_crc32 = crc32_be(~0, initial_boot_params, fdt_totalsize(initial_boot_params));

    // 返回 true,表示设备树验证和初始化成功
    return true;
}

第4行:该进行参数的有效性检查,如果 params 为空,则直接返回 false,表示参数无效。

第9行:检查设备树头部的有效性。fdt_check_header 是一个用于检查设备树头部的函数,如果设备树头部无效,则返回 false,表示设备树不合法。

第13行:如果设备树头部有效,程序继续执行,将传入的 params 赋值给全局变量 initial_boot_params,用来保存设备树的指针。

第17行,使用 crc32_be 函数计算设备树的 CRC32 校验值,其中 crc32_be 是一个用于计算 CRC32 校验值的函数,~0 表示初始值为全1的位模式。计算完成后,将结果保存在全局变量 of_fdt_crc32 中。

然后继续回到early_init_dt_scan() 函数中,如果设备树验证成功(即 status 为真),则调用 early_init_dt_scan_nodes() 函数。这个函数的作用是扫描设备树的节点并进行相应的处理,该函数的具体内容如下所示:

void __init early_init_dt_scan_nodes(void)
{
	/* 从 /chosen 节点中检索各种信息 */
	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

	/* 初始化 {size,address}-cells 信息 */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);

	/* 设置内存信息,调用 early_init_dt_add_memory_arch 函数 */
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

函数early_init_dt_scan_nodes被声明为__init,这表示它是在内核初始化阶段被调用,并且在初始化完成后不再需要。该函数的目的是在早期阶段扫描设备树节点,并执行一些初始化操作。

函数中主要调用了of_scan_flat_dt函数,该函数用于扫描平面设备树(flat device tree)。平面设备树是一种将设备树以紧凑形式表示的数据结构,它不使用树状结构,而是使用线性结构,以节省内存空间。

具体来看,early_init_dt_scan_nodes函数的执行步骤如下:

(1)of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line):从设备树的/chosen节点中检索各种信息。/chosen节点通常包含了一些系统的全局配置参数,比如命令行参数。early_init_dt_scan_chosen是一个回调函数,用于处理/chosen节点的信息。boot_command_line是一个参数,表示内核启动时的命令行参数。

(2)of_scan_flat_dt(early_init_dt_scan_root, NULL):初始化{size,address}-cells信息。{size,address}-cells描述了设备节点中地址和大小的编码方式。early_init_dt_scan_root是一个回调函数,用于处理设备树的根节点。

(3)of_scan_flat_dt(early_init_dt_scan_memory, NULL):设置内存信息,并调用early_init_dt_add_memory_arch函数。这个步骤主要用于在设备树中获取内存的相关信息,并将其传递给内核的内存管理模块。early_init_dt_scan_memory是一个回调函数,用于处理内存信息。

至此,关于setup_machine_fdt(__fdt_pointer)代码的分析就完成了。

63.2.2 unflatten_device_tree

该函数用于解析设备树,将紧凑的设备树数据结构转换为树状结构的设备树,该函数定义在内核源码目录下的“/drivers/of/fdt.c”文件中,具体内容如下所示:

void __init unflatten_device_tree(void)
{
    /* 解析设备树 */
    __unflatten_device_tree(initial_boot_params, NULL, &of_root,
                            early_init_dt_alloc_memory_arch, false);

    /* 获取指向 "/chosen" 和 "/aliases" 节点的指针,以供全局使用 */
    of_alias_scan(early_init_dt_alloc_memory_arch);

    /* 运行设备树的单元测试 */
    unittest_unflatten_overlay_base();
}

该函数主要用于解析设备树,并将解析后的设备树存储在全局变量of_root中。

函数首先调用__unflatten_device_tree函数来执行设备树的解析操作。解析后的设备树将使用of_root指针进行存储。

接下来,函数调用of_alias_scan函数。这个函数用于扫描设备树中的/chosen和/aliases节点,并为它们分配内存。这样,其他部分的代码可以通过全局变量访问这些节点。

最后,函数调用unittest_unflatten_overlay_base函数,用于运行设备树的单元测试。

然后对__unflatten_device_tree这一设备树的解析函数进行详细的介绍,该函数的具体内容如下所示:

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;
	}

	/* 第一遍扫描,计算大小 */
	size = unflatten_dt_nodes(blob, NULL, dad, NULL);
	if (size < 0)
		return NULL;

	size = ALIGN(size, 4);
	pr_debug("  大小为 %d,正在分配内存...\n", size);

	/* 为展开的设备树分配内存 */
	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("  正在展开 %p...\n", mem);

	/* 第二遍扫描,实际展开设备树 */
	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;
}

该函数的重点在两次设备树的扫描上,第一遍扫描的目的是计算展开设备树所需的内存大小。

第29行:unflatten_dt_nodes函数的作用是递归地遍历设备树数据块,并计算展开设备树所需的内存大小。它接受四个参数:blob(设备树数据块指针)、start(当前节点的起始地址,初始为NULL)、dad(父节点指针)和mynodes(用于存储节点指针数组的指针,初始为NULL)。

第一遍扫描完成后,unflatten_dt_nodes函数会返回展开设备树所需的内存大小,然后在对大小进行对齐操作,并为展开的设备树分配内存。

第二遍扫描的目的是实际展开设备树,并填充设备节点的名称、类型和属性等信息。

第49行:再次调用了unflatten_dt_nodes函数进行第二遍扫描。通过这样的过程,第二遍扫描会将设备树数据块中的节点展开为真正的设备节点,并填充节点的名称、类型和属性等信息。这样就完成了设备树的展开过程。

最后我们来对unflatten_dt_nodes函数内容进行一下深究,unflatten_dt_nodes函数具体定义如下所示:

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  // 最大深度
	struct device_node *nps[FDT_MAX_DEPTH];  // 设备节点数组
	void *base = mem;  // 基地址,用于计算偏移量
	bool dryrun = !base;  // 是否只是模拟运行,不实际处理

	if (nodepp)
		*nodepp = NULL;  // 如果指针不为空,将其置为空指针

	/*
	 * 如果 @dad 有效,则表示正在展开设备子树。
	 * 在第一层深度可能有多个节点。
	 * 将 @depth 设置为 1,以使 fdt_next_node() 正常工作。
	 * 当发现负的 @depth 时,该函数会立即退出。
	 * 否则,除第一个节点外的设备节点将无法成功展开。
	 */
	if (dad)
		depth = initial_depth = 1;

	root = dad;  // 根节点为 @dad
	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;

		// 如果未启用 CONFIG_OF_KOBJ 并且节点不可用,则跳过该节点
		if (!IS_ENABLED(CONFIG_OF_KOBJ) &&
		    !of_fdt_device_is_available(blob, offset))
			continue;

		// 填充节点信息,并将子节点添加到设备节点数组
		if (!populate_node(blob, offset, &mem, nps[depth],
				   &nps[depth+1], dryrun))
			return mem - base;

		if (!dryrun && nodepp && !*nodepp)
			*nodepp = nps[depth+1];  // 将子节点指针赋值给 @nodepp
		if (!dryrun && !root)
			root = nps[depth+1];  // 如果根节点为空,则将子节点设置为根节点
	}

	if (offset < 0 && offset != -FDT_ERR_NOTFOUND) {
		pr_err("Error %d processing FDT\n", offset);
		return -EINVAL;
	}

	// 反转子节点列表。一些驱动程序假设节点顺序与 .dts 文件中的节点顺序一致
	if (!dryrun)
		reverse_nodes(root);

	return mem - base;  // 返回处理的字节数
}

unflatten_dt_nodes 函数的作用我们在上面已经讲解过了,这里重点介绍第31行的 fdt_next_node()函数和第41行的populate_node函数。

fdt_next_node() 函数用来遍历设备树的节点。从偏移量为 0 开始,只要偏移量大于等于 0 且深度大于等于初始深度,就执行循环。循环中的每次迭代都会处理一个设备树节点。

在每次迭代中,首先检查深度是否超过了最大深度 FDT_MAX_DEPTH,如果超过了,则跳过该节点。

如果未启用 CONFIG_OF_KOBJ 并且节点不可用(通过 of_fdt_device_is_available() 函数判断),则跳过该节点。

随后调用 populate_node() 函数填充节点信息,并将子节点添加到设备节点数组 nps 中。 populate_node() 函数定义如下所示:

static bool populate_node(const void *blob,
			  int offset,
			  void **mem,
			  struct device_node *dad,
			  struct device_node **pnp,
			  bool dryrun)
{
	struct device_node *np;  // 设备节点指针
	const char *pathp;  // 节点路径字符串指针
	unsigned int l, allocl;  // 路径字符串长度和分配的内存大小

	pathp = fdt_get_name(blob, offset, &l);  // 获取节点路径和长度
	if (!pathp) {
		*pnp = NULL;
		return false;
	}

	allocl = ++l;  // 分配内存大小为路径长度加一,用于存储节点路径字符串

	np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
				__alignof__(struct device_node));  // 分配设备节点内存
	if (!dryrun) {
		char *fn;
		of_node_init(np);  // 初始化设备节点
		np->full_name = fn = ((char *)np) + sizeof(*np);  // 设置设备节点的完整路径名

		memcpy(fn, pathp, l);  // 将节点路径字符串复制到设备节点的完整路径名中

		if (dad != NULL) {
			np->parent = dad;  // 设置设备节点的父节点
			np->sibling = dad->child;  // 设置设备节点的兄弟节点
			dad->child = np;  // 将设备节点添加为父节点的子节点
		}
	}

	populate_properties(blob, offset, mem, np, pathp, dryrun);  // 填充设备节点的属性信息
	if (!dryrun) {
		np->name = of_get_property(np, "name", NULL);  // 获取设备节点的名称属性
		np->type = of_get_property(np, "device_type", NULL);  // 获取设备节点的设备类型属性

		if (!np->name)
			np->name = "<NULL>";  // 如果设备节点没有名称属性,则设置为"<NULL>"
		if (!np->type)
			np->type = "<NULL>";  // 如果设备节点没有设备类型属性,则设置为"<NULL>"
	}

	*pnp = np;  // 将设备节点指针赋值给*pnp
	return true;
}

在populate_node 函数中首先会调用第18行的 unflatten_dt_alloc 函数分配设备节点内存。分配的内存大小为 sizeof(struct device_node) + allocl 字节,并使用 __alignof__(struct device_node) 对齐。然后调用 populate_properties 函数填充设备节点的属性信息。该函数会解析设备节点的属性,并根据需要分配内存来存储属性值。

至此,关于dtb二进制文件的解析过程就讲解完成了,完整的源码分析流程图如下(图 63-2)所示:


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/138062.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++——const成员

这里先用队列举例&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <assert.h> using namespace std; class SeqList { public:void pushBack(int data){if (_size _capacity){int* tmp (int*)realloc(a, sizeof(int) * 4);if (tm…

el-upload上传附件预览只能上传一个,上传玩没有+号

el-upload上传附件预览只能上传一个&#xff0c;上传玩没有号 一、效果图二、主要代码 一、效果图 二、主要代码 实现原理是通过控制css显隐hideUpload 字段 <template><div id"uploadOne"><!-- 预览附件上传一个 --><el-upload:class"{ h…

ChatkBQA:一个基于大语言模型的知识库问题生成-检索框架11.13

ChatkBQA&#xff1a;一个基于大语言模型的知识库问题生成-检索框架 摘要1 引言3 准备工作4 方法4.1 ChatKBQA概述4.2 在LLMS上进行高效微调4.3 用微调LLMS生成逻辑形式4.4 实体和关系的非监督检索4.5 可解释查询执行 摘要 基于知识的问答&#xff08;KBQA&#xff09;旨在从大…

C 语言递归

C 语言递归 在本教程中&#xff0c;您将借助示例学习使用C语言编程编写递归函数。 调用自身的函数称为递归函数。并且&#xff0c;这种技术称为递归。 递归如何工作&#xff1f; void recurse() {... .. ...recurse();... .. ... }int main() {... .. ...recurse();... .. …

【C++初阶】类与对象(三)

目录 一、再谈构造函数1.1 初始化列表1.1.1 初始化列表写法1.1.2 哪些成员要使用初始化列表 1.2 初始化列表的特点1.2.1 队列类问题解决1.2.2 声明顺序是初始化列表的顺序 1.3 explicit关键字1.3.1 explicit关键字的作用 二、static成员2.1 类的静态成员概念2.2 类里创建了多少…

GPTS应用怎么创建?GPTS无法创建应用很卡怎么办

在首届开发者大会上&#xff0c;OpenAI宣布推出了GPTs功能&#xff0c;也就是GPT Store&#xff0c;类似App Store的应用商店&#xff0c;任何用户都可以去参与创建应用。那么GPTS应用该如何创建?碰到应用无法创建很卡怎么办呢?下面就为大家带来GPTS应用创建图文教程&#xf…

php在线审稿系统mysql数据库web结构layUI布局apache计算机软件工程网页wamp

一、源码特点 php在线审稿系统是一套完善的web设计系统mysql数据库 &#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 php在线审稿系统 代码 https://download.csdn.net/download/qq_41221322/885…

11种方法判断​软件的安全可靠性​

软件的安全可靠性是衡量软件好坏的一个重要标准&#xff0c;安全性指与防止对程序及数据的非授权的故意或意外访问的能力有关的软件属性&#xff0c;可靠性指与在规定的一段时间和条件下&#xff0c;软件 软件的安全可靠性是衡量软件好坏的一个重要标准&#xff0c;安全性指与防…

开通和配置支付宝支付完整教程

开通和配置支付宝支付 登录支付宝 1.进入支付宝商家中心 支付宝商家中心-国内领先的第三方支付和金融服务平台 进入产品中心 2.点击产品中心 找到网站支付,进行申请接入支付,如果已经开通请跳过 进入开放中心 3.进入支付宝商家开发者中心登录 - 支付宝 创建应用 4.点击创…

跨机RPCLIB编译

Q1. 按照以下编码增加rpclib报错 find_package(rpclib REQUIRED)include_directories(${RPCLIB_INCLUDE_DIR})add_executable(calculator_server calculator_server.cc) target_link_libraries(calculator_server ${RPCLIB_LIBS} ${CMAKE_THREAD_LIBS_INIT}) Q2. 编译rpclib是…

SOME/IP学习笔记2

1. SOME/IP 协议 SOME/IP目前支持UDP&#xff08;用户传输协议&#xff09;和TCP&#xff08;传输控制协议&#xff09;&#xff0c; PS:UDP和TCP区别如下 TCP面向连接的&#xff0c;可靠的数据传输服务&#xff1b;UDP面向无连接的&#xff0c;尽最大努力的数据传输服务&…

操作系统——内存管理(一文搞懂操作系统的内存管理)

VIRT(虚拟内存)、RES(常驻内存)和SHR(共享内存) VIRT&#xff08;虚拟内存&#xff09; 进程“需要的”虚拟内存大小&#xff0c;包括进程使用的库、代码、数据&#xff0c;以及malloc、new分配的堆空间和分配的栈空间等&#xff1b;假如进程新申请10MB的内存&#xff0c;但实…

时代变迁,你背的“八股文”可能已经过时了

随着技术的不断更新迭代&#xff0c;一些曾经被认为是 “标准答案” 的观点和方法&#xff0c;已经不再适应当前的需求&#xff0c;甚至被视为过时的做法。在新的 JDK 版本中&#xff0c;许多新的特性、工具和方法被引入&#xff0c;使得 Java 编程变得更加简洁、高效和强大。所…

金蝶云星空和管易云接口打通对接实战

金蝶云星空和管易云接口打通对接实战 对接系统&#xff1a;金蝶云星空 金蝶K/3Cloud结合当今先进管理理论和数十万家国内客户最佳应用实践&#xff0c;面向事业部制、多地点、多工厂等运营协同与管控型企业及集团公司&#xff0c;提供一个通用的ERP服务平台。K/3Cloud支持的协同…

一个进程最多可以创建多少个线程?

前言 话不多说&#xff0c;先来张脑图~ linux 虚拟内存知识回顾 虚拟内存空间长啥样 在 Linux 操作系统中&#xff0c;虚拟地址空间的内部又被分为内核空间和用户空间两部分&#xff0c;不同位数的系统&#xff0c;地址空间的范围也不同。比如最常见的 32 位和 64 位系统&am…

MySQL查询原理与优化

文章目录 前言执行查询的过程逻辑连接器查询缓存解析器优化器执行器 衡量查询开销的三个指标响应时间扫描行数返回的行数 重构查询的几种选择一个复杂的查询还是多个简单的查询切分查询关联查询解决关联查询的原则 总结 前言 上一篇文章中&#xff08;MySQL索引全解&#xff1…

TikTok数字艺术:短视频背后的视觉盛宴

在当今数字时代&#xff0c;社交媒体平台已经成为创意表达和文化传播的重要场所之一。其中&#xff0c;以短视频为代表的形式在TikTok这一平台上崭露头角&#xff0c;为创作者和观众提供了一个数字艺术的舞台。 本文将深入探讨TikTok数字艺术的独特之处&#xff0c;剖析短视频…

idea报错java: 程序包com.alibaba.fastjson不存在,明明存在!

经常从线上拉下来代码后编译运行时会报这个错误。刷新maven也没用&#xff0c;重新导入项目也没用 发现解决方法如下&#xff1a; 找到当前报错文件的路径。找到iml文件 删除它&#xff01;然后刷新maven 就好了&#xff01;&#xff01;&#xff01; 记录一下我的解决方法&…

酷柚易汛ERP- 备份与恢复操作指南

1、应用场景 该界面只有管理员才会显示&#xff0c;对已有数据的账套进行备份与恢复。 2、操作指南 2.1 开始备份 对当前系统内的所有数据进行备份&#xff0c;备份成功后当前数据则保存至当前服务器上&#xff0c;同时也会在列表内新增一条当前操作的备份文件记录 2.2 上传…
最新文章