开发板: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 <[email protected]> # 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);