u-boot完整分析

来源:互联网 发布:系统动力学软件 介绍 编辑:程序博客网 时间:2024/05/29 04:37

1. u-boot介绍

U-Boot是一种通用的Bootloader, U-Boot可以方便地移植到其他硬件平台上,其源代码也值得开发者们研究学习。

最早,DENX软件工程中心的Wolfgang Denk基于8xxrom的源码创建了PPCBOOT工程,并且不断添加处理器的支持。后来,Sysgo Gmbh把ppcboot移植到ARM平台上,创建了ARMboot工程。然后以ppcboot工程和armboot工程为基础,创建了U-Boot工程。

现在U-Boot已经能够支持PowerPC、ARM、X86、MIPS体系结构的上百种开发板,已经成为功能最多、灵活性最强并且开发最积极的开放源码Bootloader。目前仍然由DENX的Wolfgang Denk维护。

U-Boot的源码包可以从sourceforge网站下载,还可以订阅该网站活跃的U-Boot Users邮件论坛,这个邮件论坛对于U-Boot的开发和使用都很有帮助。

U-Boot软件包下载网站:http://sourceforge.net/project/u-boot。

    U-Boot邮件列表网站:http://lists.sourceforge.net/lists/listinfo /u-boot-users/。

    DENX相关的网站:http://www.denx.de/re/DPLG.html。

本系统采用是u-boot-2010.06-psp04.04.00.01

4.2  u-boot源代码目录结构

-board  存放电路板相关的目录文件

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

- cpu存放CPU相关的目录文件

- disk硬盘接口驱动程序

- doc开发使用文档

- drivers通用驱动驱动程序

- dtt数字温度测量器或者传感器的驱动

- examples   例子程序.

- fs   文件系统相关代码

- includ    头文件

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

- lib_generic通用库函数的实现

- lib_i386   存放对i386体系结构通用的库文件

       -lib_m68k 存放对m86k体系结构通用的库文件

-lib_microblaze存放对microblaze体系结构通用的库文件

- lib_mips 存放对MIPS体系结构通用的库文件

- lib_nios 存放对NIOS体系结构通用的库文件

- lib_nios2  存放对NIOS2体系结构通用的库文件

- lib_ppc存放对Power PC体系结构通用的库文件

- net网络相关代码

- post 开机自检的代码

- rtc  RTC驱动

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

- Makefile  生成u-boot镜像的Makefile文件

- mkconfig 用来生成各个板子的配置文件

- config.mk    被Makefile所包含,存放编译选项和规则

除了这些目录外,在根目录下还有其他的一些文件,同时该目录下还有个README的文件,我们可以通过这个文件来熟悉u-boot。移植过程中重点关注的是board, cpu, 以及Makefile等与平台相关的目录和文件。

2 u-boot源代码编译

    U-Boot的源码是通过GCC和Makefile组织编译的。顶层目录下的Makefile首先可以设置开发板的定义,然后递归地调用各级子目录下的Makefile,最后把编译过的程序链接成U-Boot映像。

当我们拿到u-boot的代码后我们就可以尝试给我们自己的板子编译一个u-boot的镜像,而根目录下的Makefile就是用来生成u-boot镜像的。 但在编译之前我们需要为我们的特定板子产生一个描述这块板子的配置文件,然后才能指导Makefile如何去编译能跑在这块板子上的u-boot镜像。

首先分析Makfile中对于ti8168的配置,分析如下,以nand中的u-boot为例。

ti8168_evm_config \

ti8168_evm_config_nand   \

ti8168_evm_config_nor    \

ti8168_evm_config_spi    \

ti8168_evm_config_sd \

ti8168_evm_min_ocmc  \

ti8168_evm_min_sd:   unconfig

@mkdir -p $(obj)include

@echo "#define CONFIG_TI81XX"   >>$(obj)include/config.h

@echo "#define CONFIG_TI816X"   >>$(obj)include/config.h

@if [ "$(findstring _nand,$@)" ] ; then \

echo "#define CONFIG_SYS_NO_FLASH"    >>$(obj)include/config.h ; \

echo "#define CONFIG_NAND_ENV"    >>$(obj)include/config.h ; \

echo "#define CONFIG_TI81XX_VIDEO"    >>$(obj)include/config.h ; \

echo "Setting up TI8168 NAND build with ENV inNAND..." ; \

……

fi;

@$(MKCONFIG) -a ti8168_evm arm arm_cortexa8 ti8168 ti ti81xx

这段代码会生成./include/config.h配置文件文件,并写入配置参数参数,参数的含义如下:

arm:cpu架构

arm_cortexa8:cpu类型

ti8168:开发板型号

ti:开发者或者经销商

ti81xx:片上系统SOC

详细的参数信息,和如何生成config.mk的内容可看mkconfig脚本,这样配置后硬件平台依赖的目录文件也就确定下来了。

Ti8168_evm平台相关的目录:

board/ti/ti8168

arch/arm/cpu/arm_cortexa8

lib_arm/

include/asm-arm/

同时还有一个重要的文件:

include/configs/ti8168_evm.h

这个要我们自己添加,用来定义开发板的配置选项或者参数(具体参数参见README),我们在移植的时候也按这种步骤添加代码就行了。

u-boot的编译见3.2,通常生成如下的文件

u-boot.bin : 原始的二进制镜像

u-boot: ELF二进制格式的镜像

u-boot.srec: 是motorolaS-Record格式的文件。

U-Boot的3种映像格式都可以烧写到Flash中,但需要看加载器能否识别这些格式。一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中就可以了。U-Boot和u-boot.srec格式映像都自带定位信息。

几个重要的文件:

几个重要的文件

./include/configs/ti8168_evm.h  DM8168开发板的全局配置文件。

./mkconfig   在Makefile中被调用,用来生成./include/config.mk和./include/config.h

./include/config.mk 中存放具体开发板的型号信息

./board/ti/ti8168/config.mk 存放TEXT_BASE地址,在系统启动后内部ROM会将u-boot.noxip.bin文件从nandflash搬移到TEXT_BASE地址

./arch/arm/cpu/arm_cortexa8/u-boot.lds   u-boot 重要的链接文件

./config.mk中   LDFLAGS += -Bstatic -T $(obj)u-boot.lds$(PLATFORM_LDFLAGS)                    

LDFLAGS += -Ttext $(TEXT_BASE) 定义了uboot.bin 的链接地址

3 u-boot启动分析

    u-boot启动分析分为两个阶段:汇编阶段(stage1)和C语言阶段(stage2),系统启动后内部ROM的固化代码会将u-boot搬移到DDR3的TEXT_BASE地址处,本系统是0x80007000。

    u-boot启动后首先执行start.s,这个有连接文件决定,见./arch/arm/cpu/arm_cortexa8/u-boot.lds。

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

OUTPUT_ARCH(arm)

ENTRY(_start)

SECTIONS

{

    . = 0x00000000;

    . = ALIGN(4);

    .text  :

    {

       arch/arm/cpu/arm_cortexa8/start.o  (.text)

       *(.text)

    }

    . = ALIGN(4);

    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

    . = ALIGN(4);

    .data : { *(.data) }

    . = ALIGN(4);

    .got : { *(.got) }

    __u_boot_cmd_start = .;

    .u_boot_cmd : { *(.u_boot_cmd) }

    __u_boot_cmd_end = .;

    . = ALIGN(4);

    __bss_start = .;

    .bss : { *(.bss) }

    _end = .;

}

   由此可知u-boot启动后首先会执行start.S中的汇编代码,并从_start字段开始。

4.  u-boot汇编阶段

    u-boot的汇编代码在./arch/arm/cpu/arm_cortexa8/start.S中,并从_start字段开始执行,这由u-boot.lds可知。

    .globl _start

_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

_undefined_instruction: .word undefined_instruction

_software_interrupt:  .wordsoftware_interrupt

_prefetch_abort:  .wordprefetch_abort

_data_abort:      .worddata_abort

_not_used:     .wordnot_used

_irq:          .word irq

_fiq:          .word fiq

在Nandlash中首先放置8条跳转指令,一个复位和7个异常指令,u-boot启动后会跳转到reset复位处进行执行

reset:

/*

 * set the cpu to SVC32 mode

 */

mrs r0, cpsr

bic r0, r0, #0x1f

orr r0, r0, #0xd3

msr cpsr,r0

主要是设置cpu的工作模式为:SVC32 mode

接下来执行简单的芯片初始化工作

……

bl  cpu_init_crit

cpu_init_crit:

/*

 * Invalidate L1 I/D

 */

mov r0, #0        @ set up for MCR

mcr p15, 0, r0, c8, c7, 0    @invalidate TLBs

mcr p15, 0, r0, c7, c5, 0    @invalidate icache

 

/*

 * disable MMU stuff and caches

 */

mrc p15, 0, r0, c1, c0, 0

bic r0, r0, #0x00002000  @ clearbits 13 (--V-)

bic r0, r0, #0x00000007  @ clearbits 2:0 (-CAM)

orr r0, r0, #0x00000002  @ set bit1 (--A-) Align

orr r0, r0, #0x00000800  @ set bit12 (Z---) BTB

mcr p15, 0, r0, c1, c0, 0

 

/*

 * Jump to board specific initialization...

 * The Mask ROM will have already initialized

 * basic memory. Go here to bump up clock rateand handle

 * wake up conditions.

 */

mov ip, lr        @ persevere linkreg across call

bl  lowlevel_init     @ go setuppll,mux,memory

mov lr, ip        @ restore link

mov pc, lr        @ back to mycaller

在接下来是执行代码重定位功能,如果代码是从flash中执行的话就要将u-boot代码搬移到DDR3中。

relocate:             @relocate U-Boot to RAM

adr r0, _start    @ r0 <-current position of code

ldr r1, _TEXT_BASE       @ test ifwe run from flash or RAM

cmp r0, r1        @ don't relocduring debug

beq stack_setup

 

ldr r2, _armboot_start

ldr r3, _bss_start

sub r2, r3, r2    @ r2 <- sizeof armboot

add r2, r0, r2    @ r2 <- sourceend address

adr r0, _start这句代码是将_start标签处运行时的地址值装进r0,这条指令到底取一个什么值呢?假如针对该程序来说,当被装载到0x0000处运行时,r0就是 0,假设装载到TEXT_BASE处运行,则r0=TEXT_。

汇编代码字段的执行地址和链接地址是不一样的,执行地址是当前cpu执行到该字段时的地址,而链接地址是编译的地址,cpu正常工作时应该是链接地址才对。

几条指令分析如下:

将正在运行的程序_start标签处地址到r0,将_TEXT_BASE变量值装到r1,,比较是不是从Flash中执行的,还是这段代码已装载到_TEXT_BASE处执行的。如果不等,则将FLASH中的u-boot代码搬到_TEXT_BASE处。

copy_loop:            @copy 32 bytes at a time

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

stmia  r1!, {r3 - r10}      @ copy to   targetaddress [r1]

cmp r0, r2        @ until sourceend addreee [r2]

ble copy_loop

 

开始把代码从flash中搬运到RAM中

copy_loop:            @copy 32 bytes at a time

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

stmia  r1!, {r3 - r10}      @ copy to   targetaddress [r1]

cmp r0, r2        @ until sourceend addreee [r2]

ble copy_loop

接下来时建立堆栈和代码段数据段

stack_setup:

ldr r0, _TEXT_BASE       @ upper128 KiB: relocated uboot

sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area

sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo

……

最后程序跳转到start_armboot即c语言代码,第二阶段。

ldr pc, _start_armboot   @ jump to C code

_start_armboot: .word start_armboot

总结reset这块代码,主要完成了一下几个部分:

1.重要部分的初始化工作

2.重定位bootloader到ram

3.设置好堆栈,设置PLL, master clock  ,sdram controller的设置。

4.跳转到第2阶段执行

完成这些后,此时内存的分布情况如下:

5 u-boot第二阶段分析

     u-boot阶段执行到start_armboot对应c代码中start_armboot函数地址,start_armboot在./arch/arm/lib/board.c中定义。

    start_armboot首先会获取全局数据段

    gd = (gd_t*)(_armboot_start -CONFIG_SYS_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));

    gd->flags |= GD_FLG_RELOC;

    monitor_flash_len = _bss_start - _armboot_start;

接下来执行一序列开发板体系相关的函数

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

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

          hang ();

       }

}

序列函数对应如下(./arch/arm/lib/board.c):后面重点分析会分析


上面的函数大多数能在./board/ti/ti8168/evm.c中可以找到。

接下来会初始化一序列外部设备,如串口,网卡、phy、nand、mmc等设备,并加载设备驱动,同时初始化和获取一些环境变量。

……

puts ("NAND: ");

nand_init();     /* go initthe NAND */

puts ("MMC:  ");

mmc_initialize (gd->bd);

env_relocate ();

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

stdio_init ();    /* getthe devices list going. */

jumptable_init ();

console_init_r ();   /*fully init console as a device */

enable_interrupts ();

eth_initialize(gd->bd);

eth_initialize()函数会调用board_eth_init(),board_eth_init()会初始化网络,加载网卡和PHY驱动,board_eth_init()在./driver/net/davincin_emac.c中。

 最后系统进入一个死循环中。

 for (;;) {

       main_loop ();

}

      主循环的代码定义在./common中,主循环的是一个死循环,系统不会从中跳出,在主循环中,系统执行分为两种情况,一种是在启动规定时间内有任意按键按下时,进入u-boot命令状态;另一种是直接启动。两种情况最终都会将内核uImage搬移到内存中,并进行解压,最后u-boot将程序控制权交给linux内核。

    #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >=0)

    s = getenv ("bootdelay");

    bootdelay = s ? (int)simple_strtol(s, NULL, 10) :CONFIG_BOOTDELAY;

    debug ("### main_loop entered: bootdelay=%d\n\n",bootdelay);

    获取启动等待时间,最多10秒。

s = getenv ("bootcmd");//获取内核启动参数

if (bootdelay >= 0 && s && !abortboot(bootdelay)) {

# ifdef CONFIG_AUTOBOOT_KEYED

int prev = disable_ctrlc(1);   /*disable Control C checking */

# endif

# ifndef CONFIG_SYS_HUSH_PARSER

       run_command (s, 0);

# else

       parse_string_outer(s,FLAG_PARSE_SEMICOLON |

                  FLAG_EXIT_FROM_LOOP);

# endif

# ifdef CONFIG_AUTOBOOT_KEYED

       disable_ctrlc(prev); /* restore Control C checking */

# endif

}

abortboot作用是判断规定时间内是否有按键按下。

在启动等待时间内,若无按键按下按照bootcmd参数进行内核的启动,最后将在bootm命令的控制下,进入linux内核,并将控制权交给uImage。

在启动等待时间内,若有按键按下时,会进入到u-boot命令行状态,通过与用户进行交互,从控制台获取命令参数,执行相应命令,如tftp命令、设置环境变量、nand烧写等命名,最后也是通过bootm命令,进入到linux内核,u-boot将系统控制权交给Linux内核uImage。

for (;;) {

       len = readline(CONFIG_SYS_PROMPT);

       flag = 0;  /* assume no special flags for now */

       if (len > 0)

          strcpy(lastcommand, console_buffer);

       else if (len == 0)

          flag |=CMD_FLAG_REPEAT;

       if (len == -1)

          puts("<INTERRUPT>\n");

       else

          rc = run_command(lastcommand, flag);

       if (rc <= 0) {

          /* invalidcommand or not repeatable, forget it */

          lastcommand[0] =0;

       }

   }

 该函数比较长,但核心就是不停的从串口获取命令并解析执行此命令, 这个功能就在函数尾部的那个死循环里, 我们重点就是分析这个循环。这个循环最终通过bootm命令进入到uImage内核态中。

bootm命令会调用do_bootm_linux()函数,进入到Linux内核中运行。

u-boot中可执行的命令的源码在./common目录下,也可以自己定义u-boot命令。

更为具体的u-boot启动分析参考自己所写的newmsg9260的u-boot启动分析文档,启动流程图见ti1868_evm_uboot_start.img。



0 0