3、Bootloader与内核启动:U-Boot移植与配置、Linux内核裁剪与编译、设备树解析与修改
好,咱们进入第三章。这一章可以说是整个座舱系统启动的「第一道关卡」。你想想看,芯片上电后,第一段代码是谁跑的?就是 Bootloader。而 U-Boot 几乎是嵌入式 Linux 世界的标配。我这些年经手的项目,从低端单片机到高端车规芯片,无一例外都在跟它打交道。
说白了,Bootloader 的任务就两件:初始化硬件,然后加载内核。但这两件事背后牵扯的东西,远比想象中复杂。尤其是座舱系统这种对启动时间有严苛要求的场景,U-Boot 的移植和配置,直接决定了你的产品能不能在 3 秒内亮屏。
3.1 U-Boot 移植与配置
U-Boot 的全称是 Universal Bootloader,它支持几十种架构。但移植到一块新板子上,核心工作其实就几块:板级初始化、驱动适配、存储布局。
我个人习惯,拿到一块新芯片的参考板后,第一步不是急着改代码,而是先看芯片厂商提供的 board 目录下的参考板配置。比如对于 NXP i.MX8 系列,我会先复制一份 board/freescale/imx8qm_mek 到自己的板子目录下。
核心移植步骤:
- 创建板级目录:在
board/下新建你的板子文件夹,复制参考板的Kconfig、Makefile、imx8qm_xxx.c等文件。 - 修改内存初始化:DDR 时序参数是重中之重。我遇到过最坑的一次,就是 DDR 的刷新周期配错了,导致系统跑几分钟就随机死机。
- 配置时钟树:座舱芯片通常有复杂的 PLL 和时钟域,必须确保 CPU、DDR、外设总线时钟正确。
- 添加设备树文件:U-Boot 本身也使用设备树,需要创建
arch/arm/dts/下的.dts文件。 - 配置存储设备:eMMC、NAND Flash 或 SD 卡,驱动要能正确识别。
配置 U-Boot 用的是 make menuconfig,这个大家应该不陌生。但我要提醒一点:不要贪多。座舱系统对启动时间敏感,U-Boot 里能关的功能尽量关掉。比如网络协议栈、USB 主机模式、文件系统支持,如果启动阶段用不到,就果断去掉。
我的经验:U-Boot 的启动延迟,很多时候不是 CPU 慢,而是驱动初始化时做了太多轮询等待。比如某个 I2C 设备没接,但驱动还在傻等超时。我建议在板级初始化函数 board_init() 里加上超时控制,或者直接跳过不存在的设备。
3.2 Linux 内核裁剪与编译
内核裁剪,说白了就是「瘦身」。座舱系统的内核,不需要跑服务器那一套。你想想看,一个座舱芯片,需要支持几十个 USB 设备吗?需要上千个网络协议吗?不需要。
我刚开始做座舱项目时,直接拿厂商的默认配置编译,结果内核镜像 12MB。后来一查,光是不用的驱动就占了 5MB。裁剪后,内核降到了 4.2MB,启动时间缩短了将近 1 秒。
裁剪的核心思路,我总结成一张表:
| 裁剪方向 | 具体操作 | 典型节省空间 |
|---|---|---|
| 驱动模块 | 关闭不用的外设驱动(如未使用的网卡、USB 控制器) | 1-3 MB |
| 文件系统 | 只保留 ext4、FAT,去掉 btrfs、xfs 等 | 0.5-1 MB |
| 网络协议 | 去掉 IPv6、IPsec、Netfilter 等 | 0.5-2 MB |
| 调试选项 | 关闭 DEBUG、PRINTK、LOCKDEP 等 | 0.3-0.8 MB |
| 架构无关代码 | 只保留 ARM64,去掉 x86、ARM32 等 | 0.2-0.5 MB |
编译命令很简单,但有几个坑要注意:
# 配置内核
make ARCH=arm64 defconfig
make ARCH=arm64 menuconfig
# 编译内核镜像
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8 Image
# 编译设备树
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8 dtbs
# 编译内核模块
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8 modules
警告:我曾经犯过一个低级错误——编译内核时用了错误的交叉编译器版本。比如用 gcc 7.x 编译 Linux 5.10 内核,结果链接阶段报一堆 undefined reference。后来查了内核文档,才发现 5.10 要求最低 gcc 8.x。所以,编译前一定先看内核源码根目录下的 Documentation/process/changes.rst,确认工具链版本。
3.3 设备树(Device Tree)解析与修改
设备树,是嵌入式 Linux 里用来描述硬件信息的「说明书」。它把 CPU、内存、外设、中断、GPIO 等信息,用一种树形结构写出来。内核启动时,通过解析设备树来知道「我有哪些硬件,它们怎么连接」。
座舱系统的设备树,通常非常庞大。一块 i.MX8QM 的参考设备树,光 .dts 文件就有 3000 多行。但别怕,核心要改的地方其实不多。
我一般把设备树修改分成三类:
- 内存节点:修改
reg = <0x0 0x80000000>;来匹配实际 DDR 大小。 - 外设节点:使能或禁用某个外设,比如
status = "okay";或status = "disabled";。 - 引脚复用:修改
pinctrl子节点,配置 GPIO 功能。
举个例子,假设我们要在座舱系统中添加一个 I2C 触摸屏:
&i2c3 {
status = "okay";
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c3>;
touch@38 {
compatible = "edt,edt-ft5406";
reg = <0x38>;
interrupt-parent = <&gpio1>;
interrupts = <13 IRQ_TYPE_EDGE_FALLING>;
};
};
这里要注意的是 compatible 属性,它必须和内核驱动里的 of_match_table 完全一致,否则驱动不会 probe。
避坑指南:我曾经在调试一个显示驱动时,发现内核死活不认我的 LCD 面板。查了两天,最后发现是设备树里 display-timings 的 hactive 和 vactive 写反了。嗯,这种低级错误,一旦犯过,这辈子都不会再犯。
3.4 本章知识体系
为了让你更直观地理解这三者之间的关系,我画了一张流程图:
从这张图可以清楚看到,整个启动链路是串行的。任何一个环节出问题,系统都起不来。我调试时,习惯在 U-Boot 的 bootcmd 里加上 bootdelay=3,这样可以在启动时按任意键进入 U-Boot 命令行,手动验证每个环节。
本章核心要点:
- U-Boot 移植的核心是板级初始化和驱动适配,不要贪多,能关的功能就关。
- 内核裁剪要有的放矢,座舱系统不需要的功能一律去掉,启动时间比功能丰富更重要。
- 设备树是硬件和内核之间的桥梁,修改时务必确认
compatible和引脚复用是否正确。 - 整个启动流程是串行的,建议在 U-Boot 命令行中逐级验证,不要等到内核启动失败才回头查。