首页 / 资讯中心 / 文章详情

U-Boot移植必懂:Kconfig配置体系与defconfig实操指南

U-Boot移植必懂:Kconfig配置体系与defconfig实操指南 ★ FEATURED ARTICLE
年前接手一块新板子SoC用的是老平台理论上移植U-Boot应该不算难。我按十年前的习惯翻了翻老代码直接在include/configs/myboard.h里堆了一排#define CONFIG_CMD_NET、CONFIG_BOOTCOMMAND之类的宏自认为配置已经很齐全。结果 make 完烧进板子网卡起不来命令少了一半环境变量也不对。折腾了一个下午才反应过来这套代码的配置体系早就不是头文件的天下了所有选项都被上层的 Kconfig 树接管我在头文件里写的宏要么和 Kconfig 里的同名符号冲突要么压根没在配置树里挂过号等于白写。这篇文章就聊 U-Boot 移植里绕不开的 Kconfig 这一环。适合刚接触新平台移植、被配置系统绕晕的开发者也适合那些从老版本 U-Boot 升到新版、还在靠include/configs头文件习惯做事的人。看完你会弄清楚 defconfig、Kconfig、auto.conf 这中间的真正关系也能直接按我给的流程给新板子建一套完整配置。1. 先说清楚Kconfig在U-Boot移植里到底扮演什么角色1.1 从“头文件配宏”到“配置树配符号”老版 U-Boot大约 2014 年之前的大量板子基本是一个板子配一个include/configs/board.h里面堆满供整个代码库使用的 CONFIG 宏编译时把这个头文件包含进来Makefile 和 C 源码都能看到。这个模式最简单直接但看一圈 i.MX6 时代的老头文件就知道一个头文件里塞几百个 CONFIG 宏非常常见根本没有人能说清哪个宏依赖哪个宏换一个配置经常引起连锁错误排查全靠 grep 和猜。Kconfig 体系是从 Linux 内核继承过来的。它把“配置项”描述成一个个符号symbol每个符号可以有提示文本、依赖关系depends on、强制连带select、默认值default和帮助信息。移植 U-Boot 时大多数时间我们其实不是写 C 代码而是在这棵配置树上添加自己的分支告诉构建系统我的板子属于哪个 machine、要编哪些驱动、用哪个设备树、SPL 要编多大、环境变量默认是什么。一个很直接的变化是以前在头文件里加一个宏就能打开某个功能现在你得先在对应 Kconfig 文件里确认这个符号存在然后在 defconfig 里把它设成 y必要时还要先满足它的依赖条件。多了一步但整个配置变得可追溯、可审查。1.2 一次移植会撞上哪几层KconfigU-Boot 会把 Kconfig 文件组织成一棵树。真要给一块新板子做移植至少要接触这几层顶层 Kconfig决定目标架构是 ARM、RISC-V 还是其他把后续 Kconfig 文件 source 进来。arch/arm/Kconfig以及各arch/arm/mach-xxx/Kconfig决定 SoC 型号、CPU 架构、启动级别最典型的TARGET_BOARD多选变量就定义在这里。board/xxx/Kconfig板级配置很多平台把SYS_CONFIG_NAME和板级默认值放在这里。驱动和子系统的 Kconfig比如drivers/net/Kconfig、cmd/Kconfig、common/Kconfig决定最终具体要哪些功能。每一层只管自己的符号不需要在一处看到全部配置。对移植者来说这反而是好事你只需要关心自己板子涉及的几个文件改动范围可控。1.3 defconfig只是“源配置”不是最终配置这是最容易误解的一点。configs/myboard_defconfig看起来像一份完整配置实际上它只是给 Kconfig 构建器的“初始给值”。真正生效的是它和整棵 Kconfig 树合并后生成的.config以及从.config导出的include/config/auto.conf和include/generated/autoconf.h。defconfig 里写的每一行如果和 Kconfig 树中的某个符号对不上构建工具会直接忽略并给出 warning如果依赖条件不满足也会在 olddefconfig 阶段被丢掉。所以“defconfig 里写了”不等于“最终配置里有”。我习惯把这条链路记成一个点菜单的类比defconfig 是点菜单Kconfig 是菜品和配料关系表后厨是编译系统。你点了宫保鸡丁系统会自动帮你加上花生米但如果你连主菜都没点光在菜单上写了“加花生米”后厨根本不会理你。2. 移植前必须理清的三方联动defconfig、Kconfig文件、Makefile2.1 defconfig里每一行都要在Kconfig树里有归属实际表现是执行make myboard_defconfig时如果 defconfig 里某个符号在任何一个 Kconfig 文件里都没有定义终端会出类似 warning 的提示大意是“unknown symbol”或者“ignoring unknown option”。不同 U-Boot 版本提示措辞略有差异但结果一致这一行被忽略。更隐蔽的情况是“符号存在但不可见”。很多 CONFIG 选项在 Kconfig 里有定义但因为depends on条件不满足它在 menuconfig 里不显示赋值也会被后续配置流程吞掉。我以前在 defconfig 里写了CONFIG_USB_EHCI_HCDy编译之后 grep auto.conf 发现根本没有排查半天才看到它depends on USB DM_USB而当时 DM_USB 压根没开。问题就出在依赖链上不是写法问题。2.2 哪些配置写defconfig哪些写在Kconfig的default里这块我踩过一阵子才找到平衡。经验法则如下配置类型放哪里举例板级差异项板与板之间不同defconfigCONFIG_TARGET_MYBOARD、CONFIG_DEFAULT_DEVICE_TREE、CONFIG_BOOTDELAY平台公共默认值同 SoC 通用Kconfig 的 default调试串口基址、环境变量分区大小驱动开关跨板复用defconfig 控制default 由驱动 Kconfig 给出CONFIG_DM_SERIAL这类通用机制写 defconfig 时要坚持“增量原则”只保留和参考板不同的那几项其他让 Kconfig 的 default 去接管。这样以后跑make savedefconfig整理时diff 会非常干净不会跳出一堆跟参考板重复的符号。defconfig 里几种写法也容易搞混布尔打开用CONFIG_XXXy布尔关闭不要写CONFIG_XXXn而是写成# CONFIG_XXX is not set整型直接给数比如CONFIG_SYS_MALLOC_F_LEN0x4000字符串必须带英文引号比如CONFIG_BOOTCOMMANDrun distro_bootcmd。2.3 Makefile到底从哪读配置配置走到编译系统的完整路径是make xxx_defconfig生成.config编译系统随后调用 scripts/kconfig 生成include/config/auto.conf和include/generated/autoconf.h。前者是 Makefile 可以 include 的配置片段后者是 C 代码包含的宏定义头。驱动目录里的 Makefile 写法一般是obj-$(CONFIG_XXX) xxx.o这个CONFIG_XXX就来自 auto.conf。C 源码里则是#ifdef CONFIG_XXXautoconf.h 提供宏。老式 board 头文件include/configs/myboard.h则通过include/config.h被引用。要记住配置链路走到 auto.conf 这一层基本就是“能否编译进去”的最终裁决。出问题先查这里别对着源码头文件猜。3. 实操为一块新板子建立Kconfig配置链3.1 第一步从同平台参考板抄一份最接近的defconfig最稳妥的方法是找同 SoC 平台已有的板子。比如新板子用的是厂商某颗芯片那先找到官方 EVB 板的configs/xxx_defconfig拷贝一份cp configs/xxx_defconfig configs/myboard_defconfig然后逐项修改板级字段比如CONFIG_TARGET_MYBOARD、CONFIG_DEFAULT_DEVICE_TREE。不要试图从零写 defconfig。Kconfig 树里的默认值、依赖、select 关系没人能全记住从参考板起步能避开大量“漏依赖”问题。3.2 第二步在mach和board Kconfig里把自己的板子挂上去典型位置是arch/arm/mach-xxx/Kconfig。代码结构大致长这样choice prompt My SoC target boards optional config TARGET_MYBOARD bool Support MyBoard select ARM64 select DM select DM_SERIAL endchoice config SYS_CONFIG_NAME default myboard if TARGET_MYBOARD如果平台把板级配置放在board/xxx/Kconfig那SYS_CONFIG_NAME的 default 一般写在板级 Kconfig 里并通过某个 source 语句被引入到arch/arm/Kconfig。注意 choice 里的每个 config 本质上是一个“是否选这块板”的开关选一块板子就相当于告诉 Kconfig按TARGET_MYBOARD关联的 default 和 select 去展开配置。3.3 第三步让defconfig里的TARGET生效并生成.config接着执行make myboard_defconfig此时.config里应该能看到CONFIG_TARGET_MYBOARDy并且由 select 带出的那些选项也自动置位。验证一下grep TARGET .config如果没出现回去检查 defconfig 文件名和内容、Kconfig 文件是否真的被 source 进 arch/arm/Kconfig。source 语句很容易漏漏了之后整个目标板都挂不上去。3.4 第四步用menuconfig做增量微调不要手写整份defconfig打开交互界面make menuconfig在界面里搜索要的符号按/输入关键字看它是哪一层、依赖谁。勾选好之后一路 Exit 退出选择 Yes 保存它会写回.config。最后再生成一份干净的 defconfigmake savedefconfig cp defconfig configs/myboard_defconfigsavedefconfig的输出是最精简但等效的配置凡是 Kconfig 能自动补的默认值都不会写进去。这个动作建议每次调完配置都来一次团队协作时 diff 非常干净。3.5 还有几个容易漏的板级承接SYS_CONFIG_NAME决定了旧式板级头文件include/configs/myboard.h是否会被包含。如果平台代码还需要这个头文件做私有宏就要新建如果已经完全 Kconfig 化理论上可以不依赖老式头文件但很多 SoC 框架代码仍会读它移植时要看平台现状。CONFIG_DEFAULT_DEVICE_TREE的字符串必须和arch/arm/dts/下的 dts 文件名一致否则编译到 dtb 阶段会报找不到设备树。另外power domain、clock 这类强依赖一般已经在 mach Kconfig 里用 select 带出来了不需要在 defconfig 里显式写。4. 移植中最容易翻车的四个Kconfig细节4.1 select和depends on用反配置会被静默吞掉select是“强制选中”depends on是“前提条件”。U-Boot 各 mach Kconfig 里大量用 select目的是让用户选了 SoC 后自动带上配套驱动。问题在于select并不会绕过depends on。如果 A select B但 Bdepends on C且 C 没选最终 B 并不会真正被激活而且 Kconfig 往往不报错。这是我实际撞过最隐蔽的翻车点。更严重的是循环依赖。两个符号互相 select或者 A depends on B、B depends on AKconfig 构建会直接报 “recursive dependency detected”。我的建议是如果“没有这个功能板子就跑不起来”用 select如果“有这个更好但用户可以选择”用default y加depends on更安全。4.2 defconfig写了CONFIG_XXXy编译产物里却没有这个现象太常见了。排查方向按优先级排列Kconfig 树里压根没有这个符号检查拼写特别是芯片手册缩写容易混淆的地方注意 make defconfig 时的 warning。符号存在但 prompt 被 depends 遮蔽给不可见选项赋值会在 olddefconfig 阶段被丢弃。你改过 Kconfig 文件但没有重新生成.config编译系统一直拿旧配置在跑。符号被同一层级的另一个 default 或 choice 覆盖。排查就三层后面第五部分详细展开。先记住.config没有defconfig 就是白写auto.conf 没有编译产物就是没有。4.3 Kconfig符号名与C宏名保持一致的坑常规情况下Kconfig 里的config XXX对应 C 代码宏CONFIG_XXX但三个位置的写法不一样Kconfig 文件里写config CMD_GPIOdefconfig 里写CONFIG_CMD_GPIOyC 代码里用#ifdef CONFIG_CMD_GPIO。新手最容易把 defconfig 写成config CMD_GPIOy或者把 Kconfig 里的符号名写成CONFIG_CMD_GPIO这两种都不会按预期工作。还有一个容易困惑的点新版 U-Boot大约 v2022 年前后开始把一部分运行期配置宏从CONFIG_改成CFG_这是代码层面的重构但 Kconfig 符号名仍然以CONFIG_开头。移植时看到源码里出现CFG_开头的宏不要慌它背后还是由 Kconfig 统一管理只是 C 代码里的宏名规范化了。4.4 老式头文件和新Kconfig体系的冲突不少平台的代码里还保留着include/configs/xxx.h里面也堆着一堆 CONFIG 宏。如果 Kconfig 树同时管理同名符号两边就可能产生二义性。尤其是在从老板复制头文件建新板时很容易出现“Kconfig 里改了头文件里还留着一份旧的”最终行为取决于包含顺序和宏覆盖非常难查。我的判断标准是新板移植只要能力 Kconfig 表达的一律迁到 Kconfig 和 defconfig。老式头文件只保留真正平台私有、Kconfig 没有对应符号的东西比如某些暂存地址、厂商私有配置。两边出现同名宏时一定要确认最终生效值别靠猜。5. 一套高效的Kconfig排查与验证流程5.1 用savedefconfig反向校验defconfig合法性这个方法几乎零成本我每次动配置都先跑一遍make savedefconfig diff defconfig configs/myboard_defconfig如果 diff 显示手写 defconfig 里有一堆行在 savedefconfig 里消失了说明这些行不是冗余就是被丢弃。此时可以直接用工具生成的版本替换手写版本cp defconfig configs/myboard_defconfigsavedefconfig 本身不会改变源码安全。它能暴露两类问题根本不在 Kconfig 树里的符号以及和 default 重复的冗余项。5.2 用menuconfig可视化查依赖链遇到“为什么这个配置项是灰的”menuconfig 比 grep 任何文件都快。开启之后按/输入关键词搜索符号进入 symbol info 界面能看到该符号在哪个 Kconfig 文件定义、depends on哪些符号、被哪些符号 select、当前值是什么。很多时候一眼就能看出是依赖前提没成立不用翻源码。这个界面其实就是配置树的“调试器”比对着 Kconfig 文件数括号要直观得多。5.3 用auto.conf和autoconf.h验证配置是否真正落盘三层检查grep CONFIG_XXX .config grep CONFIG_XXX include/config/auto.conf grep CONFIG_XXX include/generated/autoconf.h第一层看 defconfig 和 Kconfig 树的合并结果第二层看 Makefile 视角的最终值第三层看 C 代码视角的宏定义。如果第二层没有即使 defconfig 里写了也不具备编译意义。这个方法可以避免“板子行为不对怀疑源码又怀疑设备树”的无头苍蝇模式先确认配置确实到位。5.4 修改Kconfig后必做的重启配置仪式很多人改完 Kconfig 文件后直接 make结果没有任何变化原因就在于编译系统认为.config还是最新的没有触发 conf 重新执行。稳妥的做法是一套固定流程make myboard_defconfig make menuconfig # 如果还要微调 make savedefconfig make -j$(nproc)另外跨版本升级 U-Boot 源码时不要沿用老的.config建议先make distclean或者直接删除.config。老配置里可能带着新 Kconfig 树中已经不存在的符号这些“幽灵配置”会干扰判断让问题看起来像是源码 bug其实只是配置没刷新。最后说点个人体会。U-Boot 的 Kconfig 体系一开始确实繁琐尤其是从老版本转过来的人总觉得“我在头文件里写一行宏就完事你现在让我找三四个文件”。但适应之后会发现它逼着你把板子差异显式化每次移植都能留下清晰的配置变更记录review 时比几百行头文件的 diff 轻松太多。我现在移植新板的基本流程就是抄参考板 defconfig挂 TARGETsavedefconfig 规整menuconfig 微调grep auto.conf 确认基本很少再为“配置没生效”熬夜。你按这个流程走一遍大概率也能省下大半天的排错时间。
阅读完成 · 觉得有帮助?
咨询建站