资讯详情

资讯详情

U-Boot移植实战:Kconfig配置体系详解与避坑指南

刚接触U-Boot移植那会儿我对Kconfig的态度是又爱又恨。恨的是明明在board头文件里加一个宏就能解决的问题非要绕一大圈去改什么Kconfig、defconfig爱的是等真把一套Kconfig逻辑理清楚之后新建板子、调试外设、切换方案配置效率完全不在一个量级。这篇文章就把我在U-Boot移植过程中和Kconfig打交道的实战经验完整记录下来包括Kconfig在U-Boot源码树里的位置、新板级目录怎么挂进去、defconfig怎么生成和精简、配置项为什么不生效以及若干很容易踩的坑。适合刚接新平台、准备做U-Boot首发移植的嵌入式工程师也适合想彻底弄懂U-Boot配置体系、之前一直停留在menuconfig界面能看不改的人。1. 为什么U-Boot移植绕不开Kconfig1.1 Kconfig不是Linux内核的专属玩具在Kconfig全面进入U-Boot之前配置板子基本靠三板斧顶层boards.cfg里写一行记录、board对应目录里堆一堆config.mk、以及include/configs/某某.h头文件里放成吨的#define。这种配置方式在板子少的时候还算简单但U-Boot要支持的SoC和开发板数量爆炸之后问题就藏不住了一个外设命令的开关散落在各板子头文件里想统一改默认值找不到地方想判断某个配置是否依赖另一个配置只能靠人工翻代码配置项连个帮助说明都没地方写。Kconfig来自Linux内核本质上是一套描述配置项、默认值、依赖关系、菜单结构的领域语言。U-Boot大约从2014年前后开始引入并逐年把大量传统配置迁移到这套体系里。现在你去上游拉一个新板子的代码看看几乎不可能绕开Kconfig新建板级目录要改Kconfig生成板级defconfig要用Kconfig工具想图形化改配置要跑make menuconfig。为什么说移植绕不开因为U-Boot构建流程的关键第一步就是make 板子_defconfig。这一步本质是用Kconfig工具读取所有Kconfig文件结合你给的defconfig生成整个编译链需要的中转文件。如果这个环节没打通后面编译出来的U-Boot甚至不会引用你写的板级配置你在board头文件里写再多宏也不会被正确组装。1.2 Kconfig到底替移植者干了哪些活我总结下来Kconfig在移植中承担的事情可以分成六件定义配置项以及配置项在menuconfig里的菜单结构。哪个配置该出现、哪个配置是隐藏的都由它决定。管理配置之间的依赖关系。比如CONFIG_DM_SERIAL依赖CONFIG_DMKconfig能把这种关系表达清楚用户不需要手动保证。为配置项提供默认值。很多平台级默认配置如默认波特率、默认环境变量偏移直接写在Kconfig里defconfig里不写也能生效。生成make menuconfig的交互界面。内核里能用的那套图形化、命令行问答式配置在U-Boot里原样保留。生成两个关键产物include/config/auto.conf给Makefile用include/generated/autoconf.h给C代码用。通过savedefconfig帮我们把defconfig整理干净去掉所有和默认值相同的冗余项。移植者最直接的体感是不再需要去记这个配置在哪个头文件里所有逻辑依赖都被Kconfig收口了。1.3 一次典型移植任务里的Kconfig时间线我回忆一次完整的新板子移植过程Kconfig的工作基本贯穿整天。早上拿到原理图先找一块参考板复制它的board目录和defconfig改完板级Kconfig后执行make xxx_defconfig下午裁剪外设配置跑menuconfig把用不到的驱动全部关掉再savedefconfig归档晚上调SPL和DDR初始化还要不断回头改Kconfig里的truly依赖项。可以说Kconfig已经不只是配置工具而是U-Boot移植工作流里的骨架。2. 新板子进Kconfig源码树里的三处改动2.1 Kconfig文件在U-Boot源码树里的分布很多新手移植时的第一反应是只改defconfig结果make xxx_defconfig直接报找不到目标。原因是U-Boot的Kconfig体系是分层的你得先知道这些文件都在哪。根目录Kconfig全局入口负责source各个子系统的Kconfig。arch/arm/Kconfig选择ARM平台架构根据厂商source对应的mach目录Kconfig。arch/arm/mach-xxx/Kconfig具体SoC的配置包括SoC内部驱动、以及板级choice列表。board/myvendor/myboard/Kconfig板级Kconfig定义SYS_BOARD、SYS_VENDOR、SYS_SOC、SYS_CONFIG_NAME等。configs/myboard_defconfig最终生效的配置清单由Kconfig工具读取。include/configs/myboard.h传统板级头文件仍然存在负责Kconfig管理不到的硬件参数。这六个位置里前三处决定你的板子能不能被Kconfig识别后三处决定识别之后编译产出是什么。我第一次移植时犯过的错就是直接在configs里新建defconfig结果arch下面的Kconfig完全没有这个板型的choicedefconfig里的CONFIG_TARGET被Kconfig工具忽略掉编译出来的还是默认板。2.2 在board目录和arch目录里挂上新板假设参考板是refboard我们要在新SoC上做一块myboard标准动作是这样的。第一步复制board目录cp -r board/refvendor/refboard board/myvendor/myboard第二步改board/myvendor/myboard/Kconfig把里面的TARGET_REFBOARD全部改成TARGET_MYBOARD并把SYS_BOARD、SYS_VENDOR、SYS_SOC、SYS_CONFIG_NAME改成自己的值。注意SYS_CONFIG_NAME很关键它决定了最终include/configs/下面的头文件名如果这里写错了编译时config.h可能引用到一个不存在的头文件。第三步在对应的mach目录Kconfig里注册板型。一般是在arch/arm/mach-xxx/Kconfig里找到board choice块加一个config TARGET_MYBOARD。比如choice prompt MySOC board config TARGET_MYBOARD bool MyBoard evaluation board select DM select DM_SERIAL select SYSRESET imply CMD_DM endchoice第四步查看该mach目录Kconfig里是否已经source了board/myvendor/myboard/Kconfig没有就补上。我踩过的坑是只加了arch侧board choice忘了在src目录的Kconfig里source新板子或者source路径写错编译时提示找不到board Kconfig。2.3 defconfig生成与savedefconfig瘦身defconfig的推荐生成流程不是手写而是从参考板复制后整理cp configs/refboard_defconfig configs/myboard_defconfig然后把里面CONFIG_TARGET_REFBOARDy改成CONFIG_TARGET_MYBOARDy执行make myboard_defconfig make menuconfig make savedefconfig cp defconfig configs/myboard_defconfig为什么最后一定要savedefconfig因为Kconfig会给每个配置项定义默认值你的defconfig里如果写了一个和默认值相同的配置放那里没有意义反而会造成这次生效、下次不生效的假象。比如某个外设驱动在新版U-Boot里的默认值从y变成了n你的defconfig还写着 y虽然这次能编译但你根本不知道这是你自己配置的还是碰巧命中的。savedefconfig会调用Kconfig工具把所有与默认值不同的配置项保留下来输出到defconfig其余全部丢弃。这样归档出来的defconfig既短又干净后续对比配置差异也一目了然。3. Kconfig语法与配置项生效的底层原理3.1 board级Kconfig示例逐行读很多板级Kconfig的写法是高度统一的我给出一个虚拟例子几乎每个board目录里都能看到类似结构if TARGET_MYBOARD config SYS_BOARD default myboard config SYS_VENDOR default myvendor config SYS_SOC default mysoc config SYS_CONFIG_NAME default myboard endif这几行的含义是当板型被选中为TARGET_MYBOARD时自动把SYS_BOARD、SYS_VENDOR、SYS_SOC、SYS_CONFIG_NAME设置为对应字符串。这里没有prompt意味着它们不是用户可改的配置项而是依赖于TARGET_MYBOARD的隐含配置。为什么U-Boot要搞这么一层因为编译系统需要根据这些字符串去拼接路径比如去board/myvendor/myboard目录找Makefile去include/configs/myboard.h找板级头文件。如果这些值是散的不同板子目录一旦重名或者头文件拼错移植就成了玄学。再看mach目录里的板型choiceselect和imply的区别值得单独说。select表示强制拉进来的依赖比如DM_SERIAL必须依赖DM直接select DM可以保证用户开启串口驱动时DM不会被漏掉imply表示推荐默认开启但允许用户关闭适合那些一般需要但也可以不要的功能。我见过有人在板级Kconfig里把所有东西都写成select结果一个板型配置编译出来的镜像比你正常裁剪的大了整整一圈因为GPIO、clock、pinctrl这些其实可以imply。3.2 从defconfig到autoconf.h的完整链路理解这条链路的原理很多配置不生效的问题就能自动解决。第一步执行make myboard_defconfig时scripts/kconfig/conf会读取各级Kconfig文件把defconfig里的显式配置项填充进去并对所有配置项做依赖校验和默认值补全。第二步生成include/config/auto.conf。这个文件里每行都是CONFIG_XXXy或者CONFIG_XXX值它是给Makefile和Kbuild用的C代码不会直接include它。第三步每次make的时候syncconfig会检查Kconfig和defconfig是否有变化如果有变化就会触发重新生成。其中include/generated/autoconf.h就是把auto.conf翻译成C语言的预处理宏。第四步最终编译每一个.c文件时都会通过include/config.h间接包含autoconf.h。这就是为什么你在Kconfig里定义了一个CONFIG_CMD_BOOTMC代码里就能用#ifdef CONFIG_CMD_BOOTM来判断是否编译相关代码。我建议遇到配置项没生效的问题时先手动看include/config/auto.conf里有没有这一行配置。如果这里都没有说明Kconfig解析阶段就已经把你的配置吞掉了如果这里有但C代码里不认才轮到怀疑头文件路径和宏展开的问题。3.3 Makefile与Kconfig的配合驱动源码是否被编译主要看各级Makefile里的obj-$(CONFIG_XXX)写法。比如drivers/serial/Makefile里可能有一行obj-$(CONFIG_DM_SERIAL) serial-uclass.o这行Makefile会去auto.conf里查CONFIG_DM_SERIAL的值如果是y就把serial-uclass.o编译进镜像。所以你在defconfig里开了CONFIG_DM_SERIALy但前提是Kconfig里确实存在这个config并且它的依赖全部满足。如果依赖不满足Kconfig在生成auto.conf时不会报错只会悄悄把不满足条件的配置项置为n这就是最隐蔽的失效方式。同样的原则也适用于U-Boot的各个子系统例如命令行、网络、MTD、USB等。移植新板子做最小系统时我建议先把不需要的子系统在menuconfig里全部关掉只保留串口和基本命令驱动里的obj-$(CONFIG_XXX)才是你观察镜像体积变化的最直接入口。4. U-Boot特殊的两条腿走路Kconfig与板级头文件并存4.1 为什么还保留include/configs/*.h上游U-Boot到现在也没有把include/configs/*.h彻底消灭这让习惯了纯Kconfig的人很困惑。原因是历史包袱和实际需求并存大量老板子的配置写在这些头文件里迁移工作量大另有一些配置项不适合放进Kconfig交互菜单比如DDR芯片的时序参数、整段寄存器初始化序列、环境变量的默认布局等。对移植者来说最安全的做法是把Kconfig当成功能开关来看把board头文件当成硬件参数描述来看。功能开关用#ifdef和obj-$(CONFIG_XXX)控制编译硬件参数则直接以宏形式被板级驱动引用。两者并不矛盾但你不能把同一个宏在两个地方都定义。4.2 同名宏冲突的判断标准判断一个宏该放哪我的标准很简单先在Kconfig文件里grep如果Kconfig里已经定义了这个CONFIG_XXX并且有config块那就不要再去board头文件里写#define CONFIG_XXX。典型的例子是CONFIG_SYS_TEXT_BASE。新平台通常在arch或board的Kconfig里定义为hex类型defconfig或Kconfig的default直接控制U-Boot的链接地址。如果board头文件里再写一遍轻则编译警告重则autoconf.h里宏重复定义链接地址变成你根本没想到过的值。反过来的情况也存在某些老宏在Kconfig里找不到任何定义但它们确实能控制代码比如CONFIG_SYS_NAND_BASE这类板级寄存器地址。这些宏就放在include/configs/myboard.h里由另一条脚本路径生成uboot.autoconf.h最后同样被config.h汇总。我的实操建议是移植新板子时先从参考板的defconfig和Kconfig里把能看到的CONFIG全部找齐然后翻一遍board头文件凡是和Kconfig重复的宏都删掉头文件里的定义凡是不重复的硬件参数宏留着。这样既能享受Kconfig的依赖校验又不会丢掉硬件细节。4.3 SPL阶段的独立Kconfig配置SPL是U-Boot里最容易让新人懵掉的部分因为同一份源码要编译出两个镜像普通U-Boot和轻量级SPL。很多普通阶段的配置项到SPL阶段根本不需要所以Kconfig里出现了一套独立的CONFIG_SPL_XXX系列。需要特别说明的是CONFIG_SPL_BUILD它并不是你在menuconfig里能看到、能打开或关闭的配置项而是Makefile在编译SPL镜像时自动追加给编译器的宏。C代码里经常用#ifdef CONFIG_SPL_BUILD /* SPL下的精简路径 */ #else /* 完整U-Boot路径 */ #endifdefconfig里能配置的是CONFIG_SPL_DM、CONFIG_SPL_DM_SERIAL、CONFIG_SPL_GPIO这类实际开关。SPL阶段串口起不来但普通U-Boot串口正常原因十有八九是CONFIG_SPL_DM_SERIAL没有开而不是驱动本身有问题。我的排查习惯是SPL阶段出问题先打开menuconfig找到SPL相关菜单按字母序检查依赖项全不全。比如开启CONFIG_SPL_FRAMEWORK之前要确认SPL本身已经使能开启SPL串口要确认CONFIG_SPL_DM和CONFIG_SPL_DM_SERIAL同时为y否则驱动代码虽然编译进去了但uclass初始化不会被调用。5. Kconfig移植的常见坑与排查技巧5.1 make xxx_defconfig就报错我把实际中遇到过的错误类型整理成一个速查表见下表。报错现象常见原因解决办法找不到xxx_defconfig目标configs目录下的文件名不对或未生成检查configs/myboard_defconfig是否存在文件名必须完全一致Kconfig文件不存在board目录下的Kconfig路径没source检查mach目录Kconfig里的source路径通常是相对路径写错TARGET_XXX未定义arch侧板型choice里没有注册打开arch/arm/mach-xxx/Kconfig确认config TARGET_XXX存在SYS_CONFIG_NAME不匹配include/configs/下的头文件名字不对确认board/.../Kconfig里SYS_CONFIG_NAME和头文名一致select导致循环依赖Kconfig依赖关系写坏了配置停在choose检查select链路上是否有环尝试改用imply或depends on这里最需要留意的是第二类source路径拼写。U-Boot的source路径是相对路径很容易因为目录名大小写或者层级不对导致找不到报错信息不一定很直白可能只在make时提示Kconfig语法错误。我的做法是顺着source链一层层grep从根目录Kconfig开始追。5.2 改了menuconfig编译产物毫无变化这是我最常被问到的问题之一。常见原因有三个。第一个是旧的中转文件没有清理。只要include/config/auto.conf以及include/generated/autoconf.h还存在并且make认为source没有变化配置就可能停留在旧状态。我的处理方式是干脆撤掉依赖重新走一次make distclean make myboard_defconfig make menuconfig make -j8第二个原因是board头文件里的宏覆盖了Kconfig结果。比如Kconfig里把CONFIG_ENV_OFFSET定义为一个值board头文件里又写了#define CONFIG_ENV_OFFSET 0x100000最终C代码拿到的是头文件版本menuconfig里改的是Kconfig版本自然没变化。第三个原因是Kconfig依赖不满足配置项被自动置n。这在3.3里提过最隐蔽的就是这个Kconfig不报错只是悄悄把你配置里的y改成n。排查方法是手动打开include/config/auto.conf搜索这个CONFIG如果变成注释或干脆消失就去查Kconfig里的depends on和select条件。5.3 select、imply、depends on的取舍这三个关键字是Kconfig的精髓也是移植时最容易被滥用的一组。select适合强制性硬依赖。比如开启CONFIG_DM_SERIAL逻辑上必须要有CONFIG_DM否则uclass框架不存在驱动代码没有根基这种情况用select没问题。但select有个坑它会向外传播依赖如果A select BB又depends on AKconfig就会陷入循环配置过程卡住。imply适合推荐但不强制的依赖。比如某个平台强烈推荐开启CONFIG_CMD_DM但用户一定要裁剪掉调试命令也应该允许。imply的语义是默认帮你打开但留了关闭的余地它是比select更温和的选择。depends on只是限制配置项在什么条件下可见或生效它不会主动把别的配置拉进来。写配置项时不要依赖defconfig里的顺序因为defconfig是扁平文件行顺序不会影响Kconfig依赖解析真正决定关系的是Kconfig文件里的depends on和select/imply。我还总结了一句口诀硬依赖用select建议依赖用imply条件约束用depends on。宁可少写几个select也不要为了方便把整个驱动树都拉进来。5.4 写Kconfig前最后的自检清单一个config块改完我会在编译前快速过一遍五件小事避免低级错误新加的config名字在全局是否唯一。直接grep源码树不要凭感觉。板型choice里的bool后面写了板子名方便menuconfig里辨识。SYS_BOARD、SYS_VENDOR、SYS_SOC、SYS_CONFIG_NAME是否和实际目录、头文件名一致。defconfig第一行是否写了CONFIG_TARGET_XXXy这一行是板型识别的关键。include/configs/头文件里是否残留了与Kconfig重复的CONFIG定义。这套自检流程帮我挡掉了很多次为什么我的板子编译出来跑不起来的尴尬因为绝大多数问题并不是代码逻辑而是配置宏没对上。写到这里我脑海里浮现的还是第一次在Kconfig里加板型时的狼狈——一个config块抄来抄去结果忘改SYS_CONFIG_NAME编译出来头文件都没被引用。现在我的习惯是动手改任何Kconfig之前先跑一遍make savedefconfig看基线改完再跑一遍对照凡是多出来的配置项都能看出是谁拉进来的。Kconfig不是什么神秘的东西它就是一套有规则的清单规则在源码里都能查到。你用到哪个板子、遇到哪个报错顺着Kconfig文件往下追基本都能找到答案。
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →