Chinaunix首页 | 论坛 | 博客
  • 博客访问: 208162
  • 博文数量: 54
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 230
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-25 13:58
文章分类

全部博文(54)

文章存档

2014年(12)

2013年(42)

分类: 嵌入式

2014-09-02 22:51:57

原文地址:uboot分析之旅 作者:Yiran_Linux

首先给出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
#

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 " >>config.h//将#include追加进文件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就相当于#include,以适应不同的体系结构。
注释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
#include


/*
 *************************************************************************
 *
 * 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
ldr sp, FIQ_STACK_START
.endm

/*
 * exception handlers
 */
.align  5
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction

.align 5
software_interrupt:
get_bad_stack
bad_save_user_regs
bl do_software_interrupt

.align 5
prefetch_abort:
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort

.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort

.align 5
not_used:
get_bad_stack
bad_save_user_regs
bl do_not_used

#ifdef CONFIG_USE_IRQ

.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs

.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs

#else

.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq

.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq
#endif

注释1:lowlevel_ini函数并不复杂,只是要注意这时的代码、数据都只保存在NOR Flash上,内存中还没有,所以读取数据时要变换地址
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr     r0, =SMRDATA  //以下三行为地址变换。SMRDATA表示这13个寄存器的值存放的开始地址(连接地址),值为0x33f8xxxx,处于内存中
ldr r1, _TEXT_BASE//获得代码的起始地址,它就是0x33f80000
sub r0, r0, r1              //由此获得13个寄存器的值在NOR Flash上存放的开始地址
ldr r1, =BWSCON /* Bus Width Status Controller */
add     r2, r0, #13*4
0:
ldr     r3, [r0], #4
str     r3, [r1], #4
cmp     r2, r0
bne     0b

/* everything is fine now */
mov pc, lr

.ltorg
/* the literal pools origin */
/*13个寄存器值存放的起始地址*/
SMRDATA:
    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
    .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
    .word 0x32
    .word 0x30
    .word 0x30

start.S总结:
第一阶段分析
(1)硬件设备初始化:
将cpu的工作模式设为管理模式(svr),关闭WATCHDOG,设置FCLK,HCLK,PCLK的比例(设置CLKDEVN寄存器),关闭MMU、CACHE
(2)为加载bootloader的第二阶段代码准备空间:
所谓准备RAM空间,就是初始化内存芯片,使它可用。对于S3C2410/S3C2440,通过在start.S中调用lowlevel_init函数来设置存储控制器,使得外接的SDRAM可用。lowlevel_init函数定义在boadr/smdk2410/lowlevel_init.S中
(3)复制bootloader的第二阶段代码到RAM空间中。相关代码在cpu/arm920t/start.S中实现。
(4)设置好栈,栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存就可以了
到了这一步就基本上知道了内存的使用情况,如下图所示:
uboot分析之旅 - 航天 - 航天的博客
 (5)跳转到第二阶段代码的C入口点,在跳转之前,还要清除BSS段,现在C函数的运行环境已经准备好,然后跳转到c程序入口地址,这之后程序在内存中运行,它将调用lib_arm/board.c中的start_armboot函数,这是第二阶段的入口点。
第二阶段分析
我们先贴出这一阶段的程序流程图,以便理解:
uboot分析之旅 - 航天 - 航天的博客
 
我们再贴出代码:
对应的初始化函数定义在一个结构体里:
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
interrupt_init, /* set up exceptions */
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
dram_init, /* configure available RAM banks */
display_dram_config,
NULL,
};
start_armboot函数代码如下:
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
char *s;
#ifndef CFG_NO_FLASH
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif

/* 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;

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}

#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init ();
display_flash_config (size);
#endif /* CFG_NO_FLASH */

#ifdef CONFIG_VFD
# ifndef PAGE_SIZE
#  define PAGE_SIZE 4096
# endif
/*
* reserve memory for VFD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = vfd_setmem (addr);
gd->fb_base = addr;
#endif /* CONFIG_VFD */

#ifdef CONFIG_LCD
# ifndef PAGE_SIZE
#  define PAGE_SIZE 4096
# endif
/*
* reserve memory for LCD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = lcd_setmem (addr);
gd->fb_base = addr;
#endif /* CONFIG_LCD */

/* armboot_start is defined in the board-specific linker script */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND:  ");
nand_init(); /* go init the NAND */
#endif

#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif

/* initialize environment */
env_relocate ();

#ifdef CONFIG_VFD
/* must do this after the framebuffer is allocated */
drv_vfd_init();
#endif /* CONFIG_VFD */

/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];

i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;

for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}

#ifdef CONFIG_HAS_ETH1
i = getenv_r ("eth1addr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;

for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
#endif
}

devices_init (); /* get the devices list going. */

#ifdef CONFIG_CMC_PU2
load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */

jumptable_init ();

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

#if defined(CONFIG_MISC_INIT_R)
/* miscellaneous platform dependent initialisations */
misc_init_r ();
#endif

/* enable exceptions */
enable_interrupts ();

/* Perform network card initialisation if necessary */
#ifdef CONFIG_DRIVER_CS8900
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif

#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
if (getenv ("ethaddr")) {
smc_set_mac_addr(gd->bd->bi_enetaddr);
}
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */

/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if (CONFIG_COMMANDS & CFG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif /* CFG_CMD_NET */

#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
#if (CONFIG_COMMANDS & CFG_CMD_NET)
#if defined(CONFIG_NET_MULTI)
puts ("Net:   ");
#endif
eth_initialize(gd->bd);
#endif
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}

/* NOTREACHED - no way out of command loop except booting */
}
(1)初始化本阶段要用的硬件设
最主要的是设置系统时钟、初始化串口,只要这两个设置就好了,就可以从串口打印信息了
board_init 函数设置MPLL、改变系统时钟,它是开发板相关的函数,在board/smdk2410/smdk2410.c中实现。值得注意的 是,board_init函数中还保存了机器类型id,这将在调用内核时传给内核,board_init()函数代码如下:
int board_init (void)
{
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFF;

/* configure MPLL */
clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);

/* some delay between MPLL and UPLL */
delay (4000);

/* configure UPLL */
clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);

/* some delay between MPLL and UPLL */
delay (8000);

/* set up the I/O ports */
gpio->GPACON = 0x007FFFFF;
gpio->GPBCON = 0x00044555;
gpio->GPBUP = 0x000007FF;
gpio->GPCCON = 0xAAAAAAAA;
gpio->GPCUP = 0x0000FFFF;
gpio->GPDCON = 0xAAAAAAAA;
gpio->GPDUP = 0x0000FFFF;
gpio->GPECON = 0xAAAAAAAA;
gpio->GPEUP = 0x0000FFFF;
gpio->GPFCON = 0x000055AA;
gpio->GPFUP = 0x000000FF;
gpio->GPGCON = 0xFF95FFBA;
gpio->GPGUP = 0x0000FFFF;
gpio->GPHCON = 0x002AFAAA;
gpio->GPHUP = 0x000007FF;

/* arch number of SMDK2410-Board */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;/*传给内核的机器码*/

/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100;/*启动参数*/

icache_enable();
dcache_enable();

return 0;
}
在cpu/arm920t/s3c24x0/serial.c中定义了串口初始化程序serial_int(),贴出相关代码:
int serial_init (void)
{
serial_setbrg ();

return (0);
}
为简化分析步骤就不具体分析了
(2)检测系统内存映射(memory map)
对于特定的开发板,其内存的分布是明确的,所以可以直接设置。board/smdk2410/smdk2410.c中的dram.init函数指定了本开发板的内存起始地址为0x30000000,大小为0x4000000,代码如下:
int dram_init (void)
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;//0x30000000
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;//0x4000000

return 0;
}
这些设置的参数,将在后面向内核传递参数时用到
(3)我们知道即便是内核启动,也是通过u-boot命令来实现的,u-boot中每个命令都通过U_BOOT_CMD宏来定义,格式如下:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
参数意义如下:
name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)
maxargs:最大的参数个数
repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行
command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *,int,int,char *[])。
usage:简短的使用说明,这是个字符串。
help:叫详细的使用说明,这时个字符串。
宏U_BOOT_CMD在include/commond.h中定义,如下所示:
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) 
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
Struct_Section也是在include/commond.h中定义的,如下所示:
#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))
比如对于bootm命令,它如下定义:
U_BOOT_CMD(
bootm,CFG_MAXARGS,1,do_bootm,
"string1",
"string2"
)
宏U_BOOT_CMD扩展开后如下所示:
cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd")))=
{"bootm",CFG_MAXARGS,1,do_bootm."string1","string2"};
对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在”.u_boot_cmd“段中定义一个cmd_tbl_t结构。连接脚本中有如下代码:
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
程序中就是根据命令的名字在内存段__u_boot_cmd_start~__u_boot_cmd_end 之间找到它的cmd_tbl_t结构,然后调用它的函数find_cmd,关于这个函数我们可以参考common/command.c中的find_cmd函数。
我们已nand read.jffs2和bootm命令为例来分析一下,顺便可以把我们u_boot最核心的部分(启动内核)来分析一下:
(1)首先我们在命令行里输入nand read.jffs2 0x30007fc0 kernel //这句话用来从0x30007fc0处读取kernel分区内容
(2)程序运行在common/main.c中,s = getenv("bootcmd")来获得输入的命令,然后调用run_command (s, 0)函数。在这个函数里首先对命令进行解析,并提取命令参数,然后调用函数 find_cmd()  __u_boot_cmd_start~__u_boot_cmd_end 之间寻找相同的命令,并返回对应的 cmd_tbl_t 结构体,find_cmd()函数代码如下:
cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; /*Init value */
const char *p;
int len;
int n_found = 0;

/*
* Some commands allow length modifiers (like "cp.b");
* compare command name only until first dot.
*/
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);

for (cmdtp = &__u_boot_cmd_start;cmdtp != &__u_boot_cmd_end; cmdtp++)//这就是我们所说的遍历了
     {
if (strncmp (cmd, cmdtp->name, len) == 0)//名字要相同
             {
if (len == strlen (cmdtp->name))//长度要相同
return cmdtp; /* full match */

cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == 1) { /* exactly one match */
return cmdtp_temp;
}

return NULL; /* not found or ambiguous command */
}
(3)找到命令后会调用命令对应的函数,代码如下:
(cmdtp->cmd) (cmdtp, flag, argc, argv)
在分析内核启动之前我们先来补充一些知识:
分区,在嵌入式中,各个分区都在内核里写死了,具体在include/configs中的smdk2410.h中,不过我没有发现有具体的代码,估计是在移植的时候自己添加进去的!这里我们把韦东山老师的代码贴出来,分析一下:
uboot分析之旅 - 航天 - 航天的博客
 在nandflash上从0地址开始,前256k存放bootloader,接下来128k存放环境变量,然后是2M存放kernel,剩下的是root分区。
那么 nand read.jffs2 0x30007fc0 kernel 对应的函数是common/cmd_nand.c文件里的do_nand()函数,具体代码我们不分析了,只是说一下它的作用:读kernel分区内容到内存中
(4)在命令行输入:bootm 0x30007fc0 //这是启动内核的
跟上面一样找到相应的命令,执行相应的程序 。我们也要补充一点知识:
u_boot下要用到uImage,uImage由一个头和真正的内核组成
其中头部的定义为:
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load  Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
里面两个成员比较重要:
ih_load:加载地址,表示内核运行时要放在哪里
ih_ep   :内核入口地址
bootm 0x30007fc0时,如果发现内核的存放地址与加载uImage的头里面的加载地址不相符,就会把内核重新拷贝到加载地址处。这当然要花费一定的时间,所以我们一般会让它们相符,以加快内核启动速度。
对应的函数是common/cmd_bootm.c下的do_bootm()函数,关于这个函数我们不再分析具体代码,只是说一下它的功能:
  .根据头部将内核移动到合适的地方
  .启动内核,启动内核会调用armlinux.c中函数:do_bootm_linux()对这个函数我们也不分析其具体代码,我们只说一下它的功能:设置启动参数并跳到入口地址处。
我们还得来说一说启动参数的问题,我们知道当内核启动起来之后,u_boot就没有用了,所以u_boot需要将一些内核参数放在跟内核约定好的某个位置,以便内核能够在无u_boot下访问,下面一些函数完成这项功能
setup_start_tag (bd)
setup_memory_tags (bd)
setup_commandline_tag (bd, commandline)
setup_end_tag (bd)
在来谈一谈启动内核,下面一个函数完成内核的启动工作:theKernel (0, bd->bi_arch_number, bd->bi_boot_params)
它带了三个参数:bd->bi_arch_number表示机器码
                              bd->bi_boot_params启动参数地址
这两个参数在board/smdk2410/smdk2410.c文件里可以找到定义:
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410 //不解释,在内核分析里会详细分析
gd->bd->bi_boot_params = 0x30000100 //启动参数放在0x30000100开始的位置,要记清楚,内核分析里要用到的
好了,就分析到这里
阅读(1978) | 评论(0) | 转发(0) |
0

上一篇:U-Boot启动过程完全分析

下一篇:没有了

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