文章目录
- 总体流程图
- 传递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;
}