首先给出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寄存器指向一段没有使用的内存就可以了
到了这一步就基本上知道了内存的使用情况,如下图所示:
(5)跳转到第二阶段代码的C入口点,在跳转之前,还要清除BSS段,现在C函数的运行环境已经准备好,然后跳转到c程序入口地址,这之后程序在内存中运行,它将调用lib_arm/board.c中的start_armboot函数,这是第二阶段的入口点。
第二阶段分析
我们先贴出这一阶段的程序流程图,以便理解:
我们再贴出代码:
对应的初始化函数定义在一个结构体里:
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中,不过我没有发现有具体的代码,估计是在移植的时候自己添加进去的!这里我们把韦东山老师的代码贴出来,分析一下:
在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开始的位置,要记清楚,内核分析里要用到的
好了,就分析到这里