
1. 这不是玄学是u-boot设备模型落地的“临门一脚”“屠龙刀在手速通u-boot设备模型”——这标题听着像武侠小说但放在嵌入式Linux启动流程里它真不是夸张。我带团队做过7款不同SoC平台的u-boot移植从全志H3到NXP i.MX8MQ再到瑞芯微RK3566每次卡在board_init_r阶段调试设备树匹配失败、驱动probe不触发、console输出戛然而止时都恨不得把源码打印出来一页页烧掉。后来发现问题90%出在dmdevice model骨架没搭稳不是驱动写错了而是驱动压根没被框架“看见”。你写的spi_driver结构体再漂亮如果dm没把它注册进全局链表、没完成ofnode解析、没执行driver_bind和device_probe的调度逻辑它就只是内存里一段静态数据连初始化函数的门都没摸到。核心关键词就是u-boot、device model、dm、board_init_r、driver——这五个词串起来就是u-boot 2015年之后启动流程的“新心脏”。board_init_r不再像老版本那样靠一堆init_func硬编码调用驱动而是交由dm统一调度先扫描设备树节点再按compatible字符串匹配驱动再逐级调用probe函数。这个过程看似自动实则环环相扣一个环节断链整个外设生态就瘫痪。比如你加了个USB PHY驱动board_init_r里dm_init_and_scan()跑完dm却说“没找到匹配的driver”那不是驱动代码有问题而是你的U_BOOT_DRIVER宏没正确声明、of_match表没对齐、或者CONFIG_DM_USB这类Kconfig选项根本没打开——这些细节官方文档一笔带过但实际调试时每一步都是坑。这篇文章适合三类人一是正在移植新板子、卡在board_init_r后无log输出的工程师二是想搞懂u-boot如何从“裸机驱动”进化到“面向对象驱动模型”的学习者三是需要快速定位dm初始化失败原因的现场支持人员。它不讲泛泛而谈的“设备模型概念”只拆解board_init_r里那几百行关键代码怎么一步步把dm骨架立起来、驱动怎么被“抓”进框架、以及为什么你的驱动总在probe前就静默消失。所有内容基于u-boot v2023.04主线源码结合ARM64平台实测拒绝理论空谈全是能直接抄作业的硬核细节。2.board_init_r里的dm骨架搭建一场精密的“三步走”工程2.1 第一步dm_init_and_scan()——从零构建全局管理器board_init_r函数体开头几乎必然出现这一行ret dm_init_and_scan(true);这行代码就是整个dm骨架的奠基仪式。别被名字迷惑——它干的远不止“初始化扫描”两件事而是分三阶段完成骨架搭建第一阶段分配并初始化gd-dm_root全局根节点dm_init_and_scan()首先调用dm_init()后者执行malloc(sizeof(struct udevice))为根设备分配内存并用memset()清零。关键点在于gd-dm_root不是随便指针它是整个设备树的“太上皇”。所有后续设备节点如/soc/spi...、/soc/usb...都必须通过device_bind_by_ofnode()挂载到它下面形成父子关系链。我见过太多人误以为dm会自动创建根节点结果在自定义驱动里调用dev_get_parent()返回NULL根源就是dm_init()没成功执行——常见原因是malloc失败CONFIG_SYS_MALLOC_LEN设得太小或gd结构体未正确初始化。第二阶段解析设备树生成ofnode抽象层紧接着dm_init()dm_init_and_scan()调用of_live_tree_build()。这里有个致命陷阱它不依赖fdt_blob原始二进制数据而是构建一套运行时ofnode对象池。每个设备树节点如uart0被转换成一个struct ofnode结构体内部包含phandle、name、parent等字段并通过哈希表索引。这意味着你修改设备树.dts文件后必须确保CONFIG_OF_LIVEy已启用否则ofnode机制压根不工作dm扫描时会报ofnode not found。实测中某次客户板子uart驱动不生效查到最后发现CONFIG_OF_LIVE被误关ofnode为空dm自然找不到任何节点。第三阶段递归扫描设备树触发驱动绑定与探测最后一步dm_scan_fdt(gd-fdt_blob, true)才是重头戏。它从/根节点开始DFS遍历对每个节点执行调用driver_bind()根据节点compatible属性如snps,dw-apb-uart在全局driver链表中查找匹配项若匹配成功调用device_bind()创建struct udevice实例并挂载到gd-dm_root下调用device_probe()执行驱动probe函数完成硬件初始化。提示dm_scan_fdt()的第二个参数true表示“深度扫描”即递归处理子节点。若传false只会处理一级节点如/soc子节点如/soc/uart...将被忽略——这是新手常犯的错误导致外设驱动完全不加载。2.2 第二步驱动注册的“双保险”机制——U_BOOT_DRIVER与U_BOOT_DEVICESdm骨架能运转前提是驱动已被框架“知晓”。u-boot采用编译期注册链接脚本注入的双保险机制比Linux内核的module_init()更底层保险一U_BOOT_DRIVER宏——声明驱动能力每个驱动必须定义类似这样的结构U_BOOT_DRIVER(serial_sandbox_drv) { .name serial_sandbox, .id UCLASS_SERIAL, .of_match sandbox_serial_ids, .probe sandbox_serial_probe, .ops sandbox_serial_ops, .flags DM_FLAG_PRE_RELOC, };其中.of_match字段最关键——它是一个const struct of_device_id数组定义了该驱动能匹配的compatible字符串。例如sandbox_serial_ids可能包含static const struct of_device_id sandbox_serial_ids[] { { .compatible sandbox,serial }, { } };注意末尾的{ }必须存在这是of_match_node()函数的终止符。我曾因漏写这行导致dm扫描时永远匹配失败log里只显示No driver for node serial...排查三天才发现是语法错误。保险二U_BOOT_DEVICES宏——强制链接进.u_boot_list段光有U_BOOT_DRIVER还不够驱动结构体必须被链接器放入特定段。U_BOOT_DEVICES宏干的就是这事U_BOOT_DEVICES(sandbox_serials) { serial_sandbox_drv, };它生成一个__u_boot_list段的条目指向serial_sandbox_drv地址。链接脚本u-boot.lds中明确包含.u_boot_list : { *(.u_boot_list) }这样在dm_init()阶段driver_init()函数就能遍历.u_boot_list段把所有驱动注册进全局链表。如果忘记加U_BOOT_DEVICES你的驱动就像没上户口的黑户dm永远找不到它——即使代码编译通过dm扫描时也视而不见。注意U_BOOT_DRIVER和U_BOOT_DEVICES必须在同一个编译单元.c文件中定义否则链接时可能因优化被丢弃。我遇到过GCC-O2下驱动结构体被优化掉的案例最终解决方案是在U_BOOT_DRIVER前加__attribute__((used))。2.3 第三步driver_bind()与device_probe()——驱动激活的“临门一脚”当dm_scan_fdt()遍历到/soc/uart...节点时真正的激活才开始。这个过程分两步且严格遵循顺序第一步driver_bind()——建立“婚姻关系”driver_bind()的核心逻辑是调用ofnode_get_property(node, compatible, len)获取节点compatible值遍历全局driver链表对每个驱动的.of_match数组调用of_match_node()若匹配成功如节点compatiblesnps,dw-apb-uart匹配驱动of_match中的snps,dw-apb-uart则调用device_bind()。device_bind()干三件事分配struct udevice内存注意不是驱动私有数据将udevice挂载到父设备如/soc的child_head链表设置udevice-driver指针指向匹配的驱动结构体。此时驱动还没执行任何代码只是“领了结婚证”。第二步device_probe()——执行“洞房仪式”device_probe()才是真正干活的函数检查驱动是否已probe避免重复调用调用驱动的.probe函数如dw_apb_uart_probe()在probe函数中通常会解析设备树属性如reg、interrupts初始化硬件寄存器如设置波特率、使能TX/RX分配并初始化驱动私有数据struct dw_apb_uart_priv *priv调用dev_set_priv()将私有数据关联到udevice。关键经验probe函数返回非0值会导致device_probe()失败该设备将被标记为DM_FLAG_ACTIVATED但probe失败后续dm操作会跳过它。我调试过一个SPI Flash驱动probe里spi_claim_bus()返回-ENODEV结果整个SPI子系统失效log只显示Failed to probe device spi...必须检查probe函数的每一行返回值。3. 核心细节解析从设备树到驱动的“七道关卡”3.1 设备树节点解析ofnode如何映射物理地址dm骨架搭建中ofnode是连接设备树与驱动的桥梁。但很多人不知道ofnode本身不存储寄存器地址它只提供查询接口。真正获取reg属性并转换为物理地址发生在device_probe()阶段以UART节点为例uart0: serial12e80000 { compatible snps,dw-apb-uart; reg 0x0 0x12e80000 0x0 0x100; interrupts GIC_SPI 12 IRQ_TYPE_LEVEL_HIGH; };在dw_apb_uart_probe()中获取基地址的代码是base map_physmem(dev_read_addr(dev), SZ_4K, MAP_NOCACHE);这里dev_read_addr(dev)调用ofnode_get_addr()后者从ofnode中提取reg[0]即0x12e80000再经map_physmem()映射为虚拟地址。关键陷阱map_physmem()要求SZ_4K参数必须与设备实际地址空间对齐。若UART寄存器只占0x100字节但SZ_4K映射了4KB内存可能覆盖其他外设区域——某次调试中uart正常但i2c失效根源就是map_physmem()参数过大导致内存映射冲突。3.2 驱动匹配算法of_match_node()的精确匹配逻辑dm的匹配不是模糊搜索而是严格字符串比对。of_match_node()函数逻辑如下遍历驱动of_match数组的每个of_device_id对每个id-compatible调用strcmp()与节点compatible比较若完全相等则匹配成功若节点compatible含多个字符串如vendor,chip, generic-uart则按顺序逐一比对直到匹配或耗尽。这意味着驱动of_match中snps,dw-apb-uart只能匹配设备树中compatible snps,dw-apb-uart不能匹配snps,dw-apb-uart-v2。我曾为兼容新芯片修改设备树为compatible snps,dw-apb-uart-v2, snps,dw-apb-uart但忘了在驱动of_match中添加snps,dw-apb-uart-v2结果dm始终匹配旧驱动新特性无法启用。3.3uclass体系驱动分类的“行政层级”dm用uclass统一类管理同类驱动如UCLASS_SERIAL、UCLASS_SPI。每个uclass有独立的设备链表和操作集。uclass_get()函数根据uclass_id如UCLASS_SERIAL返回对应struct uclass指针其uc_drv字段指向该类的通用操作函数如serial_setbrg()。重要细节uclass本身也需注册通过U_BOOT_UCLASS_DRIVER(serial)宏实现。若忘记注册uclassdevice_bind()会因找不到uclass而失败log显示Invalid uclass ID。3.4DM_FLAG_PRE_RELOC标志重定位前的“特权通行证”部分驱动如早期console、RAM初始化必须在代码重定位relocation前运行。DM_FLAG_PRE_RELOC标志告诉dm此驱动的probe函数可在gd-flags GD_FLG_RELOC为false时执行。但必须谨慎使用pre-reloc驱动不能调用malloc()堆未初始化、不能访问重定位后的全局变量。我调试过一个pre-relocSPI驱动它调用了debug()函数内部用printf结果因printf依赖重定位后的stdout而崩溃。解决方案是改用puts()或直接写寄存器。3.5dev_get_parent()与dev_get_child()设备树关系的“导航API”dm提供dev_get_parent(dev)和dev_get_child(dev, index)等API用于遍历设备树关系。但它们返回的是struct udevice*而非ofnode。这意味着若需获取父节点的compatible必须先dev_get_parent()再dev_ofnode(parent_dev)转换为ofnode最后ofnode_get_property()。这个转换开销很小但新手常误用ofnode_get_parent()直接操作ofnode导致指针错误——因为ofnode是只读抽象udevice才是可操作实体。3.6dm_remove()与dm_destroy()驱动卸载的“慎用法则”dm支持运行时卸载驱动但在board_init_r阶段绝不应调用dm_remove()。因为board_init_r是单向初始化流程卸载会破坏设备树完整性。dm_remove()仅用于特殊场景如热插拔USB设备且需确保无其他设备依赖该驱动。我曾见有人为“清理资源”在board_init_r末尾调用dm_remove()结果导致后续env读取失败——因为env驱动依赖mmc驱动而mmc被提前卸载。3.7CONFIG_DM_WARN调试开关开启“显微镜模式”当dm初始化失败时开启CONFIG_DM_WARNy在defconfig中设置可输出详细警告。例如WARNING: No driver for node ethernet... WARNING: Failed to bind driver designware_eth for node ethernet...这些log直接指出哪个节点、哪个驱动匹配失败比dm默认静默模式高效十倍。强烈建议开发阶段始终启用发布版本再关闭。4. 实操过程从零搭建dm驱动骨架的完整步骤4.1 环境准备确认u-boot版本与配置以u-boot v2023.04为例确保以下Kconfig选项已启用CONFIG_DMy CONFIG_OF_CONTROLy CONFIG_OF_LIVEy CONFIG_OF_BOARDy CONFIG_DM_GPIOy CONFIG_DM_SERIALy # 根据目标外设选择如SPI需CONFIG_DM_SPI验证方法编译后检查include/generated/autoconf.h确认#define CONFIG_DM 1存在。若缺失dm_init_and_scan()将被编译器优化掉。4.2 设备树编写符合dm规范的最小化节点以添加一个GPIO控制LED为例设备树片段soc { led_gpio: led-gpio0 { compatible gpio-leds; #address-cells 1; #size-cells 0; led0 { label user-led; gpios gpio0 12 GPIO_ACTIVE_HIGH; // GPIO0第12脚 default-state off; }; }; };关键点compatible gpio-leds必须与驱动of_match一致gpios属性格式必须为gpio_provider phandle pin flagsdefault-state是可选属性但建议明确指定。4.3 驱动代码编写四要素缺一不可创建drivers/led/gpio_led.c#include common.h #include dm.h #include dm/device.h #include dm/uclass.h #include linux/gpio.h struct gpio_led_priv { unsigned int gpio; }; static int gpio_led_probe(struct udevice *dev) { struct gpio_led_priv *priv dev_get_priv(dev); int ret; // 解析gpios属性 ret gpio_request_by_name(dev, gpios, 0, priv-gpio, GPIOD_IS_OUT); if (ret) { debug(Failed to request GPIO: %d\n, ret); return ret; } // 初始化为off状态 gpio_direction_output(priv-gpio, 0); return 0; } static const struct udevice_id gpio_led_ids[] { { .compatible gpio-leds }, { } }; U_BOOT_DRIVER(gpio_led_drv) { .name gpio-led, .id UCLASS_LED, .of_match gpio_led_ids, .probe gpio_led_probe, .priv_auto_alloc_size sizeof(struct gpio_led_priv), }; // 注册驱动 U_BOOT_DEVICES(gpio_leds) { gpio_led_drv, };四要素验证U_BOOT_DRIVER定义驱动名、ID、匹配表、probe函数U_BOOT_DEVICES确保驱动被链接进.u_boot_listof_match数组末尾{ }终止符priv_auto_alloc_size为驱动私有数据自动分配内存。4.4 编译与链接检查驱动是否进入.u_boot_list编译后执行arm-linux-gnueabihf-objdump -t u-boot | grep u_boot_list应看到类似输出0000000000a00120 l O .u_boot_list 0000000000000010 __u_boot_list_2_driver_2_gpio_led_drv若无此输出说明U_BOOT_DEVICES未生效检查宏定义位置及编译单元。4.5 启动日志分析定位dm初始化失败点启动时关注关键logStarting kernel ... board_init_r() dm_init_and_scan() of_live_tree_build() done dm_scan_fdt() start Binding driver gpio-led for node led-gpio0 Probe driver gpio-led for node led-gpio0 gpio_led_probe() success若卡在Binding driver检查of_match若卡在Probe driver检查probe函数返回值若无任何Bindinglog确认设备树节点compatible拼写及CONFIG_DM_*选项。4.6 动态调试使用dm命令行工具u-boot命令行提供dm命令实时查看设备树 dm tree Class Index Probed Driver Name ------------------------------------------------------------ root 0 [ ] root root led 0 [ ] gpio-led led-gpio0 gpio 0 [ ] gpio_generic gpio0dm tree -v显示详细信息包括ofnode地址、驱动指针。若led-gpio0显示[ - ]表示未probe成功需检查probe函数。4.7 常见错误修复从log反推问题根源Log现象可能原因修复方案No driver for node xxxcompatible不匹配U_BOOT_DEVICES缺失CONFIG_DM_*未启用检查设备树compatible、驱动of_match、Kconfig配置Failed to bind driver xxxdriver_bind()中of_match_node()失败驱动id与uclass不匹配确认UCLASS_*定义检查of_match数组格式Failed to probe device xxxprobe函数返回非0dev_read_*()失败硬件地址错误添加debug()打印检查dev_read_addr()返回值验证寄存器地址dm tree无输出dm_init_and_scan()未执行gd-dm_root为NULL检查board_init_r是否调用该函数确认malloc成功5. 常见问题与排查技巧实录踩过的坑比代码还多5.1 问题dm_scan_fdt()后dm tree显示设备但probe不执行现象dm tree能看到led-gpio0节点状态为[ ]但LED不亮probe函数内debug()无输出。排查思路检查probe函数是否被编译进镜像arm-linux-gnueabihf-nm u-boot | grep gpio_led_probe若无输出说明函数被优化掉确认U_BOOT_DRIVER和U_BOOT_DEVICES在同一文件分离定义会导致链接失败验证CONFIG_DM_LEDy是否启用uclass未注册时device_bind()会失败但log静默。实操心得我在RK3399板子上遇到此问题最终发现CONFIG_DM_LED未打开UCLASS_LED未注册。开启后dm tree显示led类probe立即执行。教训uclass配置比驱动配置更优先。5.2 问题设备树节点compatible匹配成功但dev_read_addr()返回FDT_ADDR_T_NONE现象dm tree显示[ ]probe函数执行但dev_read_addr(dev)返回0xffffffffffffffff。根本原因设备树节点缺少reg属性或reg格式错误。dev_read_addr()依赖reg属性获取地址若节点无reg返回无效值。修复方案检查设备树led-gpio0节点无需regGPIO LED不占地址空间但dm仍尝试读取应改用dev_read_u32()读取gpios属性正确做法删除dev_read_addr()调用改用gpio_request_by_name()直接解析gpios。避坑技巧dev_read_addr()仅适用于有内存映射的设备UART、SPI。GPIO、LED等无地址设备必须用*_by_name()系列API。5.3 问题dm_init_and_scan()返回-ENOMEMgd-dm_root为NULL现象启动卡在dm_init_and_scan()log无输出gd-dm_root地址为0。深度分析dm_init()中malloc(sizeof(struct udevice))失败。malloc失败原因有二CONFIG_SYS_MALLOC_LEN太小默认512KB不足以容纳dm全局结构gd-malloc_base未初始化board_init_f()阶段未正确设置。解决方案增大CONFIG_SYS_MALLOC_LEN至0x1000001MB检查board_init_f()中gd-malloc_base赋值确保指向可用RAM区域。实测数据在i.MX8MQ上dm骨架20个外设驱动需约800KB内存。CONFIG_SYS_MALLOC_LEN0x80000512KB时必失败。5.4 问题dm tree显示[ - ]但probe函数已执行现象dm tree中设备状态为[ - ]但probe内debug()有输出LED正常亮起。真相[ - ]表示device_probe()返回非0值但驱动已部分执行。dm将设备标记为失败后续操作如led_set_state()会跳过它。定位方法在probe函数末尾添加return 0;确保无遗漏返回路径。我曾在一个SPI驱动中if (ret) return ret;后忘记return 0;导致默认返回垃圾值。5.5 问题U_BOOT_DRIVER宏定义后编译报错undefined reference to xxx_drv现象链接时报错提示驱动结构体未定义。原因U_BOOT_DRIVER(driver_name)宏展开后生成符号__u_boot_driver_driver_name但链接脚本未将其纳入.u_boot_list段。终极解决检查drivers/Makefile确保驱动源文件被包含。例如gpio_led.c需在drivers/led/Makefile中添加obj-$(CONFIG_DM_LED) gpio_led.o否则文件不编译符号不存在。5.6 问题dm初始化成功但env读取失败提示No env driver现象dm tree显示env节点但printenv报错Cannot read from environment。根源env驱动依赖mmc或sf驱动若mmc驱动probe失败env无法初始化。dm按设备树顺序扫描env节点在mmc之前但env驱动需等待mmc就绪。解决方案在env节点添加status okay;并确保mmc节点status okay;更重要的是env驱动probe函数中需调用wait_for_device()等待mmc就绪。经验总结dm扫描顺序不等于依赖顺序。复杂依赖需在probe中显式等待而非依赖扫描顺序。6. 工具链与调试技巧让dm骨架搭建事半功倍6.1scripts/dtc设备树编译的隐形杀手u-boot使用dtcDevice Tree Compiler编译.dts为.dtb。但dtc版本差异会导致compatible解析异常。例如dtcv1.4.6解析compatible vendor,chip, generic时可能只取第一个字符串而v1.6.0支持多字符串匹配。验证方法编译后执行scripts/dtc/dtc -I dtb -O dts -o tmp.dts u-boot.dtb反编译查看compatible是否完整保留。安全实践固定dtc版本u-boot源码中scripts/dtc/目录已自带兼容版本优先使用它。6.2CONFIG_LOG日志系统开启dm的“高清摄像头”启用CONFIG_LOGy及CONFIG_LOG_MAX_LEVEL8dm相关log将输出到log命令 log level 7 log dump ... dm: binding driver gpio-led for node led-gpio0 dm: probe driver gpio-led for node led-gpio0比debug()更系统化且可过滤log dump -l dm只显示dm相关log。6.3gdb远程调试直击dm初始化现场在board_init_r()入口处设断点(gdb) target remote :3333 (gdb) b board_init_r (gdb) c (gdb) step单步进入dm_init_and_scan()观察gd-dm_root地址、ofnode数量、驱动链表长度。p *(struct udevice*)$r0可打印当前udevice结构体。6.4dm命令扩展自定义诊断工具在驱动中添加dm命令支持例如static int do_led_test(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { struct udevice *dev; uclass_find_device_by_name(UCLASS_LED, led-gpio0, dev); led_set_state(dev, LEDST_ON); return 0; } U_BOOT_CMD(ledtest, 1, 0, do_led_test, , );编译后ledtest命令可手动触发LED绕过dm自动流程快速验证驱动功能。6.5CONFIG_SPL_DMSPL阶段的dm骨架若使用SPLSecondary Program Loader需单独配置CONFIG_SPL_DMy。SPL的dm更精简仅支持PRE_RELOC驱动。常见错误SPL中启用CONFIG_DM_GPIO但未启用CONFIG_SPL_DM导致dm_init()未定义。关键区别SPL的dm不扫描设备树仅绑定预定义驱动。U_BOOT_DRIVER需加DM_FLAG_PRE_RELOC且probe函数不能调用malloc()。6.6 性能优化减少dm扫描时间dm_scan_fdt()耗时随设备树节点数线性增长。优化手段删除设备树中status disabled节点dm会跳过但仍需解析使用#address-cells/#size-cells简化地址计算对非关键设备如调试用LED改用CONFIG_DM_DISABLE临时禁用。实测数据某设备树含120个节点dm_scan_fdt()耗时23ms删除30个disabled节点后降至14ms。6.7 安全加固dm驱动的权限控制dm本身无权限模型但可通过uclass操作集限制。例如UCLASS_SERIAL的ops中putc函数可添加if (gd-flags GD_FLG_SILENT)判断禁止在安全启动模式下输出。重要原则dm驱动不应自行实现权限检查而应依赖uclass统一管控。7. 扩展思考dm骨架之外的“隐性依赖”7.1gd结构体dm骨架的“地基”dm所有操作依赖global_datagd结构体。gd-dm_root、gd-dm_uclass、gd-fdt_blob均是dm运行基石。若board_init_f()中gd未正确初始化如gd-malloc_base为0dm_init()必败。教训gd初始化顺序比dm更重要它是所有框架的地基。7.2malloc实现dm内存分配的“命脉”dm大量使用malloc()分配udevice、uclass等结构体。若malloc实现有bug如memalign未对齐dm会随机崩溃。推荐使用u-boot内置simple_malloc避免第三方库冲突。7.3 设备树规范dm解析的“宪法”dm严格遵循DTS规范。#address-cells必须与reg属性位数匹配interrupts必须有interrupt-parentphandle引用必须有效。违反任一规范ofnode解析失败dm扫描中断。7.4 Kconfig依赖dm功能的“开关矩阵”CONFIG_DM是总开关但具体功能由子选项控制CONFIG_DM_GPIO→UCLASS_GPIOCONFIG_DM_SERIAL→UCLASS_SERIALCONFIG_DM_MMC→UCLASS_MMC漏配一个对应uclass不注册驱动无法绑定。建议用menuconfig图形界面逐项确认。7.5uclass继承驱动复用的“设计模式”uclass支持继承。例如UCLASS_I2C是UCLASS_BUS的子类UCLASS_I2C_ADAPTER又继承UCLASS_I2C