title: 【从零写OS内核】336 U-Boot 移植:从零让你的开发板跑起来
category: os
tag: [u-boot, embedded, arm,移植]
source: https://github.com/golang12306/os-kernel-from-scratch
【从零写OS内核】336 U-Boot 移植:从零让你的开发板跑起来
1. U-Boot 移植的本质
U-Boot 移植本质是让引导程序认识你的硬件。芯片原厂提供参考板代码,我们在此基础上修改 GPIO、时钟、DDR、存储驱动,使 U-Boot 能在新板上稳定运行。
类比 Windows:把 U-Boot 想象成 Windows PE 环境下的 BIOS 配置工具——你需要告诉系统怎么初始化内存、怎么访问磁盘、怎么加载内核。
┌─────────────────────────────────────────────────────────────┐
│ U-Boot 移植分层架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ cmd_xxx │ │ net_xxx │ │ drivers/ │ │
│ │ (命令层) │ │ (网络层) │ │ (驱动层) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ common/ │ (通用代码) │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ │ │ │ │
│ ┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐ │
│ │ board/ │ │ arch/ │ │ configs/ │ │
│ │ 板级 │ │ 架构层 │ │ defconfig │ │
│ │ 配置 │ │ ARM/RISC │ │ 编译配置 │ │
│ └───────────┘ └───────────┘ └───────────┘ │
└─────────────────────────────────────────────────────────────┘2. 移植前的硬件资料准备
移植前必须拿到以下资料(通常向芯片厂申请):
| 资料 | 说明 | 获取途径 |
|---|---|---|
| SoC datasheet | 寄存器定义、时钟树 | 原厂 |
| Hardware Manual | 硬件设计参考 | 原厂 |
| DDR timing spec | 内存时序参数 | DDR 芯片厂 |
| Board schematic | 原理图 | 板厂 |
| Reference U-Boot | 原厂提供的参考代码 | 原厂/社区 |
| BSP code | 板级支持包 | 原厂 |
2.1 原理图关键网络
必须确认的网络:
1. DDR 数据线 DQ[0:31] — 连到哪个 GPIO 组
2. DDR 地址线 A[0:15] — 连到哪个 GPIO 组
3. DDR CLK/QSPI — 时钟和片选
4. eMMC/SD 卡信号 — 哪个 GPIO 组
5. UART TX/RX — 调试串口
6. PMIC I2C — 电源管理芯片通信
7.reset 复位电路 — 上电时序要求3. 移植第一步:创建板级目录
U-Boot 源码结构树(关键路径):
u-boot/
├── arch/
│ └── arm/
│ └── dts/
│ └── exynos4412-scp.dts # 设备树
├── board/
│ └── samsung/
│ └── smdk4412/ # 你的板子目录
│ ├── Kconfig
│ ├── MAINTAINERS
│ ├── Makefile
│ └── board.c # 板级初始化
├── configs/
│ └── smdk4412_defconfig # 编译配置文件
├── drivers/
│ ├── dwmci/ # SD/MMC 驱动
│ ├── exynos/ # 三星 SoC 驱动
│ └── ddr/ # DDR 驱动
└── include/
└── configs/
└── smdk4412.h # 板级头文件3.1 新建 board/samsung/mybsp
# 1. 复制参考板目录作为模板
cp -r board/samsung/smdk4412 board/samsung/mybsp
# 2. 修改目录名和文件名
cd board/samsung/mybsp
mv board.c board_mybsp.c
# 3. 修改 Makefile
# 原来:obj-y += board.o
# 改为:obj-y += board_mybsp.o3.2 configs/defconfig
# configs/mybsp_defconfig
CONFIG_ARM=y
CONFIG_ARCH_EXYNOS=y
CONFIG_CPU_EXYNOS4412=y
CONFIG_TARGET_MYBSP=y
CONFIG_SOC_EXYNOS4412=y
# 串口配置
CONFIG_DEBUG_UART=y
CONFIG_DEBUG_UART_S5PV210=y
CONFIG_DEBUG_UART_BASE=0x13800000
CONFIG_BAUDRATE=115200
# DDR 配置
CONFIG_DDR_16BIT=y
CONFIG_DDR_16BIT_BUS_WIDTH=32
# 启动命令
CONFIG_BOOTCOMMAND="load mmc 0:1 0x4000_7000 uImage; load mmc 0:1 0x4000_1000 mybsp.dtb; bootm 0x4000_7000"
CONFIG_BOOTARGS="console=ttySAC0,115200 root=/dev/mmcblk0p2"4. 核心移植文件解析
4.1 board.c — 板级初始化
// board/samsung/mybsp/board_mybsp.c
#include <common.h>
#include <clk.h>
#include <asm/io.h>
#include <asm/arch/clock.h>
#include <asm/arch/gpio.h>
#include <asm/arch/pinmux.h>
DECLARE_GLOBAL_DATA_PTR;
int board_init(void)
{
/* 1. GDTM 初始化 (Global Data Structure) */
gd = (gd_t *)CONFIG_SYS_INIT_SP_ADDR;
/* 2. 时钟初始化 */
struct exynos_clock *clock = (struct exynos_clock *)EXYNOS4_CLOCK_BASE;
/* 设置 ARMCLK = 1500MHz, HCLK = 150MHz, PCLK = 75MHz */
clock->apll_con1 = 0x20203; @ APLL M=250, P=6, S=1 → 1.5GHz
clock->set_div = 0x141403; @ DIV_CORE2=2, HCLK_G=2, PCLK_G=2
/* 3. GPIO 引脚配置 */
/* UART: GPA0_0=UART_TXD, GPA0_1=UART_RXD */
pinmux_config(GPA0, 0, UART0);
/* 4. PMIC (电源管理) 初始化 */
/* 检查 POWEN_KEY 按下进入下载模式 */
if (gpio_get_value(GPIO_PWREN_KEY) == 0) {
/* 进入 U-Boot 下载模式 (fastboot) */
board_fastboot_setup();
}
/* 5. 存储介质初始化 */
#ifdef CONFIG_MMC
exynos_mmc_init();
#endif
return 0;
}4.2 DDR 移植 — 最关键的部分
DDR 是移植中最复杂的一步。DDR 初始化失败会导致 U-Boot 运行不稳定。
// drivers/ddr/mybsp/ddr_ctrl.c (DDR 驱动核心)
/*
* DDR3 初始化序列(基于 JEDEC 标准)
* 实际参数需参考 DDR 芯片手册和 PCB 走线
*/
int ddr_init(void)
{
struct ddr_ctrl *ddr = (struct ddr_ctrl *)EXYNOS4_DMC_BASE;
/* Step 1: PHY 初始化 — 配置 DQ 引脚 */
ddr->phy_con0 = 0x11110010;
ddr->phy_con1 = 0x10000010;
ddr->phy_con2 = 0x00330000;
/* Step 2: 时序参数 (Timing Inject)
* 这些参数由 DDR 厂商提供(tRCD, tRP, tRL, tWL等)
*/
ddr->timing1 = 0x1845008A; @ tRDC=18, tRP=18, tRC=52
ddr->timing2 = 0x38387090; @ tRAS=36, tWTR=8, tRRD=8
ddr->timing3 = 0x2F186810; @ tXP=3, tXPDLL=36, tRTP=8
/* Step 3: DLL 使能(同步时钟输出) */
ddr->mdma_con = 0x00102312;
/* Step 4: DRAM Mode Register 配置 */
/* MR0: 突发长度 8, 突发类型 顺序 */
ddr->mode = 0x00000330; @ BL=8, BT=0, CL=6, WR=5
ddr->mode >> start_dll(); @ 启动 DLL
/* Step 5: ODT 配置(片上终结电阻) */
#ifdef DDR_ODT_ON
ddr->phy_odt = 0x00000F03;
#endif
/* Step 6: ZQ 校准 */
ddr->ZQ_con = 0xC000107A; @ ZQ 校准使能
/* Step 7: 刷新配置 */
ddr->refresh_con = 0x000003C3; @ 7.8us 刷新一次
/* Step 8: 验证 DRAM 工作 */
mem_ctrl_write(0x00000000, 0x12345678);
if (mem_ctrl_read(0x00000000) != 0x12345678) {
debug("DDR read/write test failed!n");
return -1;
}
printf("DDR3 %dMB initialized OKn", DDR_SIZE_MB);
return 0;
}4.3 DDR Training — 信号校准
DDR 训练是让控制器自动调整 DQ 采样窗口的过程:
/*
* Read DQS Gate Training(读门限训练)
* DQS 是 DDR 数据的同步时钟,门限不准会读错数据
*/
int ddr_read_gate_training(void)
{
struct ddr_ctrl *ddr = (struct ddr_ctrl *)EXYNOS4_DMC_BASE;
/* 写入已知模式 */
mem_ctrl_write_pattern(DDR_TEST_ADDR, 0x55555555);
mem_ctrl_write_pattern(DDR_TEST_ADDR+4, 0xAAAAAAAA);
/* 手动扫描最佳 DQ 采样点 */
for (int dqs_delay = 0; dqs_delay < 0x40; dqs_delay++) {
ddr->phy_dqs_con = dqs_delay;
u32 val1 = mem_ctrl_read(DDR_TEST_ADDR);
u32 val2 = mem_ctrl_read(DDR_TEST_ADDR+4);
if (val1 == 0x55555555 && val2 == 0xAAAAAAAA) {
printf("Read gate training PASS: DQS delay=0x%02Xn", dqs_delay);
return 0;
}
}
printf("Read gate training FAILEDn");
return -1;
}5. 引脚复用 (Pinmux) 配置
引脚复用是让同一个 GPIO 引脚在不同功能间切换:
// arch/arm/include/asm/arch-exynos/pinmux.h
/* GPA0 组: UART0 */
static inline void pinmux_config(int group, int pin, int func)
{
struct exynos_pinmux *pmu = (struct exynos_pinmux *)EXYNOS4_PINMUX_BASE;
u32 reg = pmu->con[group][pin];
reg &= ~(0xF << (pin * 4)); @ 清除 4 位
reg |= (func & 0xF) << (pin * 4); @ 写入功能值
pmu->con[group][pin] = reg;
/* 设置上下拉 */
pmu->pull[group][pin] = EXYNOS_PULL_UP; @ 上拉
}
/* 典型引脚配置表 */
static const struct pin_config board_pinmux[] = {
/* UART0: GPA0_0=TXD, GPA0_1=RXD */
{ GPA0, 0, PIN_UART0_TX },
{ GPA0, 1, PIN_UART0_RX },
/* SD/MMC: GPK2 组 */
{ GPK2, 0, PIN_SD_CMD },
{ GPK2, 1, PIN_SD_CLK },
{ GPK2, 2, PIN_SD_D0 },
{ GPK2, 3, PIN_SD_D1 },
{ GPK2, 4, PIN_SD_D2 },
{ GPK2, 5, PIN_SD_D3 },
/* I2C0: GPD0 组 */
{ GPD0, 0, PIN_I2C0_SCL },
{ GPD0, 1, PIN_I2C0_SDA },
};6. 编译与烧写
# 1. 交叉编译工具链(ARM Cortex-A9)
export CROSS_COMPILE=arm-none-linux-gnueabi-
export ARCH=arm
# 2. 清理后编译
make distclean
make mybsp_defconfig
make -j$(nproc)
# 3. 生成的镜像
# u-boot.bin — 完整 U-Boot (烧到 SD/eMMC)
# u-boot-spl.bin — SPL (烧到 SRAM 起始位置)
# spl/u-boot-spl.bin — SPL 二进制文件
# 4. 烧写到 SD 卡
dd if=u-boot-spl.bin of=/dev/sdb bs=512 seek=8
dd if=u-boot.bin of=/dev/sdb bs=512 seek=16896
# 5. SD 卡分区布局
# ┌────────────────┬──────────────────────────────┐
# │ 0-7 sectors │ 8-16895 sectors │ ← 16896 = 8 + 16*1024
# │ SPL (4KB) │ U-Boot (8MB) │
# ├────────────────┼──────────────────────────────┤
# │ 16896-... │ ... │
# │ Kernel │ Device Tree │
# └────────────────┴──────────────────────────────┘6.1 编译错误排查
# 错误1: "libgcc is different for guest vs target"
export CROSS_COMPILE=arm-none-linux-gnueabi-
# 错误2: "compiler version mismatch"
# 检查 GCC 版本和 U-Boot 支持的版本
arm-none-linux-gnueabi-gcc --version
# 错误3: DDR 初始化失败 — 串口输出卡住
# 检查:
# 1. 串口波特率是否正确 (115200 8N1)
# 2. DDR timing 参数是否匹配 DDR 芯片手册
# 3. GPIO 引脚配置是否正确
# 调试技巧:U-Boot 启动时加 delay 留出串口接收时间
# 在 board.c 的 board_init() 开头加:
printf("board_init startn");
udelay(1000000); @ 1秒延迟,给串口时间7. Windows WDM vs U-Boot 分层对比
| 层次 | U-Boot | Windows WDM Driver |
|---|---|---|
| 初始化 | board_init() | DriverEntry() |
| 硬件抽象 | arch层面 drivers | HAL.DLL |
| 配置存储 | env vars (环境变量) | Registry |
| 驱动模型 | driver model | KMDF/WDM |
| 启动参数 | bootargs | BCD store |
| 升级方式 | mmc update / tftp | Windows Update |
8. 调试实践
# 串口调试:连接 TTL 串口 (FTDI 3.3V)
# TX ──── RX
# RX ──── TX
# GND ─── GND
screen /dev/ttyUSB1 115200
# U-Boot 内置调试命令
printenv # 查看所有环境变量
setenv baudrate 115200
saveenv # 保存环境变量到存储介质
md 0x40000000 16 # 内存显示 (dump)
mw 0x40000000 0x1234ABCD 1 # 内存写入
mmc info # MMC 信息
mmc read 0x4000_7000 0x100 0x200 # 读取 MMC
# 网络烧写 (在 U-Boot 中)
setenv ipaddr 192.168.1.100
setenv serverip 192.168.1.1
tftp 0x4000_7000 uImage.bin # 从 TFTP 服务器下载9. 小结
U-Boot 移植步骤:
1. 硬件资料准备
└── SoC datasheet + DDR timing + schematic
2. 创建板级目录
└── board/samsung/mybsp/
3. 配置 Pinmux
└── 引脚复用:UART/I2C/MMC
4. DDR 初始化
└── timing 参数 + Training
5. 存储驱动
└── eMMC/SD/MMC
6. 环境变量
└── bootcmd / bootargs
7. 编译烧写
└── make + dd
每块板子都不一样,移植的核心是:
把"初始化代码"适配到"你的硬件设计"。
像 Windows 装机一样,先 BIOS 设置好,才能跑系统。微信公众号
· 深入浅出技术解读 ☕️
· 欢迎搜索关注「码农的螺丝刀」
评论