S5PV210开发板UBOOT第二阶段启动流程详解
1. UBOOT启动第二阶段概述
在S5PV210开发板的UBOOT移植过程中,启动第二阶段是整个引导流程的核心环节。这个阶段主要负责完成硬件平台的初始化、内存映射配置、设备树加载等关键操作,为后续内核引导奠定基础。与第一阶段(汇编语言实现的底层初始化)不同,第二阶段完全由C语言实现,具有更好的可读性和可维护性。
我曾在多个基于S5PV210的工业控制项目中移植过UBOOT,发现启动第二阶段的稳定性直接决定了系统能否正常引导。这个阶段会完成以下关键任务:
- 内存控制器(DRAM Controller)的精细调校
- 时钟树(Clock Tree)的最终配置
- 存储设备(MMC/NAND)的驱动加载
- 环境变量的初始化与校验
- 设备树(Device Tree)的解析准备
2. 源码结构与入口分析
2.1 代码目录结构
UBOOT第二阶段的主要源码集中在以下目录:
arch/arm/cpu/armv7/s5pv210/ common/ drivers/关键文件包括:
arch/arm/lib/board.c:包含board_init_f和board_init_r函数common/main.c:主循环处理逻辑arch/arm/cpu/armv7/start.S:第一阶段到第二阶段的过渡代码
2.2 执行流程解析
从汇编到C语言的过渡发生在_main标签(位于arch/arm/lib/crt0.S):
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) bic sp, sp, #7 bl board_init_fboard_init_f函数完成早期的C环境初始化后,会调用重定位函数将UBOOT自身代码复制到DRAM的高地址区域。这个过程中需要特别注意:
gd->relocaddr = gd->mon_len + gd->bd->bi_dram[0].start; memcpy((void *)gd->relocaddr, (void *)gd->mon_len, gd->ram_size);重要提示:S5PV210的内存控制器初始化必须在重定位前完成,否则会导致拷贝失败。这是新手最容易踩的坑。
3. 关键初始化过程详解
3.1 内存控制器配置
S5PV210采用双通道DDR2控制器,配置参数存储在include/configs/x210.h:
#define CONFIG_SYS_SDRAM_BASE 0x30000000 #define CONFIG_SYS_LOAD_ADDR (CONFIG_SYS_SDRAM_BASE + 0x00100000)实际初始化代码在arch/arm/cpu/armv7/s5pv210/dmc_init.c中,包含以下关键步骤:
- 设置PHY控制寄存器(DMC_PHYCONTROL)
- 配置时序参数(tRFC、tWR等)
- 启用自动刷新(auto-refresh)
- 设置内存分区(Memory Partition)
void mem_ctrl_init(void) { struct s5pv210_dmc *dmc = (struct s5pv210_dmc *)S5PV210_DMC_BASE; /* PHY初始化 */ writel(PHY_CON0_VAL, &dmc->phycontrol0); udelay(500); /* 时序配置 */ writel(TIMING_ROW_VAL, &dmc->timingrow); writel(TIMING_DATA_VAL, &dmc->timingdata); /* 通道配置 */ writel(MEM_CFG_VAL, &dmc->memconfig); }3.2 时钟系统初始化
S5PV210的时钟树配置非常复杂,涉及以下关键组件:
- APLL/MPLL/EPLL/VPLL时钟源
- 分频器(DIV)配置
- 门控时钟(Gate)管理
典型配置流程:
void clock_init(void) { /* 1. 设置PLL锁定时间 */ writel(PLL_LOCKTIME, &clk->apll_lock); /* 2. 配置APLL输出频率 */ writel(APLL_VAL, &clk->apll_con); /* 3. 设置分频比 */ writel(CLK_DIV0_VAL, &clk->clk_div0); /* 4. 选择时钟源 */ writel(CLK_SRC0_VAL, &clk->clk_src0); }实测经验:时钟配置不当会导致串口输出乱码,建议先使用保守的低频配置,稳定后再逐步提高频率。
4. 设备驱动加载
4.1 MMC/SD卡驱动
S5PV210内置3个MMC控制器,UBOOT中对应的驱动位于:
drivers/mmc/s5p_mmc.c初始化过程需要注意:
- 引脚复用配置(GPIO_MP0_*)
- 时钟使能(CLK_SRC_FSYS)
- 电压选择(1.8V/3.3V)
int mmc_initialize(bd_t *bis) { struct s5pv210_gpio *gpio = (struct s5pv210_gpio *)S5PV210_GPIO_BASE; /* 配置GPIO为MMC功能 */ writel(0x22222222, &gpio->mp0_0con); writel(0x00002222, &gpio->mp0_1con); /* 初始化控制器 */ return s5p_mmc_init(0, 400000); }4.2 串口调试输出
调试阶段最常用的功能,相关驱动在:
drivers/serial/serial_s5p.c波特率计算公式:
baud = (source_clock / (16 * divisor))典型配置:
void serial_init(void) { struct s5pv210_uart *uart = (struct s5pv210_uart *)S5PV210_UART0_BASE; /* 115200波特率 @ PCLK=66.5MHz */ writel(35, &uart->ubrdiv); // divisor = (66500000/(115200*16))-1 writel(0x3, &uart->ulcon); // 8N1模式 }5. 环境变量处理
UBOOT环境变量存储在特定的Flash区域,处理流程包括:
- 检查CRC校验
- 加载到内存环境
- 设置默认值
关键数据结构:
typedef struct environment_s { uint32_t crc; /* CRC32校验值 */ unsigned char flags;/* 活动/冗余标志 */ char data[ENV_SIZE];/* 实际环境数据 */ } env_t;环境初始化函数调用链:
env_init() → env_relocate() → env_import()常见问题:环境变量损坏会导致UBOOT卡在启动阶段,可以通过
env default -a命令恢复默认值。
6. 设备树处理流程
虽然S5PV210传统上不使用设备树,但新版UBOOT已支持设备树机制。处理流程:
- 加载dtb到内存
- 解析设备树结构
- 匹配对应驱动
关键函数:
int fdtdec_setup(void) { void *blob = (void *)gd->fdt_blob; /* 基础检查 */ if (fdt_check_header(blob)) return -1; /* 解析内存节点 */ fdtdec_setup_memory_banksize(); /* 初始化驱动 */ fdtdec_setup_drivers(); }7. 启动内核准备
最终跳转到内核的代码路径:
do_bootm() → do_bootm_linux() → boot_jump_linux()ARM架构特有的处理:
void boot_jump_linux(bootm_headers_t *images) { void (*kernel_entry)(int zero, int arch, uint params); kernel_entry = (void (*)(int, int, uint))images->ep; /* 设置启动参数 */ cleanup_before_linux(); /* 跳转到内核 */ kernel_entry(0, machid, r2); }8. 调试技巧与常见问题
8.1 启动卡住排查步骤
- 确认串口输出是否正常
- 检查DDR初始化是否正确(测量内存总线信号)
- 验证时钟配置(使用示波器测量关键时钟)
- 检查重定位地址是否正确
8.2 性能优化建议
- 调整DDR时序参数提升带宽
- 启用MMC高速模式(HS200)
- 优化环境变量存储布局
8.3 典型错误示例
/* 错误:未正确对齐内存地址 */ void *addr = (void *)0x30000001; memcpy(addr, src, len); // 会导致数据异常 /* 正确做法 */ void *addr = (void *)((ulong)0x30000000 + 0x1000);在完成多个S5PV210项目后,我发现UBOOT第二阶段最关键的其实是时序控制 - 无论是内存访问时序、总线仲裁还是启动流程的节奏把控,都需要精确的延时控制。建议在移植时准备一个精确的示波器,实测各关键信号的时序关系。
