资讯详情

资讯详情

u-boot设备模型解析:board_init_r中dm驱动骨架搭建与调试

1. 从 board_init_r 切入为什么设备模型是绕不开的坎搞过 u-boot 移植的人都有一个共识板子能不能跑起来串口能不能出字存储能不能识别最后都卡在board_init_r这个函数上。很多人第一次看 u-boot 源码从_start一路跟到board_init_f再到board_init_r前面内存分配、重定位这些还能勉强理解一到board_init_r里面密密麻麻的initr_xxx调用就懵了。尤其是现在主流的 u-boot 版本board_init_r里大量函数都跟dm 驱动Driver Model设备模型绑在一起什么dm_init_and_scan、dm_scan_platdata、dm_scan_fdt_dev名字看着像天书。我当初啃这块的时候最大的困惑就是设备模型到底在board_init_r里干了什么驱动骨架是怎么一步步搭起来的为什么有的板子board_init_r里几行就搞定有的板子要调一堆initr_函数后来把drivers/core目录下的代码翻了个底朝天又拿几块不同架构的板子反复验证才算把这条链路理清楚。这篇内容就是把我踩过的坑、验证过的流程、以及实际调试时用的手段整理出来。适合已经能编译 u-boot、能烧录、但看board_init_r里 dm 相关代码一头雾水的朋友。如果你还在纠结怎么配交叉编译工具链那这篇可能稍微超前了一点但看看思路也没坏处。核心就一件事把board_init_r里设备模型从初始化到驱动绑定的骨架一根一根拆给你看。需要提前说明的是u-boot 版本差异很大我下面讲的内容主要基于 2020.10 到 2023.07 这几个版本区间更老的版本比如 2016 之前dm 还没这么普及新版本2024 之后有些函数名和流程有微调但核心骨架没变。你对照自己手上的代码看的时候注意一下版本号。2. 设备模型骨架的整体设计思路2.1 为什么 u-boot 要引入 dm 驱动在 dm 出现之前u-boot 的驱动调用方式非常“原始”。比如你要初始化一个串口直接调serial_init()这个函数在drivers/serial/下面某个文件里实现编译时通过 Makefile 决定链接哪个文件。这种方式在板子少、驱动少的时候没问题但后来 u-boot 支持的 SoC 和板子爆炸式增长同一个驱动可能被几十个板子共用初始化顺序、设备树解析、驱动匹配全乱套了。dm 的核心思路借鉴了 Linux 的设备驱动模型把“设备”和“驱动”分开描述通过一个统一的框架来匹配和绑定。设备信息可以来自设备树Device Tree、平台数据platdata或者运行时动态创建驱动则通过U_BOOT_DRIVER宏注册到框架里。框架负责在合适的时机扫描设备、匹配驱动、调用驱动的bind和probe回调。这样做的好处很直接驱动代码不用再关心“我是被哪个板子调用的”只需要实现标准接口板级代码也不用硬编码调用哪个驱动只需要在设备树里描述硬件就行。对于board_init_r来说它不再需要逐个调用具体驱动而是调用 dm 框架的扫描和初始化函数剩下的交给框架去处理。2.2 board_init_r 在启动流程中的位置要理解 dm 在board_init_r里的作用得先知道board_init_r处在什么阶段。u-boot 的启动分两大阶段board_init_f和board_init_r。board_init_f在重定位之前运行主要做内存分配、全局数据gd初始化、串口早期初始化这些事。board_init_r在重定位之后运行此时代码已经搬到内存高端可以访问完整的 RAM主要任务是初始化各种外设、加载内核、启动系统。board_init_r的典型结构是这样的以 ARM 为例void board_init_r(gd_t *new_gd, ulong dest_addr) { // 一些早期设置 ... // 设备模型初始化 dm_init_and_scan(true); ... // 各种 initr_ 函数 initr_serial(); initr_env(); initr_mmc(); ... // 进入主循环 main_loop(); }注意dm_init_and_scan(true)这个调用它就是设备模型骨架搭建的入口。参数true表示“允许扫描设备树”。这个函数执行完之后系统中所有在设备树里描述的设备都应该已经被绑定到对应的驱动上了。后续的initr_serial、initr_mmc这些函数内部其实是通过 dm 框架去查找设备并 probe 的而不是直接调用底层驱动。2.3 dm 骨架的三个核心层次dm 框架的骨架可以分成三层来理解第一层是“设备”struct udevice。每个硬件实例在 dm 里都对应一个udevice它记录了设备的名字、所属总线、驱动指针、父设备、子设备列表等信息。设备可以来自设备树节点也可以是平台数据定义的还可以是代码里动态创建的。第二层是“驱动”struct driver。每个驱动用U_BOOT_DRIVER宏定义里面包含驱动名字、对应的设备 ID、bind/probe/remove等回调函数。驱动通过名字或者 compatible 字符串与设备匹配。第三层是“总线”struct uclass。uclass 是一类设备的抽象比如所有串口设备都属于UCLASS_SERIAL所有 MMC 设备都属于UCLASS_MMC。uclass 提供了统一的操作接口比如serial_putc最终会通过 uclass 找到具体的串口设备并调用其驱动方法。这三层的关系是uclass 管理一类 udeviceudevice 绑定到一个 driverdriver 实现具体操作。board_init_r里的 dm 初始化就是把这套关系建立起来。3. dm_init_and_scan 内部拆解骨架是怎么搭起来的3.1 dm_init_and_scan 的调用链dm_init_and_scan在drivers/core/root.c里定义它本身不长但调用链很深。简化后的逻辑是这样的int dm_init_and_scan(bool pre_reloc_only) { int ret; ret dm_init(pre_reloc_only); if (ret) return ret; ret dm_scan_platdata(pre_reloc_only); if (ret) return ret; ret dm_scan_fdt_dev(gd-fdt_blob, pre_reloc_only); if (ret) return ret; ret dm_scan_other(pre_reloc_only); return ret; }四个步骤各有分工dm_init初始化 dm 框架本身创建根设备dm_scan_platdata扫描平台数据定义的设备dm_scan_fdt_dev扫描设备树里的设备dm_scan_other留给板级代码补充扫描。下面逐个拆。3.2 dm_init创建根设备和 uclass 链表dm_init做的第一件重要事是创建“根设备”root device。根设备是所有设备的祖先它不对应任何真实硬件只是一个逻辑上的顶层节点。根设备的 driver 是root_driver在drivers/core/root.c里用U_BOOT_DRIVER(root_driver)定义。创建根设备的过程会初始化gd-dm_root并建立 uclass 链表gd-uclass_root。uclass 链表里一开始只有一些“空”的 uclass 节点等后续扫描到具体设备时再动态创建对应的 uclass 实例。这里有个细节值得注意dm_init还会调用dm_scan_platdata之前的一些准备工作比如初始化devres机制用于自动释放资源。如果你在调试时发现gd-dm_root是 NULL那说明dm_init没跑成功后面所有 dm 操作都会失败。3.3 dm_scan_platdata处理平台数据设备平台数据设备是指那些没有在设备树里描述、而是通过U_BOOT_DEVICE宏在板级文件里静态定义的设备。这种用法在设备树不完善的板子上很常见比如一些老的 ARM 板子。dm_scan_platdata会遍历链接器生成的一个特殊段.u_boot_list_2_driver_platdata之类的把每个U_BOOT_DEVICE定义的设备信息取出来调用device_bind创建 udevice 并绑定到对应驱动。这里的关键是driver 的匹配方式平台数据设备通常直接指定驱动名字框架根据名字在 driver 链表里查找。如果找不到同名驱动绑定就会失败启动时可能直接卡住或者报错。我遇到过一种情况板级文件里定义了一个U_BOOT_DEVICE但对应的驱动因为配置项没打开而没被编译进去结果dm_scan_platdata返回错误整个启动流程挂掉。排查这种问题要看dm_scan_platdata的返回值以及device_bind内部的错误日志。3.4 dm_scan_fdt_dev设备树扫描的重头戏现在大多数板子都用设备树所以dm_scan_fdt_dev是 dm 骨架搭建的核心。它的逻辑是从设备树的根节点开始递归扫描每个子节点对每个节点调用dm_scan_fdt_node。dm_scan_fdt_node会做几件事检查节点是否有compatible属性没有就跳过但会继续扫描子节点。检查节点的status属性如果是disabled或fail跳过。检查节点是否有u-boot,dm-pre-reloc或u-boot,dm-spl等属性根据pre_reloc_only参数决定是否处理。调用lists_bind_fdt在 driver 链表里查找 compatible 匹配的驱动。找到驱动后调用device_bind_with_driver_data创建 udevice。这里最关键的机制是compatible 匹配。每个驱动在U_BOOT_DRIVER宏里通过.of_match字段指定一个of_device_id数组里面列出该驱动支持的 compatible 字符串。框架会把设备树节点的compatible属性与所有驱动的of_match表逐一比对找到匹配的驱动。如果某个设备树节点没有找到匹配的驱动dm 不会报错而是静默跳过。这其实是个坑你以为设备已经绑定了结果 probe 的时候发现找不到设备。所以调试时如果发现某个外设不工作先确认它的设备树节点有没有被正确绑定。3.5 绑定与探测的区别bind 和 probe很多人分不清bind和probe这里必须说清楚。bind 是“建立设备与驱动的关联”probe 是“真正初始化硬件”。dm_scan_fdt_dev只做 bind不做 probe。probe 是在后续initr_函数或者驱动主动调用device_probe时才执行的。bind 阶段做的事情包括分配 udevice 结构体内存、设置设备名字、记录父设备、把设备加入 uclass 的设备链表、调用驱动的bind回调如果有。probe 阶段才会调用驱动的probe回调去操作硬件寄存器、申请资源、初始化硬件状态。这个区分非常重要。因为board_init_r里dm_init_and_scan执行完之后设备只是“绑定”了还没“探测”。真正的硬件初始化是分散在后续各个initr_函数里的。比如initr_serial会调用serial_init而serial_init内部通过 uclass 找到串口设备并 probe 它。4. 驱动骨架的关键细节与实操要点4.1 U_BOOT_DRIVER 宏展开后是什么U_BOOT_DRIVER是驱动注册的入口它的定义在include/dm/device.h里。展开后大致是这样的#define U_BOOT_DRIVER(__name) \ ll_entry_declare(struct driver, __name, driver) { \ .name #__name, \ .id UCLASS_##__name, \ ... }实际展开比这复杂但核心是它在链接器的.u_boot_list_2_driver段里放了一个struct driver实例。dm 框架启动时会遍历这个段把所有驱动加入内部链表。这里有个实操要点驱动的.name字段必须唯一。如果你两个驱动用了同一个名字链接时可能不报错但运行时框架只会找到其中一个另一个永远匹配不上。我见过有人复制驱动代码时忘了改名字结果调试了半天。另外.id字段指定了驱动所属的 uclass。比如串口驱动.id UCLASS_SERIALMMC 驱动.id UCLASS_MMC。如果.id写错设备会被归到错误的 uclass 下后续通过 uclass 查找设备时就会找不到。4.2 of_match 表的编写与匹配规则of_match表是设备树匹配的核心。一个典型的定义如下static const struct udevice_id my_serial_ids[] { { .compatible vendor,my-uart }, { .compatible vendor,my-uart-v2 }, { } }; U_BOOT_DRIVER(my_serial) { .name my_serial, .id UCLASS_SERIAL, .of_match my_serial_ids, .probe my_serial_probe, ... };匹配规则是设备树节点的compatible属性值字符串与of_match表里的compatible字段逐一比对找到第一个匹配的就停止。注意of_match表必须以空条目{ }结尾否则框架会越界读取。有个容易忽略的点compatible 字符串的优先级。如果设备树节点的compatible属性有多个字符串比如vendor,my-uart-v2, vendor,my-uart框架会按顺序匹配先匹配到的驱动生效。所以驱动里of_match表的顺序也很重要更具体的 compatible 应该放在前面。4.3 设备树节点的必备属性不是所有设备树节点都会被 dm 扫描。一个节点要被 dm 处理至少需要满足有compatible属性否则跳过但子节点继续扫描status不是disabled或fail如果pre_reloc_only为 true还需要有u-boot,dm-pre-reloc属性此外节点还可以有u-boot,dm-spl、u-boot,dm-tpl等属性用于控制在不同启动阶段是否处理。这些属性在 SPL 和 TPL 阶段特别重要因为那时候内存有限不能把所有设备都绑定进来。实操中常见的坑是设备树节点写了compatible但忘了加u-boot,dm-pre-reloc结果在board_init_f阶段想用这个设备时发现没绑定。或者反过来加了u-boot,dm-pre-reloc但驱动没实现bind回调导致早期初始化失败。4.4 uclass 的注册与查找uclass 是设备的分类容器。每个 uclass 有一个uclass_driver用UCLASS_DRIVER宏定义。比如串口的 uclass driverUCLASS_DRIVER(serial) { .id UCLASS_SERIAL, .name serial, .post_bind serial_post_bind, .pre_probe serial_pre_probe, ... };uclass driver 的.post_bind回调在设备绑定到该 uclass 后被调用.pre_probe在设备 probe 前调用。这些回调给 uclass 提供了统一处理设备的机会比如串口 uclass 可以在post_bind里给设备分配序列号。查找设备时通常用uclass_get_device_by_name、uclass_get_device_by_seq、uclass_first_device这些函数。它们内部会遍历 uclass 的设备链表找到匹配的设备并 probe 它。如果设备还没 probe这些函数会自动触发 probe。这里有个性能相关的注意点uclass 查找是线性遍历。如果某个 uclass 下设备很多频繁查找会有性能开销。不过在board_init_r阶段设备数量通常不多影响可以忽略。5. 实操过程从零跟踪一次 dm 骨架搭建5.1 准备调试环境要跟踪 dm 骨架搭建最直接的方法是加打印。u-boot 自带debug宏可以在drivers/core相关文件里打开。具体做法是在文件开头加#define DEBUG或者编译时加-DDEBUG。不过全局打开 DEBUG 会导致输出爆炸建议只在你关心的文件里开。另一个手段是用gd-dm_root和gd-uclass_root在关键节点打印设备树状态。比如在dm_init_and_scan返回后遍历gd-dm_root的子设备列表打印每个设备的名字和驱动名。这个遍历代码可以临时加在board_init_r里调试完删掉。如果你有 JTAG 调试器那更直接在dm_scan_fdt_dev入口下断点单步跟进去看每个设备树节点是怎么被处理的。不过 u-boot 早期阶段 JTAG 配置比较麻烦串口打印通常更快。5.2 跟踪 dm_init 的执行在dm_init入口加打印确认它被调用了。然后看gd-dm_root是否被正确赋值。如果dm_init返回非零后面所有 dm 操作都会失败所以这一步必须确认通过。dm_init内部会调用device_bind创建根设备。如果根设备创建失败常见原因是gd结构体里的 dm 相关字段没有初始化或者CONFIG_DM没打开。检查.config里CONFIG_DMy是必须的。5.3 跟踪设备树扫描过程在dm_scan_fdt_node里加打印输出当前处理的节点名字和 compatible 值。这样你能清楚看到哪些节点被处理了哪些被跳过了。如果某个节点你期望被处理但没出现检查它的compatible和status属性。匹配驱动时在lists_bind_fdt里加打印输出匹配到的驱动名字。如果某个节点没有匹配到驱动这里会显示为空。这时候就要去检查驱动的of_match表是否包含了该 compatible。绑定成功后device_bind_with_driver_data会创建 udevice。在这里加打印输出设备名字和驱动名字可以确认绑定关系是否正确。5.4 验证 probe 是否按预期执行bind 完成后probe 是分散执行的。要验证某个设备是否被 probe可以在驱动的probe回调里加打印。如果probe没被调用说明没有代码去主动 probe 这个设备。常见的 probe 触发点包括initr_serial里的serial_init、initr_mmc里的mmc_initialize、initr_net里的eth_initialize。这些函数内部会通过 uclass 查找设备并 probe。如果你发现某个设备一直没 probe检查对应的initr_函数是否被调用以及 uclass 查找是否成功。5.5 一个完整的调试案例我曾经遇到一块板子MMC 设备在设备树里有节点驱动也编译进去了但initr_mmc执行时找不到设备。按上面的流程排查在dm_scan_fdt_dev里加打印发现 MMC 节点被处理了compatible 是vendor,my-mmc。在lists_bind_fdt里加打印发现没有匹配到驱动。检查 MMC 驱动的of_match表发现里面写的是vendor,my-mmc-v2与设备树的vendor,my-mmc不一致。修改of_match表加上vendor,my-mmc重新编译问题解决。这个案例说明compatible 字符串必须完全一致包括大小写和连字符。差一个字符都匹配不上。6. 常见问题与排查技巧实录6.1 设备树节点没被绑定现象设备树里有节点但 dm 扫描后没有对应的 udevice。排查步骤检查节点是否有compatible属性。检查status是否为disabled或fail。检查pre_reloc_only参数与u-boot,dm-pre-reloc属性是否匹配。检查驱动的of_match表是否包含该 compatible。检查驱动是否被编译进去看.config和 Makefile。6.2 驱动匹配到了但 probe 失败现象设备绑定成功但 probe 时返回错误硬件不工作。排查步骤在驱动的probe回调里加打印确认是否被调用。检查probe里访问的寄存器地址、时钟、复位等资源是否正确。检查依赖的设备是否已经 probe比如时钟驱动、电源驱动。检查probe返回值非零会导致设备标记为 probe 失败。6.3 uclass 查找不到设备现象调用uclass_get_device_by_name返回-ENODEV。排查步骤确认设备已经绑定到正确的 uclass检查驱动的.id。确认设备名字拼写正确。确认设备没有被标记为disabled。如果设备在 SPL 阶段绑定但主 u-boot 阶段没绑定检查u-boot,dm-pre-reloc属性。6.4 启动卡在 dm_init_and_scan现象串口没有任何输出或者输出停在dm_init_and_scan附近。排查步骤检查dm_init是否返回错误常见原因是gd未初始化。检查dm_scan_platdata是否有设备绑定失败。检查dm_scan_fdt_dev是否因为设备树损坏而崩溃。用 JTAG 或早期串口打印定位具体卡在哪一步。6.5 常见问题速查表问题现象可能原因排查方法设备树节点未绑定compatible 不匹配对比设备树和 of_match 表驱动未编译配置项未打开检查 .config 和 Makefileprobe 失败资源未就绪检查时钟、复位、电源依赖uclass 查找失败驱动 .id 错误确认驱动所属 uclass启动卡死dm_init 失败检查 gd 和 CONFIG_DM设备重复绑定驱动名字冲突确保驱动名字唯一6.6 独家避坑技巧技巧一用dm tree命令查看设备树状态。如果 u-boot 编译时打开了CONFIG_CMD_DM启动后可以在命令行执行dm tree它会打印出完整的设备树结构包括每个设备的名字、驱动、uclass、probe 状态。这是排查 dm 问题最直观的工具。技巧二在board_init_r里临时加设备遍历代码。如果dm tree命令不可用可以在board_init_r里加一段代码遍历gd-dm_root的子设备并打印。虽然粗糙但很有效。技巧三注意pre_reloc_only参数的影响。dm_init_and_scan(true)里的true表示只扫描u-boot,dm-pre-reloc设备。如果你在board_init_f阶段调用 dm 函数必须确保设备有这个属性。主 u-boot 阶段通常传false扫描所有设备。技巧四驱动 probe 顺序很重要。如果设备 A 依赖设备 B必须确保 B 先 probe。dm 框架本身不保证 probe 顺序需要代码里显式控制。常见做法是在 A 的probe里调用device_probe(B)或者通过uclass_get_device触发 B 的 probe。技巧五设备树里u-boot,dm-pre-reloc不要滥用。这个属性会让设备在重定位前就被绑定占用早期内存。只给真正需要的设备加比如串口、时钟、pinctrl。滥用会导致board_init_f阶段内存不足。7. 驱动骨架的扩展与定制7.1 添加一个新驱动的完整流程假设你要添加一个名为my_device的驱动步骤如下在drivers/下合适目录创建my_device.c。定义of_match表包含设备树里的 compatible 字符串。实现probe回调初始化硬件。用U_BOOT_DRIVER(my_device)注册驱动指定.id、.of_match、.probe。在drivers/对应目录的Makefile里添加编译规则。在Kconfig里添加配置项并在板级 defconfig 里打开。在设备树里添加对应节点确保compatible与of_match一致。编译烧录用dm tree验证设备是否绑定和 probe。这个流程看起来简单但每一步都有坑。比如 Makefile 里的编译规则如果写错驱动不会被编译进去但编译过程不报错运行时才发现设备找不到驱动。7.2 自定义 uclass 的注意事项如果现有 uclass 不能满足需求可以自定义 uclass。步骤是在include/dm/uclass-id.h里添加新的 uclass ID。用UCLASS_DRIVER宏定义 uclass driver。在驱动里把.id设为新的 uclass ID。实现 uclass 的操作接口可选。自定义 uclass 的坑在于uclass ID 必须唯一且不能与现有 ID 冲突。另外uclass 的操作接口需要通过uclass_get_ops获取如果没实现调用时会返回 NULL。7.3 设备树与驱动的解耦设计dm 框架最大的价值之一是设备树与驱动的解耦。同一个驱动可以支持多个设备树节点只要 compatible 匹配。比如一个 I2C 控制器驱动可以同时支持 SoC 内部的 I2C0 和 I2C1只需要设备树里两个节点都用同一个 compatible。这种设计的好处是驱动代码不需要知道具体是哪个实例所有实例相关的信息寄存器基地址、中断号、时钟都从设备树读取。驱动通过dev_read_addr、dev_read_irq这些 API 获取资源而不是硬编码。实操中要注意设备树节点的属性名必须与驱动读取时一致。比如驱动用dev_read_addr_index(dev, 0)读取第一个地址设备树里就要有reg属性且第一个地址范围正确。属性名写错会导致读取失败但不会报错只是返回默认值。8. 性能与内存占用的权衡8.1 dm 框架的内存开销dm 框架本身会占用一定内存。每个 udevice 结构体大约几十到上百字节每个 driver 结构体也类似。对于设备多的板子几百个设备就是几十 KB 的开销。在 SPL 阶段内存通常只有几十 KB所以 SPL 里 dm 设备要严格控制。减少内存占用的方法只给必要的设备加u-boot,dm-pre-relocSPL 里只绑定这些设备。主 u-boot 阶段内存充足可以全量绑定。8.2 扫描速度的优化dm_scan_fdt_dev是递归扫描设备树节点越多扫描越慢。对于设备树很大的板子扫描可能耗时几十毫秒。优化方法包括减少不必要的设备树节点、用u-boot,dm-pre-reloc控制扫描范围、在 SPL 里跳过非关键设备。不过说实话在board_init_r阶段几十毫秒的扫描时间通常可以接受除非你对启动时间有极致要求。我一般不建议过度优化这块除非实测发现确实是瓶颈。8.3 probe 时机的选择probe 是硬件初始化的实际执行点时机选择很重要。太早 probe 可能依赖的资源还没准备好太晚 probe 可能影响后续流程。常见做法是在对应的initr_函数里 probe比如串口在initr_serial里 probeMMC 在initr_mmc里 probe。如果某个设备被多个地方依赖可以在第一个依赖点之前主动 probe。比如时钟驱动通常被很多设备依赖可以在board_init_r早期就 probe 它。9. 版本差异与兼容性处理9.1 不同 u-boot 版本的 dm 差异u-boot 的 dm 框架从 2014 年开始引入到 2016 年基本成熟。主要版本差异包括2016 之前dm 还不完善很多驱动没迁移board_init_r里还是直接调用驱动。2016 到 2019dm 快速普及dm_init_and_scan的调用方式基本定型。2020 之后dm 成为默认新增了dm_scan_other、devres等机制。2023 之后部分函数名有调整比如dm_init_and_scan的参数含义有变化。看代码时先确认版本号再对照对应的文档和源码。不要拿老版本的代码套新版本的流程。9.2 从旧版迁移到新版的注意事项如果你要把老代码迁移到新版 u-bootdm 相关的改动可能很大。主要工作包括把直接调用驱动改成通过 uclass 查找设备、把板级硬编码资源改成设备树描述、给驱动添加of_match表和probe回调。迁移过程中最常见的坑是设备树节点没加u-boot,dm-pre-reloc导致早期初始化失败。另一个坑是驱动名字冲突新旧驱动用了同一个名字链接时可能不报错但运行时行为异常。10. 实际项目中的经验总结10.1 调试 dm 问题的通用思路我总结了一个排查 dm 问题的通用流程先确认设备是否绑定用dm tree或加打印再确认驱动是否匹配检查of_match然后确认 probe 是否执行在probe里加打印最后确认硬件操作是否正确检查寄存器和资源。这个流程看起来简单但能覆盖 90% 以上的 dm 问题。关键是每一步都要有明确的验证手段不能靠猜。10.2 设备树编写的经验设备树是 dm 的“配置中心”写得好能省很多事。我的经验是compatible 字符串要规范用vendor,device格式vendor 用公司或项目名device 用具体型号。属性名要统一比如地址用reg中断用interrupts时钟用clocks。status 要明确不用的设备写disabled避免被误绑定。另外设备树里不要放太多板级特定的东西尽量保持通用。板级差异可以通过u-boot,dm-pre-reloc或者不同的设备树文件来区分。10.3 驱动编写的经验驱动编写要遵循“单一职责”原则一个驱动只做一件事不要试图在一个驱动里处理所有变体。变体可以通过of_match表的data字段区分或者用不同的 compatible。probe回调里要做完整的错误处理任何一步失败都要返回错误码不要静默忽略。因为 dm 框架会根据probe返回值决定设备状态返回错误会让设备标记为失败后续查找会跳过它。10.4 最后分享一个小技巧如果你在board_init_r里加了调试代码记得用#ifdef DEBUG包起来调试完删掉或者关掉。我见过有人把调试打印留在正式代码里结果量产时串口输出一大堆影响启动速度还暴露内部信息。另外dm tree命令虽然好用但它依赖CONFIG_CMD_DM有些精简配置里没打开。如果不能用dm tree可以自己写一个简单的遍历函数打印设备名字和驱动名字效果差不多。这个内容后续还可以这样扩展把 dm 框架与 Linux 设备模型的对比写一写或者深入分析devres资源管理机制又或者针对某个具体 uclass比如 MMC 或网络做完整的驱动分析。这些方向都有不少值得挖的细节等有机会再展开。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →