3. Bootloader移植(上):U-Boot源码结构分析与板级目录添加
好,咱们正式开始啃Bootloader这块硬骨头。很多刚入行的兄弟觉得U-Boot移植特别神秘,好像得懂什么了不得的魔法。其实说白了,U-Boot就是一个跑在裸机上的、专门用来加载操作系统的小程序。你把它想成一个超级精简的BIOS,就很好理解了。
我个人习惯把U-Boot移植分成三步走:看懂结构 → 配置裁剪 → 添加自己的板子。这一章我们先搞定前两步,顺便把第三步的准备工作做完。
核心认知:U-Boot不是 monolithic(单体)的。它是高度模块化的。你移植的本质,就是告诉U-Boot:“嘿,我的板子CPU是这颗,内存大小是这样,外设接在那个GPIO上。” 仅此而已。
3.1 U-Boot源码结构:别被目录吓到
我第一次解压U-Boot源码的时候,看到几十个目录也懵了一下。但后来我发现,真正需要我们关心的,其实就四个核心目录。其他的大多是辅助工具或者文档。
来,我带你快速过一遍这张图,看完你就知道该往哪儿看了。
你看,arch/ 管CPU的事,board/ 管板子的事,common/ 管公共逻辑,drivers/ 管外设。层次非常清楚。我们移植的时候,主要动 board/ 和 arch/ 下的东西,偶尔改改 drivers/。
3.2 配置系统:Kconfig 和 Makefile 怎么配合
搞懂了目录,接下来要明白U-Boot是怎么被“组装”起来的。这就不得不提Kconfig和Makefile这对黄金搭档。
Kconfig 负责“有什么选项”。它定义了一个个的配置开关,比如“要不要支持网络”、“串口用哪个”。
Makefile 负责“怎么编译”。它根据Kconfig选出来的结果,决定编译哪些文件、传什么参数。
我举个例子,你一看就懂:
// drivers/serial/Kconfig 中的一段
config UART_BASE
hex "UART base address"
default 0x10000000
help
Base address of the UART controller.
// drivers/serial/Makefile 中的对应
obj-$(CONFIG_UART_BASE) += serial_myuart.o
这段代码的意思是:如果你在 make menuconfig 里配置了 UART_BASE 这个选项,Makefile 就会把 serial_myuart.c 编译进去。没配置?那就不编译。干净利落。
我的小技巧:刚开始移植时,别急着自己写Kconfig。先找一个和你板子最接近的参考板,把它的Kconfig复制过来,改改地址和名字就行。我在项目里都是这么干的,省时省力。
3.3 添加自己的板级目录:从复制开始
好,重头戏来了。怎么让U-Boot认识你的板子?
核心就三步:建目录 → 写配置 → 注册到Kconfig。我习惯用一块“火星开发板”来做演示,SoC是 sunxi 家族的。
步骤一:创建板级目录
在 board/ 下,一般按 厂商/板子名 的层级组织。比如:
mkdir -p board/sunxi/mars_devkit
然后在这个目录里,你需要至少三个文件:
| 文件名 | 作用 | 核心内容 |
|---|---|---|
Kconfig |
定义板级配置选项 | 选择SoC型号、DDR类型、默认环境变量地址等 |
MAINTAINERS |
声明维护者信息 | 你的名字和邮箱,方便别人找你修bug |
mars_devkit.c |
板级初始化代码 | GPIO初始化、时钟设置、板级复位逻辑 |
步骤二:编写板级Kconfig
打开 board/sunxi/mars_devkit/Kconfig,写入:
if TARGET_MARS_DEVKIT
config SYS_BOARD
default "mars_devkit"
config SYS_VENDOR
default "sunxi"
config SYS_SOC
default "sun8i"
config SYS_CONFIG_NAME
default "mars_devkit"
endif
这段代码告诉U-Boot:我的板子叫 mars_devkit,厂商是 sunxi,SoC 是 sun8i 系列。这些信息会被后续的编译流程用到。
步骤三:注册到上层Kconfig
这一步很多人会忘。你必须在 board/sunxi/Kconfig 里添加一行,让上层知道你的板子存在:
source "board/sunxi/mars_devkit/Kconfig"
同时,在 arch/arm/Kconfig 里,找到 sunxi 相关的配置块,添加你的板子选项:
config TARGET_MARS_DEVKIT
bool "Support Mars Devkit Board"
select CPU_V7
help
This is the Mars Devkit board based on sun8i SoC.
我曾经踩过的坑:有一次我添加了新板子,make menuconfig 里死活看不到。查了半天,发现是 arch/arm/Kconfig 里的 source 语句路径写错了。记住,Kconfig 的 source 是相对路径,而且必须保证每个层级都引用了下一级。你可以在根目录执行 make menuconfig 验证,如果能看到你的板子选项,就说明注册成功了。
3.4 板级配置头文件:最后的拼图
除了Kconfig,我们还需要一个头文件来定义板级的硬件参数。这个文件放在 include/configs/ 下,名字要和 SYS_CONFIG_NAME 一致。
创建 include/configs/mars_devkit.h,内容大致如下:
#ifndef __MARS_DEVKIT_H
#define __MARS_DEVKIT_H
#include <configs/sun8i.h>
/* 内存配置 */
#define CONFIG_SYS_SDRAM_BASE 0x40000000
#define CONFIG_SYS_SDRAM_SIZE 0x20000000 /* 512MB */
/* 串口配置 */
#define CONFIG_CONS_INDEX 1
#define CONFIG_BAUDRATE 115200
/* 网络配置 */
#define CONFIG_ETHADDR 00:11:22:33:44:55
#define CONFIG_IPADDR 192.168.1.100
/* 环境变量偏移(在NAND中的位置) */
#define CONFIG_ENV_OFFSET 0x80000
#define CONFIG_ENV_SIZE 0x20000
#endif /* __MARS_DEVKIT_H */
这个头文件是板级移植的“最后一公里”。它直接决定了U-Boot启动后能不能认到内存、能不能打印信息、能不能从网络启动。
关键提醒:新版本的U-Boot(2016年以后)强烈推荐使用设备树(Device Tree)来描述硬件。如果你的U-Boot版本较新,很多板级信息应该放在 dts/ 目录下的 .dts 文件中,而不是这个头文件里。具体用哪种方式,取决于你的U-Boot版本和SoC平台。我建议你查看一下参考板的做法,保持一致。
3.5 验证你的板级目录
做完以上步骤,我们来验证一下:
- 检查Kconfig链路:执行
make menuconfig,在“Board selection”下找到你的板子。 - 检查编译:执行
make mars_devkit_defconfig(前提是你已经在configs/下创建了默认配置)。 - 检查头文件:确保
include/configs/mars_devkit.h能被正确包含。
如果这三步都通过了,恭喜你,你的板子已经被U-Boot“认识”了。下一节我们会真正开始编译它,让它跑起来。
嗯,这一章的内容就到这里。结构、配置、目录添加,这三板斧抡好了,移植工作就完成了大半。剩下的就是根据硬件手册,把各个外设的驱动调通。别急,我们一步一步来。