开发板:JZ2440V3
uboot代码版本:u-boot-1.1.6
本文的主要内容是韦东山讲解的uboot分析笔记。如果有错误欢迎指出讨论。谢谢!
1.将源码压缩包上传服务器并解压源码:tar -xvf u-boot-1.1.6.tar.bz2;
2.打补丁:cd u-boot-1.1.6
patch -p1 < ../u-boot-1.1.6_jz2440.patch
"-p1"表示忽略第一个"/"符合之前的东西;
补丁文件的简要介绍:
--- u-boot-1.1.6/board/100ask24x0/100ask24x0.c 1970-01-01 07:00:00.000000000 +0700
+++ u-boot-1.1.6_jz2440/board/100ask24x0/100ask24x0.c 2010-11-26 12:54:37.034090906 +0800
"---"表示原来的代码
"+++"表示修改后的代码
3.配置uboot:
执行:make 100ask24x0_config
输出:Configuring for 100ask24x0 board...
4.编译uboot:
make
编译完成之后,使用openjtag下载uboot.bin文件看看是否能成功运行。打印信息如下:
U-Boot 1.1.6 (Apr 29 2018 - 08:50:02)
DRAM: 64 MB
Flash: 0 kB
NAND: 256 MiB
In: serial
Out: serial
Err: serial
UPLLVal [M:38h,P:2h,S:2h]
MPLLVal [M:5ch,P:1h,S:1h]
CLKDIVN:5h
+---------------------------------------------+
| S3C2440A USB Downloader ver R0.03 2004 Jan |
+---------------------------------------------+
USB: IN_ENDPOINT:1 OUT_ENDPOINT:3
FORMAT: <ADDR(DATA):4>+<SIZE(n+10):4>+<DATA:n>+<CS:2>
NOTE: Power off/on or press the reset button for 1 sec
in order to get a valid USB device address.
Hit any key to stop autoboot: 0
说明配置并编译成功。
uboot有哪些命令,可以在倒数计时时按任意键,进入菜单选项,然后输入q退出菜单选项。再输入help或?就可以打印出你的uboot支持哪些命令了。
OpenJTAG> ?
? - alias for 'help'
autoscr - run script from memory
base - print or set address offset
bdinfo - print Board Info structure
boot - boot default, i.e., run 'bootcmd'
bootd - boot default, i.e., run 'bootcmd'
bootelf - Boot from an ELF image in memory
bootm - boot application image from memory
bootp - boot image via network using BootP/TFTP protocol
bootvx - Boot vxWorks from an ELF image
chpart - change active partition
cmp - memory compare
coninfo - print console devices and information
cp - memory copy
crc32 - checksum calculation
date - get/set/reset date & time
dcache - enable or disable data cache
echo - echo args to console
erase - erase FLASH memory
flinfo - print FLASH memory information
fsinfo - print information about filesystems
fsload - load binary file from a filesystem image
go - start application at address 'addr'
help - print online help
icache - enable or disable instruction cache
iminfo - print header information for application image
imls - list all images found in flash
itest - return true/false on integer compare
loadb - load binary file over serial line (kermit mode)
loads - load S-Record file over serial line
loadx - load binary file over serial line (xmodem mode)
loady - load binary file over serial line (ymodem mode)
loop - infinite loop on address range
ls - list files in a directory (default /)
md - memory display
menu - display a menu, to select the items to do something
mm - memory modify (auto-incrementing)
mtdparts- define flash/nand partitions
mtest - simple RAM test
mw - memory write (fill)
nand - NAND sub-system
nboot - boot from NAND device
nfs - boot image via network using NFS protocol
nm - memory modify (constant address)
ping - send ICMP ECHO_REQUEST to network host
printenv- print environment variables
protect - enable or disable FLASH write protection
rarpboot- boot image via network using RARP/TFTP protocol
reset - Perform RESET of the CPU
run - run commands in an environment variable
saveenv - save environment variables to persistent storage
setenv - set environment variables
sleep - delay execution for some time
tftpboot- boot image via network using TFTP protocol
usbslave - get file from host(PC)
version - print monitor version
OpenJTAG>
如果想查看具体命令的使用说明可以使用这种方式:? 命令。比如:? md
OpenJTAG> ? md
md [.b, .w, .l] address [# of objects]
- memory display
在这一节里我们不详细讲解uboot的命令时怎么实现的,之后我会专门写一篇文章,分析uboot中命令的实现以及常见命令的使用方法。
uboot是怎么启动内核的,uboot将内核kernel从flash上面读出放到sdram中,然后启动内核。
uboot要实现的功能:
1.能够读flash;
2.初始化sdram,初始化时钟,关闭看门狗,初始化串口。
3.启动内核。
u-boot分析之Makefile结构分析:
想分析一个文件的链接结构最简单的方法就是分析其makefile文件。
我们在编译之前要先配置,再编译,为什么要这样做呢,因为在uboot的根目录下有的README说明文件,建议大家阅读一遍会收获满满。
首先分析配置过程:
在配置时执行的是make 100ask24x0_config命令,查看根目录下的makefile文件发现:
100ask24x0_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0
有这样两行,起始在执行make 100ask24x0_config命令时,就相当于执行"@$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0"这句话,好了,我们来看看这句话都是做了哪些工作。
MKCONFIG := $(SRCTREE)/mkconfig
100ask24x0_config : unconfig
@$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0
其中,MKCONFIG代表的就是源码树目录下的mkconfig文件,此文件是个shell脚本文件。其实就相当于执行:
./mkconfig 100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0
也就是说,在配置时是执行的mkconfig脚本文件,执行脚本文件的参数是:100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0。
现在我们分析mkconfig这个脚本文件,在分析时我们只分析执行到的代码,省略不执行的代码。
首先看到mkconfig文件里面前几行注释一段话:
Parameters: Target Architecture CPU Board [VENDOR] [SOC]
$1 $2 $3 $4 $5 $6
100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0
就是执行脚本文件的参数含义。
#!/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]
# 100ask24x0 arm arm920t 100ask24x0 NULL s3c24x0
# (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
while [ $# -gt 0 ] ; do # 如果参数个数大于0,条件成立
case "$1" in # $1也就是参数1有下面的符合,那就执行响应的项,没有,不执行
--) shift ; break ;;
-a) shift ; APPEND=yes ;;
-n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;
*) break ;;
esac
done
[ "${BOARD_NAME}" ] || BOARD_NAME="$1" # BOARD_NAME变量为空,所以BOARD_NAME=$1,也就是BOARD_NAME=100ask24x0
[ $# -lt 4 ] && exit 1 # 如果参数个数小于4,退出
[ $# -gt 6 ] && exit 1 # 如果参数个数大于6,退出;这个两个条件都不成立,继续往下执行
echo "Configuring for ${BOARD_NAME} board..." # 输出打印信息:Configuring for 100ask24x0 board...
#
# Create link to architecture specific headers 建立和架构相关的头文件
#
if [ "$SRCTREE" != "$OBJTREE" ] ; then # 这两个变量在makefile文件中定义,是相等的,所以条件不成立
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 # 建立链接文件:ln -s asm-arm asm 也就是asm链接asm-arm而成
fi
rm -f asm-$2/arch # 删除rm -f asm-arm/arch文件夹
if [ -z "$6" -o "$6" = "NULL" ] ; then # 如果$6不存在或为NULL执行下面的分支,但是条件不成立
ln -s ${LNPREFIX}arch-$3 asm-$2/arch
else # 执行下面的分支语句
ln -s ${LNPREFIX}arch-$6 asm-$2/arch # 建立链接文件,ln -s arch-s3c24x0 asm-arm/arch
fi
if [ "$2" = "arm" ] ; then # 如果$2等于arm,成立
rm -f asm-$2/proc # 执行rm -f asm-arm/proc
ln -s ${LNPREFIX}proc-armv asm-$2/proc # 执行ln -s proc-armv asm-arm/proc
fi
#
# Create include file for Make 创建头文件,其中">"表示新建一个文件,">>"表示追加
#
echo "ARCH = $2" > config.mk # 在include目录下新建一个config.mk文件,并将ARCH = arm 这句话输出到里面
echo "CPU = $3" >> config.mk # 将CPU = arm920t 追加到config.mk文件
echo "BOARD = $4" >> config.mk # 将BOARD = 100ask24x0 追加到config.mk文件
# 我们可以查看config.mk文件的内容,确实是这样的。
# $5为NULL,条件不成立
[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk
# $6为s3c24x0条件成立,输出SOC = s3c24x0到config.mk文件中
[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC = $6" >> config.mk
#
# Create board specific header file 创建单板相关的头文件
#
if [ "$APPEND" = "yes" ] # Append变量为no,条件不成立,不执行下面分支的语句
then
echo >> config.h
else # 执行下面分支的语句
> config.h # 在include目录下新建一个config.h文件
fi
# 输出"/* Automatically generated - do not edit */" 到config.h文件中
echo "/* Automatically generated - do not edit */" >>config.h
# 输出"#include <configs/100ask24x0.h>"到config.h文件中
echo "#include <configs/$1.h>" >>config.h
exit 0
好了,配置过程已经分析完毕了,里面有详细的注释,不在单独详细介绍了。
现在来分析编译过程,我们在编译时直接执行的make命令,现在我们来继续分析makefile文件:
include $(OBJTREE)/include/config.mk
上面这句话就是包含配置时生产的config.mk文件,里面的内容是:
ARCH = arm
CPU = arm920t
BOARD = 100ask24x0
SOC = s3c24x0
而这些东西在makefile文件里面会用得到。
ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-linux-
endif
CPU是ARM架构的,声明交叉编译工具链是"arm-linux-"。
OBJS = cpu/arm920t/start.o 给OBJS变量赋值,这个变量很重要,继续往下看。
LIBS = lib_generic/libgeneric.a
LIBS += board/100ask24x0/lib100ask24x0.a
LIBS += cpu/arm920t/libarm920t.a
LIBS += cpu/arm920t/s3c24x0/libs3c24x0.a
LIBS += lib_arm/libarm.a
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \
fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/usb/libusb.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += common/libcommon.a
上面的意思就是将各个目录下的东西打包生成.a文件。
all: $(ALL)
在执行make的时候,如果没有指定目标,那就执行上面这句话,也就是目标是all。依赖是$(ALL)。
ALL = $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND)
目的是生成u-boot.bin文件,而u-boot.bin文件的依赖是:
$(obj)u-boot.bin: $(obj)u-boot # ELF格式的文件
$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
而u-boot的依赖是:
$(obj)u-boot: depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \
--start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \
-Map u-boot.map -o u-boot
但是把上面的东西每个都展开发现工作量很大,但是我们可以执行make命令,查看控制台输出:
UNDEF_SYM=`arm-linux-objdump -x lib_generic/libgeneric.a board/100ask24x0/lib100ask24x0.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/usb/libusb.a drivers/sk98lin/libsk98lin.a
common/libcommon.a | sed -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
arm-linux-ld -Bstatic -T /work/jz2440/u-boot-1.1.6/board/100ask24x0/u-boot.lds -Ttext 0x33F80000
$UNDEF_SYM cpu/arm920t/start.o \
--start-group lib_generic/libgeneric.a board/100ask24x0/lib100ask24x0.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/usb/libusb.a drivers/sk98lin/libsk98lin.a
common/libcommon.a --end-group -L /work/tools/gcc-3.4.5-glibc-2.3.6/lib/gcc/arm-linux/3.4.5 -lgcc
\-Map u-boot.map -o u-boot
我们可以发现链接脚本文件是:"board/100ask24x0/u-boot.lds"文件。代码段的基地址0x33F80000。链接的文件是"$UNDEF_SYM"变量代表的文件。
而第一个链接的文件时"cpu/arm920t/start.o";而uboot开始运行的地址是0x33F80000,首先运行的文件时start.S文件。
通过分析makefile文件可以知道:
1.第一个执行的文件时"cpu/arm920t/start.S";
2.链接脚本文件是"board/100ask24x0/u-boot.lds"文件;
3.链接地址是0x33F80000;这个值是在"/board/100ask24x0/config.mk"文件中定义的;变量名称是TEXT_BASE;
u-boot分析之源码第1阶段:
cpu/arm920t/start.S:
.globl _start #一上电CPU从这里开始执行
_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
分析reset处的代码:
/*
* the actual reset code
*/
reset:
/*
* set the cpu to SVC32 mode
* 设置CPU为SVC模式
*/
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 INTMOD 0X4A000004
# 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
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
什么的代码主要完成设置CPU为SVC模式,关闭看门狗,屏蔽所有中断。继续往下分析。
#ifndef CONFIG_SKIP_LOWLEVEL_INIT //没有定义此宏,执行
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
blne cpu_init_crit #如果不相等,这执行,不管是Nor或NAND启动都是不相等的,跳转到cpu_init_crit处执行代码
#endif
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches 刷新I/D 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和Cache
*/
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 #保存返回地址到ip寄存器
bl lowlevel_init #跳转到lowlevel_init处执行代码
mov lr, ip #将ip保存的返回地址移动到lr寄存器中
mov pc, lr #将lr中的返回地址保存到pc寄存器,也就是跳转到返回地址处执行代码
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
上面的代码主要完成了刷新I/D Caches,关闭MMU和Caches,并跳转到lowlevel_init处执行代码,而此代码在"board/100ask24x0/lowlevel_init.S"文件中定义。
.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
ldr r1, _TEXT_BASE
sub r0, r0, r1
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 */
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 0xb1
.word 0x30
.word 0x30
上面的代码主要完成SDRAM控制器的初始化工作,这里不详细介绍。
执行完之后代码返回到下面代码处执行:
/* Set up the stack */
stack_setup: /* 设置栈 */
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot r0=0x33F80000 */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area CFG_MALLOC_LEN = 0x40000 将ro减去0x40000 */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo CFG_GBL_DATA_SIZE = 128 将r0减去128 */
#ifdef CONFIG_USE_IRQ /* 有定义此宏,执行 */
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #将r0减去0x2000 用作中断处理
#endif
sub sp, r0, #12 /* 设置栈,之后就可以跳转到C代码执行了 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT /* 没有定义此宏,执行跳转命令 */
bl clock_init /* 跳转到初始化时钟的代码处执行 */
#endif
上面的代码主要给各种情况预留内存。内存分布如下图所示:
现在我们看clock_init出的代码,主要完成时钟的初始化;此处的代码在"board/100ask24x0/boot_init.c"文件中定义。
void clock_init(void)
{
S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000;
/* support both of S3C2410 and S3C2440, by www.100ask.net */
if (isS3C2410)
{
/* FCLK:HCLK:PCLK = 1:2:4 */
clk_power->CLKDIVN = S3C2410_CLKDIV;
/* change to asynchronous bus mod */
__asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
"orr r1, r1, #0xc0000000\n" /* Asynchronous */
"mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
:::"r1"
);
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFFFF;
/* configure UPLL */
clk_power->UPLLCON = S3C2410_UPLL_48MHZ;
/* some delay between MPLL and UPLL */
delay (4000);
/* configure MPLL */
clk_power->MPLLCON = S3C2410_MPLL_200MHZ;
/* some delay between MPLL and UPLL */
delay (8000);
}
else
{
/* FCLK:HCLK:PCLK = 1:4:8 */
clk_power->CLKDIVN = S3C2440_CLKDIV;
/* change to asynchronous bus mod */
__asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
"orr r1, r1, #0xc0000000\n" /* Asynchronous */
"mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
:::"r1"
);
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFFFF;
/* configure UPLL */
clk_power->UPLLCON = S3C2440_UPLL_48MHZ;
/* some delay between MPLL and UPLL */
delay (4000);
/* configure MPLL */
clk_power->MPLLCON = S3C2440_MPLL_400MHZ;
/* some delay between MPLL and UPLL */
delay (8000);
}
}
上面的代码主要完成时钟的初始化,我们现在不具体分析代码,值分析uboot代码的执行流程。设置的时钟频率是FCLK是400MHz,HCLK是100MHz,PCLK是50MHz。继续往下分析代码。
#ifndef CONFIG_SKIP_RELOCATE_UBOOT /* 没有定义此宏,执行 */
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0寄存器的值等于_start当前位于的地址,也就是0 */
ldr r1, _TEXT_BASE /* r1寄存器的值等于0x33F8_0000 */
cmp r0, r1 /* 比较两个地址是否相等 */
beq clear_bss /* 如果相等就跳转到清bss段处执行,因为到现在为止还没有完成代码的重定位,所有不相等 */
ldr r2, _armboot_start /* r2的值等于0x33F8_0000 */
ldr r3, _bss_start /* r3的值等于bss段的开始地址 */
sub r2, r3, r2 /* 现在r2的值等于bin文件中有效代码的长度 */
/* 跳转拷贝代码,r0=0,r1=0x33F8_0000,r2=拷贝长度 */
bl CopyCode2Ram /* r0: source, r1: dest, r2: size */
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
clear_bss: /* 清除bss段 */
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* 循环往bss段中写0就可以了 */
add r0, r0, #4
cmp r0, r1
ble clbss_l
SetLoadFlag:
/* Set a global flag, PreLoadedONRAM 这段代码不知道干什么用的,先不管了 */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
ldr r2, =PreLoadedONRAM
mov r3, #1
streq r3, [r2]
#跳转_start_armboot执行,也就是跳转到start_armboot处执行
ldr pc, _start_armboot /* 执行第二阶段代码 */
_start_armboot: .word start_armboot
现在代码跳转到start_armboot函数执行,在"lib_arm/board.c"文件中定义。
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
char *s;
ulong size;
/* gd的定义:#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
* 在include/asm-arm/global_data.h中定义
*/
/* Pointer is writable since we allocated a register for it
* 给global data分配内存,并清空
*/
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;
/* 通过函数指针,遍历init_sequence数组中的函数 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/* configure available FLASH banks */
size = flash_init ();
display_flash_config (size);
/* armboot_start is defined in the board-specific linker script */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
puts ("NAND: ");
nand_init(); /* go init the NAND */
/* initialize environment */
env_relocate ();
/* 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;
}
}
devices_init (); /* get the devices list going. */
jumptable_init ();
console_init_r (); /* fully init console as a device */
Port_Init();
if (!PreLoadedONRAM) {
/* enable exceptions */
enable_interrupts ();
/* add by www.100ask.net */
usb_init();
}
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
eth_initialize(gd->bd);
/* 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 */
}
函数主要完成,各种外设的初始化,环境变量的初始化等,我们这里不详细分析。最后跳转到死循环"main_loop()"函数处执行代码。我们来总结下上面的这段代码主要做了哪些工作。
1.给gd指针分配空间,为以后的设置其做准备;
2.CPU、单板、环境变量、波特率、控制台和SDRAM大小相关的初始化;
3.Nor和Nand Flash识别;
4.跳转到main_loop函数去执行。
而main_loop函数主要做的工作是检查环境变量bootdelay是否倒计时为0,或是否检测到串口有输入,如果没有就获取bootcmd环境变量的值,并调用run_command函数执行命令。
uboot命令介绍:
uboot在启动内核时,也是通过Uboot命令来实现的,uboot中每个命令通过U_BOOT_CMD宏来定义的,格式如下:
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)
各项参数的意义如下:
name:命令的名字,注意,它不是一个字符串;
maxargs:最大参数的个数;
rep:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行;
cmd:对应的函数指针,类型为“(struct cmd_tbl_s *, int, int, char *[])”;
usage:简短的使用说明, 这是个字符串;
help:较详细的使用说明,这是个字符串。
宏U_BOOT_CMD在include/command.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/command.h中定义,如下所示:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
比如对于bootm命令,它如下定义:
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"strings1",
"strings2"
);
宏U_BOOT_CMD扩展开后如下所示:
cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) = {"bootm", CFG_MAXARGS, 1, do_bootm, "strings1", "strings2"}
对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在".u_boot_cmd"段中定义一个cmd_tbl_t结构。链接脚本u-boot.lds中有如下代码:
__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结构体,然后调用它的函数(可以参考common/command.c中的find_cmd函数)。
内核的复制和启动,可以通过如下命令来完成,bootm从内存,ROM,Nor Flash中启动内核。它们都是先将内核映像从各种媒介中读出,存放在指定的位置,然后设置标记列表以给内核传递参数;最后跳转到内核的入口点去执行。具体的细节不在描述。
uboot启动内核:
启动命令是:
启动内核的命令是:
nand read.jffs2 0x30007FC0 kernel
bootm 0x30007FC0
从Nand中读出内核:从哪里读取,读到那里去?
从kernel分区读取内核,放到0x30007FC0地址去。
我们可以使用mtd命令查看具体分区情况:
device nand0 <nandflash0>, # parts = 4
#: name size offset mask_flags
0: bootloader 0x00040000 0x00000000 0
1: params 0x00020000 0x00040000 0
2: kernel 0x00200000 0x00060000 0
3: root 0x0fda0000 0x00260000 0
现在来看nand read是怎么读取的?
nand read[.jffs2] - addr off|partition size
addr:表示放到位置;
off:读取偏移量;或partition:表示分区名字;
size:表示读取的大小。
具体读取过程就不分析,大家感兴趣可以自己看看。
启动内核:
在Nand Flash上的内核是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;
根据头部将内核移动到加载地址去。调用do_bootm_linux函数启动内核,此函数在lib_arm/armlinux.c文件中定义。
首先获取参数:
char *commandline = getenv ("bootargs");
定义一个函数指针方便跳转;
void (*theKernel)(int zero, int arch, uint params);
给函数指针复制,地址指向的内核的入口地址;
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
设置参数:
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);