Chinaunix首页 | 论坛 | 博客
  • 博客访问: 353310
  • 博文数量: 49
  • 博客积分: 3229
  • 博客等级: 中校
  • 技术积分: 616
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-26 21:46
文章分类

全部博文(49)

文章存档

2011年(8)

2010年(2)

2009年(3)

2008年(36)

我的朋友

分类: 嵌入式

2011-02-06 18:48:49

作者:old five zhang
地址:swust
UBoot版本:记不太清楚了

http://blog.chinaunix.net/space.php?uid=14782631

本文档是自己学习UBoot的总结,只涉及到自己以前迷惑的地方


初探

执行一下make,看下make的过程中发生了什么。

UNDEF_SYM=`arm_v5t_le-objdump -x lib_generic/libgeneric.a board/dahua_davinci/libdahua_davinci.a cpu/arm926ejs/libarm926ejs.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a net/libnet.a disk/libdisk.a rtc/librtc.a dtt/libdtt.a drivers/libdrivers.a drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;

查找各源文件中的u_boot_cmd段。

arm_v5t_le-ld -Bstatic -T uboot/Trunk/board/dahua_davinci/u-boot.lds -Ttext 0x81080000  $UNDEF_SYM cpu/arm926ejs/start.o --start-group lib_generic/libgeneric.a board/dahua_davinci/libdahua_davinci.a cpu/arm926ejs/libarm926ejs.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a net/libnet.a disk/libdisk.a rtc/librtc.a dtt/libdtt.a drivers/libdrivers.a drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a --end-group -L /opt/toolchains/davinci/pro/devkit/arm/v5t_le/bin/../lib/gcc/armv5tl-montavista-linuxeabi/3.4.3 -lgcc  -Map u-boot.map -o u-boot

链接各源程序为u-boot可执行文件,并生成u-boot.map文件。U-boot中包含了一些调试信息,比如说debug_frame debug_info等。u-boot.map中记录了各函数/变量的地址。

arm_v5t_le-objcopy --gap-fill=0xff -O srec u-boot u-boot.srec

u-boot生成S记录文件,这个文件一般在MOTOROLA的机子上有用。

objcopy的作用是拷贝一个目标文件的内容到另一个目标文件中。Objcopy使用GNU BFD库去读或写目标文件。Objcopy可以使用不同于源目标文件的格式来写目的目标文件(也即是说可以将一种格式的目标文件转换成另一种格式的目标文件)。通过以上命令行选项可以控制Objcopy的具体操作。

通过使用srec作为输出目标(使用命令行选项-o srec),Objcopy可以产生S记录格式文件。
通过使用binary作为输出目标(使用命令行选项-o binary),Objcopy可以产生原始的二进制文件。使用Objcopy产生一个原始的二进制文件,实质上是进行了一回输入目标文件内容的内存转储。所有的符号和重定位信息都将被丢弃。内存转储起始于输入目标文件中那些将要拷贝到输出目标文件去的部分的最小虚地址处。
使用Objcopy生成S记录格式文件或者原始的二进制文件的过程中,-S选项和-R选项可能会比较有用。-S选项是用来删掉包含调试信息的部分,-R选项是用来删掉包含了二进制文件不需要的内容的那些部分。

arm_v5t_le-objcopy --gap-fill=0xff -O binary u-boot dhboot.bin

uboot可执行文件生成RAW二进制文件。对于在PC上运行的程序或者在设备上运行的应用程序,没有这一步,因为操作系统会解析并根据可执行文件中的信息加载程序执行。但对于系统运行的第一个程序u-boot,没有谁会去解析加载这个程序,所以,需要将其做成RAW的二进制文件,使其能一开始就能在设备上运行。

连接脚本

对于.lds文件,它定义了整个程序编译之后的连接过程,决定了一个可执行程序的各个段的存储位置。虽然现在我还没怎么用它,但感觉还是挺重要的,有必要了解一下。

先看一下.lds文件形式的完整描述:

SECTIONS {
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
  { contents } >region :phdr =fill
...
}

secnamecontents是必须的,其他的都是可选的。下面挑几个常用的看看:

1secname:段名

2contents:决定哪些内容放在本段,可以是整个目标文件,也可以是目标文件中的某段(代码段、数据段等)

3start:本段连接(运行)的地址,如果没有使用ATldadr),本段存储的地址也是startGNU网站上说start可以用任意一种描述地址的符号来描述。

4ATldadr):定义本段存储(加载)的地址。

看一个简单的例子:(摘自《2410完全开发》)

/* nand.lds */
SECTIONS { 
firtst 0x00000000 : { head.o init.o } 
second 0x30000000 : AT(4096) { main.o } 
}

以上,head.o放在0x00000000地址开始处,init.o放在head.o后面,他们的运行地址也是0x00000000,即连接和存储地址相同(没有AT指定);main.o放在40960x1000,是AT指定的,存储地址)开始处,但是它的运行地址在0x30000000,运行之前需要从0x1000(加载处)复制到0x30000000(运行处),此过程也就用到了读取Nand flash。这就是存储地址和连接(运行)地址的不同,称为加载时域和运行时域,可以在.lds连接脚本文件中分别指定。 

arm_v5t_le-ld -Bstatic -T uboot/Trunk/board/dahua_davinci/u-boot.lds -Ttext 0x81080000  $UNDEF_SYM cpu/arm926ejs/start.o --start-group lib_generic/libgeneric.a board/dahua_davinci/libdahua_davinci.a cpu/arm926ejs/libarm926ejs.a lib_arm/libarm.a fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a net/libnet.a disk/libdisk.a rtc/librtc.a dtt/libdtt.a drivers/libdrivers.a drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a --end-group -L /opt/toolchains/davinci/pro/devkit/arm/v5t_le/bin/../lib/gcc/armv5tl-montavista-linuxeabi/3.4.3 -lgcc  -Map u-boot.map -o u-boot

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

OUTPUT_ARCH(arm)

ENTRY(_start)

SECTIONS

{

. = 0x00000000;

. = ALIGN(4);

.text :

{

  cpu/arm926ejs/start.o (.text)  /*.text段表示代码段,起始位置由-Ttext选项指定 */

  *(.text)

}

. = ALIGN(4);

.rodata : { *(.rodata) }  /*只读数据段保存已经初始化的全局只读数据,比如只读字符串*/

. = ALIGN(4);

.data : { *(.data) }  /*数据段保存已经初始化的全局数据 */

. = ALIGN(4);

.got : { *(.got) }

. = .;

__u_boot_cmd_start = .;/* 指定u_boot_cmd, uboot把所有的uboot命令放在该段. */

.u_boot_cmd : { *(.u_boot_cmd) }

__u_boot_cmd_end = .;

. = ALIGN(4);

__bss_start = .;

.bss : { *(.bss) }  /*堆栈段,未初始化的全局变量也保存在此*/

_end = .;

}

__u_boot_cmd_end __bss_start_end相当是在这里定义的变量,变量的值是‘.’的地址,变量的地址也是‘.’的地址。这个在程序里可以引用,但__u_boot_cmd_end __bss_start_end它们不占用存储空间。以__bss_start为例,在反汇编的代码中,有

                0x8109928c                __u_boot_cmd_version

                0x8109925c                __u_boot_cmd_help

                0x810992a4                __u_boot_cmd_end = .

                0x810992a4                . = ALIGN (0x4)

                0x810992a4                __bss_start = .

.bss            0x810992a4    0x197f8

 *(.bss)

 .bss           0x810992a4        0x8 cpu/arm926ejs/libarm926ejs.a(interrupts.o)

 .bss           0x810992ac       0x30 lib_arm/libarm.a(board.o)

                0x810992ac                monitor_flash_len

                0x810992b0                hwid

从这里来看,__u_boot_cmd_end__bss_start不占用存储空间,因为他们的值一样且与cpu/arm926ejs/libarm926ejs.a(interrupts.o)的起始位置的地址一样。通常,我们只是去读__u_boot_cmd_end__bss_start的值去确定一个边界,例如,在start.S中,

_bss_start:

.word __bss_start

这句话的意思是,在_bss_start这个地址的位置放一个值,该值为_bss_start的地址。

查看其汇编代码:

81080048 <_bss_start>:

81080048:       810992a4        smlatbhi        r9, r4, r2, r9

可见,81080048这个地址中的值就是810992a4。而810992a4正好是__bss_start的值。

再来看另外一个例子,在do_help()中,C代码为:

int do_help (cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])

{

int i;

int rcode = 0;

if (argc == 1) { /*show list of commands */

int cmd_items = &__u_boot_cmd_end -

&__u_boot_cmd_start; /* pointer arith! */

其汇编代码为:

8108aca4 :

8108aca4:       e1a0c00d        mov     ip, sp

8108aca8:       e92ddef0        stmdb   sp!, {r4, r5, r6, r7, r9, sl, fp, ip, lr, pc}

8108acac:       e3520001        cmp     r2, #1  ; 0x1

8108acb0:       e24cb004        sub     fp, ip, #4      ; 0x4

8108acb4:       e1a04002        mov     r4, r2

8108acb8:       e1a0a003        mov     sl, r3

8108acbc:       e3a07000        mov     r7, #0  ; 0x0

8108acc0:       13a06001        movne   r6, #1  ; 0x1

8108acc4:       e24dd008        sub     sp, sp, #8      ; 0x8

8108acc8:       1a000044        bne     8108ade0 

8108accc:       e59f3184        ldr     r3, [pc, #388]  ; 8108ae58 <.text+0xae58>

8108acd0:       e59f2184        ldr     r2, [pc, #388]  ; 8108ae5c <.text+0xae5c>

8108ae58:       81098e0c        tsthi   r9, ip, lsl #28

8108ae5c:       810992a4        smlatbhi        r9, r4, r2, r9

可见,对&__u_boot_cmd_end的引用被解析成了对810992a4这个地址,810992a4正好是__u_boot_cmd_end的地址。

(可以在内核中测试一下,修改这个变量,如能修改,说明。打印出这个变量的值和地址。。。。)

Start.S

Start.S的作用是实现UBOOT对中断的处理以及一些必须要做的低级的工作,比如代码拷贝等。有三种情况需要拷贝:

1. 因为有些CPU的代码可以在NOR FLASH中执行,有些可以在片内的ROM中执行,这样的话,速度有了限制。所以,在START.S中会有代码的COPY,将代码拷贝到RAM

2.程序虽然一开始在RAM中运行,但期待的运行地址与程序一开始运行的地址不一样。比如说程序一开始运行在0x81000000,但这个位置我们需要存放LINUX内核或者我想将其作为栈的起始位置,那我们就需要把代码转移到另外一个位置。

3.还有种情况是代码开始在片内ROM中执行,因为片内ROM容量太小,不包含所有的代码,也需要拷贝,但这里对于我们所用的CPU不考虑这情况。

程序一开始,程序做一些初始化,比如禁止MMUCACHE,然后就跳到函数lowlevel_init 中去了。对于我们的程序,lowlever_initboard/dahua_davinci/lowlevel_init.S中定义。这个函数主要做一些PLLDDR的初始化。

初始化完成后,程序看是否需要拷贝代码。

adr r0, _start /* r0 <- current position of code   */

ldr r1, _TEXT_BASE /* test if we run from flash or RAM */

cmp     r0, r1                  /* don't reloc during debug         */

beq     stack_setup

adr r0, _start_start的相对位置加载到r0 到,ADR指令的效果与当前PC指针的位置有关,其汇编代码为:

81080064:       e24f006c        sub     r0, pc, #108    ; 0x6c

如果代码一开始在0X0运行,那这里的r0就为0,如果在0x8000运行,那么r0就为0x8000

ldr r1, _TEXT_BASE_TEXT_BASE的值加载到r1中,效果与PC的位置无关,即是说无论开始在0X0运行,还是在81080000运行,这里r1的值都为0x81080000(_TEXT_BASE=0x81080000)

81080000 <_start>:

81080000:       ea000012        b       81080050 

cmp     r0, r1比较两者的值,如果相等,说明是在RAM中运行,如果不等,则是在其它地方运行,需要COPY

拷贝首先计算拷贝的大小和结束的位置,大小size可以用_bss_start -_armboot_start,结束的地址可以用_start+size。拷贝的过程使用ldmstm的循环完成。

ldr r2, _armboot_start

ldr r3, _bss_start

sub r2, r3, r2 /* r2 <- size of armboot            */

add r2, r0, r2 /* r2 <- source end address         */

拷贝完成后就是栈的设置,将_TEXT_BASE-CFG_MALLOC_LEN-CFG_GBL_DATA_SIZE_TEXT_BASE 这段空间设为栈,使SP指针的值为_TEXT_BASE-CFG_MALLOC_LEN-CFG_GBL_DATA_SIZE 。

然后就是bss的初始化,将__bss_start__bss_end这段空间的内容初始化为0

接着,程序跳到RAM中执行,也就是810800c4这个地址:

ldr pc, _start_armboot

_start_armboot:

.word start_armboot

其汇编代码为:

810800c0:       e51ff004        ldr     pc, [pc, #-4]   ; 810800c4 <_start_armboot>

810800c4 <_start_armboot>:

810800c4:       810810c0        smlabthi        r8, r0, r0, r1




start_armboot

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

/*这个声明告诉编译器使用寄存器r8来存储gd_t类型的指针gd,即这个定义声明了一个指针,并且指明了它的存储位置。*/

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

/*

1__asm__用于指示编译器在此插入汇编语句 

2__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。 

3) memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registerscache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registerscache中的数据用于去优化指令,而避免去访问内存。 

4"":::表示这是个空指令。

*/

 C语言中嵌入汇编

   格式: _asm_("asm statements":outputs:inputs:registers-modified)

   其中,"asm statements"是汇编语句表达式,outputs,inputs,register-modified都是可选参数,以冒号隔开,且一次以09编号,如outputs的寄存器是0号,inputs寄存器是1号,往后依次类推。outputs是汇编语句执行完后输出到的寄存器,inputs是输入到某个寄存器。

   1_asm_("pushl %%eax\n\t" "movl $0,%%eax\n\t" "popl %%eax");

   在嵌入汇编中,寄存器前面要加两个%,因为gcc在编译是,会先去掉一个%再输出成汇编格式。

   2{ register char _res;\

         asm("push %%fs\n\t"

         "movw %%ax,%%fs\n\t"

         "movb %%fs:%2,%%al\n\t"

         "pop %%fs"

         :"=a"(_res):"0"(seg),"m"(*(addr)));\

         _res;}

    movb %%fs:%2,%%al\n\t一句中是把以fs为段地址,以后面的第二号寄存器即后面的seg中的值为偏移地址所对应的值装入al"=a"(_res):"0"(seg),"m"(*(addr)))一句中,"=a"(_res)表示把a寄存器中的内容给_res"0"(seg)表示把seg中的内容给0所对应的寄存器,而0即表示使用和前一个寄存器相同的寄存器,这里即使用a寄存器,也就是说把seg中的内容个a寄存器。

   需要解释以下的是,a,b,c,d分别表示寄存器eaxebxecxedx

                  SD分别表示寄存器esiedi

                  r表示任意寄存器

                  0(数字0,不是o!)表示使用上一个寄存器


阅读(1761) | 评论(0) | 转发(0) |
0

上一篇:驱动模块读写文件方法

下一篇:DM6446时钟

给主人留下些什么吧!~~