S5PV210开发板UBOOT第二阶段启动流程详解

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_f

board_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中,包含以下关键步骤:

  1. 设置PHY控制寄存器(DMC_PHYCONTROL)
  2. 配置时序参数(tRFC、tWR等)
  3. 启用自动刷新(auto-refresh)
  4. 设置内存分区(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

初始化过程需要注意:

  1. 引脚复用配置(GPIO_MP0_*)
  2. 时钟使能(CLK_SRC_FSYS)
  3. 电压选择(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区域,处理流程包括:

  1. 检查CRC校验
  2. 加载到内存环境
  3. 设置默认值

关键数据结构:

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已支持设备树机制。处理流程:

  1. 加载dtb到内存
  2. 解析设备树结构
  3. 匹配对应驱动

关键函数:

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 启动卡住排查步骤

  1. 确认串口输出是否正常
  2. 检查DDR初始化是否正确(测量内存总线信号)
  3. 验证时钟配置(使用示波器测量关键时钟)
  4. 检查重定位地址是否正确

8.2 性能优化建议

  • 调整DDR时序参数提升带宽
  • 启用MMC高速模式(HS200)
  • 优化环境变量存储布局

8.3 典型错误示例

/* 错误:未正确对齐内存地址 */ void *addr = (void *)0x30000001; memcpy(addr, src, len); // 会导致数据异常 /* 正确做法 */ void *addr = (void *)((ulong)0x30000000 + 0x1000);

在完成多个S5PV210项目后,我发现UBOOT第二阶段最关键的其实是时序控制 - 无论是内存访问时序、总线仲裁还是启动流程的节奏把控,都需要精确的延时控制。建议在移植时准备一个精确的示波器,实测各关键信号的时序关系。