3. Bootloader裁剪与配置:U-Boot源码获取、配置与编译、裁剪不必要的驱动与命令、适配自定义硬件
Bootloader 是系统上电后跑的第一段代码。说白了,它负责把内核从 Flash 搬到内存,然后跳过去执行。U-Boot 是目前嵌入式 Linux 用得最多的 Bootloader,没有之一。我这些年经手的项目,从 ARM9 到 Cortex-A72,几乎都在用 U-Boot。
但 U-Boot 默认配置很臃肿。你想想看,它要支持几十种开发板、几百种驱动、上千条命令。而我们实际产品只需要其中一小部分。所以裁剪是必须的。
3.1 U-Boot 源码获取与版本选择
U-Boot 官方源码托管在 https://source.denx.de/u-boot/u-boot。我个人习惯用 Git 拉取,方便切分支和打补丁。
git clone https://source.denx.de/u-boot/u-boot.git
cd u-boot
git checkout v2024.01 # 选一个长期支持版本
版本选好后,先看看 configs/ 目录下有没有接近你硬件的 defconfig。比如我用的是 i.MX6ULL,那就找 mx6ull_14x14_evk_defconfig。没有完全匹配的,就选最接近的,后面再改。
3.2 配置与编译:从 defconfig 到 u-boot.bin
U-Boot 的配置系统跟 Linux 内核很像,都是 Kconfig + Makefile。流程很简单:
- 加载默认配置:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig - 菜单配置:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig - 编译:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j4
编译完成后,当前目录下会生成 u-boot.bin、u-boot-dtb.bin(带设备树)、u-boot.imx(i.MX 专用格式)等文件。具体生成哪些,取决于你的平台。
3.3 裁剪不必要的驱动与命令
U-Boot 的裁剪主要分两块:驱动 和 命令。驱动占 Flash 空间,命令占内存和 Flash。我一般按以下步骤来:
3.3.1 裁剪驱动
驱动裁剪在 Device Drivers 菜单里。以下是我常用的裁剪清单:
| 驱动类别 | 裁剪建议 | 说明 |
|---|---|---|
| 串口驱动 | 保留板级对应 UART | 至少留一个调试串口 |
| MMC/SD 驱动 | 保留实际使用的控制器 | 如果从 eMMC 启动,必须保留 |
| NAND/SPI Flash 驱动 | 按实际存储介质保留 | 用 SPI NOR 就关掉 NAND |
| 网络驱动 | 保留板载 PHY 对应驱动 | 调试阶段建议保留,量产可关 |
| USB 驱动 | 量产阶段可全部关闭 | 除非你需要 USB 烧录 |
| 显示/GPU 驱动 | 无显示需求直接关 | 能省不少空间 |
| PCIe 驱动 | 不用 PCIe 就关 | 很多 ARM 平台用不到 |
具体操作:进入 Device Drivers,把不需要的驱动前面的 [*] 改成 [ ]。注意有些驱动有依赖关系,关掉后会自动关掉子选项。
3.3.2 裁剪命令
命令裁剪在 Command line interface 菜单里。U-Boot 默认开了上百条命令,很多你一辈子都用不上。我一般这样处理:
- 必留命令:
bootm、bootz、go、load、mmc、sf、setenv、saveenv、printenv、reset - 调试阶段保留:
md、mw、cp、cmp、ping、tftp - 量产可关:
fat、ext4、ubi、jffs2、usb、pxe、dhcp、bootp - 直接关掉:
ide、scsi、sata、virtio、cramfs、squashfs
3.4 适配自定义硬件
适配自定义硬件,核心是改 设备树 和 板级文件。U-Boot 从 v2016 开始全面使用设备树来描述硬件,所以大部分适配工作都在 .dts 里完成。
3.4.1 设备树修改
设备树文件在 arch/arm/dts/ 目录下。找到你板子对应的 .dts,比如 imx6ull-myboard.dts。主要修改点:
- 内存节点:修改
reg = <0x80000000 0x10000000>;匹配实际 DDR 大小 - 串口节点:确认引脚复用和时钟配置
- MMC 节点:调整
bus-width、no-1-8-v等属性 - 网络节点:修改 PHY 地址、复位 GPIO
- GPIO/LED/按键:按原理图添加
// 示例:修改内存大小为 512MB
memory {
reg = <0x80000000 0x20000000>;
};
// 修改 FEC 网卡的 PHY 地址
&fec1 {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_enet1>;
phy-mode = "rmii";
phy-handle = <ðphy0>;
status = "okay";
};
&mdio {
ethphy0: ethernet-phy@1 {
reg = <1>; // PHY 地址改为 1
};
};
3.4.2 板级文件修改
如果设备树搞不定(比如特殊的上电时序、GPIO 初始化顺序),就需要改板级 C 文件。文件在 board/<厂商>/<板子名>/ 目录下。常见的修改点:
board_init():早期硬件初始化,比如配置 PMIC、设置时钟board_late_init():后期初始化,比如设置环境变量默认值board_mmc_init():MMC 控制器初始化board_eth_init():以太网初始化
board_init() 里加了一段 GPIO 操作,结果导致 DDR 初始化失败,U-Boot 连串口都没打印。后来发现是 GPIO 时钟还没开。所以改硬件相关代码时,一定要确认时钟和电源域已经就绪。
3.5 裁剪与适配的完整流程
下面这张图总结了从拿到 U-Boot 源码到生成最终镜像的完整流程:
3.6 避坑指南
做 U-Boot 裁剪这几年,我踩过不少坑。挑几个典型的说说:
- 裁剪后无法启动:我曾经把
CONFIG_CMD_BOOTM关了,结果内核启动时直接 hang 住。后来才发现 bootm 是启动内核的核心命令,不能关。所以裁剪前先搞清楚每条命令的作用。 - 设备树编译不过:U-Boot 的设备树语法跟 Linux 内核完全一样,但有些属性是 U-Boot 特有的。比如
u-boot,dm-pre-reloc,不加的话驱动在 SPL 阶段不会初始化。我遇到过网卡在 SPL 里不工作,查了两天才发现是少了这个属性。 - 环境变量保存失败:裁剪时如果把
CONFIG_ENV_IS_IN_MMC或CONFIG_ENV_IS_IN_SPI_FLASH搞错了,环境变量就存不进去。我建议先确认你的存储介质,再选对应的环境变量后端。 - SPL 太大放不下:很多芯片的 SPL 有大小限制,比如 i.MX6ULL 的 SPL 不能超过 68KB。如果裁剪不彻底,SPL 编译出来超了,就会覆盖后面的区域。用
make size可以提前检查。
U-Boot 裁剪说难不难,说简单也不简单。核心就三点:知道要什么、知道怎么关、知道怎么测。把这三点吃透了,你就能根据任何硬件定制出最精简的 Bootloader。