先把结论放这儿U-Boot 的设备模型DMDriver Model真正成体系、成骨架是从board_init_r里的initr_dm()开始的。你之前看到的 TPL/SPL 里那些 DM 操作只是提前热身到了board_init_r整个驱动树、uclass 链表、设备绑定才正式像模像样搭起来。这篇文章不讲泛泛的概念我直接带你沿调用链走一遍把board_init_r里 DM 驱动骨架是怎么一步步“焊”出来的拆清楚。适合正在做板级移植、U-Boot 驱动开发、或者被dm tree打印搞到怀疑人生的朋友。1. 先从 board_init_r 往上看DM 到底被谁唤醒1.1 两个初始化阶段的明确分工U-Boot 从上电到运行C 代码要经历两个大阶段board_init_f前初始化和board_init_r重定位后初始化。区分这对兄弟特别关键因为 DM 的完整骨架基本是在board_init_r里搭的。board_init_f阶段DDR 还没完全就绪代码可能还在 Flash 里跑全局变量虽然能用但堆空间非常局促。所以这个阶段 U-Boot 只做最基础的事初始化串口、计算重定位地址、把栈和 GDglobal data准备好。有些板子在这个阶段也会提前用 DM 驱动串口但是属于“够用就好”的尺度不会把整棵设备树扫一遍。到了board_init_r代码已经重定位到内存里堆够大设备树也已经找到这才是 DM 正式部署的时机。你去看common/board_r.c里的初始化序列会看到一组函数指针数组其中一个就是initr_dm。很多搞驱动的人第一次看板级代码会迷路就是因为没抓住这个入口——那个传说中的“驱动骨架”就是在这里被拉起来的。1.2 一条实实在在的调用链以常见 U-Boot 版本为例board_init_r会按数组顺序执行初始化函数。核心片段大概是这样的static int initr_dm(void) { return dm_init_and_scan(); }这函数在init_sequence_r[]数组里排在比较靠前的位置。你顺着它往下挖会发现真正做事的是dm_init_and_scan()这个函数定义在drivers/core/root.c。它的动作逻辑很清晰int dm_init_and_scan(void) { int ret; ret dm_init(); if (ret) return ret; ret dm_scan(); if (ret) return ret; return 0; }dm_init()负责把 DM 的根设备建好把所有已注册的uclass登记到全局链表里dm_scan()则负责拿着设备树或板级配置把所有需要用到的节点绑定成udevice。绑定完骨架就有了。这里有个关键认知骨架是“绑定”出来的不是“探测”出来的。绑定只是在内存里建立udevice对象和链表关系探测才是真正调用驱动的probe去初始化硬件。board_init_r在initr_dm这个阶段主要完成的是绑定层面的扫荡真正的硬件操作是后续各子系统按需触发的。理解不了这层后面看dm tree输出会觉得一团乱麻。2. 骨架的钢筋uclass、udevice、driver 三件套2.1 三个核心数据结构的关系DM 之所以叫“模型”就是因为它把驱动抽象成了几个固定角色。核心有三个struct driver驱动程序、struct uclass_driver设备类驱动、struct udevice设备实例。它们之间的关系有点像人和户口本driver是“能力”uclass_driver是“类别的管理规则”udevice是“具体登记的个体”。写驱动时你一般用宏声明一个driverU_BOOT_DRIVER(sandbox_uart) { .name sandbox_uart, .id UCLASS_SERIAL, .probe sandbox_uart_probe, .remove sandbox_uart_remove, };.id UCLASS_SERIAL就是告诉 DM这个驱动属于串口类。struct uclass_driver则定义了串口类的公共方法比如.post_bind、.pre_probe、.post_probe以及.per_device_plat_auto这类自动分配内存的大小。这些回调很多人不重视实际上绑定时会调post_bind探测前会调pre_probe顺序错一点就可能出诡异问题。2.2 链表到底怎么把设备串起来DM 的骨架本质是几根链表穿起来的树。看struct uclass和struct udevice的关键字段struct uclass { struct list_head list_head; /* uclass 挂在全局链表 */ struct list_head dev_head; /* 该类下所有设备链表 */ struct uclass_driver *uc_drv; ... }; struct udevice { struct list_head uclass_node; /* 挂在 uclass-dev_head */ struct list_head child_head; /* 子设备链表头 */ struct list_head sibling_node; /* 兄弟节点链表 */ struct uclass *uclass; struct driver *driver; ... };全局的入口是drivers/core/uclass.c里的一个静态链表头uclass_root所有已登记的uclass都挂在这根链上。dm_init()做的事之一就是让每个U_BOOT_UCLASS宏声明的 uclass 进入这条链表。每个 uclass 自己又有一根dev_head管着所有属于这个类别的设备。设备之间则通过child_head和sibling_node组成一棵树根就是gd-dm_root。你可以把uclass_root想成“户籍总册”每个 uclass 是一本“分类名册”udevice就是名册上的一条条记录。board_init_r里dm_scan()做的核心工作就是往这些名册里登记记录。3. 绑定时骨架是怎么“焊”上去的3.1 链接器 Section 的神秘力量刚接触 DM 的人总会好奇U_BOOT_DRIVER只是声明了一个结构体没有调用构造函数它怎么能被系统找到答案藏在链接脚本和宏里。U_BOOT_DRIVER本质上是用ll_entry_declare把一个struct driver放到特定的.u_boot_list_2_driver_2_name段。u_boot_list是 U-Boot 特有的一套链表式快捷方式链接器会把所有驱动和 uclass 按顺序排布运行时就能通过ll_entry_count和ll_entry_get遍历到。所以你在代码里“写了一个驱动”编译链接后它就会自动出现在系统的“能力清单”里。运行时扫描设备树时每个设备树节点会根据compatible字符串去匹配这份清单里的驱动匹配成功就进行绑定。这也是为什么很多老工程师会说驱动能不能被识别先看链接段在不在、段序对不对。如果某次裁剪配置把驱动所在的目标文件排除掉你会看到驱动明明在源码里dm tree却怎么都找不到它。3.2 device_bind_common 的关键动作当dm_scan_fdt_node决定要给某个设备树节点创建udevice时最终会落到device_bind_common()。它做的事很多我挑重点讲根据驱动和 uclass 需要的私有数据大小分配一块零初始化的内存做udevice。填充dev-driver、dev-uclass、dev-parent、dev-name。把设备挂到父设备的child_head并设置sibling_node。把设备挂到对应 uclass 的dev_head链表上。调用 uclass_driver 的post_bind回调允许类别做额外初始化。如果上述任何一步失败释放内存并返回错误码。这一步完成后设备树里的一个节点就在内存里有了对应的“登记记录”但它还没有真正碰硬件。整个过程可以概括为bind 是礼成probe 才是洞房。这个模型的好处是U-Boot 可以在启动初期只花很少代价把设备树“物化”成设备链表而不急着把所有外设点亮。我在实际调试中有相当多的问题都出在 bind 的post_bind回调里某个 uclass 的post_bind会尝试给设备补一个“伴生设备”如果设备树节点里缺少某个属性post_bind返回错误整个节点就绑不上。这种错误在启动日志里往往很不起眼你只会在后续使用该设备时看到一个-ENODEV。3.3 probe 与 bind 语义上的巨大差异很多人搞混bind和probe以为绑定了就会调probe。实际上绑定只是建对象、挂链表探测才会做硬件初始化。驱动开发中常见的流程是int device_probe(struct udevice *dev) { // 先探测父设备保证层级依赖 // 调 uclass_driver-pre_probe // 调 driver-ofdata_to_platdata 解析设备树属性 // 调 driver-probe 做硬件初始化 // 调 uclass_driver-post_probe }probe是有引用计数的调一次后再次调用不会重复执行。这个机制保证了设备可以被多个使用者共享而不会反复 reset 硬件。我见过不少驱动probe里做了 GPIO 申请第二次使用时因为没走probe而以为引脚不存在最后才发现问题不在驱动本身而在设备在第一次使用时已经处于“已探测”状态。4. 设备树扫描骨架自动生长的过程4.1 dm_scan 如何对设备树节点批量绑定dm_scan()最终调用的是dm_scan_fdt_node()这个函数会从根设备开始递归遍历设备树节点。每遇到一个节点都会尝试读取节点compatible属性在驱动链表里找匹配的driver如果找到则device_bind到当前父设备下递归进入该节点子节点继续处理。这个递归是深度优先的。所以树里父子设备的绑定顺序是先绑父再绑子。这样每个设备在绑定子设备时父设备的链表已经存在不至于子设备找不到挂靠点。对应代码里会看到类似这样的逻辑static int dm_scan_fdt_node(struct udevice *parent, const void *blob, int offset, bool pre_reloc_only) { // 遍历节点 for (; depth 0; offset fdt_next_node(blob, offset, depth)) { // 判断是否需要 pre-reloc if (pre_reloc_only !dm_fdt_pre_reloc(blob, offset)) continue; // 尝试匹配并绑定 ret device_bind_common(...); // 递归扫描子节点 ... } }4.2 pre_reloc 过滤器的作用你不用在board_init_r里特别关心“所有节点必须全部绑定”这种压力因为有些节点会被过滤。dm_fdt_pre_reloc函数会检查节点是否带u-boot,dm-pre-reloc属性或者驱动是否声明了DM_FLAG_PRE_RELOC又或者是 U-Boot 需要的早期设备类型比如串口。在完整 U-Boot 阶段dm_scan通常会去掉pre_reloc_only的限制把普遍设备都扫进来。但在 TPL/SPL 阶段这个筛选会非常严格只绑定极少数关键设备目的就是省内存、省启动时间。如果你发现 SPL 里某个设备明明在设备树中有却始终没有udevice先怀疑是不是没加u-boot,dm-pre-reloc属性。4.3 board_init_r 中 DM 骨架的完整时间线我习惯把board_init_r启动过程画成一条时间线重定位完成GD、环境变量准备好执行initr_dmdm_init()建根设备和 uclass 链表执行dm_scan()递归绑定设备树中的节点形成设备树状链表后续初始化代码按需探测设备。比如网络子系统会在某个阶段遍历UCLASS_ETH逐个probe找到的第一个可用网卡控制台、存储、命令等用到具体设备时设备已经处于“已绑定未探测”状态调用 API 才真正触发探测。所以这个骨架的本质不是把硬件全部初始化而是把 CPU 眼中的“世界地图”提前画好。地图画好之后谁要去哪个外设办业务顺着表找过去再点亮设备即可。5. 排障实战如何看待一棵搭不起来或搭错位的驱动树5.1 现场调试三板斧dm tree、dm uclass、dm dump真刀真枪调驱动光看代码远远不够。U-Boot 命令行提供了几个 DM 调试命令我几乎天天用dm tree打印当前设备树状链表。它会把每个udevice的父子和状态列出来能一眼看出哪些设备已经绑定、哪些已经探测过、哪些探测失败。dm uclass打印系统所有已登记的 uclass 和驱动数量。如果你注册的驱动没出现在这里说明链接配置有问题。dm dump按驱动类型 dump 出驱动列表适合快速确认一个驱动是否被链接进镜像。dm tree输出长这样root 0 root_driver ├─ serial 0 serial ├─ gpio0 0 gpio0 ├─ mmc 0 mmc700 └─ usb 0 usb100看到某个节点有设备但没有探测状态或者整个树缺了一根分支就能顺着方向查是匹配失败、扫描被跳过还是probe返回错误。我调网络驱动时第一步永远是dm tree | grep eth先看udecice建立没有再看它有没有进入探测状态。这一步能省下一半的查错时间。5.2 常见失败模式与排查速查表我在多个板子上踩过的坑总结成下面这个表基本覆盖最常见的 DM 初始化问题现象常见原因排查思路设备树节点有但dm tree里没有compatible 没匹配上驱动pre_reloc 过滤检查fdt nodecompat查驱动是否链接进镜像设备已绑定但 probe 失败父设备没探测pinctrl 设置失败属性解析出错开CONFIG_DEBUG_DEVICE在 probe 入口加打印系统启动时某个外设“没反应”驱动没被任何子系统触发探测初始化顺序靠后业务代码里用uclass_get_device主动探测dm uclass里没有某个类别U_BOOT_UCLASS宏对应的目标文件没链接看链接脚本或配置项确认.u_boot_list_2_uclass绑定时返回-ENOMEMuclass/post_bind 自动分配的私有数据太大堆不够调小per_device_auto检查 ram size 配置同名驱动存在两个链接器段里同名驱动覆盖检查是否有重复定义或残留下游 patch每个板子的失败方式都不同但核心排查思路都是一致的先看链表上有没有设备再看设备状态有没有探测最后再看硬件访问有没有异常。5.3 调试日志与辅助手段如果只靠dm tree不够我会开启CONFIG_DM_DEBUG。这个配置能让 DM 的 bind/probe 过程打印大量调试信息尤其是设备树匹配的细节。运行启动时串口输出会多出很多scan bus for devices... probe: gpio0 bind: serial5 Device serial5 probed有了这些日志整个绑定扫描过程就像被点开了“上帝视角”。不过要注意这个选项会显著增加编译体积和启动时间只建议调试阶段开合板量产前记得关掉。经常有人问为什么驱动里明明加了printf启动时却一个字符都看不见答案十有八九是串口驱动自身还没probe或者串口设备在 DM 链表里根本没绑上。这个时候不要死磕驱动代码先跑一个dm tree看串口节点状态往往就真相大白了。6. 我实际移植板卡时的几条硬经验6.1 那次 GPIO 灯全不亮的教训有一回我在一块 ARM 板卡上调 GPIO。设备树没问题GPIO 驱动看起来也没问题但dm tree里 gpio 节点始终是“已绑定未探测”的状态。找了一上午最后发现是 pin controller 设备没在链表里。GPIO 驱动probe里需要找pinctrl设备来设置引脚复用而pinctrl节点因为 compatible 写成了foo,pinctrl驱动里的名称匹配不上导致 pin controller 没被绑定。GPIO 探测依赖它自然跟着翻车。这个经验让我养成了习惯任何驱动要访问别的设备先确认对方是不是已经在dm tree里“显灵”。特别是设备树里层层嵌套的父子依赖光是面向层级的递归绑定就可能让某个中间节点被跳过。6.2 使用“主动探测”而非赌初始化顺序另一个经验是关于初始化顺序的。U-Boot 的probe是按需触发的但不同子系统触发时机不同。我自己写板级初始化代码时不喜欢赌“网络子系统一定在跑我的逻辑前把设备 probe 掉”。更稳的写法是显式调用uclass_get_device或uclass_first_device_err来确保目标设备已被探测struct udevice *dev; int ret; ret uclass_get_device_by_name(UCLASS_ETH, eth0, dev); if (ret) return ret;这样的好处是即使系统启动序列调整我的代码也不会因为设备还没探测而拿到一个空指针。尤其在board_init_r后续添加自己的板级逻辑时这条原则能避免大量无意义的时序问题。6.3 骨架搭好之后如何继续“速通”改造一旦你理解了board_init_r里 DM 骨架是由“全局 uclass 链表 根设备树 递归绑定设备树节点”三层构成后面所有的驱动调试都会变得非常顺手。新增一个驱动的流程自然就清晰了写struct driver用U_BOOT_DRIVER声明确认.id选对 uclass必要时补U_BOOT_UCLASS或 uclass_driver确认设备树节点的 compatible 能匹配链接进镜像用一个临时命令或dm tree验证绑定在probe里做硬件初始化必要时安排post_bind做辅助设备。这套流程基本覆盖了我这些年绝大多数驱动移植场景。DM 骨架这东西第一眼看很玄乎全是链表和宏第二眼看就是一套挺朴素的“注册 匹配 挂链表 按需探测”机制。真正踩过一次 GPIO 灯全不亮的坑再回头看board_init_r里的initr_dm你会觉得手里这把屠龙刀其实也就是一把很好用的螺丝刀——关键是懂得从哪颗螺丝拧起。最后多嘴一句U-Boot 不同版本之间initr_dm的名字和顺序可能略有调整但骨架逻辑没变过。看代码时别死背函数名把“根设备 - uclass 链表 - 递归绑定设备树 - 按需探测”这条主线抓在手里换成哪个版本你都能两小时摸透一个新板卡的驱动现状。
企业数字化 ERP 产品动态
相关推荐
一键开关机电源管理芯片选型四大核心维度 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/7 10:52:05
STM32新手如何高效找到可信参考方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/6 3:03:15
AUTOSAR诊断事件管理DEM:DTC从诞生到老化的一生 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/7 22:28:14
临汾特种工怎么报名真实经历告诉你怎么稳过实操考试 临汾特种工怎么报名真实经历告诉你怎么稳过实操考试 实操考试心里没底怕挂科?别慌,我认识一个在濮阳参加培训的老张,他第一次考高压电工证时,也是手心冒汗,连安全帽都戴反了。但最后他不仅通过了考试,还拿到了证书,现在在本地工地一个月能多挣3000… · 2026/11/8 12:18:43
神木低压电工证培实操怎么考?电子证书怎么查? 神木低压电工证培实操怎么考?电子证书怎么查? 实操考试心里没底怕挂科,这几乎是所有准备报考低压电工证的人的共同心声。尤其是现在,随着安全生产法规的不断收紧,低压电工证不仅是上岗的“门槛”,更是职业发展的“通行证”。如果你在神木或者周边地区,… · 2026/11/8 12:18:43
宜宾电工证在哪办通过率揭秘:找工作没证真不行 宜宾电工证在哪办通过率揭秘:找工作没证真不行 你是不是正在为找工作发愁?电工证是很多岗位的硬门槛,尤其是在宜宾这样的地方,没有证连面试机会都可能没有。而电工证的 通过率 到底是高还是低? 宜宾电工证在哪办… · 2026/11/8 12:18:43
焦化厂能考电工证 郑州报考避坑指南:新手避雷全攻略 焦化厂能考电工证 郑州报考避坑指南:新手避雷全攻略 你是不是也遇到过这种情况:在焦化厂工作几年了,想考个电工证,但不知道去哪儿报名,怕被中介坑?这种焦虑很多人都有,特别是刚进厂的年轻人,对流程和规定一无所知。今天就用我亲身经历的案例,带你看… · 2026/11/8 12:18:43
宝安考电工证怎么考?实操考试心里没底怕挂科?含金量决定一切 宝安考电工证怎么考?实操考试心里没底怕挂科?含金量决定一切 实操考试心里没底怕挂科?你不是一个人。在宝安考电工证的小伙伴中,很多人都有这种担忧。电工证不仅关系到能否顺利上岗,也直接影响着工作的含金量。尤其是现在越来越多的单位开始严格审核电工… · 2026/11/8 12:18:43
台州电工证考证含金量有多高?考证前必须知道的5件事 台州电工证考证含金量有多高?考证前必须知道的5件事 你是不是正在为找不到工作发愁?是不是还在为没有特种作业证而焦虑?别急, 台州电工证考证 正是你求职路上最硬的“敲门砖”,它的 含金量… · 2026/11/8 12:17:43
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/8 0:00:20
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/8 0:00:20
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/8 0:00:20
电工证丢失还能补吗?千万别踩坑,丢了证等于失业 电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。… · 2026/11/8 0:00:20
电力电缆作业证直播报名入口与办理流程良心建议 电力电缆作业证直播报名入口与办理流程良心建议 你是不是也担心学历不高报不了名?别急,今天就给你讲讲 电力电缆作业证直播 的报名流程和那些你必须知道的干货, 良心建议 在这里,别错过。 电力电缆作业证直播报名需要什么条件?… · 2026/11/8 0:00:20
英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 英德电工证报考费多少钱,怕考不过白交培训费?看完这篇别再踩坑 怕考不过白交培训费?英德电工证报考费多少钱,你真的了解吗?现在市面上培训机构五花八门,价格参差不齐,很多人一不小心就花了冤枉钱,结果还考不过。别急,今天就来给你说清楚英德电工证的… · 2026/11/8 0:00:20
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/7 11:25:08
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/7 12:02:18
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/8 1:18:25