版权声明:本文为博主原创文章,未经博主允许不得转载。
申明
本着学习交流的原则, 将个人移植u-boot的过程做一个记录. 文章参考了csdn blog里面的很多内容, 有的已经记不得出处了, 只好把当时的摘要直接贴出来. 如果冒犯, 还请见谅. 如有侵权, 请与我邮件联系. 谢谢!
SPL
SPL是uboot第一阶段执行的代码. 主要负责搬移uboot第二阶段的代码到内存中运行. SPL是由固化在芯片内部的ROM引导的. 我们知道很多芯片厂商固化的ROM支持从nandflash, SDCARD等外部介质启动. 所谓启动, 就是从这些外部介质中搬移一段固定大小(4K/8K/16K等)的代码到内部RAM中运行. 这里搬移的就是SPL. 在最新版本的uboot中, 可以看到SPL也支持nandflash, SDCARD等多种启动方式. 当SPL本身被搬移到内部RAM中运行时, 它会从nandflash, SDCARD等外部介质中搬移uboot第二阶段的代码到外部内存中.
SPL的文件组成
当我们在uboot下执行make命令的时候, 它最核心的功能是执行Makefile中的all目标编译出相应的文件. 我们来看看这个all目标
all依赖于
$(ALL-y) 和 $(SUBDIR_EXAMPLES), 这里我只关注ALL-y, 如下:
因为本节是讨论SPL, 所以我们只关注其中的一句
ALL-$(CONFIG_SPL) += $(obj)spl/u-boot-spl.bin
这句话表明
- 必须定义CONFIG_SPL才能编译出spl的bin: 一般在"include/configs/${CONFIG_NAME}.h"中定义
- SPL的bin依赖于u-boot-spl.bin
接着往下看
这里可以发现, u-boot-spl.bin依赖于 $(SUBDIR_TOOLS) depend
- $(SUBDIR_TOOLS) : 暂不分析
- depend: 参考附录中的depend
- 进入spl目录, 执行make all
接下来进入spl目录, 看看它的Makefile : 这里只分析与SPL相关的部分
- export CONFIG_SPL_BUILD: 在接下来的编译中, 这个变量为y. 从后面的分析中可以看到, uboot的stage1, stage2阶段的代码用的是同一个Start.S, 只不过在Start.S中用#ifdef CONFIG_SPL_BUILD这种条件编译来区分. 类似的还有其他一些文件.
-
- 我们这里没有定义CONFIG_SPL_START_S_PATH, 所以START_PATH := $(CPUDIR)
- 依赖start.o, 综合来看, 就是要把CPUDIR下的start.S编译进来.
- 依赖lib$(ARCH).o, 具体来看, 就是依赖arch/arm/lib/libarm.o
- 依赖lib$(CPU).o, 具体来看, 就是依赖arch/arm/cpu/armv7/libarmv7.o
- 如果定义了SOC, 则依赖lib$(SOC).o, 具体来看, 就是依赖arch/arm/cpu/s5pc1xx/libs5pc1xx.o
- 依赖lib$(BOARD).o, 具体来看, 就是依赖board/samsung/tiny210/libtiny210.o
- 如果HAVE_VENDOR_COMMON_LIB为y, 则依赖lib$(VENDOR).o, 具体来看, 就是依赖board/samsung/common/libsamsung.o
- 根据具体配置, 选择相应的依赖关系
- 如果SOC为exynos, 则依赖libs5p-common.o, 我们这里的SOC为s5pc1xx, 所以不依赖
- 给START和LIBS加上前缀, $(SPLTREE), 具体来看, 就是编译过程中生成的.o文件都会放到spl/目录下面
- 找到spl的链接配置文件, 具体来看, 用的是arch/arm/cpu下的u-boot-spl.lds
- all: $(ALL-y), 还记得本篇上面说的, 进入到spl目录下之后干啥事吗? 没错, 执行make all, 其实执行的就是这里的all目标. 它依赖$(ALL-y). 具体的依赖关系就不赘述了, 顺藤摸瓜即可
- ifdef CONFIG_SAMSUNG: 这个是针对Samsung平台的特殊之处.
- 利用tools/mk$(BOARD)spl这个工具, 将u-boot-spl.bin转为$(BOARD)-spl.bin
- 转换的本质, 就是在u-boot-spl.bin前面加入head info.关于head info是什么, 可以参考文档S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf. 顺便可以理解一下samsung芯片的启动流程
- tools/mk$(BOARD)spl这个工具是在tools/Makefile里面生成的.
OK, 分析结束, 接下来, 就基于我们上面的分析开始分析代码了.
SPL代码分析
u-boot-spl.lds: 它的位置在上文中我们分析了
- 根据u-boot-spl.lds中的规则, 我们知道CPUDIR/start.o被放在了最前面. 它所对应的文件就是arch/arm/cpu/armv7/start.S
start.S
下面我们看看start.S
- _start是我们在lds里面指定的ENTRY(_start)
- 首先会跳转到reset处
- ldr pc, _xxx定义的是中断向量表
- 当CONFIG了SPL_BUILD之后, 一旦发生异常中断, 就会进入死循环. 所以我们的SPL里面不允许出发异常中断
- 不过正常的uboot(即stage2阶段)还是可以处理异常中断的.
当初次上电或者复位时, Uboot最新运行的就是这里的代码
- bl save_boot_params: 如果没有重新定义save_boot_params,则使用<arch/arm/cpu/armv7/start.S>中的save_boot_params。其不做任何事情,直接返回
- 禁止FIQ, IRQ; 设置CPU工作在SVC32模式
- bl cpu_init_cp15: (I/D-Cache, MMU, TLBs),具体见下面代码中注释
- bl cpu_init_crit : 主要是设置CPU的PLL, GPIO管脚复用, memory等. 具体见下面
- bl _main : 跳转到<arch/arm/lib/crt0.S>中的_main. 具体见下面
cpu_init_cp15
cpu_init_crit
- b lowlevel_init : 跳转到<arch/arm/cpu/armv7/lowlevel_init.S>中的lowlevel_init
lowlevel_init.S
lowlevel_init
以前老版本的uboot, lowlevel_init一般都是在board/xxx下面的板级文件夹下面实现的. 现在直接放到CPUDIR下面了, 那它做了什么事情呢
- 对stack pointer赋值成CONFIG_SYS_INIT_SP_ADDR
- 确保sp是8字节对齐
- 将gdata的地址存入到r9寄存器中
- 跳转到 s_init: 这个s_init就需要芯片厂商或者我们自己在板级文件里面实现了. 它主要做的事情
- setup pll, mux, memory
我个人感觉, 新版本的uboot在CPUDIR下实现了一个lowlevel_init.S文件, 主要目标是初始化sp, 这样s_init就可以用C语言实现了. 而以前的老版本里面, s_init里面要做的事情都是用汇编做的.
crt0.S
_main
- 重新对SP赋值, 确认sp是8字对齐
- 在栈顶保留一个global_data的大小, 这个global_data是uboot里面的一个全局数据, 很多地方都会用到. 俗称 gd_t
- 确认更新后的sp是8字对齐
- r9指向global_data, 后面别的地方想用global_data时候, 可以直接从r9里面获取地址.
- r0赋值0
- bl board_init_f: 跳转到board_init_f. 在编译SPL时, 分析Makefile可以看出, 该函数的实现是在<arch/arm/lib/spl.c>.
arch/arm/lib/spl.c
board_init_f
- __weak: 表明该函数可以被重新定义
- 对BSS段进行清零操作
- gd = &gdata;
- gd的定义在DECLARE_GLOBAL_DATA_PTR <arch/arm/include/asm/global_data.h>
- #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
- 还记得r9这个寄存器吗, 在上面初始化过了
- gdata的定义在本文件中: gd_t gdata __attribute__ ((section(".data")));
- 它是一个 gd_t 也就是global_data类型的变量
- __attribute__表示这个变量会被放到".data"这个输入段中. 连接器会把输入段按照链接脚本(u-boot-spl.lds)里面指定的规则存放到输出段.
- 为什么会有这个赋值操作, 不太明白...
- board_init_r : 在编译SPL时, 分析Makefile可以看出, 该函数的实现是在<common/spl/spl.c>
common/spl/spl.c
board_init_r
- 如果定义了:CONFIG_SYS_SPL_MALLOC_START, 则进行memory的malloc池初始化. 以后调用malloc就在这个池子里面分配内存
- 如果没有定义:CONFIG_PPC, 则进行timer的初始化. <arch/arm/cpu/armv7/s5p-common/timer.c>里面定义
- SPL阶段, 如果还需要做什么初始化动作, 可以放在这里. 具体的实现可以在BOARDDIR下面.
- 必须实现spl_boot_device, 返回是从哪个外部设备启动的(NAND/SDCARD/NOR...). 可以厂商或者自己在BOARDDIR下面实现
- 将image从具体的外部设备中load到ram中. 这里暂时先不分析具体的load过程.
- 判断image的类型
- 如果是u-boot,则直接break, 去运行u-boot
- 如果是Linux,则启动Linux
至此,SPL结束它的生命,控制权交于u-boot或Linux
在接下来的一篇中, 我们会分析当控制权交给u-boot之后, uboot的运行流程
总结
SPL移植注意点
- s_init: C语言实现此函数, 必须的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成以下功能
- 设置CPU的PLL, GPIO管脚复用, memory等
- spl_board_init: C语言实现, 可选的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成的功能自己决定
- spl_boot_device: C语言实现, 必须的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成的功能
- 返回是从什么设备启动的(NAND/SDCARD/Nor ...). 像Atmel, Samsung的芯片, 都是有办法做这个事情的.
版权声明:本文为博主原创文章,未经博主允许不得转载。
申明
本着学习交流的原则, 将个人移植u-boot的过程做一个记录. 文章参考了csdn blog里面的很多内容, 有的已经记不得出处了, 只好把当时的摘要直接贴出来. 如果冒犯, 还请见谅. 如有侵权, 请与我邮件联系. 谢谢!
SPL
SPL是uboot第一阶段执行的代码. 主要负责搬移uboot第二阶段的代码到内存中运行. SPL是由固化在芯片内部的ROM引导的. 我们知道很多芯片厂商固化的ROM支持从nandflash, SDCARD等外部介质启动. 所谓启动, 就是从这些外部介质中搬移一段固定大小(4K/8K/16K等)的代码到内部RAM中运行. 这里搬移的就是SPL. 在最新版本的uboot中, 可以看到SPL也支持nandflash, SDCARD等多种启动方式. 当SPL本身被搬移到内部RAM中运行时, 它会从nandflash, SDCARD等外部介质中搬移uboot第二阶段的代码到外部内存中.
SPL的文件组成
当我们在uboot下执行make命令的时候, 它最核心的功能是执行Makefile中的all目标编译出相应的文件. 我们来看看这个all目标
all依赖于 $(ALL-y) 和 $(SUBDIR_EXAMPLES), 这里我只关注ALL-y, 如下:
因为本节是讨论SPL, 所以我们只关注其中的一句 ALL-$(CONFIG_SPL) += $(obj)spl/u-boot-spl.bin
这句话表明
- 必须定义CONFIG_SPL才能编译出spl的bin: 一般在"include/configs/${CONFIG_NAME}.h"中定义
- SPL的bin依赖于u-boot-spl.bin
接着往下看
- $(SUBDIR_TOOLS) : 暂不分析
- depend: 参考附录中的depend
- 进入spl目录, 执行make all
- export CONFIG_SPL_BUILD: 在接下来的编译中, 这个变量为y. 从后面的分析中可以看到, uboot的stage1, stage2阶段的代码用的是同一个Start.S, 只不过在Start.S中用#ifdef CONFIG_SPL_BUILD这种条件编译来区分. 类似的还有其他一些文件.
- 我们这里没有定义CONFIG_SPL_START_S_PATH, 所以START_PATH := $(CPUDIR)
- 依赖start.o, 综合来看, 就是要把CPUDIR下的start.S编译进来.
- 依赖lib$(ARCH).o, 具体来看, 就是依赖arch/arm/lib/libarm.o
- 依赖lib$(CPU).o, 具体来看, 就是依赖arch/arm/cpu/armv7/libarmv7.o
- 如果定义了SOC, 则依赖lib$(SOC).o, 具体来看, 就是依赖arch/arm/cpu/s5pc1xx/libs5pc1xx.o
- 依赖lib$(BOARD).o, 具体来看, 就是依赖board/samsung/tiny210/libtiny210.o
- 如果HAVE_VENDOR_COMMON_LIB为y, 则依赖lib$(VENDOR).o, 具体来看, 就是依赖board/samsung/common/libsamsung.o
- 根据具体配置, 选择相应的依赖关系
- 如果SOC为exynos, 则依赖libs5p-common.o, 我们这里的SOC为s5pc1xx, 所以不依赖
- 给START和LIBS加上前缀, $(SPLTREE), 具体来看, 就是编译过程中生成的.o文件都会放到spl/目录下面
- 找到spl的链接配置文件, 具体来看, 用的是arch/arm/cpu下的u-boot-spl.lds
- all: $(ALL-y), 还记得本篇上面说的, 进入到spl目录下之后干啥事吗? 没错, 执行make all, 其实执行的就是这里的all目标. 它依赖$(ALL-y). 具体的依赖关系就不赘述了, 顺藤摸瓜即可
- ifdef CONFIG_SAMSUNG: 这个是针对Samsung平台的特殊之处.
- 利用tools/mk$(BOARD)spl这个工具, 将u-boot-spl.bin转为$(BOARD)-spl.bin
- 转换的本质, 就是在u-boot-spl.bin前面加入head info.关于head info是什么, 可以参考文档S5PV210_iROM_ApplicationNote_Preliminary_20091126.pdf. 顺便可以理解一下samsung芯片的启动流程
- tools/mk$(BOARD)spl这个工具是在tools/Makefile里面生成的.
- 利用tools/mk$(BOARD)spl这个工具, 将u-boot-spl.bin转为$(BOARD)-spl.bin
SPL代码分析
u-boot-spl.lds: 它的位置在上文中我们分析了
- 根据u-boot-spl.lds中的规则, 我们知道CPUDIR/start.o被放在了最前面. 它所对应的文件就是arch/arm/cpu/armv7/start.S
start.S
下面我们看看start.S
- _start是我们在lds里面指定的ENTRY(_start)
- 首先会跳转到reset处
- ldr pc, _xxx定义的是中断向量表
- 当CONFIG了SPL_BUILD之后, 一旦发生异常中断, 就会进入死循环. 所以我们的SPL里面不允许出发异常中断
- 不过正常的uboot(即stage2阶段)还是可以处理异常中断的.
- bl save_boot_params: 如果没有重新定义save_boot_params,则使用<arch/arm/cpu/armv7/start.S>中的save_boot_params。其不做任何事情,直接返回
- 禁止FIQ, IRQ; 设置CPU工作在SVC32模式
- bl cpu_init_cp15: (I/D-Cache, MMU, TLBs),具体见下面代码中注释
- bl cpu_init_crit : 主要是设置CPU的PLL, GPIO管脚复用, memory等. 具体见下面
- bl _main : 跳转到<arch/arm/lib/crt0.S>中的_main. 具体见下面
cpu_init_cp15
cpu_init_crit
- b lowlevel_init : 跳转到<arch/arm/cpu/armv7/lowlevel_init.S>中的lowlevel_init
lowlevel_init.S
lowlevel_init
- 对stack pointer赋值成CONFIG_SYS_INIT_SP_ADDR
- 确保sp是8字节对齐
- 将gdata的地址存入到r9寄存器中
- 跳转到 s_init: 这个s_init就需要芯片厂商或者我们自己在板级文件里面实现了. 它主要做的事情
- setup pll, mux, memory
- setup pll, mux, memory
我个人感觉, 新版本的uboot在CPUDIR下实现了一个lowlevel_init.S文件, 主要目标是初始化sp, 这样s_init就可以用C语言实现了. 而以前的老版本里面, s_init里面要做的事情都是用汇编做的.
crt0.S
_main
- 重新对SP赋值, 确认sp是8字对齐
- 在栈顶保留一个global_data的大小, 这个global_data是uboot里面的一个全局数据, 很多地方都会用到. 俗称 gd_t
- 确认更新后的sp是8字对齐
- r9指向global_data, 后面别的地方想用global_data时候, 可以直接从r9里面获取地址.
- r0赋值0
- bl board_init_f: 跳转到board_init_f. 在编译SPL时, 分析Makefile可以看出, 该函数的实现是在<arch/arm/lib/spl.c>.
arch/arm/lib/spl.c
board_init_f
- __weak: 表明该函数可以被重新定义
- 对BSS段进行清零操作
- gd = &gdata;
- gd的定义在DECLARE_GLOBAL_DATA_PTR <arch/arm/include/asm/global_data.h>
- #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
- 还记得r9这个寄存器吗, 在上面初始化过了
- #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
- gdata的定义在本文件中: gd_t gdata __attribute__ ((section(".data")));
- 它是一个 gd_t 也就是global_data类型的变量
- __attribute__表示这个变量会被放到".data"这个输入段中. 连接器会把输入段按照链接脚本(u-boot-spl.lds)里面指定的规则存放到输出段.
- 为什么会有这个赋值操作, 不太明白...
- gd的定义在DECLARE_GLOBAL_DATA_PTR <arch/arm/include/asm/global_data.h>
- board_init_r : 在编译SPL时, 分析Makefile可以看出, 该函数的实现是在<common/spl/spl.c>
common/spl/spl.c
board_init_r
- 如果定义了:CONFIG_SYS_SPL_MALLOC_START, 则进行memory的malloc池初始化. 以后调用malloc就在这个池子里面分配内存
- 如果没有定义:CONFIG_PPC, 则进行timer的初始化. <arch/arm/cpu/armv7/s5p-common/timer.c>里面定义
- SPL阶段, 如果还需要做什么初始化动作, 可以放在这里. 具体的实现可以在BOARDDIR下面.
- 必须实现spl_boot_device, 返回是从哪个外部设备启动的(NAND/SDCARD/NOR...). 可以厂商或者自己在BOARDDIR下面实现
- 将image从具体的外部设备中load到ram中. 这里暂时先不分析具体的load过程.
- 判断image的类型
- 如果是u-boot,则直接break, 去运行u-boot
- 如果是Linux,则启动Linux
- 如果是u-boot,则直接break, 去运行u-boot
至此,SPL结束它的生命,控制权交于u-boot或Linux
在接下来的一篇中, 我们会分析当控制权交给u-boot之后, uboot的运行流程
总结
SPL移植注意点
- s_init: C语言实现此函数, 必须的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成以下功能
- 设置CPU的PLL, GPIO管脚复用, memory等
- 设置CPU的PLL, GPIO管脚复用, memory等
- spl_board_init: C语言实现, 可选的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成的功能自己决定
- spl_boot_device: C语言实现, 必须的. 如果是厂商提供的, 一般在arch/arm/cpu/xxx下面. 如果厂商没有提供, 我们可以在BOARDDIR下面实现. 主要完成的功能
- 返回是从什么设备启动的(NAND/SDCARD/Nor ...). 像Atmel, Samsung的芯片, 都是有办法做这个事情的.