现在的位置: 首页 > 综合 > 正文

uboot分析之旅

2013年11月18日 ⁄ 综合 ⁄ 共 13585字 ⁄ 字号 评论关闭

首先给出uboot源码下载地址:

http://www.icdev.com.cn/batch.viewlink.php?itemid=1694 

1、uboot功能:
1、硬件相关的初始化 关看门狗、初始化时钟、初始化SDRAM(为了开发方便还需要加入以下功能:烧写flash、支持网卡、支持usb、支持串口)
2、从flash读出内核  
3、启动内核  
2、分析
(1)顶层makefile
我们先从顶层makefile来分析,在使用make编译之前我们会先选择板级配置,比如make smdk2410_config。它对应着顶层makefile中的:
smdk2410_config
: unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
也就是说当运行命令make smdk2410_config时,就相当于执行@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
下面我们对@$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0来深入分析一下:
MKCONFIG:我们在makefile文件中找到下一行代码:MKCONFIG
:= $(SRCTREE)/mkconfig,
它代表源码树下面的mkconfig文件,我们确实在顶层目录发现了mkconfig文件。那我们就知道MKCONFIG就是执行顶层目录的mkconfig文件里的命令。
$(@:_config=):就是将smdk2410_config中的_config替换为空,变成了smdk2410
因此make smdk2410_config这一命令的真实效果就是:./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0
(2)顶层mkconfig
基于(1)我们现在就是要来分析./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24这个命令
首先是顶层mkconfig,我们来分析一下它的作用:
#!/bin/sh -e

# Script to create header files and links to configure
# U-Boot for a specific board.
#
# Parameters:  Target  Architecture  CPU  Board [VENDOR] [SOC]
#
# (C) 2002-2006 DENX Software Engineering, Wolfgang Denk <wd@denx.de>
#

APPEND=no # Default: Create new config file
BOARD_NAME=""
# Name to print in make output

/*判断有没有--、-a、-n、*这些参数,因为我们没有,所以不用分析*/
while [ $# -gt 0 ] ; do
case "$1" in
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*)  break ;;
esac
done

[ "${BOARD_NAME}" ] || BOARD_NAME="$1"//确定开发版BOARD_NAME,$1表示第一个参数($0表示第0个参数

[ $# -lt 4 ] && exit 1  //$#表示参数的个数,小于4个会退出,大于6个也会退出
[ $# -gt 6 ] && exit 1

echo "Configuring for ${BOARD_NAME} board..."//回显这句话

#
# Create link to architecture specific headers
#
if [ "$SRCTREE" != "$OBJTREE" ] ; then         //见注释1
mkdir -p ${OBJTREE}/include
mkdir -p ${OBJTREE}/include2
cd ${OBJTREE}/include2
rm -f asm
ln -s ${SRCTREE}/include/asm-$2 asm
LNPREFIX="../../include2/asm/"
cd ../include
rm -rf asm-$2
rm -f asm
mkdir asm-$2
ln -s asm-$2 asm
else
cd ./include  //进入include文件
rm -f asm    
//删除asm
ln -s asm-$2 asm
//建立一个连接文件asm,并使它指向asm-arm,第二个参数为arm,这样做的原因请参考注释2
fi

rm -f asm-$2/arch

/*如果第六个参数为空或者等于NULL,显然我们不满足*/
if [ -z "$6" -o "$6" = "NULL" ] ; then
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else
ln -s ${LNPREFIX}arch-$6 asm-$2/arch
//LNPREFIX没有定义,所以这句话的意思就是,在asm-arm目录下建立arch文件并使其指向arch-                                                                                   //s3c24x0
fi
/*显然成立*/
if [ "$2" = "arm" ] ; then
rm -f asm-$2/proc //删除asm-arm/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc //在asm-arm下建立proc文件,并使其指向proc-armv
fi

#
# Create include file for Make
#
echo "ARCH   = $2" >  config.mk//创建config.mk文件,并把ARCH=arm追加进文件里
echo "CPU    = $3" >> config.mk//把CPU=arm920t追加进config.mk文件
echo "BOARD  = $4" >> config.mk//把BOARD=sdmk2410追加进config.mk文件

[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk//$5为NULL,显然不成立

[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk//把SOC=s3c24x0追加进config.mk文件


#
# Create board specific header file:创建单板相关的头文件
#
if [ "$APPEND" = "yes" ]
# Append to existing config file
then
echo >> config.h
else
> config.h
#
Create new config file:新建config.h文件
fi
echo "/* Automatically generated - do not edit */" >>config.h
echo "#include <configs/$1.h>" >>config.h//将#include<configs/arm920t.h>追加进文件config.h

exit 0
注释1:
OBJTREE := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))//如果定义了BUILD_DIR,OBJTREE=BUILD_DIR,否则OBJTREE=CURDIR
SRCTREE := $(CURDIR)
显然OBJTREE和SRCTREE有可能相等,也有可能不相等,我们假设不相等先将其略过
注释2:我们在头文件里如果有这么一行:#include<asm/type.h>就相当于#include<asm-arm/type.h>,以适应不同的体系结构。
注释3:我们来总结一下在mkconfig文件里完成了那些工作
(1)开发版名称BOARD_NAME等于$1
(2)创建到平台/开发版相关的头文件的链接
(3)创建顶层Makefile包含的文件:include/config.mk
(4)创建开发版相关的头文件include/config.h
以上四点就是总结

(3)回到顶层makefile
当我们执行编译命令make时,肯定也是依据makefile来进行编译的,所以还要从头来分析makefile文件,引文makefile文件比较大,我们分析其中比较重要的部分:
117  include $(OBJTREE)/include/config.mk//我们在配置的时候创建了这个文件,这里就用到了

124  ifeq ($(ARCH),arm)
125  CROSS_COMPILE = arm-linux- //不用解释了吧

169  OBJS  = cpu/$(CPU)/start.o //OBJS  = cpu/arm920t/start.o
193  LIBS  = lib_generic/libgeneric.a //将lib_generic文件夹下的libgeneric.a 打包为库,下同
194  LIBS += board/$(BOARDDIR)/lib$(BOARD).a
195  LIBS += cpu/$(CPU)/lib$(CPU).a

239  ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)//我们的目的是生成u-boot.bin
241  all:
$(ALL)//在make时如果不指定目标,就会执行这一行

249  $(obj)u-boot.bin:
$(obj)u-boot
//u-boot.bin依赖于u-boot
250   $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@

/*这里面的_OBJS、_LIBS就是上面出现的那些目标和库*/
262       $(obj)u-boot:
depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
263   UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
264   cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
265   --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
266   -Map u-boot.map -o u-boot
我们看到这些代码真实TM的难懂,不过韦老师告诉我们make编译的话,最后的输出信息可以帮助我们理解以上代码,那好,我们make后将最后的输出信息贴出来:
UNDEF_SYM=`arm-linux-objdump -x lib_generic/libgeneric.a board/smdk2410/libsmdk2410.a cpu/arm920t/libarm920t.a cpu/arm920t/s3c24x0/libs3c24x0.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/nand/libnand.a drivers/nand_legacy/libnand_legacy.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`;\
                cd /home/lht/fl2440/u-boot-1.1.6 &&
arm-linux-ld -Bstatic -T /home/lht/fl2440/u-boot-1.1.6/board/smdk2410/u-boot.lds -Ttext 0x33F80000  $UNDEF_SYM cpu/arm920t/start.o           /*链接文件依赖于链接脚本u-boot.lds和原材料(即start.o和下面的库文件)*/
 
    
                        --start-group lib_generic/libgeneric.a board/smdk2410/libsmdk2410.a cpu/arm920t/libarm920t.a cpu/arm920t/s3c24x0/libs3c24x0.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/nand/libnand.a drivers/nand_legacy/libnand_legacy.a
drivers/sk98lin/libsk98lin.a post/libpost.a post/cpu/libcpu.a common/libcommon.a --end-group -L /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1 -lgcc 
                        -Map u-boot.map -o u-boot//输出u-boot
综上来说就是链接脚本描述如何组织原材料,链接脚本生成链接文件,然后原材料根据链接文件链接成u-boot。那么链接脚本是如何组织原材料的呢?我们来看一看:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)

SECTIONS
{   
        . = 0x00000000;  //当前地址是0x00000000,不过后面这些东西所放的地址会加上一个偏移地址:0x33F80000 ,关于这个偏移地址怎么来的请
                                    //看注释1
        . = ALIGN(4); 
        .text      :
        {
          cpu/arm920t/start.o   (.text)//先放start.o的代码段
          *(.text)//然后放所有其他文件的代码段
        }

        . = ALIGN(4);
        .rodata : { *(.rodata) }//放所有文件的只读数据段

        . = ALIGN(4);
        .data : { *(.data) }//放所有文件的数据段

        . = ALIGN(4);
        .got : { *(.got) }

        . = .;
        __u_boot_cmd_start = .;
        .u_boot_cmd : { *(.u_boot_cmd) }//放u-boot命令段
        __u_boot_cmd_end = .;

        . = ALIGN(4);
        __bss_start = .;
        .bss : { *(.bss) }
        _end = .;
ot_cmd_start = .;
        .u_boot_cmd : { *(.u_boot_cmd) }
        __u_boot_cmd_end = .;

        . = ALIGN(4);
        __bss_start = .;
        .bss : { *(.bss) }
        _end = .;
}
注释1:
在顶层目录下有个config.mk,里面有这么一行:189 LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
我们可以在输出信息里找到对应的一行: arm-linux-ld -Bstatic -T /home/lht/fl2440/u-boot-1.1.6/board/smdk2410/u-boot.lds -Ttext 0x33F80000
 $UNDEF_SYM cpu/arm920t/start.o 
可以看出LDSCRIPT代表链接脚本,TEXT_BASE就是 0x33F80000 ,那么 0x33F80000 在哪里定义的呢?我们来找上一找,最终我们在board/smdk2410/config.mk文件里发现了定义:TEXT_BASE
= 0x33F80000。
那如果想让u-boot放在另外的地址,就可以修改这个值
这样我们就知道了,u-boot启动之后最先运行的是start.S文件,这个文件也是我们要分析的核心。其实makefile就是规定文件应该如何组织的,具体的程序如何执行当然还要看具体的文件,makefile就说到这里,接下来我们分析start.S
(4)start.S分析
由于这个文件TM的重要,所以虽然代码也挺长的,我们还是把代码全部贴了出来:
#include <config.h>
#include <version.h>


/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */


.globl _start
_start: b       reset            
 //首先跳转到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:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq: .word irq
_fiq: .word fiq

.balignl 16,0xdeadbeef


/*
 *************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * relocate armboot to ram
 * setup stack
 * jump to second stage
 *
 *************************************************************************
 */

_TEXT_BASE:
.word
TEXT_BASE

.globl _armboot_start
_armboot_start:
.word _start

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
.word __bss_start

.globl _bss_end
_bss_end:
.word _end

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word
0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif


/*
 * the actual reset code
 */

reset:
/*
* set the cpu to SVC32 mode,切换到管理模式
*/
mrs
r0,cpsr
bic
r0,r0,#0x1f
orr
r0,r0,#0xd3
msr
cpsr,r0

/* turn off the watchdog,关闭看门狗 */
#if defined(CONFIG_S3C2400)
# define pWTCON
0x15300000
# define INTMSK
0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN
0x14800014 /* clock divisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON
0x53000000
# define INTMSK
0x4A000008 /* Interupt-Controller base addresses */
# define INTSUBMSK
0x4A00001C
# define CLKDIVN
0x4C000014 /* clock divisor register */
#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
ldr     r0, =pWTCON
mov     r1, #0x0
str     r1, [r0]

/*
* mask all IRQs by setting all bits in the INTMR - default,屏蔽所有中断
*/
mov
r1, #0xffffffff
ldr
r0, =INTMSK
str
r1, [r0]
# if defined(CONFIG_S3C2410)
ldr
r1, =0x3ff
ldr
r0, =INTSUBMSK
str
r1, [r0]
# endif
         /*设置时钟*/
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr
r0, =CLKDIVN
mov
r1, #3
str
r1, [r0]
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */

/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl
cpu_init_crit      
         //初始化cpu,我跳
#endif
/*复制bootloader的第二阶段代码到RAM空间中*/
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:
/* 将u-boot复制到RAM中*/
adr
r0, _start
/* r0当前代码的开始地址 */
ldr
r1, _TEXT_BASE
/*r1 代码段的连接地址 */
cmp     r0, r1                  /* 测试现在是在flash中还是在ram中 */
beq     stack_setup         /*如果已经在RAM中(这通常是调试时直接下载到RAM中),则不需要复制*/

ldr
r2, _armboot_start  
/* _armboot_start在前面定义,是第一条指令的运行地址*/
ldr
r3, _bss_start    
/*在连接脚本u-boot.lds中定义,是代码段的结束地址*/
sub
r2, r3, r2
/* r2 =代码段长度*/
add
r2, r0, r2
/* r2=NOR FLASH上代码段的结束地址*/

copy_loop:
ldmia
r0!, {r3-r10}
/* 从地址[r0]处获得数据*/
stmia
r1!, {r3-r10}
/* 复制到地址r[1]处*/
cmp
r0, r2
/* 判断是否复制完毕*/
ble
copy_loop              
 /*没复制完就继续*/
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */

/* Set up the stack:设置栈*/
stack_setup:
ldr
r0, _TEXT_BASE
/* upper 128 KiB: relocated uboot  :这是 0x33F80000 没忘记吧,作为代码段的起始地址*/
sub
r0, r0, #CFG_MALLOC_LEN
/* malloc area 代码段下留出一段内存用于malloc*/
sub
r0, r0, #CFG_GBL_DATA_SIZE
/* bdinfo再留出一段内存用作全局变量 */
#ifdef CONFIG_USE_IRQ
sub
r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
/*IRQ、FIQ模式的栈*/
#endif
sub
sp, r0, #12
/* leave 3 words for abort-stack :留出12个字节用作abort */

/*清BSS段(初始值为0、无初始值的全局变量、静态变量放在BSS段)*/
clear_bss:
ldr
r0, _bss_start /*BSS段的开始地址,它的值在链接脚本文件u-boot.lds里确定 */
ldr
r1, _bss_end /* BSS段的结束地址,
它的值在链接脚本文件u-boot.lds里确定  */
mov
r2, #0x00000000 /* clear                            */

clbss_l:str
r2, [r0] /* clear loop...                    */
add
r0, r0, #4
cmp
r0, r1
ble
clbss_l

#if 0
/* try doing this stuff after the relocation */
ldr     r0, =pWTCON
mov     r1, #0x0
str     r1, [r0]

/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov
r1, #0xffffffff
ldr
r0, =INTMR
str
r1, [r0]

/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr
r0, =CLKDIVN
mov
r1, #3
str
r1, [r0]
/* END stuff after relocation */
#endif

ldr
pc, _start_armboot //跳转

_start_armboot:
.word start_armboot


/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */


#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches,清caches
*/
mov
r0, #0
mcr
p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
mcr
p15, 0, r0, c8, c7, 0 /* flush v4 TLB */

/*
* disable MMU stuff and caches,关闭mmu和caches
*/
mrc
p15, 0, r0, c1, c0, 0
bic
r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic
r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr
r0, r0, #0x00000002 @ set bit 2 (A) Align
orr
r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr
p15, 0, r0, c1, c0, 0

/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov
ip, lr
bl
lowlevel_init  
//跳转喽。这个字函数的作用是:初始化内存控制器,初始化之后内存才能使用,这段代码在注释1里贴出来
mov
lr, ip
mov
pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */

/*
 *************************************************************************
 *
 * Interrupt handling
 *
 *************************************************************************
 */

@
@ IRQ stack frame.
@
#define S_FRAME_SIZE
72

#define S_OLD_R0
68
#define S_PSR
64
#define S_PC
60
#define S_LR
56
#define S_SP
52

#define S_IP
48
#define S_FP
44
#define S_R10
40
#define S_R9
36
#define S_R8
32
#define S_R7
28
#define S_R6
24
#define S_R5
20
#define S_R4
16
#define S_R3
12
#define S_R2
8
#define S_R1
4
#define S_R0
0

#define MODE_SVC 0x13
#define I_BIT
0x80

/*
 * use bad_save_user_regs for abort/prefetch/undef/swi ...
 * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
 */

.macro
bad_save_user_regs
sub
sp, sp, #S_FRAME_SIZE
stmia
sp, {r0 - r12} @ Calling r0-r12
ldr
r2, _armboot_start
sub
r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub
r2, r2, #(CFG_GBL_DATA_SIZE+8)  @ set base 2 words into abort stack
ldmia
r2, {r2 - r3} @ get pc, cpsr
add
r0, sp, #S_FRAME_SIZE @ restore sp_SVC

add
r5, sp, #S_SP
mov
r1, lr
stmia
r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov
r0, sp
.endm

.macro
irq_save_user_regs
sub
sp, sp, #S_FRAME_SIZE
stmia
sp, {r0 - r12} @ Calling r0-r12
add     r8, sp, #S_PC
stmdb   r8, {sp, lr}^                   @ Calling SP, LR
str     lr, [r8, #0]                    @ Save calling PC
mrs     r6, spsr
str     r6, [r8, #4]                    @ Save CPSR
str     r0, [r8, #8]                    @ Save OLD_R0
mov
r0, sp
.endm

.macro
irq_restore_user_regs
ldmia
sp, {r0 - lr}^ @ Calling r0 - lr
mov
r0, r0
ldr
lr, [sp, #S_PC] @ Get PC
add
sp, sp, #S_FRAME_SIZE
subs
pc, lr, #4 @ return & move spsr_svc into cpsr
.endm

.macro get_bad_stack
ldr
r13, _armboot_start @ setup our mode stack
sub
r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub
r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack

str
lr, [r13] @ save caller lr / spsr
mrs
lr, spsr
str     lr, [r13, #4]

mov
r13, #MODE_SVC @ prepare SVC-Mode
@ msr
spsr_c, r13
msr
spsr, r13
mov
lr, pc
movs
pc, lr
.endm

.macro get_irq_stack
@ setup IRQ stack
ldr
sp, IRQ_STACK_START
.endm

.macro get_fiq_stack
@ setup FIQ stack
ld

抱歉!评论已关闭.