Linux驱动开发—设备树传递给内核,匹配驱动过程分析
文章目录
- 总体流程图
- 传递DTB过程
- 编译设备树源文件
- 将 `.dtb` 文件与内核或引导加载程序集成
- 内核初始化阶段解析DTB
- 内核启动阶段
- 解析 DTB
- 注册设备树节点
- 驱动程序绑定
内核解析设备树二进制文件(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
)。 - 将设备树二进制文件指定为内核构建的一部分,通常通过内核的
Makefile
和Kconfig
文件。
b. 通过引导加载程序加载设备树
引导加载程序(例如 U-Boot)负责加载内核,并在加载内核之前传递设备树:
- 引导加载程序首先加载设备树二进制文件(
.dtb
)。 - 然后,引导加载程序将设备树传递给内核。
在 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;
}