1. U-Boot设备驱动模型初始化概述
在嵌入式系统启动过程中,U-Boot作为关键的引导加载程序,其设备驱动模型(DM)的初始化过程直接影响着后续系统的正常运行。本文将深入分析U-Boot SPL阶段对设备树的处理流程和设备驱动模型的初始化机制。
设备驱动模型(DM)是U-Boot中管理硬件设备的框架,它通过树状拓扑和链表分类相结合的方式组织系统中的所有设备。理解这一机制对于嵌入式系统开发、驱动调试以及启动优化都至关重要。
2. SPL启动流程与设备树获取
2.1 SPL启动前期准备
芯片上电复位后,CPU从固定地址开始执行指令。通常情况下:
- PC寄存器指向芯片内部的ROM区域
- 完成CPU和必要外设的初始化
- 从非易失存储器(如FLASH/SD卡)加载SPL到片上RAM
- 跳转到SPL入口点执行
注意:这里的片上RAM不同于DDR内存,它容量更小但访问延迟更低,仅需单周期即可完成访问。
进入SPL后,首先执行架构相关的汇编代码(start.S),完成:
- 设置执行栈
- 选择并初始化主核心
- 为全局数据结构体(gd_t)分配空间
- 跳转到第一个C语言初始化函数board_init_f
2.2 spl_common_init函数分析
spl_common_init是board_init_f调用的第一个重要初始化函数,其核心逻辑如下:
c复制static int spl_common_init(bool setup_malloc) {
/* 设备树处理 */
if (CONFIG_IS_ENABLED(OF_REAL)) {
ret = fdtdec_setup();
if (ret) return ret;
}
/* 设备驱动模型初始化 */
if (CONFIG_IS_ENABLED(DM)) {
ret = dm_init_and_scan(!CONFIG_IS_ENABLED(OF_PLATDATA));
if (ret) return ret;
}
return 0;
}
这个函数完成了两个关键任务:
- 确定并获取设备树(fdtdec_setup)
- 初始化设备驱动模型并扫描设备(dm_init_and_scan)
3. 设备树获取机制详解
3.1 fdtdec_setup函数工作流程
fdtdec_setup函数负责从多个可能的位置获取设备树,其查询顺序和优先级如下:
-
Bloblist检查(如果启用):
- 检查之前启动阶段是否在bloblist中保存了设备树
- 适用于多阶段启动共享状态的情况
-
独立存储的设备树:
- CONFIG_OF_SEPARATE:设备树与SPL镜像分离存放
- 否则设备树嵌入在SPL镜像内部
-
板级自定义地址:
- 通过board_fdt_blob_setup()获取板级定义的设备树位置
-
环境变量指定(仅U-Boot Proper阶段):
- 从环境变量fdtcontroladdr获取设备树地址
-
多设备树支持:
- 如果启用MULTI_DTB_FIT,会进一步选择最适合的设备树
3.2 设备树存储位置分析
根据配置不同,设备树可能存储在以下几个位置:
情况A:SPL阶段,BSS段与镜像分离(CONFIG_SPL_SEPARATE_BSS)
- 设备树紧跟在SPL镜像之后(_image_binary_end)
- 适用于需要大量全局变量的场景
- BSS段被放置在独立的内存区域(CONFIG_SPL_BSS_START_ADDR)
情况B:SPL阶段,BSS段与镜像合并
- 设备树位于BSS段末尾(__bss_end)
- 生成镜像时会添加全0的padding区域作为BSS段占位
情况C:U-Boot Proper阶段
- 设备树位于镜像末尾(_end)
- 重定位时会保护设备树数据
3.3 设备树合法性检查与调整
获取设备树后,会进行以下处理:
- 调用fdtdec_prepare_fdt进行基础合法性检查
- 执行板级特定的调整函数fdtdec_board_setup
- 重置设备树解析状态(oftree_reset)
4. 设备驱动模型(DM)初始化
4.1 DM模型核心概念
U-Boot DM模型采用正交组织结构,包含以下核心元素:
-
uclass:
- 驱动分类,收集功能相似的驱动
- 所有uclass通过链表连接,头节点保存在gd->uclass_root
-
uclass_driver:
- 提供统一访问接口
- 按照生命周期组织操作函数(post_bind/pre_unbind等)
-
udevice:
- 设备实例,同时存在于树状结构和uclass链表中
- 包含指向父设备、子设备、兄弟设备和所属uclass的指针
-
driver:
- 实际设备驱动,包含硬件操作函数
- 一个驱动可以对应多个设备实例
4.2 dm_init_and_scan函数解析
c复制int dm_init_and_scan(bool pre_reloc_only) {
/* 初始化uclass并绑定根设备 */
ret = dm_init(CONFIG_IS_ENABLED(OF_LIVE));
/* 扫描并绑定设备 */
if (!CONFIG_IS_ENABLED(OF_PLATDATA_INST)) {
ret = dm_scan(pre_reloc_only);
}
/* 触发事件系统 */
if (CONFIG_IS_ENABLED(DM_EVENT)) {
event_notify_null(...);
}
return 0;
}
4.3 设备绑定(bind)与初始化(probe)过程
设备管理分为两个阶段:
-
bind阶段:
- 创建udevice结构体
- 分配必要的内存空间
- 将设备插入树状和链表拓扑
- 调用驱动的bind钩子函数
-
probe阶段:
- 解析设备树信息
- 递归初始化父设备
- 配置pinctrl、时钟等基础资源
- 调用驱动的probe函数初始化硬件
5. 设备扫描与绑定细节
5.1 静态声明设备的绑定
通过U_BOOT_DRVINFO宏声明的设备会经历以下绑定过程:
- 遍历driver_info数组
- 为每个entry查找匹配的驱动
- 调用device_bind_common创建设备实例
- 处理父子设备依赖关系
5.2 设备树中设备的绑定
对于设备树中的节点,绑定流程如下:
- 遍历父节点的直接子节点
- 检查节点状态(disabled节点跳过)
- 根据compatible属性匹配驱动
- 调用lists_bind_fdt完成绑定
5.3 设备初始化顺序
设备初始化采用深度优先策略:
- 递归初始化父设备
- 初始化当前设备的pinctrl、时钟等
- 调用驱动的probe函数
- 初始化子设备
6. 关键数据结构与函数关系
6.1 核心数据结构关系图
code复制uclass_root (链表头)
|
v
uclass1 -> uclass2 -> uclass3
| | |
v v v
device1 device2 device3
|
v
child1 -> child2 -> child3
6.2 重要函数调用链
code复制dm_init_and_scan
├── dm_init
│ ├── device_bind_by_name (绑定根设备)
│ └── device_probe (初始化根设备)
└── dm_scan
├── dm_scan_plat (静态设备)
├── dm_extended_scan (设备树设备)
└── dm_probe_devices (深度优先初始化)
7. 实际开发中的注意事项
-
设备树配置:
- 确保compatible属性与驱动匹配
- 合理使用status属性控制设备启用状态
-
驱动开发:
- 正确实现bind和probe函数
- 合理分配各类自动内存空间(plat_auto等)
-
启动优化:
- 合理使用CONFIG_SPL_SEPARATE_BSS
- 控制pre-reloc设备的数量减少内存占用
-
调试技巧:
- 使用dm tree命令查看设备拓扑
- 通过dm uclass查看uclass信息
8. 常见问题与解决方案
问题1:设备无法正确绑定
可能原因:
- compatible属性不匹配
- 驱动未正确声明(U_BOOT_DRIVER)
- 设备树节点status为disabled
解决方案:
- 检查驱动中的of_match表
- 确认驱动是否被编译进镜像
- 检查设备树节点状态
问题2:设备初始化失败
可能原因:
- 父设备未正确初始化
- 资源(pinctrl/clock)配置错误
- probe函数实现有问题
解决方案:
- 检查设备依赖关系
- 验证基础资源配置
- 调试probe函数执行流程
问题3:内存不足
可能原因:
- 设备自动分配空间过大
- 同时初始化的设备过多
解决方案:
- 优化plat_auto等空间分配
- 合理使用pre-reloc控制初始化时机
- 考虑启用CONFIG_SPL_SEPARATE_BSS
9. 性能优化建议
-
SPL阶段优化:
- 只初始化必要的设备
- 合理使用DM_FLAG_PRE_RELOC
- 考虑使用OF_PLATDATA减少设备树解析开销
-
内存使用优化:
- 减少自动分配内存大小
- 共享公共数据结构
- 及时释放不再使用的资源
-
启动速度优化:
- 并行初始化无依赖的设备
- 延迟非关键设备初始化
- 优化时钟和pinctrl配置顺序
10. 实例分析:RISC-V平台上的实现
以RISC-V平台为例,其DM初始化有以下特点:
-
设备树获取:
- 通常采用OF_SEPARATE方式
- 设备树与SPL镜像拼接存放
-
核心设备初始化:
- 先初始化时钟、串口等基础外设
- 再初始化存储、网络等复杂外设
-
特殊处理:
- RISC-V架构相关的初始化钩子
- 特定于厂商的扩展功能支持
典型启动日志分析:
code复制U-Boot SPL 2023.04 (May 01 2023 - 15:30:25 +0800)
Trying to boot from MMC
DM: dm_init_and_scan: start
DM: dm_init: uclass_root initialized
DM: device_bind_by_name: root_driver bound
DM: dm_scan_plat: 3 static devices found
DM: dm_extended_scan: 5 DT nodes bound
DM: dm_probe_devices: probing 8 devices
通过理解U-Boot DM初始化过程,开发者可以:
- 更高效地调试启动问题
- 优化系统启动时间
- 定制特定硬件平台的初始化流程
- 开发符合DM框架的新驱动
在实际项目中,建议结合具体平台的实现和芯片手册,深入分析设备初始化的每个环节,从而构建稳定可靠的启动流程。
