4. 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里一层层钻进去看。

下面这张图帮你理清配置系统的整体流程:

U-Boot Kconfig配置系统流程 U-Boot Kconfig 配置系统流程 make xxx_defconfig 生成 .config make menuconfig 修改配置项 保存 → 更新 .config make → 生成 u-boot.bin 关键文件 .config — 当前配置 Kconfig — 配置规则定义 defconfig — 板级默认配置

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文件。这样团队协作时,每个人都能用自己的配置,互不干扰。