建议先看《[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)》,根据例子了解一下上电之后的BL0\BL1\BL2阶段,以及各个阶段的运行位置、功能。
一、说明
1、疑问
前面文章中《[uboot] (第三章)uboot流程——uboot-spl代码流程》中,最后uboot-spl的操作是调用board_init_f了,在board_init_f中实现了加载BL2镜像(uboot.bin)到ddr上,然后跳转到相应位置。
那么前面有个坑,就是tiny210的board_init_f中,是 如何实现加载BL2镜像(uboot.bin)到ddr上,然后跳转到BL2的相应地址上?
这个也就是本文里面的主要介绍的:
s5pv210是如何实现把镜像从存储设备上加载到ddr上的?
2、原理
通过《[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)》, s5pv210的BL0会根据启动模式从相应存储设备上获取BL1的镜像到DDR上。
例如,当启动模式是SD boot的时候,BL0会从SD卡上获取BL1镜像,加载到对应位置上。当启动模式是nand flash boot的时候,BL0会从nand flash上获取BL1镜像,加载到对应位置上。
如下表格(从《[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)》截取的一部分)
模式 | 硬件支持 | BL1镜像存放起始位置 | BL1镜像是否需要header | 加载位置 | 跳转位置 |
---|---|---|---|---|---|
Nand Boot | Nand flash | page0 | 是 | 0xD002_0000 | 0xD002_0010 |
SD / MMC Boot | SD / MMC | block1 | 是 | 0xD002_0000 | 0xD002_0010 |
同时,BL0是固化在s5pv210的IROM中的,是芯片在出厂的时候已经实现好了的。相应的,其从存储设备(SD/nand/eMMC)上获取镜像的代码就也就必须是已经实现好的。
既然,从存储设备(SD/nand/eMMC)上获取镜像的代码已经实现好了,也就是存储设备代码拷贝函数,并且在BL1阶段和BL2阶段可能也要需要使用到相同的功能,于是s5pv210将这部分代码的也是固化在IROM中,并且将这些函数接口的函数指针存放到某一个固定的地方。
当BL1或者BL2需要使用到从存储设备(SD/nand/eMMC)上获取镜像的函数接口时,就可以从对应的地方获取函数指针。
而剩下的,也就是最重要的是,这些函数指针被存放在上什么地方?这也是我们后续重点关注的地方。
为什么是函数指针而不是函数地址呢,是因为可以直接使用函数指针来调用函数。
二、s5pv210代码拷贝函数介绍
现在,我们要关心的是s5pv210的存储设备代码拷贝函数的函数原型以及函数指针的存放位置。
主要参考文档:《S5PV210-iROM-ApplicationNote-Preliminary-20091126》
先一段文档里面的说明:
s5pv210的IROM中集成了一些启动存储设备的块拷贝函数。因此,开发者不需要实现对应的存储设备的拷贝函数了。这些拷贝函数可以实现从存储设备到memory的拷贝。当IROM启动完成之后,开发者也可以直接使用这些拷贝函数了。
这和我们第一节中,说明的是一致的。
1、存储设备代码拷贝函数(Device Copy Function)地址存储位置
这里要特别注意,这里既不是指函数指针,也不是指函数地址,而是指函数指针被存放到的位置。
可以通过函数指针被存放到的位置找到函数指针,调用函数指针就可以调用到相应的函数了。
函数指针的地址 | 函数名 | 功能 |
---|---|---|
0xD0037F90 | NF8_ReadPage_Adv | is advanced NF8_ReadPage function. |
0xD0037F94 | NF16_ReadPage_Adv | is advanced NF16_ReadPage function. |
0xD0037F98 | CopySDMMCtoMem | can copy any data from SD/MMC device to SDRAM. |
0xD0037F9C | CopyMMC4_3toMem | can copy any data from eMMC device to SDRAM. |
0xD0037FA0 | CopyOND_ReadMultiPages | can copy any data from OneNand device to SDRAM. |
0xD0037FA4 | CopyOND_ReadMultiPages_Adv | can copy any data from OneNand device to SDRAM. |
0xD0037FA8 | Copy_eSSDtoMem | can copy any data from eSSD device to SDRAM. |
0xD0037FAC | Copy_eSSDtoMem_Adv | can copy any data from eSSD device to SDRAM. |
0xD0037FB0 | NF8_ReadPage_Adv128p | is advanced NF8_ReadPage function. |
更详细的功能介绍建议参考文档《S5PV210-iROM-ApplicationNote-Preliminary-20091126》。
这些地址是递归的,位于0xD003_7F90-0xD003_7FB0
通过文档《[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)》,我们知道了0xD002_0000-0xD003_7FFF是属于IRAM的区域,所以说’函数指针存放地址’是放在IRAM上的,按照我猜,应该是在启动过程中,BL0把这些IROM上这些固化的函数的地址写到IRAM对应的位置上,例如0xD0037F90,对应位置就可以当作一个函数指针来使用。
2、函数原型说明
文档中的函数原型和使用过程中的函数原型有所差异。个人感觉可能是文档有问题,但是这里还是以文档为准。使用介绍中,再以实际的使用为准。
因为tiny210只支持SD boot和nand flash的方式,下面我就以SD和nand flash作为存储设备的拷贝函数做介绍。
(1)CopySDMMCtoMem
从SD/MMC拷贝(加载)块到内存中的函数。
函数结构如下(也就是一个简单的使用范例)
1
2
3
4
5
6
7
8
9
10
11
|
/**
* This Function copy MMC(MoviNAND/iNand) Card Data to memory.
* Always use EPLL source clock.
* This function works at 20Mhz.
* @param u32 StartBlkAddress : Source card(MoviNAND/iNand MMC)) Address.(It must block address.)
* @param u16 blockSize : Number of blocks to copy.
* @param u32* memoryPtr : Buffer to copy from.
* @param bool with_init : determined card initialization.
* @return bool(u8) - Success or failure.
*/
#define CopySDMMCtoMem(z,a,b,c,e) (((bool(*)(
int
, unsigned
int
, unsigned
short
, unsigned
int
*, bool))(*((unsigned
int
*)
0xD0037F98
)))(z,a,b,c,e))
|
可以简单推测出函数原型为bool CopySDMMCtoMem(int, unsigned int, unsigned short, unsigned int*, bool)。
argv0=起始块号 argv1=块数量 argv2=要拷贝到内存的什么位置上,也就是目标地址指针。 argv3=是否需要检测初始化 argv4=返回参数,用于决定是成功还是失败。 注意:以上是文档的说明,
但是在实际使用中,实际的函数原型如下:
boot copy_sd_to_ddr(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
可见,文档是有问题的。后续会说明。
(2)NF8_ReadPage_Adv
从nand flash拷贝(加载)页到内存中的函数。
8bit ECC校验的函数结构如下(也就是一个简单的使用范例)
1
2
3
4
5
6
7
8
|
/**
* This Function copies a block of page to destination memory.( 8-Bit ECC only )
* @param uint32 block : Source block address number to copy.
* @param uint32 page : Source page address number to copy.
* @param uint8 *buffer : Target Buffer pointer.
* @return int32 - Success or failure.
*/
#define NF8_ReadPage_Adv (a,b,c) (((
int
(*)(uint32, uint32, uint8*))(*((uint32 *)
0xD0037F90
)))(a,b,c))
|
其参数意义参考注释,这里不加以说明了。
三、s5pv210代码拷贝函数使用示例
s5pv210代码拷贝函数是以函数指针的方式进行使用的,因为,在这里先介绍一个简单的函数指针的使用示例。
1、函数指针简单示例说明
假设有一个函数原型是int function(int a, int b),其函数指针被存放在了0x10的位置上。
那么我们调用该函数的方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
#define CopySDMMCtoMem
0xD0037F98
// 上述我们已经说明了,CopySDMMCtoMem的函数指针是存放在0xD0037F98的。
typedef u32(*copy_sd_mmc_to_mem)
(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
// 这样就定义了copy_sd_mmc_to_mem为u32 (*)(u32, u32, u16, u32 *, u32)类型,后续可以直接用于函数指针变量的定义和类型强制转换
// argv0=通道号
// argv1=起始块号
// argv2=块数量
// argv3=要拷贝到的目标地址
// argv4不解
// 以下就是加载BL2镜像到DDR中的主体了,是在BL1中执行的。
// 主要关心一下函数指针的获取、转化和调用过程
void
copy_bl2_to_ddr(
void
)
{
u32 sdmmc_base_addr;
copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem)(*(u32*)CopySDMMCtoMem);
// 这个是重点函数,也是要重点理解的地方
// (u32*)CopySDMMCtoMem先将0xD003_7F98强制类型转换成一个指针
// (*(u32*)CopySDMMCtoMem)从0xD003_7F98中获取函数指针
// (copy_sd_mmc_to_mem)对得到的函数指针进行强制类型转换,赋值给copy_bl2,后续直接调用copy_bl2即可。
sdmmc_base_addr = *(u32 *)SDMMC_BASE;
// 获取通道,SD有两个通道,这部分在文档上没有说明,这里我们也不关心。
if
(sdmmc_base_addr == SDMMC_CH0_BASE_ADDR)
copy_bl2(
0
, MOVI_BL2_SDCARD_POS, MOVI_BL2_BLKCNT, (u32 *)CONFIG_SYS_TEXT_BASE,
0
);
// 直接调用copy_bl2,就可以调用到具体的函数指针了
// MOVI_BL2_SDCARD_POS表示我们把BL2的镜像放到了哪个块上
// MOVI_BL2_BLKCNT表示BL2占用的块长度
// CONFIG_SYS_TEXT_BASE=0x23E0_0000,定义在include/configs/tiny210.h中,表示我们要把BL2的镜像(uboot.bin)放到什么位置上。
if
(sdmmc_base_addr == SDMMC_CH2_BASE_ADDR)
copy_bl2(
2
, MOVI_BL2_SDCARD_POS, MOVI_BL2_BLKCNT, (u32 *)CONFIG_SYS_TEXT_BASE,
0
);
}
|
通过上述代码uboot-spl调用copy_bl2_to_ddr就可以实现把uboot.bin从SD中拷贝到0x23E0_0000,后面跳转到0x23E0_0000后,就可以直接进入uboot了。
uboot中把kernel、rootfs、dtb从SD中拷贝到对应位置上也是通过一样的方法,具体请自行参考代码copy_kernel_to_ddr的实现。
综上,就实现了利用IROM的代码从SD上拷贝镜像、代码到ddr中。
nand flash后续实现之后再进行补充。