[project X] tiny210(s5pv210)从存储设备加载代码到DDR

[uboot] uboot流程系列
[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)
[uboot] (第一章)uboot流程——概述
[uboot] (第二章)uboot流程——uboot-spl编译流程
[uboot] (第三章)uboot流程——uboot-spl代码流程
[project X] tiny210(s5pv210)从存储设备加载代码到DDR

参考文档:
S5PV210-iROM-ApplicationNote-Preliminary-20091126
S5PV210_UM_REV1.1

建议先看《[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》
先一段文档里面的说明:
_The S5PV210 internally has a ROM code of block copy function for boot-u device. Therefore,
developer may not needs to implements device copy functions. These internal functions can copy any
data from memory devices to SDRAM. User can use these function after ending up the internal ROM
boot process completely._
简单翻译为如下:
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拷贝(加载)块到内存中的函数。
函数结构如下(也就是一个简单的使用范例)

/**
* 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校验的函数结构如下(也就是一个简单的使用范例)

/**
* 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)首先需要获取到函数指针
        因为函数指针存放在了0x10的位置上,所以先对0x10进行强制类型转换成一个指针(unsigned int *)0x10,
        然后*((unsigned int *)0x10)就可以获取到函数指针变量。
        暂时表示如下
            f_ptr=(*((unsigned int *)0x10))
(2)根据函数原型可以得到对应的函数指针类型是int (*)(int, int),所以需要对(1)的函数指针进行强制类型转换
        (int (*)(int, int))f_ptr,
        暂时表示如下
            ok_f_ptr=((int (*)(int, int))f_ptr)
(3)调用该函数的方法为ok_f_ptr(a, b)
        等价于*ok_f_ptr(a, b),一般都是使用ok_f_ptr(a, b)
综上,展开则为((int (*)(int, int))(*((unsigned int *)0x10)))(a,b)

2、tiny210从SD上加载代码实现

目前project X的设计中,tiny210的BL1(uboot-spl)是从SD上加载BL2的镜像(uboot.bin)到ddr上的。
并且BL2(uboot)也是从SD卡上加载kenrel、rootfs、dtb到ddr上的。原理都是一样的。
以下,我们以“BL1(uboot-spl)是从SD上加载BL2的镜像(uboot.bin)到ddr上的”的代码copy_bl2_to_ddr为例。
理解了上述1之后,以下代码就很好理解了。
代码copy_bl2_to_ddr如下:
board/samsung/tiny210/board.c中

#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后续实现之后再进行补充。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章