U-Boot怎么把设备树地址准确告诉内核的?——DTB传递链路 电子说
| 导读:设备树(Device Tree)是 U‑Boot 传递给 Linux 内核最重要的数据结构。它包含了板级的全部硬件描述信息 —— 内存布局、外设地址、时钟配置、pinmux 设置等。从 FDT 加载到最终写入 x0 寄存器,DTB 经历了一条精心设计的传递链。 |
问题场景
内核启动到一半报FDT has bad magic或Kernel panic - not syncing: Unable to find DTB—— 这类问题通常不是 DTB 文件本身损坏,而是传递链中间某个环节断开了。DTB 从分区读到内存、经过校验和修补、最终放到 x0 寄存器,每一步都可能出错。本文追踪整条链路的实现代码。
一、DTB 的传递全景
从用户输入booti 命令到内核收到 DTB,整条链路如下:

二、DTB 的来源与优先级
在 RK3576 上,DTB 可以从多个来源加载。U‑Boot 按以下优先级选择:

RK3576 的典型情况:DTB 通常从 resource 分区加载。resource 分区是 Rockchip 平台特有的分区,存放了内核 DTB 和可能的其他资源文件。在引导时,U‑Boot 的 rockchip_read_dtb_file() 函数从resource 分区读取完整的设备树。
// arch/arm/mach-rockchip/resource_img.c (简化)void *rockchip_read_dtb_file(void){ /* 从resource分区读取DTB到fdt_addr_r */ int ret = rockchip_read_resource_file(dtb_buffer, CONFIG_ROCKCHIP_RESOURCE_DTB_NAME); if (ret <= 0) { printf("Failed to read DTB from resource partitionn"); return NULL; } return dtb_buffer;}
三、RD DTB 与 U‑Boot DTB 的分离
RK3576 采用了 双 DTB 策略:
•U‑Boot DTB (rk3576‑u‑boot.dtsi):仅包含 U‑Boot 自身驱动需要的设备节点,体积小,编译进 U‑Boot 二进制文件
•Kernel DTB (rk3576.dts + 板级rk3576‑evb.dts):完整的内核设备树,从 resource 分区加载或 SPI Flash 读取
之所以分离,是因为 U‑Boot 不需要完整的设备树 —— 它只需要驱动自己使用的外设(串口、MMC、网络等)。内核设备树则包含所有外设的完整描述。
四、bootm_find_images — 定位 DTB
bootm_find_images() 函数负责定位和加载 DTB 到内存。以下是经过简化的逻辑示意:
// common/bootm.c — bootm_find_images 简化解说int bootm_find_images(int flag, int argc, char *const argv[]){ /* 1. 获取fdt地址:按优先级选择 */ /* 命令行参数 argv[3] → 环境变量 fdt_addr → fdt_addr_r */ /* 2. 验证DTB magic (fdt_check_header) */ /* 3. 设置 images->ft_addr */ /* 另外也处理ramdisk和FDT overlay */ ret = boot_get_fdt(flag, argc, argv, images, &images->ft_addr, &images->ft_len); return ret;}
实际实现更复杂:它通过boot_get_fdt() 函数统一处理,支持从 FIT 镜像中提取 DTB、叠加 FDT overlay 等特性。对于 RK3576 平台,当命令行和环境变量未指定时,由 rockchip_read_dtb_file() 从 resource 分区读取。
五、image_setup_libfdt — FDT Fixup
在 DTB 定位之后,image_setup_libfdt() 对 DTB 进行最终的运行时修补。这是 DTB 传递链中最关键的一步:
// common/image-fdt.c — 实际实现(简化)int image_setup_libfdt(bootm_headers_t *images, void *blob, int of_size, struct lmb *lmb){ /* 1. 架构通用fixup */ if (arch_fixup_fdt(blob) < 0) goto err; /* 2. ★ 注入bootargs到chosen节点 */ if (fdt_chosen(blob) < 0) goto err; /* 3. 注入MAC地址(从环境变量 ethaddr 读取) */ fdt_fixup_ethernet(blob); /* 4. 板级特定fixup(通过事件回调链) */ if (IMAGE_OF_BOARD_SETUP) { if (ft_board_setup(blob, gd->bd) < 0) goto err; } /* 5. 系统级fixup(★ 包含rk_board_dm_fdt_fixup) */ if (IMAGE_OF_SYSTEM_SETUP) { if (ft_system_setup(blob, gd->bd) < 0) goto err; } /* 6. 收缩和保留 */ ret = fdt_shrink_to_minimum(blob, 0); fdt_initrd(blob, *initrd_start, *initrd_end); return 0;err: printf("ERROR: fdt fixup failedn"); return -1;}
| 注意:rk_board_dm_fdt_fixup() 并非直接在image_setup_libfdt() 中调用,而是通过IMAGE_OF_SYSTEM_SETUP → ft_system_setup() → EVT_FT_FIXUP 事件链间接触发。不同板卡通过实现rk_board_dm_fdt_fixup() 来完成板级 DTB 修改 |
六、从 ft_addr 到 x0 的最终传递
Fixup 完成后,DTB 地址存储在 images->ft_addr 中。在BOOTM_STATE_OS_GO 阶段,boot_jump_linux() 将其放入 x0:
// arch/arm/lib/bootm.cvoid boot_jump_linux(bootm_headers_t *images, int flag){ /* cleanup_before_linux()已经执行完毕 */ /* ★ 关键跳转:x0 = images->ft_addr */ kernel_entry(images->ft_addr, 0, 0, 0);}

七、调试 DTB 传递
如果在引导时遇到 DTB 相关问题,可以使用以下命令调试:
# 查看当前fdt_addr_r的值printenv fdt_addr_r# → 通常为 0x08300000# 手动加载DTB并检查load mmc 0:1 ${fdt_addr_r} /boot/rk3576‑evb.dtbfdt addr ${fdt_addr_r}fdt list / # 列出根节点fdt list /chosen # 检查bootargsfdt list /memory # 检查内存信息
八、小结
•DTB 传递是 U‑Boot 到内核信息交换的核心机制,取代了传统的 ATAGS 方式
•DTB 有多来源(命令行、环境变量、resource 分区、FIT、内置),按优先级选择
•RK3576 采用双 DTB 策略,U‑Boot DTB 简化驱动需要,Kernel DTB 从 resource 分区加载
•image_setup_libfdt() 执行关键的 FDT Fixup 链,注入运行时的硬件信息
•最终images->ft_addr 通过boot_jump_linux() 放入 x0 寄存器传递给内核
•理解 DTB 传递链,是排查 "Kernel panic" 和 "bad FDT" 类问题的前提
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !