4. U-Boot配置系统:menuconfig配置、defconfig文件、设备树(Device Tree)与配置的关系
U-Boot的配置系统,说实话,刚接触的时候容易让人一头雾水。menuconfig、defconfig、设备树,这三样东西到底怎么配合工作的?我当年第一次移植U-Boot时,就被这“三驾马车”绕晕过。今天咱们就把它们之间的关系彻底捋清楚。
4.1 配置系统的“三层架构”
我个人习惯把U-Boot的配置系统理解成一个三层结构:
- 顶层:menuconfig(图形化配置界面)
- 中间层:defconfig(默认配置文件)
- 底层:Kconfig(配置规则定义) + 设备树(硬件描述)
这三层各司其职,又互相依赖。你想想看,menuconfig就像是一个超市,你可以自由挑选需要的功能;defconfig则是你的购物清单,告诉你哪些是默认要买的;而设备树,嗯,它更像是商品的说明书,告诉你这个硬件长什么样、怎么用。
核心观点:配置系统解决的是“软件要支持哪些功能”的问题,而设备树解决的是“硬件长什么样”的问题。两者通过配置选项(CONFIG_*)和编译脚本串联起来。
4.2 menuconfig:交互式配置工具
运行 make menuconfig 时,U-Boot会读取当前架构下的Kconfig文件,生成一个图形化的配置菜单。这里我提个醒:
我的经验:在第一次移植时,别急着用menuconfig改配置。先看看同系列开发板的defconfig文件,理解别人是怎么配的,再动手改。我曾经一上来就乱改,结果编译出来的U-Boot连串口都不打印信息,排查了半天才发现是调试串口配置错了。
menuconfig的操作很简单:
- 按
Y选中功能(编译进内核) - 按
N排除功能 - 按
M编译为模块(U-Boot中较少用) - 按
?查看帮助信息
配置完成后,U-Boot会生成一个 .config 文件。这个文件就是当前配置的“快照”。
4.3 defconfig:你的配置“基准线”
defconfig文件,说白了就是一份“推荐配置”。它存放在 configs/ 目录下,命名规则通常是 <板子名称>_defconfig。
举个例子,我常用的 stm32mp157d_atk_defconfig 文件内容大致如下:
CONFIG_ARM=y
CONFIG_ARCH_STM32MP=y
CONFIG_SYS_TEXT_BASE=0xC0000000
CONFIG_SYS_MALLOC_F_LEN=0x2000
CONFIG_TARGET_STM32MP157D_ATK=y
CONFIG_ENV_SIZE=0x2000
CONFIG_DM=y
CONFIG_DM_GPIO=y
CONFIG_DM_SERIAL=y
CONFIG_DM_MMC=y
CONFIG_MMC=y
CONFIG_CMD_MMC=y
CONFIG_CMD_SF=y
CONFIG_CMD_SPI=y
CONFIG_SPI_FLASH=y
CONFIG_SPI_FLASH_MACRONIX=y
看到没?defconfig里只写“差异项”。那些默认值(比如大部分驱动默认关闭)不需要写进去。这样做的好处是:
- 文件短小精悍,容易维护
- 升级U-Boot版本时,只需要关注新增的配置项
- 多人协作时,减少冲突
避坑指南:我曾经犯过一个错误——把整个.config文件直接复制成defconfig。结果里面包含了大量自动生成的配置项,下次编译时很多选项都变了,导致板子起不来。正确的做法是:用 make savedefconfig 命令,它会自动生成一个精简版的defconfig。
4.4 设备树(Device Tree):硬件的“身份证”
设备树,我更喜欢叫它“硬件的身份证”。它用文本文件(.dts)描述了一个板子上所有硬件资源:CPU、内存地址、外设寄存器、中断号、时钟频率……
U-Boot中的设备树文件通常放在 arch/arm/dts/ 目录下。一个典型的设备树节点长这样:
&uart4 {
pinctrl-names = "default";
pinctrl-0 = <&uart4_pins_a>;
status = "okay";
};
这段代码的意思是:使能UART4外设,使用默认的引脚配置。嗯,就这么简单。
4.5 配置与设备树的关系
这里有个关键问题:配置系统和设备树是怎么配合的?
我画了一张图来说明它们的关系:
从这张图可以看出:
- defconfig是起点,它告诉menuconfig“默认选什么”
- menuconfig允许你在此基础上做调整
- 设备树独立于配置系统,但在编译时被编译成dtb文件,最终和U-Boot镜像打包在一起
4.6 配置选项如何影响设备树
这里有个细节很多人会忽略:配置选项可以控制设备树中的节点是否生效。
比如,在Kconfig中定义:
config MY_BOARD_USB_SUPPORT
bool "Enable USB support on my board"
help
Say Y here to enable USB support.
然后在设备树中,可以用条件编译:
#ifdef CONFIG_MY_BOARD_USB_SUPPORT
&usb1 {
status = "okay";
};
#endif
这样,只有当你通过menuconfig选中了USB支持,设备树中的USB节点才会被使能。这种做法在需要支持多种硬件变体时特别有用。
我的建议:尽量少用这种条件编译。设备树应该保持“纯净”,只描述硬件本身。功能的开关应该通过U-Boot的驱动模型(Driver Model)在运行时控制。条件编译多了,设备树会变得难以维护。
4.7 实际操作流程
最后,我总结一下在实际移植中,这三者的操作顺序:
- 第一步:找一个相近的defconfig作为模板,复制一份
- 第二步:修改defconfig,添加你的板子特有的配置项(比如内存大小、外设驱动)
- 第三步:创建设备树文件,描述板子的硬件资源
- 第四步:运行
make xxx_defconfig加载配置 - 第五步:运行
make menuconfig微调配置 - 第六步:运行
make编译,生成最终的U-Boot镜像
嗯,这套流程我用了很多年,基本没出过问题。你刚开始移植时,可以严格按照这个步骤来。等熟练了,就可以根据实际情况灵活调整了。
总结一下:配置系统(defconfig + menuconfig)决定了U-Boot“能做什么”,设备树决定了U-Boot“面对什么硬件”。两者通过CONFIG_*宏和编译脚本紧密配合。理解了这个关系,U-Boot移植就成功了一半。