4、Kconfig与配置系统:如何通过menuconfig配置U-Boot
U-Boot的配置系统,说白了就是一套“搭积木”的规则。你想想看,一块开发板上有CPU、内存、Flash、网卡、串口……每个外设都可能对应不同的驱动和参数。如果每次移植都要手动改几百个宏定义,那不得疯掉?
Kconfig + menuconfig这套组合拳,就是来解决这个问题的。我最早接触它是在Linux内核里,后来U-Boot也全面采用了。说实话,刚开始我也觉得这东西有点绕,但用顺手之后,真香。
核心思想:Kconfig定义“有什么选项”,menuconfig提供“怎么选”,最终生成一个.config文件,指导整个编译过程。
4.1 Kconfig的“语法”其实很简单
Kconfig文件不是C代码,也不是脚本,它是一套专用的配置描述语言。你不需要全部背下来,掌握几个常用结构就够了。
我在项目中见过不少新手,一打开Kconfig文件就懵了。其实你只要抓住三个核心元素:菜单项、依赖关系、默认值。
4.1.1 最基本的配置项
config CONFIG_SYS_TEXT_BASE
hex "U-Boot的起始地址"
default 0x87800000
help
这个地址决定了U-Boot在内存中的加载位置。
不同SoC有不同要求,别乱改。
嗯,这里要注意:config后面跟的是宏名称,hex表示这是一个十六进制参数,default是默认值。你通过menuconfig修改后,这个值就会写入.config文件。
4.1.2 布尔型与选择型
| 类型 | 关键字 | 典型场景 |
|---|---|---|
| 布尔 | bool | 是否启用某个驱动 |
| 三态 | tristate | 编译进内核/编译为模块/不编译(U-Boot少用) |
| 整型 | int | 缓冲区大小、超时时间 |
| 十六进制 | hex | 内存地址、寄存器基址 |
| 字符串 | string | 设备树路径、版本号 |
举个例子,你要加一个网卡驱动的开关:
config DRIVER_ETH_DW
bool "支持DesignWare以太网控制器"
default y
help
选上这个,U-Boot才能驱动DesignWare系列的GMAC。
4.2 menuconfig:图形化的配置界面
你可能会问:“我直接改.config文件不行吗?”行,但容易出错。menuconfig提供了依赖检查、范围校验、帮助提示,能帮你避免很多低级错误。
我个人习惯是:先用menuconfig做一次完整配置,保存后把.config作为基线,后续微调再手动改。
4.2.1 启动menuconfig
make menuconfig
就这么简单。前提是你已经执行过make <board>_defconfig,生成了初始的.config文件。否则menuconfig打开的是一张白纸,啥也没有。
小技巧:如果你在Windows的WSL或虚拟机里开发,menuconfig的界面可能显示不正常。试试make menuconfig HOSTCFLAGS=-DNCURSES_WIDECHAR,能解决大部分乱码问题。
4.2.2 界面操作速查
| 按键 | 功能 |
|---|---|
| ↑ ↓ | 上下移动光标 |
| Enter | 进入子菜单 / 确认选择 |
| Space | 切换选项状态([ ] → [*] → [M]) |
| Y / N | 直接设为启用 / 禁用 |
| ? | 查看当前选项的帮助信息 |
| / | 搜索功能(按名字找选项) |
| Esc + Esc | 返回上一级 / 退出 |
我最常用的其实是搜索键“/”。U-Boot的配置项成千上万,靠手翻?不现实。直接搜“SPI”、“NAND”、“ETH”,定位飞快。
4.3 配置的依赖与继承
这是Kconfig最强大的地方,也是新手最容易踩坑的地方。
我曾经帮一个客户调试,他明明在menuconfig里选了“支持NAND Flash”,但编译出来的U-Boot就是不认NAND。折腾了半天,发现他忘了选“NAND控制器驱动”——上层选项依赖下层驱动,但menuconfig不会自动帮你勾上。
注意:Kconfig的依赖是“单向”的。你选了A,如果A依赖B,menuconfig会提示你选B。但如果你手动取消B,A也会被自动取消。反过来,你只选B,A不会自动选上。
4.3.1 depends on 与 select
config NAND_BOOT
bool "从NAND启动"
depends on NAND_SUPPORT
select SPL_NAND_SUPPORT
help
启用NAND启动支持。
depends on是“我依赖你”,select是“我带上你”。前者是软约束(提示你选),后者是硬约束(强制选上)。
4.4 配置的层次结构
U-Boot的Kconfig文件分布在各个子目录里:
- arch/ — CPU架构相关配置(ARM、RISC-V、x86等)
- board/ — 板级配置(你的板子专属选项)
- drivers/ — 驱动配置(网卡、存储、显示等)
- cmd/ — 命令配置(哪些U-Boot命令可用)
- common/ — 通用配置(环境变量、启动参数等)
每个目录下都有一个Kconfig文件,通过source指令被顶层Kconfig包含。你可以在menuconfig里一层层钻进去看。
下面这张图帮你理清配置系统的整体流程:
4.5 实战:添加你自己的配置项
假设你正在移植一块新板子,板子上有一颗自定义的LED指示灯,你想通过配置项控制它的行为。
4.5.1 在板级Kconfig中添加
# board/mycompany/myboard/Kconfig
if TARGET_MYBOARD
config MYBOARD_LED_GPIO
int "LED连接的GPIO编号"
default 127
help
设置板载LED对应的GPIO引脚号。
例如:GPIO1_31 = 32*1 + 31 = 63
config MYBOARD_LED_BLINK
bool "启用LED闪烁"
default y
depends on MYBOARD_LED_GPIO
help
启动时LED闪烁表示系统正常运行。
endif
4.5.2 在代码中使用
/* board/mycompany/myboard/led.c */
#include <common.h>
void board_led_init(void)
{
int gpio = CONFIG_MYBOARD_LED_GPIO;
gpio_request(gpio, "board_led");
gpio_direction_output(gpio, 0);
}
void board_led_blink(void)
{
if (IS_ENABLED(CONFIG_MYBOARD_LED_BLINK)) {
/* 闪烁逻辑 */
}
}
经验之谈:用IS_ENABLED()宏比#ifdef更安全。前者在C代码层面做判断,编译器会做死代码消除,但语法检查仍然覆盖整个函数。后者在预处理阶段就砍掉了代码,容易漏掉括号匹配问题。
4.6 配置的“坑”与避坑指南
做U-Boot移植这些年,我在配置系统上栽过不少跟头。挑几个典型的说说:
- 配置冲突:我曾经同时选了SPI Flash和NAND Flash作为启动设备,结果U-Boot编译出来两个初始化代码打架,系统直接挂掉。后来才搞清楚,有些SoC的启动路径是互斥的,Kconfig里没做约束,全靠开发者自己注意。
- 默认值陷阱:某个配置项的默认值是
0x0,我以为是“无效地址”,结果U-Boot真的往地址0写数据了……嗯,那次差点把开发板搞成砖。 - 依赖链断裂:你选了A,A依赖B,B依赖C。如果你手动改了C的配置,B可能被自动取消,然后A也跟着消失。menuconfig不会每次都提醒你,保存配置后记得检查一下。
避坑指南:每次修改配置后,执行make savedefconfig生成精简的defconfig文件。这个文件只包含你修改过的、非默认的配置项。下次移植时,直接用这个defconfig,干净又省事。
4.7 配置系统的“幕后”文件
你可能会好奇:menuconfig保存后,到底发生了什么?
简单来说,.config文件会被scripts/kconfig/conf.c解析,生成两个关键产物:
- include/config.h — 被C代码直接包含的宏定义头文件
- include/autoconf.mk — 被Makefile使用的变量定义
这两个文件是自动生成的,不要手动修改。你改完.config后,执行make,它们会自动更新。
我个人习惯是:把.config加入.gitignore,只跟踪defconfig文件。这样团队协作时,每个人都能用自己的配置,互不干扰。