uboot linux配置文件,linux系统的uboot启动流程

第一部分uboot源代码分析

U-Boot源代码架构分析

U-Boot的源码顶层目录说明目    录特    性解 释 说 明

board平台依赖存放电路板相关的目录文件,例如:RPXlite(mpc8xx)、hi3520v100 (arm1176)、sc520_cdp(x86) 等目录

cpu平台依赖存放CPU相关的目录文件,例如:mpc8xx、ppc4xx、arm1176、arm920t、 xscale、i386等目录

lib_ppc平台依赖存放对PowerPC体系结构通用的文件,主要用于实现PowerPC平台通用的函数

lib_arm平台依赖存放对ARM体系结构通用的文件,主要用于实现ARM平台通用的函数

lib_i386平台依赖存放对X86体系结构通用的文件,主要用于实现X86平台通用的函数

include通用头文件和开发板配置文件,所有开发板的配置文件都在configs目录下

common通用通用的多功能函数实现

lib_generic通用通用库函数的实现

Net通用存放网络的程序

Fs通用存放文件系统的程序

Post通用存放上电自检程序

drivers通用通用的设备驱动程序,主要有FLASH驱动、以太网接口的驱动

Disk通用硬盘接口程序

examples应用例程一些独立运行的应用程序的例子,例如helloworld

tools工具存放制作S-Record 或者 U-Boot格式的映像等工具,例如mkimage

Doc文档开发使用文档

1.顶层目录下的Makefile

它负责U-Boot整体配置编译。按照配置的顺序阅读其中关键的几行。

每一种开发板在Makefile都需要有板子配置的定义。例如hi3520v100板的定义如下。

#make hiconfig

在顶层目录的Makefile下

hiconfig:   unconfig

@./hiconfig

执行hiconfig脚本,hiconfig脚本最终执行选中的板hi3520v100_mchip_200M的目标

make -C ./ hi3520v100_mchip_200M_config

在hisilicon_board-Hi3520v100.mk下

hi3520v100_mchip_200M_config:  unconfig

@./mkconfig hi3520v100_mchip_200M  arm  arm1176 hi3520v100 NULL hi3520v100

执行配置U-Boot的命令make hiconfig,通过./mkconfig脚本生成include/config.

mk的配置文件。文件内容正是根据Makefile对开发板的配置生成的。

ARCH   = arm

CPU    = arm1176

BOARD  = hi3520v100

SOC    = hi3520v100

上面的include/config.mk文件定义了ARCH、CPU、BOARD、SOC这些变量。这样硬件平台依赖的目录文件可以根据这些定义来确定。hi3520v100平台相关目录如下。

board/hi3520v100/

cpu/arm1176/

cpu/ arm1176/hi3520v100/

lib_arm/

include/asm/

include/configs/ hi3520v100_mchip_200M.h

再回到顶层目录的Makefile文件开始的部分,其中下列几行包含了这些变量的定义。

# load ARCH,BOARD, and CPU configuration

include  include/config.mk

export      ARCH CPU BOARD VENDOR SOC

Makefile的编译选项和规则在顶层目录的config.mk文件中定义。各种体系结构通用的规则直接在这个文件中定义。通过ARCH、CPU、BOARD、SOC等变量为不同硬件平台定义不同选项。不同体系结构的规则分别包含在ppc_config.mk、arm_config.mk、mips_config.mk等文件中。

顶层目录的Makefile中还要定义交叉编译器,以及编译U-Boot所依赖的目标文件。

ifeq($(ARCH),arm)

CROSS_COMPILE= arm-linux-          //交叉编译器的前缀

#endif

export CROSS_COMPILE

#U-Boot objects....order is important (i.e. start must be first)

OBJS =cpu/$(CPU)/start.o                 //处理器相关的目标文件

LIBS = lib_generic/libgeneric.a           //定义依赖的目录,每个目录下先把目标文件连接成*.a文件。

LIBS+= board/$(BOARDDIR)/lib$(BOARD).a

LIBS+= cpu/$(CPU)/lib$(CPU).a

ifdef  SOC

LIBS+= cpu/$(CPU)/$(SOC)/lib$(SOC).a

endif

LIBS+= lib_$(ARCH)/lib$(ARCH).a

然后还有U-Boot映像编译的依赖关系。

ALL =u-boot.srec u-boot.bin System.map

all:       $(ALL)

u-boot.srec:   u-boot

$(OBJCOPY) ${OBJCFLAGS} -Osrec $< $@

u-boot.bin:u-boot

$(OBJCOPY) ${OBJCFLAGS} -Obinary $< $@

……

u-boot:        depend $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)

UNDEF_SYM='$(OBJDUMP) -x$(LIBS) \

|sed  -n -e's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\

$(LD) $(LDFLAGS) $$UNDEF_SYM$(OBJS) \

--start-group $(LIBS) $(PLATFORM_LIBS) --end-group \

-Map u-boot.map -o u-boot

Makefile缺省的编译目标为all,包括u-boot.srec、u-boot.bin、System.map。u-boot.srec和u-boot.bin又依赖于U-Boot。U-Boot就是通过ld命令按照u-boot.map地址表把目标文件组装成u-boot。

2.开发板配置头文件

编译时(mkconfig脚本运行时)会在include目录下生成config.h头文件,这个头文件#include /configs/.h头文件。用相应的BOARD定义代替。

/configs/.h头文件中主要定义了两类变量。

一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等。例如:

#define CONFIG_BOOTARGS "mem=32Mconsole=ttyAMA0,115200"

#define CONFIG_BAUDRATE         115200

另一类是参数,前缀是CFG_,用来定义总线频率、串口波特率、Flash地址等参数。例如:

/*Slave boot kernel initrd addr*/

#define CFG_SLAVE_BOOT_ADDR    0x80800000

#defineCFG_SLAVE_KERNEL_ADDR  0x80280000

#defineCFG_SLAVE_INITRD_ADDR  0x80620000

U-Boot启动流程

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

1 第一阶段(Stage1)

第一阶段的启动代码在 cpu//start.s中,完成的工作主要有:

  CPU自身初始化:包括 MMU,Cache,时钟系统,SDRAM 控制器等的初始化

  重定位:把自己从非易失性存储器搬移到 RAM中

  分配堆栈空间,设置堆栈指针

  清零 BSS 数据段

  跳转到第二阶段入口函数 start_armboot()

2 第二阶段(Stage2)

第二阶段是u-boot 的主体,入口点是lib_arm/board.c 中的start_armboot()函数,完成

的主要工作包括:

  为 U-boot 内部私有数据分配存储空间,并清零

  依次调用函数指针数组 init_sequence 中定义的函数进行一系列的初始化

  如果系统支持 NOR Flash,调用 flash_init ()和 display_flash_config ()初始化并

显示检测到的器件信息(AT91SAM9260EK不需要)

  如果系统支持LCD或VFD, 调用lcd_setmem()或vfd_setmem()计算帧缓冲(Framebuffer)

大小,然后在 BSS 数据段之后为 Framebuffer 分配空间,初始化gd->fb_base 为

Framebuffer的起始地址(AT91SAM9260EK不需要)

  调用 mem_malloc_init()进行存储分配系统(类似于 C 语言中的堆)的初始化和空间分配

  如果系统支持 NAND Flash,调用 nand_init ()进行初始化

  如果系统支持 DataFlash,调用 AT91F_DataflashInit()和 dataflash_print_info()进行初始化并显示检测到的器件信息

  调用 env_relocate ()进行环境变量的重定位,即从 Flash中搬移到 RAM 中(堆区)

  如果系统支持 VFD,调用drv_vfd_init()进行 VFD 设备初始化(AT91SAM9260EK 不

需要)

  从环境变量中读取 IP 地址和 MAC 地址,初始化 gd->bd-> bi_ip_addr 和

gd->bd->bi_enetaddr

  调用 jumptable_init ()进行跳转表初始化,跳转表在 global_data 中,具体用途尚不清楚

  调用 console_init_r()进行控制台初始化

  如果需要,调用 misc_init_r ()进行杂项初始化

  调用 enable_interrupts ()打开中断

  如果需要,调用 board_late_init()进行单板后期初始化,对于 AT91SAM9260EK,主

要是以太网初始化

  进入主循环:根据用户的选择启动 linux,或者进入命令循环执行用户输入的命令

第二部分HI3520的uboot启动分析

HI3520的ARM1176启动流程

Hi3520 内部使用的地址总线为 32bit位宽,可寻址的地址空间为 4GB。Hi3520支持以

下 3 种启动方式:

1)  从 NOR Flash 启动 执行下面的跳转

2)  从 NAND Flash 启动

3)  从 DDR 启动

上面3种启动方式仅针对ARM1176。

通过对管脚EBIADR23和EBIADR24 进行设置,选择启动方式。

从NOR Flash启动

按照表进行正确设置后,才可选择从 NORFlash 启动。此时外部连接的存储器一般为异步 NORFlash,Hi3520只支持8bit 的 NOR Flash 存储器。

从 NOR Flash 启动时芯片时,一上电复位后,地址空间处于remap 状态,(由寄存器0x20050000复位默认值决定)。

1)  系统实际上提供了 2KB的 ITCM(Instruction Tightly-Coupled Memory 指令紧耦合存储器) 地址空间,因此,软件需要保证在 ITCM的程序和数据必须在 2KB 的范围内。

2)  如果需要使用 ITCM,则必须通过 ARM的系统控制协处理器 CP15提供的寄存器设置 ITCM使能,并且配置 ITCM的大小等信息。使用 MCR指令配置 C9寄存器为0xD。

启动代码在uboot的 cpu//start.s中,当一上电复位时,地址空间处于remap 状态,跑的第一步先是从绝对地址(0x80000000)norflash映射过来的代码(从0x0开始跑),当跑到要取消remap状态时,进行跳转

cmp         r4, #0                 /*boot from nor flash */

ldreq   pc, _clr_remap_rom_entry

其中_clr_remap_rom_entry的值是

_clr_remap_rom_entry:

.word   ROM_TEXT_ADRS + do_clr_remap -TEXT_BASE

//ROM_TEXT_ADRS0x80000000

也就是跑0x80000000中的代码,如下:

do_clr_remap:

ldr     r4, =REG_BASE_SCTL

@ldr         r0, =REG_VALUE_SC_NOLOCK

@str         r0, [r4, #REG_VALUE_SC_LOCKED]

ldr    r0, [r4, #REG_SC_CTRL]

@Set clear remap bit.

orr    r0, #(1<<8)

str    r0, [r4, #REG_SC_CTRL]

。。。

接下来有一个把uboot代码搬运到内存的过程

#ifndef CONFIG_SKIP_RELOCATE_UBOOT

relocate:                              /*relocate U-Boot to RAM          */

ldr     r0, =REG_BASE_SCTL

ldr     r6, [r0, #0x8c]

and     r6, #0x60

lsr   r4, r6, #5

adr   r0, _start          /* r0

/* adr r0, _start这句代码是将_start标签处运行时的地址值装进r0,这条指令到底取一个什么值呢?假设运行到该指令时pc寄存器的值是X,start标签相对此本指令有Y的偏移,则r0=X+Y(Y可能是负值).针对该程序来说。当被装载到0x0000处运行时,r0就是0,假设装载到TEXT_BASE处运行。则r0=TEXT_BASE*/

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

cmp     r0, r1                  /* don't reloc duringdebug         */

beq     stack_setup

ldr    r2, _armboot_start

ldr    r3, _img_end

sub   r2, r3, r2           /* r2

cmp         r4, #2

ldreq   r2, =(CFG_NAND_U_BOOT_SIZE)

add  r2, r0, r2           /* r2

copy_loop:

ldmia        r0!, {r3-r10}               /*copy from source address [r0]    */

stmia        r1!, {r3-r10}               /*copy to   target address [r1]    */

cmp r0, r2                           /*until source end addreee [r2]    */

ble    copy_loop

#endif       /*CONFIG_SKIP_RELOCATE_UBOOT */

最后执行下面的跳转,跑到内存执行,如果上面的拷贝过程不存在,程序执行下面的跳转时就停住了

ldr    pc, _start_armboot

_start_armboot:

.word start_armboot

至于为什么会跳到内存去,这个涉及到代码的重定位问题。

U-Boot中关于TEXT_BASE,代码重定位,链接地址相关说明:

我们知道U-BOOT分为两个阶段,第一阶段是(~/cpu/arm1176/start.S中)在FLASH上运行(一般情况下),完成对硬件的初始化,包括看门狗,中断缓存等,并且负责把代码搬移到SDRAM中(在搬移的时候检查自身代码是否在SDRAM中),然后完成C程序运行所需要环境的建立,包括堆栈的初始化等,最后执行一句跳转指令:

ldr pc, _start_armboot

_start_armboot: .word start_armboot,

进入到/lib_arm/board.c中的函数void start_armboot(void),从此就进入了第二阶段。这是在很多资料上都有讲述的,所以勿需多言了。

现在对于第一阶段有几个问题,既然在FLASH中的代码是把自己拷贝到SDRAM中,那么在地址空间,就有两份的启动代码,第一份就是在FLASH中,第二份就是在SDRAM中。根据链接脚本文件(~/board/ hi3520v100/u-boot.lds)OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm", "elf32-littlearm")/*指定输出可执行文件是elf格式,32位ARM指令,小端模式*/

OUTPUT_ARCH(arm)/*指定输出可执行文件的平台是ARM */

ENTRY(_start)/*指定输出可执行文件的起始代码段为_start */

SECTIONS

{

. = 0x00000000;

. = ALIGN(4);/*代码以4字节对齐*/

.text :

{

cpu/arm1176/start.o   (.text)/*代码的第一个代码部分*/

board/hi3520v100/libhi3520v100.a (.text)

common/dlmalloc.o (.text)

common/console.o (.text)

common/nand_boot.o (.text)

drivers/mtd/libmtd.a (.text)

drivers/mtd/nand/libnand.a (.text)

lib_generic/libgeneric.a(.text)

cpu/arm1176/libarm1176.a (.text)

cpu/arm1176/hi3520v100/libhi3520v100.a (.text)

lib_arm/libarm.a (.text)

drivers/serial/libserial.a (.text)

}

. = ALIGN(4);/* rodata以4字节对齐*/

.rodata : { *(.rodata) }/*指定只读数据段*/

. = ALIGN(4);/* .data以4字节对齐*/

.data : { *(.data) }/*指定读,写数据段*/

. = ALIGN(4);/* .got以4字节对齐*/

.got : { *(.got) }/*指定got段,got段是uboot自定义的一个段,非标准段*/

. = ALIGN(4);

.text1 :

{

*(.text)/*其他代码部分*/

}

. = ALIGN(4);

. = .;

__u_boot_cmd_start = .;/*把__u_boot_cmd_start赋值为当前位置,即起始位置*/

.u_boot_cmd : { *(.u_boot_cmd) }/*指定u_boot_cmd段,uboot把所有的uboot命令放在该段*/

__u_boot_cmd_end = .;/*把__u_boot_cmd_end赋值为当前位置,即结束位置*/

__img_end = .;

. = _start + 0x100000;

. = ALIGN(32);

__bss_start = .;/*把__bss_start赋值为当前位置,即bss段的开始位置*/

.bss1 (NOLOAD) : {*(.bss)}/*指定bss段,存放的是未初始化的全局变量和局部静态变量*/

_end = .;/*把_end赋值为当前位置,即bss段的结束位置*/

}其中的链接命令. = 0x00000000;表示地址计数器从0地址开始计数,而且_start是程序代码段的入口,那么*.text中的所有地址标号(cpu/arm1176/start.S中定义的)就应该从0地址开始计数,那么标号start_armboot(就是voidstart_armboot (void)函数的入口地址)应该在FLASH中才对啊,所以按照上边的分析,

ldr pc, _start_armboot

_start_armboot: .word start_armboot

此条语句后,并没有跳转到SDRAM中的void start_armboot (void),而是跳转到了FLASH中的void start_armboot (void)中。

所以就出现了这样的矛盾,在FLASH中有一段代码把自己拷贝到SDRAM中,产生了两份UBOOT可执行的指令流,但是最后却没有跳转到SDRAM中去运行以提高指令执行的速度。

产生以上的认识是基于以下几个认识(肯定是错误的):

1)

*.text中的所有地址标号(在链接时确定)是从0地址开始生成的。

实际上在arm-linux-ld 执行时,原来定义的0x0地址被更新为TEXT_BASE定义的地址。

2)#ifndefCONFIG_SKIP_RELOCATE_UBOOT

relocate:                       /* relocate U-Boot to RAM       */

ldr    r0, =REG_BASE_SCTL

ldr    r6, [r0, #0x8c]

and    r6, #0x60

lsr   r4,r6, #5

adr   r0,_start        /* r0

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

cmp    r0, r1                  /* don'treloc during debug         */

beq    stack_setup

ldr   r2,_armboot_start

ldr   r3,_img_end

sub  r2,r3, r2        /* r2

cmp       r4,#2

ldreq  r2, =(CFG_NAND_U_BOOT_SIZE)

add  r2,r0, r2        /* r2

copy_loop:

ldmia      r0!,{r3-r10}         /* copy from sourceaddress [r0]    */

stmia       r1!,{r3-r10}         /* copy to   target address [r1]    */

cmp r0,r2                    /* until source endaddreee [r2]    */

ble   copy_loop

#endif     /* CONFIG_SKIP_RELOCATE_UBOOT */

如果不是出于调试阶段,这段搬移代码中的r0和r1肯定不相等的,r0=#0,r1=#TEXT_BASE: 0xe2500000(在./board/ hi3520v100/config.mk中),所以执行代码的自身拷贝与搬移。

注意:在GNU中:adr r0, _start 作用是获得 _start 的实际运行所在的地址值,而ldr r1, _TEXT_BASE 为获得地址_TEXT_BASE中所存放的数据,其中adr r0, _start翻译成 addr0,(PC+#offset),offset 就是 adr r0, _start 指令到_start 的偏移量,在链接时确定,这个偏移量是地址无关的。而 ldr r1, _TEXT_BASE 指令表示以程序相对偏移的方式加载数据,是索引偏移加载的另外一种形式,等同于ldr r1,[PC+#offset],offset 是 ldr r1, _TEXT_BASE 到 _TEXT_BASE 的偏移量。

经过把U-BOOT进行make后,从所生成的两个.map文件来看(~/u-boot.map和 Systen.map),所有的地址标号都是从0xe2500000开始的,就是从SDRAM的高地址开始,等于TEXT_BASE的值,因为在连接的时候, LDFLAGS中有-Ttext  $(TEXT_BASE) ,其含义是将text section定位基址设置成TEXT_BASE,这样生成的结果里的诸如函数地址,等symbol对应的地址都是以TEXT_BASE作为基地址的。也就是说,链接器是从0xe2500000开始来链接所编译生成的目标文件的,而不是从0地址开始,经过查看,start_armboot=0xe251179c,就是说void start_armboot (void)函数的入口地址在SDRAM中(链接器决定),所以执行

ldr pc, _start_armboot

_start_armboot: .word start_armboot,

PC指针肯定就指向了SDRAM中,换句话就是说进入到SDRAM中了

主arm11和从arm9都工作的情况

启动ARM926 :

ARM926 对“0x00xx_xxxx”地址段进行访问时,实际访问的地址是“0xAAxx_xxxx” ,AA表示系统控制寄存器 SC_PERIPHCTRL17 bit[7:0]设置的值。对其他地址段的访问同 ARM1176 一致。

ARM926在 Hi3520芯片系统中是从地位的处理器,只能在 ARM1176启动后,通过配置相关寄存器才可动。

ARM926 启动过程如下:

步骤 1 配置系统控制寄存器 SC_PERIPHCTRL17 bit[7:0]。例如配置成 0xC8。

步骤 2 向相应的地址(0xC800_0000)写入ARM926 的启动程序。

步骤 3 向系统控制寄存器 SC_PERIPHCTRL8 bit[1]写1,撤消 ARM926 的复位。

步骤 4 ARM926 自动从 0xC800_0000 读取相关程序进行 boot。

从arm启动对应程序,下面的这段代码在start_armboot函数中,

#ifdefCFG_HAS_SLAVE

if ( (boot_flag & 0x1) == 1 ){

slave_start();

goto out;

}

e =getenv("slave_autostart");

if(e && (*e == '1')){

e =getenv("slave_kernel_addr");

if(e){

kernel_addr =simple_strtoul(e, NULL, 16);

}else{

kernel_addr =CFG_SLAVE_KERNEL_ADDR;

setenv = 1;

}

e =getenv("slave_initrd_addr");

if(e){

initrd_addr =simple_strtoul(e, NULL, 16);

}else{

initrd_addr =CFG_SLAVE_INITRD_ADDR;

setenv = 1;

}

if(setenv){

sprintf (cmd_buf,"setenv slave_kernel_addr %x", kernel_addr);

run_command(cmd_buf,0);

sprintf (cmd_buf,"setenv slave_initrd_addr %x", initrd_addr);

run_command(cmd_buf,0);

}

slave_start();

}

out:

#endif

slave_start函数拷贝从arm启动代码,内核等到内存(细看上面的DDRB分布图),设置软件复位让从arm启动,程序就自动从指定的地方跑。

根据上面norflash程序分布图,计算后得出下面的分区情况(分区必需是连续的)

384K(m-boot),    0x80000000 (不能超256K,因为主uboot环境变量默认保存在0x80040000占128k)

640K(config),     0x80000000+0x60000            = 0x80060000

1536K(m-kernel),  0x80100000

1536K(s-kernel),   0x80100000+0x180000           = 0x80280000

2176K(m-rootfs),  0x80400000

1920K(s-rootfs),   0x80400000+0x220000           = 0x80620000   //修改文件系统的/etc/init.d/rcS脚本挂载的分区对应关系,增加/etc/network目录(包含文件ifdown、ifup、ifcfg-eth0、ifcfg-lo四个文件,其中ifcfg-eth0修改ip成可用的ip)

128K(s-boot)      0x80800000

8192K(usr),       0x80800000+0x20000            = 0x80820000

Uboot的环境变量

slave_boot_addr   = 0x80800000

slave_kernel_addr  = 0x80280000

slave_initrd_addr   = 0x80620000

指定从arm9 的boot、kernel、fs镜像文件拷贝起始地址.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值