新版加里奥:U-boot启动分析

来源:百度文库 编辑:中财网 时间:2024/04/28 00:01:10
16) 补丁制作和测试
[root@samfei u-boot]# make distclean
[root@samfei 44b0]# mv u-boot u-boot.wx
[root@samfei 44b0]# cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/u-boot login
Logging in to server:anonymous@cvs.sourceforge.net:2401/cvsroot/u-boot
CVS password:
[root@samfei 44b0]# cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/u-boot co -P u-boot
cvs checkout: Updating u-boot
U u-boot/CHANGELOG
U u-boot/COPYING
U u-boot/CREDITS
U u-boot/MAINTAINERS
。。。。。。

[root@samfei 44b0]# diff -Naur u-boot u-boot.wx > uboot-wx-20050703.patch
[root@samfei 44b0]# vi uboot-wx-20050703.patch
去掉没有用的文件.做补丁的好处就是自己可以很清楚的知道哪些文件修改了!做完了,别忘了测试一下.

[root@samfei 44b0]# cd u-boot
[root@samfei u-boot]# patch -p1 < ../uboot-wx-20050703.patch
[root@samfei u-boot]# make wx20_config
[root@samfei u-boot]# make



由于没有接触过u-boot,因此第一步要做的就是google一些资料.
u-boot官方网站:http://sourceforge.net/projects/u-boot(比较慢)
http://u-boot.sourceforge.net/这个快一些.

DENX U-Boot及Linux使用手册: http://coosign.blogchina.com/coosign/1318487.html, 这是一遍翻译的文档.主要介绍了u-boot编译及使用的命令.但没有涉及新板子的移植流程.
根据上面手册中的说明,下载了最新的u-boot代码,命令:
#cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/u-boot login
#cvs -z6 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/u-boot co -P u-boot
 
u-boot流程
原因是比较明显的,就是参数和硬件没有符合.因此接下来做的事情就是仔细阅读S3C44B0CPU的硬件资料,包括内存配置,串口配置这些.我觉得要让串口出信息,最主要的就是CPU的初始化,内存配置和串口配置.另外就是了解u-boot程序运行流程,这个对了解需要设置的参数是非常有帮助的.我这里大致说一下u-boot运行流程.

入口: cpu/s3c44b0/start.S 
主要是CPU初始化( cpu_init_crit ), 调内存配置函数( lowlevel_init ), 然后判断u-boot是否从flash运行,如果是就把u-boot代码拷贝到TEXT_BASE定义的地方.然后转到start_armboot.
程序首先在Flash中运行CPU入口函数/cpu/arm920t/start.s。具体工作包括:设置异常的入口地址和异常处理函数;配置PLLCON寄存器,确定系统的主频;屏蔽看门狗和中断;初始化I/O寄存器;关闭MMU功能;调用/board/smdk2410中的memsetup.s,初始化存储器空间,设置刷新频率;将U-Boot的内容复制到SDRAM中;设置堆栈的大小,ldr pc, _start_armboot。

start_armboot: lib_arm/board.c
进行各种初始化设置,主要有:
cpu_init CPU相关的设置, 具体在./cpu/s3c44b0/cpu.c中.
board_init 板子相关的设置, 具体在board/wx/wx20/wx20.c 中
interrupt_init中断设置,我们没有用,具体在./cpu/s3c44b0/interrupts.c中
env_init 初始化环境变量, 具体要看用什么介质来存储环境变量,如果用flash来存贮, 程序在common/env_flash.c中.
init_baudrate 设置baud参数
serial_init 串口初始化, 具体在cpu/s3c44b0/serial.c.
console_init_f 控制台设置, 具体在./common/console.c

display_banner 显示标题.
dram_init 可用内存配置, 具体在./board/wx/wx20/wx20.c.
flash_init flash初始化,具体./drivers/cfi_flash.c.

接下来就是环境变量初始化, 网络初始化,最后到main_loop,可以运行各种命令.

"链接得到的起始地址为什么是TEXT_BASE,而不是0呢,所以现在只能够下载到ram中运行,但是无法烧写道flash中跑,这是怎么回事呢?u-boot中应该是start.S中的这段代码在flash中运行吧,后面就把自身拷贝到ram中TEXT_BASE地址处,为什么在链接文件中指定的_start的起始地址为0x00000000呢? "

“链接得到的起始地址为什么是TEXT_BASE,而不是0呢,”
因为u_boot如果从flash运行的话,那么它会将自己的代码拷贝到RAM中,然后运行。u-boot开始部分代码与编译的入口没有关系,而主要的代码是在RAM中运行,因此编译的入口地址是TEXT_BASE.因此u-boot既可以flash运行,也可以ram运行。
“为什么在链接文件中指定的_start的起始地址为0x00000000呢?”
lds文件中的起始地址为0x00000000是不起作用的,由-TTEXT_BASE参数替代的。
刚开始比较疑惑的原因是对:
126 relocate: /* relocate U-Boot to RAM */
127 adr r0, _start /* r0 <- current position of code */
adr这条指令没有理解正确,因为把它想成mv r0,_start了,实际上adr这里的_start是相对的,如果从flash运行的话,r0就是0, 如果从ram运行的话,r0就是C100000。

_start是整个程序的入口,他是ARM体系结构中的reset中断,可以看到,在reset中断向量中是一条跳转语句,b reset

   reset处理的流程如下:

  1. 将CPU设置为SVC32模式
  2. 如果没有定义了CONFIG_SKIP_LOWLEVEL_INIT,那么将忽略对cpu_init_crit,cpu_init_crit同样也是定义在start.s中.cpu_init_crit针对不同的处理器做的工作是不一样的.相关的宏定义有CONFIG_IMPA7,CONFIG_EP7312,CONFIG_ARMADILLO,CONFIG_NETARM,CONFIG_S3C4510B,CONFIG_INTEGRATOR,CONFIG_ARCH_INTEGRATOR.
  3. 接下来就是uboot的relocate,如果定义了CONFIG_SKIP_RELOCATE_UBOOT,uboot的relocate
  4. 初始化堆栈指针,这是调用函数的基础,在第二步中虽然调用了cpu_init_crit,但该函数使用汇编编写,没有进行堆栈操作.同时,ARM体系结构使用LR保存函数调用返回地址.所以可以在堆栈初始化前调用.
  5. 清除BBS段内容
  6. 调用start_armboot,进入C语言处理程序

同时start.s中包含对于中断服务程序的定义

  U- Boot 运行过程分析 

        U-Boot编译后的代码定义一般不超过100kB,并且这100 kB又分成两个阶段来执行。第一阶段的代码在start.s中定义,大小不超过10kB,它包括从系统上电后在0x00000000地址开始执行的部分。这部分代码运行在Flash中,它包括对S3C2410的一些寄存器的初始化和将U-Boot的第二阶段代码从Flash拷贝到SDRAM中。除去第一阶段的代码,剩下的部分都是第二阶段的代码。第二阶段的起始地址是在第一阶段代码中指定的,被复制到SDRAM后,就从第一阶段跳到这个入口地址开始执行剩余部分代码。第二阶段主要是进行一些BSS 段设置,堆栈的初始化等工作,最后会跳转到main-loop函数中,接受命令并进行命令处理。图1给出了U-Boot的详细的运行过程包括对内核的设置、装载及调用过程。 

        图1 U-Boot运行过程

u-boot的启动过程
系统启动的入口点。既然我们现在要分析u-boot的启动过程,就必须先找到u-boot最先实现的是哪些代码,最先完成的是哪些任务。另一方面一个可执行的image必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里。由此我们可以找到程序的入口点是在/board/lpc2210/u-boot.lds中指定的,其中ENTRY(_start)说明程序从_start开始运行,而他指向的是cpu/arm7tdmi/start.o文件。因为我们用的是ARM7TDMI的cpu架构,在复位后从地址0x00000000取它的第一条指令,所以我们将Flash映射到这个地址上,这样在系统加电后,cpu将首先执行u-boot程序。
u-boot的启动过程是多阶段实现的,分了两个阶段。依赖于cpu体系结构的代码(如设备初始化代码等)通常都放在stage1中,而且通常都是用汇编语言来实现,以达到短小精悍的目的。而stage2则通常是用C语言来实现的,这样可以实现复杂的功能,而且代码具有更好的可读性和可移植性。
下面我们先详细分析下stage1中的代码,如图2所示:

                                   图2  Start.s程序流程
代码真正开始是在_start,设置异常向量表,这样在cpu发生异常时就跳转到/cpu/arm7tdmi/interrupts中去执行相应得中断代码。在interrupts文件中大部分的异常代码都没有实现具体的功能,只是打印一些异常消息,其中关键的是reset中断代码,跳到reset入口地址。
reset复位入口之前有一些段的声明。在reset中,首先是将cpu设置为svc32模式下,并屏蔽所有irq和fiq。在u-boot中除了定时器使用了中断外,其他的基本上都不需要使用中断,比如串口通信和网络等通信等,在u-boot中只要完成一些简单的通信就可以了,所以在这里屏蔽掉了所有的中断响应。
初始化外部总线。这部分首先设置了I/O口功能,包括串口、网络接口等的设置,其他I/O口都设置为GPIO。然后设置BCFG0~BCFG3,即外部总线控制器。这里bank0对应Flash,设置为16位宽度,总线速度设为最慢,以实现稳定的操作;Bank1对应DRAM,设置和Flash相同;Bank2对应RTL8019。
接下来是cpu关键设置,包括系统重映射(告诉处理器在系统发生中断的时候到外部存储器中去读取中断向量表)和系统频率。
lowlevel_init,设定RAM的时序,并将中断控制器清零。这些部分和特定的平台有关,但大致的流程都是一样的。
下面就是代码的搬移阶段了。为了获得更快的执行速度,通常把stage2加载到RAM空间中来执行,因此必须为加载BootLoader的stage2准备好一段可用的RAM空间范围。空间大小最好是memorypage大小(通常是4KB)的倍数,一般而言,1M的RAM空间已经足够了。flash中存储的u-boot可执行文件中,代码段、数据段以及BSS段都是首尾相连存储的,所以在计算搬移大小的时候就是利用了用BSS段的首地址减去代码的首地址,这样算出来的就是实际使用的空间。程序用一个循环将代码搬移到0x81180000,即RAM底端1M空间用来存储代码。然后程序继续将中断向量表搬到RAM的顶端。由于stage2通常是C语言执行代码,所以还要建立堆栈去。在堆栈区之前还要将malloc分配的空间以及全局数据所需的空间空下来,他们的大小是由宏定义给出的,可以在相应位置修改。基本内存分布图:

图3  搬移后内存分布情况图
接下来是u-boot启动的第二个阶段,是用c代码写的,这部分是一些相对变化不大的部分,我们针对不同的板子改变它调用的一些初始化函数,并且通过设置一些宏定义来改变初始化的流程,所以这些代码在移植的过程中并不需要修改,也是错误相对较少出现的文件。在文件的开始先是定义了一个函数指针数组,通过这个数组,程序通过一个循环来按顺序进行常规的初始化,并在其后通过一些宏定义来初始化一些特定的设备。在最后程序进入一个循环,main_loop。这个循环接收用户输入的命令,以设置参数或者进行启动引导。
本篇文章将分析重点放在了前面的start.s上,是因为这部分无论在移植还是在调试过程中都是最容易出问题的地方,要解决问题就需要程序员对代码进行修改,所以在这里简单介绍了一下start.s的基本流程,希望能对大家有所帮助。

start.S 代码结构
1) 定义入口
一个可执行的Image 必须有一个入口点并且只能有一个唯一的全局入口,通常这个入口放在Rom(flash)的0x0 地址。例如start.S 中的
.globl _start
_start:
值得注意的是你必须告诉编译器知道这个入口,这个工作主要是修改连接器脚本文件(lds)。
2) 设置异常向量(Exception Vector)
异常向量表,也可称为中断向量表,必须是从0 地址开始,连续的存放。如下面的就包括了复位(reset),未定义处理(undef),软件中断(SWI),预去指令错误(Pabort),数据错误 (Dabort),保留,以及IRQ,FIQ 等。注意这里的值必须与uClinux 的vector_base 一致。这就是说如果uClinux 中vector_base(include/armnommu/proc-armv/system.h)定义为0x0c00 0000,则HandleUndef 应该在
0x0c00 0004。
3) 初始化CPU 相关的pll,clock,中断控制寄存器
依次为关闭watch dog timer,关闭中断,设置LockTime,PLL(phase lock loop),以及时钟。
这些值(除了LOCKTIME)都可从Samsung 44b0 的手册中查到。
对s3c2410的PLL的默认设置为:
/* FCLK:HCLK:PCLK = 1:2:4 */
 /* default FCLK is 120 MHz ! */    //注意:默认是120兆的
 ldr r0, =CLKDIVN
 mov r1, #3
 str r1, [r0]
如果要设置成202兆的呢?
屏蔽掉上面的然后:
#define CLK_CTL_BASE            0x4C000000
#define MDIV_200                0xa1
#define PDIV_200                0x3
#define SDIV_200                0x1
#define vMPLLCON_50             ((MDIV_50 << 12) | (PDIV_50 << 4) | (SDIV_50))
#define vMPLLCON_100        ((MDIV_100 << 12) | (PDIV_100 << 4) | (SDIV_100))
#define vMPLLCON_200        ((MDIV_200 << 12) | (PDIV_200 << 4) | (SDIV_200))
    @ initialise system clocks
        mov     r1, #CLK_CTL_BASE
        mvn     r2, #0xff000000
        str     r2, [r1, #0x0]  /*oLOCKTIME*/

        mov     r1, #CLK_CTL_BASE
        mov     r2, #0x3
        str     r2, [r1, #0x14]            /*oCLKDIVN*/   //注意:这个就相当于

        mrc     p15, 0, r1, c1, c0, 0           @ read ctrl register
        orr     r1, r1, #0xc0000000             @ Asynchronous
        mcr     p15, 0, r1, c1, c0, 0           @ write ctrl register

        @ now, CPU clock is 200 Mhz
        mov     r1, #CLK_CTL_BASE
        ldr     r2, mpll_200mhz
        str     r2, [r1, #0x4]               /*oMPLLCON*/
对S3C2440设置是:
/* FCLK:HCLK:PCLK = 1:3:6 */
 /* default FCLK is 379.2 MHz ! */
 ldr r0, =CLKDIVN
 mov r1, #7
 str r1, [r0]

    /* ghcstop add, ==> Clock asynchronous mode */ 
    mrc     p15, 0, r1, c1, c0, 0           @ read ctrl register
    orr     r1, r1, #0xc0000000            
    mcr     p15, 0, r1, c1, c0, 0           @ write ctrl register
在关闭中断的设置里对INTSUBMSK默认设置是0x3ff把它改成0x7fff
4) 初始化CPU相关设置,然后初始化内存控制器
bl cpu_init_crit
内存控制器,主要通过设置13 个从1c80000 开始的寄存器来设置,包括总线宽度,
8 个内存bank,bank 大小,sclk,以及两个bank mode。
 5) 将rom 中的程序复制到RAM 中
6) 初始化堆栈
进入各种模式设置相应模式的堆栈。
7) 转到RAM 中执行
使用指令ldr,pc,RAM 中C 函数地址就可以转到RAM 中去执行。

系统初始化部分
1. 串口部分
串口的设置主要包括初始化串口部分,值得注意的串口的Baudrate 与时钟MCLK 有很大关系,是通过:rUBRDIV0=((int)(MCLK/16./(gd ->baudrate) + 0.5) -1)计算得出。这可以在手册中查到。其他的函数包括发送,接收。这个时候没有中断,是通过循环等待来判断是否动作完成。
2. 时钟部分
3. flash 部分


程序跳转到SDRAM中执行/lib_arm/board.c中的start_armboot()函数。该函数将完成如下工作:
*设置通用端口rGPxCON;rGPxUP;设置处理器类型gd->bd->bi_arch_number = 193;设置启动参数地址gd->bd->bi_boot_params = 0x30000100;
* env_init:设置环境变量,初始化环境;
* init_baudrate:设置串口的波特率;
* serial_init:设置串口的工作方式;
* flash_init:设置ID号、每个分页的起始地址等信息,将信息送到相应的结构体中;
* dram_init:设置SDRAM的起始地址和大小;
* env_relocate:将环境变量的地址送到全局变量结构体中(gd->env_addr = (ulong)&(env_ptr->data));
* enable_interrupts:开启中断;
* main_loop:该函数主要用于设置延时等待,从而确定目标板是进入下载操作模式还是装载镜像文件启动内核。在设定的延时时间范围内,目标板将在串口等待输入命令,当目标板接到正确的命令后,系统进入下载模式。在延时时间到达后,如果没有接收到相关命令,系统将自动进入装载模式,执行bootm 30008000 30800000命令,程序进入do_bootm_linux()函数,调用内核启动函数;




















U-BOOT分析

    我之前发在 linuxforum上面, 不过讨论的人比较少 , 就贴在这里了。
具体可见:

http://www.linuxforum.net/forum/showflat.php?Cat=&Board=embedded&Number=651003&page=0&view=collapsed&sb=5&o=0&fpart=1#Post651082

再读bootm源码,现象正常!

呵呵 , 原来 tftp download到是否是 hdr->ih_load(0x30008000) 确实是有说道的。
原来对bootm源码有个地方理解错了, bootm 会根据 hdr->ih_comp 判断是否需要解压, 对于
mkimage -C none 的是不需要解压的。

1> mkimage -A arm -O linux -T kernel -C none -a 30008000 -e
30008040 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8040
这种情况 ,只能把 uImage download到 30008000的位置上 ,否则 从 30008040
是启动不了的。
2> mkimage -A arm -O linux -T kernel -C none -a 30008000 -e
30008000 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8000
这种情况download地址随便。
如果 tftp 下载地址==0x30008000 , 就从 0x30008040 启动就肯定OK 。
详细的请看代码。
研究了一下 u-boot-1.2.0 里面的 bootm的实现代码: do_bootm_linux() 函数 ,
原来由于我用mkimage的的时候的选项是 -C none , 所以下面的判断中 hdr->ih_comp =
IH_COMP_NONE
通过再读一遍代码, 也弄明白了上次问的问题, 就是 u-boot里面的解压和
内核自解压的区别: u-boot 里面的解压实际上是bootm 实现的 , 把 mkimage -C bzip2
或者gzip 生成的 uImage进行解压 ; 而kernel的自解压是对zImage进行解压,
发生在bootm解压之后。

呵呵 , 原来 tftp download到是否是 hdr->ih_load(0x30008000) 确实是有说道的。
原来对bootm源码有个地方理解错了, bootm 会根据 hdr->ih_comp 判断是否需要解压, 对于
mkimage -C none 的是不需要解压的。

1> mkimage -A arm -O linux -T kernel -C none -a 30008000 -e
30008040 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8040
这种情况 ,只能把 uImage download到 30008000的位置上 ,否则 从 30008040
是启动不了的。
2> mkimage -A arm -O linux -T kernel -C none -a 30008000 -e
30008000 -n linux-2.6.18.8 -d zImage uImage2.6.18.8-8000
这种情况download地址随便。
如果 tftp 下载地址==0x30008000 , 就从 0x30008040 启动就肯定OK 。
详细的请看代码。
研究了一下 u-boot-1.2.0 里面的 bootm的实现代码: do_bootm_linux() 函数 ,
原来由于我用mkimage的的时候的选项是 -C none , 所以下面的判断中 hdr->ih_comp =
IH_COMP_NONE
通过再读一遍代码, 也弄明白了上次问的问题, 就是 u-boot里面的解压和
内核自解压的区别: u-boot 里面的解压实际上是bootm 实现的 , 把 mkimage -C bzip2
或者gzip 生成的 uImage进行解压 ; 而kernel的自解压是对zImage进行解压,
发生在bootm解压之后。

switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == addr) { //如果你是tftp 到 0x30008000那么这里就命中了。
printf (" XIP %s ... ", name); //tftp download 这里, 确实打印出来了。
} else { //否则随便download到一个地址, 流程就进入这里了。
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG) //这个条件编译不成立
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data;
printf (" Loading %s ... ", name);
while (l > 0) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else/* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
------------------------实际执行的是这里---------------------------------
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
//从这里就可以看出一旦 tftpdownload到一个任意地址, bootm 都会把
//它(去掉header后的kernel搬运到0x30008000 的位置上,因此entry
//point 肯定要是 0x30008000 ,但是对于tftp 恰好download到
//0x30008000 的位置上的时候, 你会发现上面的代码中,就donothing
//了,因此就必须从 0x30008040

#endif/* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;
case IH_COMP_GZIP:
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len, // 把它解压到ih_load的位置上去
(uchar *)data, &len) != 0) {
puts ("GUNZIP ERROR - must RESET board to recover\n");
SHOW_BOOT_PROGRESS (-6);
do_reset (cmdtp, flag, argc, argv);
}
break;
.....
省略了 gzip和 bzip2的处理 , // 这里也说明 和 zImage的自解压是不一样的。
gzip : mkimage -C gzip ;
bzip2 mkimage -C bzip2 .
他是只uImage 本身被压缩了
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", hdr->ih_comp);
SHOW_BOOT_PROGRESS (-7);
return 1;
}
//解压完毕,对于我得板子来说,是解压到 0x8000的位置上去了,这是个物理地址
puts ("OK\n");
SHOW_BOOT_PROGRESS (7);

U-BOOT启动过程分析 经典文章汇集

6.3.3  U-Boot启动过程

尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。

开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数。函数调用顺序如图6.3所示。

看一下board/smsk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面详细分析一下程序跳转和函数的调用关系以及函数实现。

1cpu/arm920t/start.S

这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。

6.3  U-Boot启动代码流程图

 

_start: b       reset        //复位向量

       ldr   pc, _undefined_instruction

       ldr   pc, _software_interrupt

       ldr   pc, _prefetch_abort

       ldr   pc, _data_abort

       ldr   pc, _not_used

       ldr   pc, _irq      //中断向量

       ldr   pc, _fiq      //中断向量

 /* the actual reset code  */

reset:          //复位启动子程序

       /* 设置CPUSVC32模式 */

       mrs   r0,cpsr

       bic   r0,r0,#0x1f

       orr   r0,r0,#0xd3

       msr   cpsr,r0

/* 关闭看门狗 */

 

/* 这些初始化代码在系统重起的时候执行,运行时热复位从RAM中启动不执行 */

#ifdef CONFIG_INIT_CRITICAL

       bl    cpu_init_crit

#endif

 

relocate:                       /* 把U-Boot重新定位到RAM */

       adr   r0, _start          /* r0是代码的当前位置 */

       ldr   r1, _TEXT_BASE      /* 测试判断是从Flash启动,还是RAM */

       cmp     r0, r1          /* 比较r0r1,调试的时候不要执行重定位 */

       beq     stack_setup    /* 如果r0等于r1,跳过重定位代码 */

       /* 准备重新定位代码 */

       ldr   r2, _armboot_start

       ldr   r3, _bss_start

       sub   r2, r3, r2          /* r2 得到armboot的大小   */

       add   r2, r0, r2          /* r2 得到要复制代码的末尾地址 */

copy_loop: /* 重新定位代码 */

       ldmia r0!, {r3-r10}   /*从源地址[r0]复制 */

       stmia r1!, {r3-r10}   /* 复制到目的地址[r1] */

       cmp   r0, r2          /* 复制数据块直到源数据末尾地址[r2] */

       ble   copy_loop

 

       /* 初始化堆栈等    */

stack_setup:

       ldr   r0, _TEXT_BASE              /* 上面是128 KiB重定位的u-boot */

       sub   r0, r0, #CFG_MALLOC_LEN     /* 向下是内存分配空间 */

       sub   r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo结构体地址空间  */

#ifdef CONFIG_USE_IRQ

       sub   r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

       sub   sp, r0, #12     /* abort-stack预留3个字 */

clear_bss:

       ldr   r0, _bss_start      /* 找到bss段起始地址 */

       ldr   r1, _bss_end        /*  bss段末尾地址   */

       mov   r2, #0x00000000     /* 清零 */

clbss_l:str r2, [r0]        /* bss段地址空间清零循环...  */

       add   r0, r0, #4

       cmp   r0, r1

       bne   clbss_l

       /* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */

       ldr   pc, _start_armboot

_start_armboot: .word start_armboot     //start_armboot函数在lib_arm/board.c中实现

/* 关键的初始化子程序 */

cpu_init_crit:

……  //初始化CACHE,关闭MMU等操作指令

       /* 初始化RAM时钟。

       * 因为内存时钟是依赖开发板硬件的,所以在board的相应目录下可以找到memsetup.S文件。

       */

       mov   ip, lr

       bl    memsetup        //memsetup子程序在board/smdk2410/memsetup.S中实现

       mov   lr, ip

       mov   pc, lr

 

2lib_arm/board.c

start_armbootU-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。

 

 

void start_armboot (void)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong size;

       init_fnc_t **init_fnc_ptr;

       char *s;

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

       gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

       /* compiler optimization barrier needed for GCC >= 3.4 */

       __asm__ __volatile__("": : :"memory");

       memset ((void*)gd, 0, sizeof (gd_t));

       gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

       memset (gd->bd, 0, sizeof (bd_t));

       monitor_flash_len = _bss_start - _armboot_start;

       /* 顺序执行init_sequence数组中的初始化函数 */

       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                      hang ();

              }

       }

       /*配置可用的Flash */

       size = flash_init ();

       display_flash_config (size);

       /* _armboot_start u-boot.lds链接脚本中定义 */

       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

       /* 配置环境变量,重新定位 */

       env_relocate ();

       /* 从环境变量中获取IP地址 */

       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

       /* 以太网接口MAC 地址 */

       ……

       devices_init ();      /* 获取列表中的设备 */

       jumptable_init ();

       console_init_r ();    /* 完整地初始化控制台设备 */

       enable_interrupts (); /* 使能例外处理 */

       /* 通过环境变量初始化 */

       if ((s = getenv ("loadaddr")) != NULL) {

               load_addr = simple_strtoul (s, NULL, 16);

       }

       /* main_loop()总是试图自动启动,循环不断执行 */

       for (;;) {

               main_loop ();      /* 主循环函数处理执行用户命令 -- common/main.c */

       }

       /* NOTREACHED - no way out of command loop except booting */

}

 

3init_sequence[]

init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。

 

init_fnc_t *init_sequence[] = {

       cpu_init,             /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */

       board_init,           /* 基本的板级相关配置 -- board/smdk2410/smdk2410.c */

       interrupt_init,       /* 初始化例外处理 -- cpu/arm920t/s3c24x0/interrupt.c */

       env_init,             /* 初始化环境变量 -- common/cmd_flash.c */

       init_baudrate,        /* 初始化波特率设置 -- lib_arm/board.c */

       serial_init,          /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c */

       console_init_f,       /* 控制台初始化阶段1 -- common/console.c */

       display_banner,       /* 打印u-boot信息 -- lib_arm/board.c */

       dram_init,            /* 配置可用的RAM -- board/smdk2410/smdk2410.c */

       display_dram_config,  /* 显示RAM的配置大小 -- lib_arm/board.c */

       NULL,

};

 

U-BOOT start_armboot浅析

start_armboot浅析

ARM920t架构的CPU在完成基本的初始化后(ARM汇编代码),就进入它的C语言代码,而C语言代码的入口就是start_armboot, start_armbootlib_arm/board.c中。start_armboot将完成以下工作。

1.全局数据结构的初始化

比如gd_t结构的初始化:

251         gd = (gd_t*)(_armboot_start – CFG_MALLOC_LEN – sizeof(gd_t));

_armboot_startu-bootRAM中的开始地址(对于u-boot最终搬移到RAM中运行的情况),CFG_MALLOC_LENinclude/configs/.h中定义。

 

bd_t结构的初始化:

272         gd->bd = (bd_t*)((char*)gd-sizeof(bd_t));

u-bootbd_t结构紧接着gd_t结构存放。

 

内存分配的初始化

316         mem_malloc_init(_armboot_start-CFG_MALLOC_LEN);

经过以上的初始化后,u-boot在内存中的布局为(在底端为低地址)

-----------------------------

BSS

-----------------------------

U-BOOT TEXT/DATA

-----------------------------

CFG_MALLOC_LEN

-----------------------------

gd_t

-----------------------------

bd_t

-----------------------------

STACK

-----------------------------

2.调用通用初始化函数

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                     hang ();

              }

       }

init_sequence[]init_fnc_t函数指针数组,这个数组包含了众多初始化函数,比如cpu_initboard_init等。

 

3.初始化具体设备

这一部分包括对FlashLCD,网络的初始化等,例如

318  #if (CONFIG_COMMANDS & CFG_CMD_NAND)

       puts ("NAND:  ");

       nand_init();            /* go init the NAND */

#endif

 

367  devices_init();

 

386  #ifdef CONFIG_DRIVER_CS8900

       cs8900_get_enetaddr (gd->bd->bi_enetaddr);

#endif

4.初始化环境变量

环境变量在通用初始化函数里面,已经初始化一次(env_init),这里调用env_relocate对环境变量进行重新定位。在我的另一篇文章”U-BOOT ENV 实现中有对环境变量实现的讨论。

 

5.进入主循环

当然start_armboot除了以上工作外,还完成其它的初始化工作,具体参考lib_arm/board.c,在一切准备就绪之后,就进入u-boot的主循环:

416  for (;;) {

              main_loop ();

       }

main_loop的代码比较长,基本是就是执行用户的输入命令。

 

 

able-layout: fixed;" cellspacing="0" cellpadding="0" height="190" width="100%"> 下内容来自笔者在中国Linux论坛Linux嵌入技术讨论区的张贴:x`"m
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  Uw/%#*
--------------------------------------------------------------------------------"
aaronwong: u-boot中代码的疑问(_armboot_start与_start)?12Gm
---------------------------=j
我使用的是u-boot-1.3.0-rc2。在cpu/pxa/start.S中,有如下的标号定义: w'
_TEXT_BASE: 7B
.word TEXT_BASE /*uboot映像在SDRAM中的重定位地址,我设置为0xa170 0000 */ k&BnQf
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  A%
.globl _armboot_start /'b&%
_armboot_start: 50m B8
.word _start /*_start是程序入口,链接完毕它的值应该是0xa170 0000=TEXT_BASE*/ 2inlX
/* 这句话的意思应该是在_armboot_start标号处,保存了_start的值,也就是说,_armboot_start是存放_start的地址,该地址对应的存储单元内容是0xa170 0000*/ ~1
/* 南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  [-S(
* These are defined in the board-specific linker script. 下面的定义与上面应该是一个意思。 y1sDB
*/ 南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  ?Ud=F}
.globl _bss_start W8
_bss_start: 4V1kfj
.word __bss_start 5`
====================== XM
按照上面的理解,__bss_start是uboot 的bss段起始地址,那么uboot映像的大小就是__bss_start - _start;在relocate代码段中计算uboot的大小时,也体现了这一点。 fHK'f0
实际上,_armboot_start并没有实际意义,它只是在"ldr r2, _armboot_start"中用來寻址_start的值而已,_bss_start也是一样的道理,真正有意义的应该是_start和 __bss_start本身。 ;{I
但是,令我不解的是,在C入口函数start_armboot()中(对应文件为lib_arm/board.c),有如下代码: =-yz!
void start_armboot (void) 6#F[C
{ 南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  dCb
......... *=
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); //第一句话 7
.......... xfw,,
monitor_flash_len = _bss_start - _armboot_start; //第二句话 =r1m,
............... =cN^x+
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //第三句话 W
..........
} 南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  u
============================================== v#HG/
按照上面的理解,_armboot_start与_bss_start都是没有实际意义的,它们只是一个地址,有实际意义的是地址中的内容_start和__bss_start(虽然也还是地址)。象第一句话,其“意图”很明显,是把gd作为全局数据结构体的指针,并初始化为“SDRAM中的uboot起始地址(即TEXT_BASE)-CFG_MALLOC_LEN-全局数据结构体大小”。
要实现这个“意图”,应该是写成:gd = (gd_t*)(_start - CFG_MALLOC_LEN -sizeof(gd_t));或者gd = (gd_t*)(TEXT_BASE- CFG_MALLOC_LEN -sizeof(gd_t));才对阿?用_armboot_start来作运算应该是没有任何意义才对!? #0gYd?
第二句话也是一样的道理,它的意图是要计算u-boot映像的大小,应该写成__bss_start - _start才对阿? @`PVq
我使用readelf工具查看编译所得到的uboot映像文件得到信息如下: NK7,G
[aaronwong@localhost build]$ readelf -s u-boot|grep _start G
1018: a1700048 0 NOTYPE GLOBAL DEFAULT 1 _bss_start !Qgo}
1083: a1700044 0 NOTYPE GLOBAL DEFAULT 1 _armboot_start W
1142: a1700000 0 NOTYPE GLOBAL DEFAULT 1 _start b9>
1197: a171b070 0 NOTYPE GLOBAL DEFAULT ABS __bss_start m[
上面我删除了与该讨论无关的包含“_start""t的标号信息。 &:gP
显然,我前面的理解应该是正确的(_start=TEXT_BASE=0xa1700000)。那么u-boot源代码中的monitor_flash_len=_bss_start -_armboot_start=0xa1700048 - 0xa1700044 = 4,有什么意义?? p
迷茫中,期盼大虾指点迷津,谢谢~!!! <
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  M6fJvX
--------------------------------------------------------------------------------%:#-
eltshan: [Re: aaronwong]9o22#P
-----------------Zi
1018: a1700048 0 NOTYPE GLOBAL DEFAULT 1 _bss_start D3dY(
1083: a1700044 0 NOTYPE GLOBAL DEFAULT 1 _armboot_start _mAq>
1142: a1700000 0 NOTYPE GLOBAL DEFAULT 1 _start QNr+Pc
1197: a171b070 0 NOTYPE GLOBAL DEFAULT ABS __bss_start +=
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  E-Y>
我想: FCNh{M
_start所在的地址是a1700000, eHEsMt
_armboot_start 所在的地址是a1700044,
那么 根据这句: 7.Iy
_armboot_start: .word _start }
所以_armboot_start的值应该是a1700000 w34ok:
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  gh\
所以 南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  ka
monitor_flash_len = _bss_start - _armboot_start = a171b070 - a1700000 = 1b070 ~=w
而不是你说的 = 4 FYxAA@
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  *E*4z
以上个人意见.Q~St
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  ^p`Sc
--------------------------------------------------------------------------------Nkh
aaronwong: [Re: eltshan];DQlk5
-------------------p4
谢谢,eltshan!你的理解是正确的,不过我看了之后还是没能想得很明白,因为我在想,按你所说,那么_start的值应该是多少呢?难道是“breset”这条指令的机器码?所以我对ELF格式的u-boot映像文件作了反汇编,分析之后终于找到了症结所在。以下是部分分析过程,首先是反汇编:24
arm-iwmmxt-linux-gnueabi-objectdump -D u-boot > u-boot.s *{|(q#
并提取了monitor_flash_len = _bss_start - _armboot_start;这条语句相关的反汇编代码如下: \
============================== ^o#c7
a1700044 <_armboot_start>: b?
a1700044: a1700000 .word 0xa1700000 e{Zn
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  >
a1700048 <_bss_start>: l#"{w
a1700048: a171b070 .word 0xa171b070 U3sK
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  !.
a171b070 : R
a171b070: 00000000 .word 0x00000000 Q^$
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  MeE9
..... m4
a1700f40: e59f41d0 ldr r4, [pc, #464] ; a1701118 lF-4
//r4=[a1701118]=a1700044 2/NL_;
..... EW0Th
a1700f7c: e59f3198 ldr r3, [pc, #408] ; a170111c [T4Uwy
//r3=[a1700044]=a1700048 D
a1700f80: e5942000 ldr r2, [r4] 2/0N0
//r2=[a1700044]=a1700000 mV
a1700f84: e59f4194 ldr r4, [pc, #404] ; a1701120 bWFU
//r4=[a1701120]=a1719d24 #Bnq
a1700f88: e5933000 ldr r3, [r3] *
//r3=[a1700048]=a171b070
a1700f8c: e0623003 rsb r3, r2, r3 $e8I:
//r3= r3-r2 = a171b070-a1700000 = 1b070; q|
a1700f90: e59f218c ldr r2, [pc, #396] ; a1701124 f1XV
//r2=[a1701124]=a171b070 }
a1700f94: e5823000 str r3, [r2] h`lC]
//monitor_flash_len=[r2]=r3=1b070 mJT:HJ
...... =op4
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  9
a1701118: a1700044 .word 0xa1700044 Z0
a170111c: a1700048 .word 0xa1700048 fr3g(
a1701120: a1719d24 .word 0xa1719d24 EpcDe
a1701124: a171b070 .word 0xa171b070 XT&
======================================== :
上面//是我自己的注释。这表明,你的理解的确是正确的。 :}6
经过这个过程之后,我终于认识到自己的误解在哪里了。原来,我是把"汇编语言中LDR伪指令对符号的引用"与"C语言中对汇编程序中符号/常量/变量的引用"搞混淆了。我想说明以下几点:`[I
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  WY
(1) readelf以及u-boot.map和System.map所给出的符号表中符号的值,实际上是表示符号所在的地址,而不是指符号本身的值。 E?F'R
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  u
(2) 汇编语言中没有指针的概念,因此对符号的引用是"赤裸裸"的。例如: M"
========== wM
.globl _armboot_start J%
_armboot_start: .word _start d_
ldr r2, _armboot_start Kf
========== ,
实际上反汇编以后是: 466
============ ;/g-oE
a1700044 <_armboot_start>: }b
a1700044: a1700000 .word 0xa1700000 R
a1700074: e51f2038 ldr r2, [pc, #-56] ; a1700044 <_armboot_start> b b}/4
============ [7A
也就是说,_armboot_start是一个地址0xa1700044,其中的内容是0xa1700000,上面对_armboot_start的引用是直接将其替换为其表示的地址0xa1700044,而非其中的内容0xa1700000。这就是"赤裸裸"的引用。 m
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  )bR;
(3)C语言则不同,对变量/符号/常量的引用必须要通过地址来寻址,不管是全局变量还是局部变量,不同的是局部变量在生命期结束后,所占的地址空间会被释放而已。即使是函数调用时的参数传递,虽然是将实参的值"拷贝"给形参,但"拷贝"的过程也是通过实参和形参的地址来对两者进行访问的。 p
所以,在C语言中的 "monitor_flash_len = _bss_start - _armboot_start"这句话中对_armboot_star的引用,实际上是把它用作了指针,把它作为访问对象的地址来使用,通过这个地址即a1700044来访问对应存储空间所存放的内容亦即0xa1700000,_bss_start也是同样的道理。所以这句话实际上是monitor_flash_len=[a1700048]-[0xa1700044]=a171b070-a1700000 = 1b070,这样就得到了正确的结果。 eNe#ij
南开大学嵌入式系统与信息安全实验室学术论坛 -- 我的论坛,我的天地  MnK-47
现在,我们再回答最前面的问题:_start的值是什么?_start表示地址0xa1700000,在汇编语言中,对_start的"绝对引用"(这里是与用相对寻址进行跳转进行区别)就是将其替换为0xa1700000,但其中存放的内容的的确确就是"breset"这条指令的机器码,所以如果在C语言中引用_start,得到的结果反而就是这个指令的机器码了。其实这个问题很简单,只是和C语言的引用搅在一起,一些概念被偷换了而已。