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源码的时候,看到几十个目录也懵了一下。但后来我发现,真正需要我们关心的,其实就四个核心目录。其他的大多是辅助工具或者文档。

来,我带你快速过一遍这张图,看完你就知道该往哪儿看了。

U-Boot 核心源码结构图 U-Boot 源码核心目录结构 U-Boot 根目录 arch/ board/ common/ drivers/ 各目录职责说明: • arch/:架构相关代码。ARM、x86、RISC-V 都在这里。你选哪个CPU,就进哪个子目录。 • board/:板级代码。每个厂商的每款板子,在这里都有一个专属文件夹。 • common/:通用功能。命令行解析、环境变量、启动流程,都在这里。 • drivers/:驱动代码。串口、网卡、MMC、NAND Flash 等外设驱动。 其他重要目录: • include/:头文件,特别是 configs/ 下的板级配置头文件。 • configs/:默认配置文件(defconfig),用于 make xxx_defconfig。 • dts/:设备树源文件,新版本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 验证你的板级目录

做完以上步骤,我们来验证一下:

  1. 检查Kconfig链路:执行 make menuconfig,在“Board selection”下找到你的板子。
  2. 检查编译:执行 make mars_devkit_defconfig(前提是你已经在 configs/ 下创建了默认配置)。
  3. 检查头文件:确保 include/configs/mars_devkit.h 能被正确包含。

如果这三步都通过了,恭喜你,你的板子已经被U-Boot“认识”了。下一节我们会真正开始编译它,让它跑起来。

嗯,这一章的内容就到这里。结构、配置、目录添加,这三板斧抡好了,移植工作就完成了大半。剩下的就是根据硬件手册,把各个外设的驱动调通。别急,我们一步一步来。