Linux驱动开发—设备树传递给内核,匹配驱动过程分析


内核解析设备树二进制文件(DTB)的过程主要分为几个步骤,从设备树的传递到最终的硬件配置。这些步骤包括加载 DTB、解析和处理设备树节点和属性,以及将硬件信息传递给相应的驱动程序。

总体流程图

在这里插入图片描述

传递DTB过程

在系统启动时,引导加载程序(如 U-Boot)将 DTB 文件加载到内存,并将其位置传递给内核。对于 ARM 和 ARM64 平台,引导加载程序通常通过 r2 寄存器传递 DTB 的内存地址。

编译设备树源文件

设备树源文件(.dts)需要编译成设备树二进制文件(.dtb):

dtc -I dts -O dtb -o my_device_tree.dtb my_device_tree.dts

.dtb 文件与内核或引导加载程序集成

a. 将 .dtb 文件与内核镜像一起打包

在一些平台上,.dtb 文件被包含在内核镜像中。这通常通过内核构建系统中的配置来完成。例如,在 arm 平台上,可以通过以下步骤进行配置:

  • 确保内核配置中启用了设备树支持(CONFIG_OF)。
  • 将设备树二进制文件指定为内核构建的一部分,通常通过内核的 MakefileKconfig 文件。

b. 通过引导加载程序加载设备树

引导加载程序(例如 U-Boot)负责加载内核,并在加载内核之前传递设备树:

  1. 引导加载程序首先加载设备树二进制文件(.dtb)。
  2. 然后,引导加载程序将设备树传递给内核。

在 U-Boot 中,这通常通过设置环境变量来实现:

setenv fdtfile my_device_tree.dtb
load mmc 0:1 ${fdt_addr} ${fdtfile}
bootz ${kernel_addr} - ${fdt_addr}

fdtfile 是设备树二进制文件的路径。

fdt_addr 是设备树加载到内存中的地址。

kernel_addr 是内核镜像的地址。

当内核启动时,它会从引导加载程序接收设备树

内核初始化阶段解析DTB

内核解析设备树二进制文件(DTB)的过程主要分为几个步骤,从设备树的传递到最终的硬件配置。这些步骤包括加载 DTB、解析和处理设备树节点和属性,以及将硬件信息传递给相应的驱动程序

内核启动阶段

内核启动时,会在启动代码中处理传递过来的 DTB 地址,并将其保存在全局变量中。以 ARM64 为例,启动代码会保存 DTB 地址,并在后续初始化过程中使用:

void __init setup_arch(char **cmdline_p)
{
    // 保存 DTB 地址
    initial_boot_params = __va(FDT_START);
}

解析 DTB

内核在初始化过程中会调用设备树相关的函数来解析 DTB。主要函数如下:

a. 在imx_4.14.98_2.0.0_ga/arch/arm64/kernel 中setup.c 中early_init_dt_scan()

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
	void *dt_virt = fixmap_remap_fdt(dt_phys);
	const char *name;

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

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

内核首先调用 early_init_dt_scan() 来扫描和验证设备树的基本结构、总大小和根节点:

void __init early_init_dt_scan(void *params)
{
    if (fdt_check_header(params))
        panic("Invalid device tree blob");

    // 解析根节点和基本属性
    early_init_dt_verify(params);
    early_init_dt_reserve_memory();
    unflatten_device_tree();
}

b.在drivers/of/fdt.c 中定义了如何解析为树状结构函数 : unflatten_device_tree()

unflatten_device_tree() 函数将设备树的扁平结构转换为内核使用的树形结构:

/**
 * __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.
 */
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;
}

c. early_init_dt_scan_nodes()

这个函数扫描设备树的所有节点,并将其转换为内核中的数据结构:

void __init early_init_dt_scan_nodes(void)
{
	/* Retrieve various information from the /chosen node */
	of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

	/* Initialize {size,address}-cells info */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);

	/* Setup memory, calling early_init_dt_add_memory_arch */
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

注册设备树节点

内核将解析的设备树节点注册到设备模型中,通常通过位于drivers/of/platform.c的 of_platform_populate() 函数完成:

int __init of_platform_populate(void)
{
    struct device_node *root;

    root = of_find_node_by_path("/");
    of_platform_default_populate(root, NULL, NULL);
    return 0;
}

驱动程序绑定

设备树解析后,内核会根据设备树中的信息来匹配相应的驱动程序,并进行设备初始化。驱动程序通常通过 of_match_table 表来匹配设备树中的节点

static const struct of_device_id my_driver_of_match[] = {
    { .compatible = "my_vendor,my_device", },
    { }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);

驱动程序通过 of_device 结构体访问设备树节点和属性:

static int my_driver_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    // 读取属性并初始化设备
    return 0;
}
  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Trump. yang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值