DM 的核心目标:
统一驱动写法、支持设备树、支持平台数据、总线抽象、懒加载(bind 和 probe 分离),替代老 U-Boot 分散、硬编码、无统一管理的驱动模式。
一、什么是DM?
1、早期 U-Boot 驱动
① 驱动没有统一管理,UART、ETH、I2C、SPI 代码各自一套,没有标准接口;
② 硬件信息硬写在板级代码(board.c)里,换板子就要大量改代码;
③ 不支持设备树 DTS,很难复用内核 DTS;
④ 设备实例、私有数据、平台参数管理混乱;
⑤ 没有总线抽象(I2C 总线设备、SPI 从设备没法统一管理)。
2、DM(Driver Model)
U-Boot 引入的一套面向对象风格驱动框架,从 U-Boot 2014 左右引入,2016 之后逐步成熟,2021 版本已经是主力驱动模型,我们看到的 GMAC、UART、PHY、I2C 都是 DM 驱动。
一句话核心思想:
① 把「驱动」「设备实例」「设备类别 (uclass)」「总线 (bus)」拆成独立对象;
② 先 bind 绑定,再 probe 硬件初始化,延迟初始化。
两个最核心的阶段,一定要区分开
- bind(绑定):只在内存里构建设备节点、建立链表关系,不碰硬件寄存器
- probe(探测):真正初始化硬件、读寄存器、使能外设、分配私有资源

二、DM 里面 4 大核心结构体(基础四元组)
1. struct driver 驱动(驱动代码本身)
代表一个驱动程序,写驱动的人定义这个结构体,里面放:
- name:驱动名字(
lists_driver_lookup_name()就是按这个名字查找) - id:匹配的 uclass 编号
- of_match:设备树 compatible 匹配表(DTS 里
compatible = "xxx"就和这里匹配) - bind:可选 bind 回调
- probe:最重要!硬件初始化回调函数(probe 时调用)
- remove:反初始化
- flags:
DM_FLAG_*
-
plat_auto:自动分配platdata(平台数据,硬件静态参数:波特率、phy 地址、RGMII delay 等) -
priv_auto:自动分配priv(驱动私有数据,运行时状态、寄存器基地址、缓存等)struct driver {
const char *name;
enum uclass_id id;
const struct udevice_id *of_match;
int (*bind)(struct udevice *dev);
int (*probe)(struct udevice *dev);
int (*remove)(struct udevice *dev);
uint flags;
};
2. struct udevice 设备实例(一个硬件设备)
每一个 DTS 节点 / 平台设备,bind 之后就生成一个udevice,代表一个硬件实例,是 DM 里最常见的对象。 成员重点:
- name:设备名
- driver:指向上面的
struct driver - uclass:归属的 uclass
- parent:父设备(总线控制器,比如 I2C 控制器是 I2C 设备的 parent)
- platdata:平台静态数据(DTS 解析出来的配置)
- priv:驱动私有数据
- parent_priv:总线给子设备的私有数据
- flags:状态标记(
DM_FLAG_BOUND、DM_FLAG_PROBED,标记有没有 bind、有没有 probe) - node:设备树节点引用(fdt 节点偏移)
重点:udevice 只是一个 "设备描述符",bind 完就存在;probe 成功后才打上 PROBED 标记,硬件才可用。
3. struct uclass 设备类(uclass)
uclass = 一类功能相同的设备抽象,相当于 "设备类型"。 比如:
UCLASS_SERIAL:所有串口UCLASS_ETH:所有以太网 GMACUCLASS_I2C:I2C 控制器UCLASS_PHY:PHY 芯片UCLASS_GPIO:GPIO 控制器
struct uclass_driver 是 uclass 对应的驱动,定义 uclass 的行为:提供统一接口、遍历、查找、get_device 等。
uclass 提供统一 API:uclass_get_device(),上层不用关心底层是哪个芯片的驱动,只要拿同类设备。 比如:uclass_get_device(UCLASS_SERIAL, 0, &dev) 获取第 0 路串口。
4. struct dm_bus /bus 总线
总线是特殊的 uclass 驱动(I2C、SPI、PCI),负责:
- 管理子设备
- 子设备的地址翻译、总线访问(I2C 发 start、SPI 片选)
- 子设备的 probe 时机控制
总线设备是 parent,挂在总线上的外设是 child。

三、链接器列表 linker-list
我们看到的 lists_driver_lookup_name 的底层
1、U-Boot DM 驱动不是手动放到数组里注册,而是用宏U_BOOT_DRIVER(),这个宏会把struct driver放到一个专门的.u_boot_driver段(linker section)。
2、编译后所有驱动集中在这个段,运行时遍历这个段,就是全部注册的驱动。
3、lists_driver_lookup_name() 就是遍历这个链接段,按 name 查找 driver。
U_BOOT_DRIVER(xxx_gmac) = {
.name = "xxx_gmac",
.id = UCLASS_ETH,
.of_match = xxx_gmac_ids,
.probe = xxx_gmac_probe,
.flags = DM_FLAG_ALLOC_PRIV | DM_FLAG_PLAT_AUTO,
};
四、DM 完整初始化时序
和 board_init_r 里面调用链路对齐
调用顺序:
initr_dm()→dm_init_and_scan()→dm_init()+dm_scan()dm_scan()=dm_scan_plat()+dm_extended_scan()+ dm_scan_other()
阶段 1:dm_init ()
------ 搭建 DM 框架,不扫描设备树、不创建设备
① INIT_LIST_HEAD 初始化全局链表头(uclass 链表、设备链表)
② 遍历 linker-list 里所有uclass_driver,注册所有 uclass
③ 遍历 linker-list 里所有driver,把驱动挂到全局驱动链表
④ 结果:驱动和 uclass 都登记在册,还没有任何 udevice 实例。
⑤ 不解析 DTS、不 bind、不 probe、不碰硬件。
阶段 2:dm_scan_plat ()
------ 扫描平台数据设备(platdata 设备,无 DTS)
① 老平台、早期板级硬编码设备,用 C 代码构造 platdata,直接 bind 设备。
② 现在新 SOC ARM几乎很少用,大部分设备走 DTS。
阶段 3:dm_extended_scan ()
------ 扫描设备树 DTS,生成 udevice(重点!)
内部核心 dm_scan_fdt() / dm_scan_fdt_node(),递归遍历 DTS 节点:
① 读取 DTS 节点的 compatible 属性;
② 在已经注册的 driver 列表里匹配(lists_driver_lookup_name 或者 of 匹配);
③ 匹配成功 → 调用 device_bind_by_name() → device_bind_common();
④ bind 成功:分配 udevice、platdata、priv(根据plat_auto/priv_auto),加入链表;
⑤ 递归扫描子节点(总线下面的从设备)。
这里只 bind,不 probe!扫描完 DTS,内存里一大堆 udevice,但是硬件完全没初始化。
bind 完成之后:所有 DTS 节点都变成 udevice,打上 DM_FLAG_BOUND 标记。
阶段 4:按需 probe
------懒加载,DM 最经典的设计
1、什么时候 probe?
- 上层调用
uclass_get_device()/dm_dev_probe()的时候; - 比如
initr_net()里面拿 ETH 设备,触发 GMAC 的 probe; - 串口打印:console 初始化的时候拿 SERIAL 设备,触发 UART probe。
2、probe 流程:
- 检查是否已经 probe,防止重复初始化;
- 如果有 parent 总线,先 probe 父设备(比如 I2C 设备,先 probe I2C 控制器);
- 调用 driver->probe 回调函数(驱动自己写的硬件初始化:寄存器、时钟、复位、PHY);
- 标记 DM_FLAG_PROBED,设备可用。
懒加载好处:不需要的外设可以不初始化,节省启动时间、减少上电噪声。

五、两条设备来源路径
------plat 设备 vs FDT 设备
1、plat 设备:dm_scan_plat ()
板级 C 代码静态构造 platdata,不用 DTS,老方案。
2、fdt 设备:dm_extended_scan ()
从设备树节点生成,现代 SOC 主流,大多数都是这种。
六、U-Boot DM 和 Linux kernel 设备驱动模型(devicetree/driver core)对比
1、思路相似:
DTS、device、driver、bus 匹配;
2、巨大差异:
Linux 内核驱动模型支持热插拔、电源管理、子系统、中断子系统;
3、U-Boot DM 极简裁剪:
为启动速度服务,懒加载、没有内核的复杂子系统,只满足启动阶段需求,probe 之后一般不会 remove,很少热插拔。
七、SOC ARM里U-Boot DM整个启动流里的位置
board_init_f()
-> initf_dm() // 早期DM(可选,很少放硬件)
重定位
board_init_r()
-> initr_dm()
-> dm_init_and_scan()
dm_init() // 注册uclass、driver
dm_scan_plat()
dm_extended_scan() // 解析DTS,bind所有GMAC/UART/I2C设备
-> initr_net() // 去uclass里拿ETH设备,触发GMAC probe
-> initr_serial() // 拿串口设备,probe UART
-> bootcmd 启动kernel