Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3168122
  • 博文数量: 685
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 5303
  • 用 户 组: 普通用户
  • 注册时间: 2014-04-19 14:17
个人简介

文章分类

全部博文(685)

文章存档

2015年(116)

2014年(569)

分类: 嵌入式

2014-07-27 03:38:46

原文地址:

按照三星《S5PV210_UM_REV1.1》手册上说明的启动流程为:S5PV210上电将从IROM(interal ROM)处执行固化的启动代码,它对时钟等初始化、对启动设备进行判断,并从启动设备中复制BL1(最大16KB)到IRAM(0xd002_0000处,其中0xd002_0010之前的16个字节储存的BL1的校验信息和BL1尺寸)中,并对BL1进行校验,校验OK转入BL1进行执行;

首先解释一下我认为的BL0、BL1、BL2:

(1)BL0:是指S5PV210的IROM中固化的启动代码
(2)BL1:是指在IRAM自动从外扩存储器(nand /sd/usb)中拷贝的uboot.bin二进制文件的头最大16K代码

(3)BL2:是指在代码定向后在内存中执行的的UBOOT的完整代码

(4)三者之间关系是:(Interal ROM固话代码)BL0将BL1(bootloader的前16kB)加载到iRAM;BL1然后在iRAM中运行将BL2(其实整个bootloader)加载到SDRAM(DDR);BL2加载内核;BL就是bootloader的简写;

刚开始的搞210时,我们编译的代码都必须通过一个工具xxx210.xxx(如mktiny210.exe),这个是为什么呢,它的作用就是在我们生成的bin文件前面加上16个字节, 下面就说说前16字节的内容是什么:

 

S5PV210的iRAM的地址范围0xD002_0000~0xD003_FFFF,而上电后我们的iROM执行完固化的代码,并将boot设备中的代码拷贝到iRAM中,并跳转到0xD002_0010处执行,问题就在0xD002_0000~0xD002_0010这16字节数据该填写什么?

 

16字节头部信息,排列格式如下:

_____________________________

|0x0地址:BL1 size |

|0x4地址:必须设置为0 (是规定)|

|0x8地址:CheckSum |

|0xc地址:必须设置为0 (是规定)|

但是为什么要加上16字节呢,其实理解为了ECC校验用的就行了;

上面16字节的内容不是乱加的哦,下面给出uboot中参考代码,可以用的:

 

#include 
#include 
#include 
#define SEEK_SET           0 /* 文件开头的标记 */
#define SEEK_CUR    1 /* 当前位置的标记 */
#define SEEK_END    2 /* 文件末尾的标记 */
#define BUFSIZE                 (24*1024)
#define IMG_SIZE                (24*1024)
#define SPL_HEADER_SIZE         16
#define SPL_HEADER              "S5PC110 HEADER  "
int main (int argc, char *argv[])
{
FILE        *fp;    /* 定义一个文件指针 */
char        *Buf, *a;
int     BufLen;
int     nbytes, fileLen;
unsigned int    checksum, count;
int     i;
if (argc != 3) /* 如果参数个数错误,打印帮助信息,只能有两个参数*/
{
/* 应用工具时,格式必须是 ./mktiny210spl.exe old.bin new.bin */
printf("Usage: mkbl1  \n");
return -1;
}//这一点大家要注意,main函数其实也是可以接收参数的,argc就表示了参数的个数,
//argv就是参数的内容;
BufLen = BUFSIZE;
Buf = (char *)malloc(BufLen); /* 动态分配一段24k的内存空间 */
if (!Buf) /* 分配失败,将返回0 */
{
printf("Alloc buffer failed!\n");
return -1;
}
memset(Buf, 0x00, BufLen); /* 将上面分配的空间清零 */
fp = fopen(argv[1], "rb"); /* 以读二进制方式打开没有头部信号的old.bin文件 */
if( fp == NULL)
{
printf("source file open error\n");
free(Buf); /* 如果打开失败,释放掉原来分配的内存,否则会造成内存泄漏 */
return -1;
}
fseek(fp, 0L, SEEK_END); /* 让文件位置指针指向文件末尾,便于下行的统计大小的操作 */
fileLen = ftell(fp); /* 用于得到文件位置指针当前位置相对于文件首的偏移字节数,即文件大小*/
fseek(fp, 0L, SEEK_SET); /* 让文件位置指针指向文件开始 */
/* 如果old.bin文件的大小小于规定的最大大小,则count等于该文件的大小,否则等于最大大小 */
count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))
? fileLen : (IMG_SIZE - SPL_HEADER_SIZE);
memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE); /* 拷贝16字节的数据到Buf中,即初始化头部信息的位置 */
nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp); /* 将编译生成的old.bin文件拷贝到buf中,紧接着头部信息开始拷贝 */
if ( nbytes != count ) /* 返回值等于拷贝的元素的个数 */
{
printf("source file read error\n"); /* 如果个数和实际的不相等,则失败 */
free(Buf); /* 释放内存 */
fclose(fp); /* 关闭文件 */
return -1;
}
fclose(fp); /* 关闭文件 */
/* 以下三行,用于动态生成checksum,公式见上面的尝试一 */
a = Buf + SPL_HEADER_SIZE;
for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++)
checksum += (0x000000FF) & *a++;
/* 将checksum写入buf的第三个字节处,即该是checksum的位置处 */
a = Buf + 8;
*( (unsigned int *)a ) = checksum;
fp = fopen(argv[2], "wb"); /* 以二进制写的方式创建一个新的二进制文件 */
if (fp == NULL)
{
printf("destination file open error\n");
free(Buf); /* 释放内存 */
return -1;
}
a = Buf; /* 指向内存的首地址 */
nbytes  = fwrite( a, 1, BufLen, fp); /* 把buf中的数据写入新创建的bin文件 */
if ( nbytes != BufLen ) /* 返回值等于写入的元素的个数 */
{
printf("destination file write error\n");
free(Buf); /* 释放内存 */
fclose(fp);/* 关闭文件 */
return -1;
}
free(Buf); /* 释放内存 */
fclose(fp);/* 关闭文件 */
return 0;
}

 

这个代码怎么用呢,在vc或者gcc,甚至Tc中编译,生成一个xxx.exe文件,我生成的是stepmk210.exe,然后按照下面的格式编译bin文件,生成的新bin文件就是我们要用的;

 

=======================================================================

 

二、启动流程分析

按照三星《S5PV210_UM_REV1.1》手册上说明的启动流程,S5PV210上电将从IROM处执行固化的启动代码,对时钟等初始化、对启动设备进行判断,并从启动设备中复制BL1(最大16KB)到IRAM(0xd002_0000处,其中0xd002_0010之前的16个字节储存的BL1的校验信息和BL1尺寸)中,并对BL1进行校验,校验OK转入BL1进行执行;BL1继续初始化,并将BL2复制到IRAM中并对其校验,OK后转入BL2;BL2则要进行比较复杂的初始化,包括DRAM的初始化,完成后将OS代码复制到DRAM中,并转入到OS中执行并完成启动引导。

但是,上述的IRAM只有96K大小,对于日益复杂的uboot来说,肯定是不够的,所以使用UBOOT启动引导的时候,也没全按三星手册上说的执行,具体如下: 

首先解释一下我认为的BL0、BL1、BL2:

BL0:是指S5PV210的IROM中固化的启动代码

BL1:是指在IRAM中执行的UBOOT的部分代码

BL2:是指在内存中执行的的UBOOT的完整代码

此版本的UBOOT,其实会编译两个UBOOT.bin,即最终生成的tiny210-uboot.bin包括两部分,前段是spl(重用u-boot中既有的驱动,来生成更小的secondaryprogramloader)文件夹内的tiny210-spl.bin,后段是用于在RAM中执行的完整UBOOT代码。所以学习UBOOT代码是也要两个部分来看。

1、第一步与三星手册上一致,执行IROM内固化的代码,即BL0,BL0会将存储于启动设备的UBOOT代码的前16KB:tiny210-spl.bin复制到IRAM。(tiny210-spl.bin设置为24KB了,但实际绝不会超过16KB,后面全填充的0,所以拷不完也OK。这里是liukun321为了统一MMC与NAND启动而作的修改,也许改成16K就应该OK了,应为IROM里的代码最多拷贝16K进IRAM嘛,一会改改试试。所以呢实际上执行BL1就是执行tiny210-spl.bin。在UBOOT的代码中并不会看到tiny210-spl.bin和完整的UBOOT代码分开两套文件进行编写,都在同样的文件中,只是通过宏定义来控制执行流程,SPL文件夹内由自己的Makefile文件。

2、第一步入口程序是/arch/arm/cpu/armv7/start.S中的_start,设置异常向量地址(地址由ARM 架构决定,不能改变),执行reset,进入SVC32模式,并调用cpu_init_crit对ARM的CP15寄存器进行设置,如清空TLB,关闭MMU等,进入/board/samsung/tiny210/lowlevel_init.S中的lowlevel_init中,进行低级初始化,主要是对时钟、内存、串口、nand等进行初始化,为下一步的代码拷贝做准备。

3、从lowlevel_init出来之后执行call_board_init_f,通过读取OMR_OFFSET寄存器的值,对启动设备进行判断,看看是从mmc启动还是nand启动,判断好后跳入mmc_boot.c或nand_cp.c中,将UBOOT代码拷贝到内存中,地址为_TEXT_BASE:在tiny210.h中定义为0x23e0_0000,换成别的地方也可以,比如3000_0000(注:在跳转到内存中执行之前,所有的语句都是基于PC寻址的,也就是说和代码具体存放的地址没有关系,进行跳转的时候都会计算出跳转目标相对与PC的位置,在跳转过去。)。这里有一个很大的问题,稍后讨论。拷贝完成后直接冲0x23e0_0000出开始执行。那么执行的是什么呢,那先看看拷贝的是什么吧!

a.nand启动

在/arch/arm/cpu/armv7/s5pc1xx/nand_cp.c中,第105行:

for(i = (0x6000>>page_shift); i < (size>>page_shift);i++, buf+=(1<

nandll_read_page(buf,i, large_block);

}

指定了拷贝的起始page是nand起始24K之后的第一个PAGE,偏移0x6000的位置,该地址上存储的是完整的UBOOT在RAM中执行的完整启动代码

b.mmc启动

在/arch/arm/cpu/armv7/s5pc1xx/mmc_boot.c中,第49行:

#defineMOVI_BL2_POS ((eFUSE_SIZE / MOVI_BLKSIZE) +MOVI_BL1_BLKCNT + MOVI_ENV_BLKCNT)

经计算MOVI_BL2_POS位于第24.5K的位置,也即第49个block,偏移0x6200的位置,该地址上存储的同样是UBOOT在RAM中执行的完整启动代码

注:针对第一点提出的将24K偏移改成16K,经过验证:改成16KB并不可行,其实这里不仅仅是BL1的容量的问题,UBOOT_SPL.BIN实际上只有4K不到,所以对于nand启动来说4KB就足够了,但是对于MMC启动来说却不行,因为SD卡的布局并不一样,它在BL1与BL2之间还插入了16KB的ENV区,所以只能是24K,而对NAND的ENV区在BL2之后了。

所以,无论是MMC启动还是NAND启动,将代码copy到DRAM中并跳转之后,执行的是DRAM中的_start入口函数,重新执行一遍UBOOT,但是一些宏定义已经发生变化,如CONFIG_SPL_BUILD被取消定义,相应的代码也不会执行。

4、因为第一编执行UBOOT的时候ARM的状态都已经初始化OK了,包括时钟,内存等,第二遍执行时只是跳转到内存中执行了,所以这里判断是否在DRAM中执行,如果在内存中执行,则可以直接跳到call_board_init_f去进行班级初始化。当然,源码并不是这样执行的,而是重新执行RESET、LOWLEVEL_INIT等函数,但我验证过,可以不用执行。有兴趣的同志也可以验证一下。

5 、进入board_init_f以后将:

a、对gd_t数据结构(include/asm/global_data.h中定义)进行初始化,并填入相关数据,如内存大小、UBOOT镜像长度等;

b、对打印CPU信息、UBOOT版本信息等,并对串口、console进一步初始化,输出nand信息,对内存大小进行计算,并打印出来。

 

c、为UBOOT的重定向设置物理地址,重定向后UBOOT可使用的end地址为0x3fff_0000,

UBOOT镜像起始地址为0x3ff8_5000。

打印调试信息:

TLBtable at: 3fff0000

Topof RAM usable for U-Boot at: 3fff0000

Reserving424k for U-Boot at: 3ff85000

Reserving928k for malloc() at: 3fe9d000

Reserving24 Bytes for Board Info at: 3fe9cfe8

Reserving120 Bytes for Global Data at: 3fe9cf70

NewStack Pointer is: 3fe9cf60

第一个疑问:

第201行:

/*Pointer is writable since we allocated a register for it */

gd= (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);

打印CONFIG_SYS_INIT_SP_ADDR的值为0x20ff_ff80,为啥gd_t数据结构会在这个位置,之后好像没有看到重定向gd数据结构的代码

6、完成board_init_f中的处理后,转入start.S中的relocate_code函数进行代码搬运及重定向。搬运完成之后,清空BSS后转入board.c中的board_init_r函数执行。

第二个疑问:

这里重定向的意义是什么?以前S3C6410或S3C2410的UBOOT中对代码进行重定向是将代码从FLASH或nand或MMC中复制到内存中,但是这里之前的代码已经将UBOOT从nand或mmc复制到了DRAM中,而且目前也是运行在DRAM中。那么为什么在之前的代码中不直接将UBOOT复制到0x3ff8_5000,就可以省去relocate_code步骤?我尝试将CONFIG_SYS_TEXT_BASEdefine为0x3ff8_5000过之后,UBOOT可以启动,但所有的CMD都丢失了,比如:Unknowncommand 'printenv' - try 'help'?目前也没搞明白哪里出问题了!

7、在board_init_r中,会清空malloc()区的内存,对nand、MMC等进行初始化,执行env_relocate()对环境变量进行初始化(第6步中的问题不知道是不是由于这个函数没有处理好引起的,研究了半天也没发现问题),如果将环境变量保存在NAND中,则会将其读入到内存中来。如果出现什么问题的话,就使用默认的环境变量代替。此步还会对网络、中断等进行初始化。全部初始化完成之后则跳入/common/main.c中的main_loop()中执行,等待用户命令或自动加载内核。

 

三、总结

本文对S5PV210平台UBOOT的启动流程做了分析,并对一些有疑问的地方做了验证,比如哪一块代码会执行两遍,哪一块代码第二编制行的时候可以省去等。

验证的方法一般就是电灯或者判断代码执行的位置等。另外,调试过程需要多次的进行烧写,如果每次都敲命令进行烧写的华会很费时间的,所以最好是在/common/main.c中写一个自己的的菜单,并且将烧写任务设为一个命令就行了,这样会轻松一些。

本文到这里也只是初步了解了一下UBOOT的启动流程,很多具体的代码还有很多不明白的地方,比如nand、mmc、网卡的驱动啥的,慢慢在具体分析吧。还有,上面的两个问题,尤其是第二个问题,知道的同志吱一声,讨论讨论哈!


阅读(1552) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~