Chinaunix首页 | 论坛 | 博客
  • 博客访问: 891829
  • 博文数量: 132
  • 博客积分: 9976
  • 博客等级: 中将
  • 技术积分: 1781
  • 用 户 组: 普通用户
  • 注册时间: 2007-08-30 20:40
文章分类

全部博文(132)

文章存档

2013年(1)

2011年(1)

2010年(15)

2009年(77)

2008年(36)

2007年(2)

我的朋友

分类: C/C++

2009-05-18 20:12:51

u-boot介绍

================================================================================
======================================
Octeon cn3010_evb_hs5# fatls ide 0
  1048576   random.bin
       45   random.md5sum
   318432   u-boot-octeon_cn3010_evb_hs5.bin

3 file(s), 0 dir(s)

======================================
Octeon cn3010_evb_hs5# fatload ide 0 0 u-boot-octeon_cn3010_evb_hs5.bin
reading u-boot-octeon_cn3010_evb_hs5.bin

318432 bytes read

======================================
Octeon cn3010_evb_hs5# printenv
bootdelay=0
baudrate=115200
download_baudrate=115200
bootloader_flash_update=protect off $(uboot_flash_addr) +$(uboot_flash_size);erase $(uboot_flash_addr) +$(uboot_flash_size);cp.b $(fileaddr) $v
burn_app=erase $(flash_unused_addr) +$(filesize);cp.b $(fileaddr) $(flash_unused_addr) $(filesize)
bf=bootoct $(flash_unused_addr) forceboot numcores=$(numcores)
nuke_env=protect off $(env_addr) +$(env_size); erase $(env_addr) +$(env_size)
linux_cf=fatload ide 0 $(loadaddr) vmlinux.64;bootoctlinux $(loadaddr)
ls=fatls ide 0
autoload=n
ethact=octeth0
loadaddr=0x9f00000
numcores=2
stdin=serial
stdout=serial
stderr=serial
env_addr=0xbfbfe000
env_size=0x2000
flash_base_addr=0xbf400000
flash_size=0x800000
uboot_flash_addr=0xbf430000
uboot_flash_size=0x50000
flash_unused_addr=0xbf480000
flash_unused_size=0x77e000
filesize=4DBE0
fileaddr=0x9F00000
filename=u-boot-octeon_cn3010_evb_hs5.bin

Environment size: 938/8188 bytes
=======================================================
Command...................Function
tftpboot..................Download a Linux O/S image file with TFTP to
..........................the DNP/2110 RAM
iminfo....................Check the Linux O/S image file within the RAM
erase n:ss-es.............Erase flash area in bank# „n“ from start sector “ss” to
..........................end sector “es”.
cp.b source target count..Copy bytes from “source” address to
..........................“target” address, “count” specifies the byte
..........................count for this command.
====================================================================================
from:

6.2  U-Boot编程

U-Boot作为通用的Bootloader,U-Boot可以方便地移植到其他硬件平台上,其源代码也值得开发者们研究学习。

6.2.1  U-Boot工程简介

最早,DENX软件工程中心的Wolfgang Denk基于8xxrom的源码创建了PPCBOOT工程,并且不断添加处理器的支持。后来,Sysgo Gmbh把ppcboot移植到ARM平台上,创建了ARMboot工程。然后以ppcboot工程和armboot工程为基础,创建了U-Boot工程。

现在U-Boot已经能够支持PowerPC、ARM、X86、MIPS体系结构的上百种开发板,已经成为功能最多、灵活性最强并且开发最积极的开放源码Bootloader。目前仍然由DENX的Wolfgang Denk维护。

U-Boot的源码包可以从sourceforge网站下载,还可以订阅该网站活跃的U-Boot Users邮件论坛,这个邮件论坛对于U-Boot的开发和使用都很有帮助。

U-Boot软件包下载网站:。

U-Boot邮件列表网站:。

DENX相关的网站:。

6.2.2  U-Boot源码结构

从网站上下载得到U-Boot源码包,例如:U-Boot-1.1.2.tar.bz2

解压就可以得到全部U-Boot源程序。在顶层目录下有18个子目录,分别存放和管理不同的源程序。这些目录中所要存放的文件有其规则,可以分为3类。

·  第1类目录与处理器体系结构或者开发板硬件直接相关;

·  第2类目录是一些通用的函数或者驱动程序;

·  第3类目录是U-Boot的应用程序、工具或者文档。

表6.2列出了U-Boot顶层目录下各级目录存放原则。

表6.2   U-Boot的源码顶层目录说明

目录............特性..............解 释 说 明
----------------------------------------------
board..........平台依赖.....存放电路板相关的目录文件,例如:RPXlite(mpc8xx)、
..........................smdk2410(arm920t)、sc520_cdp(x86) 等目录
cpu............平台依赖.....存放CPU相关的目录文件,例如:mpc8xx、ppc4xx、arm720t、arm920t、 xscale、i386等目录
lib_ppc........平台依赖.....存放对PowerPC体系结构通用的文件,主要用于实现PowerPC平台通用的函数
lib_arm........平台依赖.....存放对ARM体系结构通用的文件,主要用于实现ARM平台通用的函数
lib_i386.......平台依赖.....存放对X86体系结构通用的文件,主要用于实现X86平台通用的函数
include........通用........头文件和开发板配置文件,所有开发板的配置文件都在configs目录下
common.........通用........通用的多功能函数实现
lib_generic....通用........通用库函数的实现
Net............通用........存放网络的程序
Fs.............通用........存放文件系统的程序
Post...........通用........存放上电自检程序
drivers........通用........通用的设备驱动程序,主要有以太网接口的驱动
Disk...........通用........硬盘接口程序
Rtc............通用........RTC的驱动程序
Dtt............通用........数字温度测量器或者传感器的驱动
examples.......应用例程.....一些独立运行的应用程序的例子,例如helloworld
tools..........工具........存放制作S-Record 或者 U-Boot格式的映像等工具,例如mkimage
Doc............文档........开发使用文档

U-Boot的源代码包含对几十种处理器、数百种开发板的支持。可是对于特定的开发板,配置编译过程只需要其中部分程序。这里具体以S3C2410 arm920t处理器为例,具体分析S3C2410处理器和开发板所依赖的程序,以及U-Boot的通用函数和工具。

6.2.3  U-Boot的编译

U-Boot的源码是通过GCC和Makefile组织编译的。顶层目录下的Makefile首先可以设置开发板的定义,
然后递归地调用各级子目录下的Makefile,最后把编译过的程序链接成U-Boot映像。
1.顶层目录下的Makefile

它负责U-Boot整体配置编译。按照配置的顺序阅读其中关键的几行。
每一种开发板在Makefile都需要有板子配置的定义。例如smdk2410开发板的定义如下。

 
smdk2410_config :   unconfig
     @./mkconfig $(@:_config=) arm arm920t smdk2410 NULL s3c24x0

执行配置U-Boot的命令make smdk2410_config,通过./mkconfig脚本生成include/config.
mk的配置文件。文件内容正是根据Makefile对开发板的配置生成的。

ARCH   = arm
CPU    = arm920t
BOARD  = smdk2410
SOC    = s3c24x0

上面的include/config.mk文件定义了ARCH、CPU、BOARD、SOC这些变量。
这样硬件平台依赖的目录文件可以根据这些定义来确定。SMDK2410平台相关目录如下。

board/smdk2410/
cpu/arm920t/
cpu/arm920t/s3c24x0/
lib_arm/
include/asm-arm/
include/configs/smdk2410.h

再回到顶层目录的Makefile文件开始的部分,其中下列几行包含了这些变量的定义。

# load ARCH, BOARD, and CPU configuration
include include/config.mk
export       ARCH CPU BOARD VENDOR SOC

Makefile的编译选项和规则在顶层目录的config.mk文件中定义。各种体系结构通用的规则直接在这个文件中定义。
通过ARCH、CPU、BOARD、SOC等变量为不同硬件平台定义不同选项。不同体系结构的规则分别包含在ppc_config.mk、
arm_config.mk、mips_config.mk等文件中。

顶层目录的Makefile中还要定义交叉编译器,以及编译U-Boot所依赖的目标文件。

ifeq ($(ARCH),arm)
CROSS_COMPILE = arm-linux-          //交叉编译器的前缀
#endif
export  CROSS_COMPILE


# U-Boot objects....order is important (i.e. start must be first)

OBJS  = cpu/$(CPU)/start.o                  //处理器相关的目标文件

LIBS  = lib_generic/libgeneric.a            //定义依赖的目录,每个目录下先把目标文件连接成*.a文件。
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a


然后还有U-Boot映像编译的依赖关系。

ALL = u-boot.srec u-boot.bin System.map
all:        $(ALL)
u-boot.srec:    u-boot
            $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@
u-boot.bin: u-boot
            $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@
……
u-boot:         depend $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT)
            UNDEF_SYM='$(OBJDUMP) -x $(LIBS) \
            |sed  -n -e 's/.*\(__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\
            $(LD) $(LDFLAGS) $$UNDEF_SYM $(OBJS) \
                 --start-group $(LIBS) $(PLATFORM_LIBS) --end-group \
                 -Map u-boot.map -o u-boot
Makefile缺省的编译目标为all,包括u-boot.srec、u-boot.bin、System.map。u-boot.srec和u-boot.bin又依赖于U-Boot。U-Boot就是通过ld命令按照u-boot.map地址表把目标文件组装成u-boot。

其他Makefile内容就不再详细分析了,上述代码分析应该可以为阅读代码提供了一个线索。
2.开发板配置头文件

除了编译过程Makefile以外,还要在程序中为开发板定义配置选项或者参数。这个头文件是include/configs/.h。用相应的BOARD定义代替。

这个头文件中主要定义了两类变量。

一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等。例如:

#define   CONFIG_ARM920T         1
#define   CONFIG_DRIVER_CS8900  1

另一类是参数,前缀是CFG_,用来定义总线频率、串口波特率、Flash地址等参数。例如:


#define     CFG_FLASH_BASE      0x00000000
#define CFG_PROMPT          "=>"
3.编译结果

根据对Makefile的分析,编译分为2步。第1步配置,例如:make smdk2410_config;第2步编译,执行make就可以了。
编译完成后,可以得到U-Boot各种格式的映像文件和符号表,如表6.3所示。
表6.3                                                  U-Boot编译生成的映像文件

文件名称.......说明................文件名称............说明....................
System.map....U-Boot映像的符号表...u-boot.bin.........U-Boot映像原始的二进制格式
u-boot........U-Boot映像的ELF格式..u-boot.srec........U-Boot映像的S-Record格式

U-Boot的3种映像格式都可以烧写到Flash中,但需要看加载器能否识别这些格式。一般u-boot.bin最为常用,
直接按照二进制格式下载,并且按照绝对地址烧写到Flash中就可以了。U-Boot和u-boot.srec格式映像都自带定位信息。

4.U-Boot工具

在tools目录下还有些U-Boot的工具。这些工具有的也经常用到。表6.4说明了几种工具的用途。

表6.4                                                              U-Boot的工具

工具名称........说明.......................工具名称.......说明
bmp_logo.......制作标记的位图结构体..........img2srec.....转换SREC格式映像
envcrc.........校验u-boot内部嵌入的环境变量...mkimage.....转换U-Boot格式映像
gen_eth_addr...生成以太网接口MAC地址.........updater......U-Boot自动更新升级工具

这些工具都有源代码,可以参考改写其他工具。其中mkimage是很常用的一个工具,Linux内核映像和ramdisk文件系统映像都可以转换成U-Boot的格式。

6.2.4  U-Boot的移植

U-Boot能够支持多种体系结构的处理器,支持的开发板也越来越多。因为Bootloader是完全依赖硬件平台的,所以在新电路板上需要移植U-Boot程序。

开始移植U-Boot之前,先要熟悉硬件电路板和处理器。确认U-Boot是否已经支持新开发板的处理器和I/O设备。假如U-Boot已经支持一块非常相似的电路板,那么移植的过程将非常简单。

移植U-Boot工作就是添加开发板硬件相关的文件、配置选项,然后配置编译。

开始移植之前,需要先分析一下U-Boot已经支持的开发板,比较出硬件配置最接近的开发板。选择的原则是,首先处理器相同,其次处理器体系结构相同,然后是以太网接口等外围接口。还要验证一下这个参考开发板的U-Boot,至少能够配置编译通过。

以S3C2410处理器的开发板为例,U-Boot-1.1.2版本已经支持SMDK2410开发板。我们可以基于SMDK2410移植,那么先把SMDK2410编译通过。

我们以S3C2410开发板fs2410为例说明。移植的过程参考SMDK2410开发板,SMDK2410在U-Boot-1.1.2中已经支持。

移植U-Boot的基本步骤如下。

(1)在顶层Makefile中为开发板添加新的配置选项,使用已有的配置项目为例。

smdk2410_config   :       unconfig
         @./mkconfig $(@:_config=) arm arm920t smdk2410 NULL s3c24x0
 
参考上面2行,添加下面2行。

fs2410_config   :       unconfig
      @./mkconfig $(@:_config=) arm arm920t fs2410 NULL s3c24x0

(2)创建一个新目录存放开发板相关的代码,并且添加文件。
board/fs2410/config.mk
board/fs2410/flash.c
board/fs2410/fs2410.c
board/fs2410/Makefile
board/fs2410/memsetup.S
board/fs2410/u-boot.lds

(3)为开发板添加新的配置文件
可以先复制参考开发板的配置文件,再修改。例如:
$cp include/configs/smdk2410.h  include/configs/fs2410.h
如果是为一颗新的CPU移植,还要创建一个新的目录存放CPU相关的代码。

(4)配置开发板
$ make fs2410_config

(5)编译U-Boot
执行make命令,编译成功可以得到U-Boot映像。有些错误是跟配置选项是有关系的,通常打开某些功能选项会带来一些错误,一开始可以尽量跟参考板配置相同。

(6)添加驱动或者功能选项
在能够编译通过的基础上,还要实现U-Boot的以太网接口、Flash擦写等功能。
对于FS2410开发板的以太网驱动和smdk2410完全相同,所以可以直接使用。CS8900驱动程序文件如下。
drivers/cs8900.c
drivers/cs8900.h
对于Flash的选择就麻烦多了,Flash芯片价格或者采购方面的因素都有影响。多数开发板大小、型号不都相同。
所以还需要移植Flash的驱动。每种开发板目录下一般都有flash.c这个文件,需要根据具体的Flash类型修改。例如:
board/fs2410/flash.c

(7)调试U-Boot源代码,直到U-Boot在开发板上能够正常启动。
调试的过程可能是很艰难的,需要借助工具,并且有些问题可能困扰很长时间。


6.2.5  添加U-Boot命令

U-Boot的命令为用户提供了交互功能,并且已经实现了几十个常用的命令。如果开发板需要很特殊的操作,可以添加新的U-Boot命令。

U-Boot的每一个命令都是通过U_Boot_CMD宏定义的。这个宏在include/command.h头文件中定义,每一个命令定义一个cmd_tbl_t结构体。

 

#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}

 

这样每一个U-Boot命令有一个结构体来描述。结构体包含的成员变量:命令名称、最大参数个数、重复数、命令执行函数、用法、帮助。

从控制台输入的命令是由common/command.c中的程序解释执行的。find_cmd()负责匹配输入的命令,从列表中找出对应的命令结构体。

基于U-Boot命令的基本框架,来分析一下简单的icache操作命令,就可以知道添加新命令的方法。

(1)定义CACHE命令。在include/cmd_confdefs.h中定义了所有U-Boot命令的标志位。
#define CFG_CMD_CACHE       0x00000010ULL   /* icache, dcache       */
如果有更多的命令,也要在这里添加定义。

(2)实现CACHE命令的操作函数。下面是common/cmd_cache.c文件中icache命令部分的代码。

#if (CONFIG_COMMANDS & CFG_CMD_CACHE)
static int on_off (const char *s)
{       //这个函数解析参数,判断是打开cache,还是关闭cache
        if (strcmp(s, "on") == 0) {  //参数为“on”
               return (1);
        } else if (strcmp(s, "off") == 0) {  //参数为“off”
               return (0);
    }
    return (-1);

}


int do_icache ( cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{     //对指令cache的操作函数
      switch (argc) {
      case 2:               /* 参数个数为1,则执行打开或者关闭指令cache操作 */
             switch (on_off(argv[1])) {
             case 0:     icache_disable();        //打开指令cache
                   break;
             case 1:     icache_enable ();        //关闭指令cache
                   break;
             }
            /* FALL TROUGH */
      case 1:           /* 参数个数为0,则获取指令cache状态*/
            printf ("Instruction Cache is %s\n",
                    icache_status() ? "ON" : "OFF");
            return 0;
      default:  //其他缺省情况下,打印命令使用说明
            printf ("Usage:\n%s\n", cmdtp->usage);
            return 1;
      }
      return 0;
}
……
U_Boot_CMD( //通过宏定义命令
    icache,   2,   1,     do_icache,  //命令为icache,命令执行函数为do_icache()
    "icache  - enable or disable instruction cache\n",   //帮助信息
    "[on, off]\n"
    "    - enable or disable instruction cache\n"
);
……
#endif

U-Boot的命令都是通过结构体__U_Boot_cmd_##name来描述的。根据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}
还有,不要忘了在common/Makefile中添加编译的目标文件。
(3)打开CONFIG_COMMANDS选项的命令标志位。这个程序文件开头有#if语句需要预处理是否包含这个命令函数。CONFIG_COMMANDS选项在开发板的配置文件中定义。例如:SMDK2410平台在include/configs/smdk2410.h中有如下定义。

/***********************************************************
 * Command definition
 ***********************************************************/
#define CONFIG_COMMANDS \
                 (CONFIG_CMD_DFL  | \
                 CFG_CMD_CACHE     | \
                 CFG_CMD_REGINFO    | \
                 CFG_CMD_DATE      | \
                 CFG_CMD_ELF)
按照这3步,就可以添加新的U-Boot命令。

6.3  U-Boot的调试

新移植的U-Boot不能正常工作,这时就需要调试了。调试U-Boot离不开工具,只有理解U-Boot启动过程,才能正确地调试U-Boot源码。

6.3.1  硬件调试器

硬件电路板制作完成以后,这时上面还没有任何程序,就叫作裸板。首要的工作是把程序或者固件加载到裸板上,这就要通过硬件工具来完成。习惯上,这种硬件工具叫作仿真器。

仿真器可以通过处理器的JTAG等接口控制板子,直接把程序下载到目标板内存,或者进行Flash编程。如果板上的Flash是可以拔插的,就可以通过专用的Flash烧写器来完成。在第4章介绍过目标板跟主机之间的连接,其中JTAG等接口就是专门用来连接仿真器的。

仿真器还有一个重要的功能就是在线调试程序,这对于调试Bootloader和硬件测试程序很有用。

从最简单的JTAG电缆,到ICE仿真器,再到可以调试Linux内核的仿真器。

复杂的仿真器可以支持与计算机间的以太网或者USB接口通信。

对于U-Boot的调试,可以采用BDI2000。BDI2000完全可以反汇编地跟踪Flash中的程序,也可以进行源码级的调试。

使用BDI2000调试U-boot的方法如下。

(1)配置BDI2000和目标板初始化程序,连接目标板。

(2)添加U-Boot的调试编译选项,重新编译。

U-Boot的程序代码是位置相关的,调试的时候尽量在内存中调试,可以修改连接定位地址TEXT_BASE。TEXT_BASE在board//config.mk中定义。

另外,如果有复位向量也需要先从链接脚本中去掉。链接脚本是board//
u-boot.lds。

添加调试选项,在config.mk文件中查找,DBGFLAGS,加上-g选项。然后重新编译U-Boot。

(3)下载U-Boot到目标板内存。

通过BDI2000的下载命令LOAD,把程序加载到目标板内存中。然后跳转到U-Boot入口。

(4)启动GDB调试。

启动GDB调试,这里是交叉调试的GDB。GDB与BDI2000建立链接,然后就可以设置断点执行了。

 

$ arm-linux-gdb u-boot

(gdb)target remote 192.168.1.100:2001

(gdb)stepi

(gdb)b start_armboot

(gdb)c

6.3.2  软件跟踪

假如U-Boot没有任何串口打印信息,手头又没有硬件调试工具,那样怎么知道U-Boot执行到什么地方了呢?可以通过开发板上的LED指示灯判断。

开发板上最好设计安装八段数码管等LED,可以用来显示数字或者数字位。

U-Boot可以定义函数show_boot_progress (int status),用来指示当前启动进度。在include/common.h头文件中声明这个函数。

 

#ifdef CONFIG_SHOW_BOOT_PROGRESS

void    show_boot_progress (int status);

#endif

 

CONFIG_SHOW_BOOT_PROGRESS是需要定义的。这个在板子配置的头文件中定义。CSB226开发板对这项功能有完整实现,可以参考。在头文件include/configs/csb226.h中,有下列一行。

 

#define CONFIG_SHOW_BOOT_PROGRESS       1

 

函数show_boot_progress (int status)的实现跟开发板关系密切,所以一般在board目录下的文件中实现。看一下CSB226在board/csb226/csb226.c中的实现函数。

 

/** 设置CSB226板的0、1、2三个指示灯的开关状态

 * csb226_set_led: - switch LEDs on or off

 * @param led:   LED to switch (0,1,2)

 * @param state: switch on (1) or off (0)

 */

void csb226_set_led(int led, int state)

{

      switch(led) {

             case 0: if (state==1) {

                              GPCR0 |= CSB226_USER_LED0;

                    } else if (state==0) {

                            GPSR0 |= CSB226_USER_LED0;

                    }

                    break;

             case 1: if (state==1) {

                              GPCR0 |= CSB226_USER_LED1;

                    } else if (state==0) {

                              GPSR0 |= CSB226_USER_LED1;

                    }

                    break;

             case 2: if (state==1) {

                              GPCR0 |= CSB226_USER_LED2;

                  } else if (state==0) {

                          GPSR0 |= CSB226_USER_LED2;

                  }

                  break;

      }

      return;

}

/** 显示启动进度函数,在比较重要的阶段,设置三个灯为亮的状态(1, 5, 15)*/

void show_boot_progress (int status)

{

      switch(status) {

            case  1: csb226_set_led(0,1); break;

            case  5: csb226_set_led(1,1); break;

            case 15: csb226_set_led(2,1); break;

      }

      return;

}

 

这样,在U-Boot启动过程中就可以通过show_boot_progresss指示执行进度。比如hang()函数是系统出错时调用的函数,这里需要根据特定的开发板给定显示的参数值。

 

void hang (void)

{

      puts ("### ERROR ### Please RESET the board ###\n");

#ifdef CONFIG_SHOW_BOOT_PROGRESS

      show_boot_progress(-30);

#endif

      for (;;);

}

6.3.3  U-Boot启动过程

尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。

开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数。函数调用顺序如图6.3所示。

看一下board/smsk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面详细分析一下程序跳转和函数的调用关系以及函数实现。

1cpu/arm920t/start.S

这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。

6.3  U-Boot启动代码流程图

 

_start: b       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      //中断向量

 /* the actual reset code  */

reset:          //复位启动子程序

       /* 设置CPUSVC32模式 */

       mrs   r0,cpsr

       bic   r0,r0,#0x1f

       orr   r0,r0,#0xd3

       msr   cpsr,r0

/* 关闭看门狗 */

 

/* 这些初始化代码在系统重起的时候执行,运行时热复位从RAM中启动不执行 */

#ifdef CONFIG_INIT_CRITICAL

       bl    cpu_init_crit

#endif

 

relocate:                       /* U-Boot重新定位到RAM */

       adr   r0, _start          /* r0是代码的当前位置 */

       ldr   r1, _TEXT_BASE      /* 测试判断是从Flash启动,还是RAM */

       cmp     r0, r1          /* 比较r0r1,调试的时候不要执行重定位 */

       beq     stack_setup    /* 如果r0等于r1,跳过重定位代码 */

       /* 准备重新定位代码 */

       ldr   r2, _armboot_start

       ldr   r3, _bss_start

       sub   r2, r3, r2          /* r2 得到armboot的大小   */

       add   r2, r0, r2          /* r2 得到要复制代码的末尾地址 */

copy_loop: /* 重新定位代码 */

       ldmia r0!, {r3-r10}   /*从源地址[r0]复制 */

       stmia r1!, {r3-r10}   /* 复制到目的地址[r1] */

       cmp   r0, r2          /* 复制数据块直到源数据末尾地址[r2] */

       ble   copy_loop

 

       /* 初始化堆栈等    */

stack_setup:

       ldr   r0, _TEXT_BASE              /* 上面是128 KiB重定位的u-boot */

       sub   r0, r0, #CFG_MALLOC_LEN     /* 向下是内存分配空间 */

       sub   r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo结构体地址空间  */

#ifdef CONFIG_USE_IRQ

       sub   r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

#endif

       sub   sp, r0, #12     /* abort-stack预留3个字 */

clear_bss:

       ldr   r0, _bss_start      /* 找到bss段起始地址 */

       ldr   r1, _bss_end        /*  bss段末尾地址   */

       mov   r2, #0x00000000     /* 清零 */

clbss_l:str r2, [r0]        /* bss段地址空间清零循环...  */

       add   r0, r0, #4

       cmp   r0, r1

       bne   clbss_l

       /* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */

       ldr   pc, _start_armboot

_start_armboot: .word start_armboot     //start_armboot函数在lib_arm/board.c中实现

/* 关键的初始化子程序 */

cpu_init_crit:

……  //初始化CACHE,关闭MMU等操作指令

       /* 初始化RAM时钟。

       * 因为内存时钟是依赖开发板硬件的,所以在board的相应目录下可以找到memsetup.S文件。

       */

       mov   ip, lr

       bl    memsetup        //memsetup子程序在board/smdk2410/memsetup.S中实现

       mov   lr, ip

       mov   pc, lr

 

2lib_arm/board.c

start_armbootU-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。

 

 

void start_armboot (void)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong size;

       init_fnc_t **init_fnc_ptr;

       char *s;

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

       /* 顺序执行init_sequence数组中的初始化函数 */

       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                      hang ();

              }

       }

       /*配置可用的Flash */

       size = flash_init ();

       display_flash_config (size);

       /* _armboot_start u-boot.lds链接脚本中定义 */

       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);

       /* 配置环境变量,重新定位 */

       env_relocate ();

       /* 从环境变量中获取IP地址 */

       gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

       /* 以太网接口MAC 地址 */

       ……

       devices_init ();      /* 获取列表中的设备 */

       jumptable_init ();

       console_init_r ();    /* 完整地初始化控制台设备 */

       enable_interrupts (); /* 使能例外处理 */

       /* 通过环境变量初始化 */

       if ((s = getenv ("loadaddr")) != NULL) {

               load_addr = simple_strtoul (s, NULL, 16);

       }

       /* main_loop()总是试图自动启动,循环不断执行 */

       for (;;) {

               main_loop ();      /* 主循环函数处理执行用户命令 -- common/main.c */

       }

       /* NOTREACHED - no way out of command loop except booting */

}

 

3init_sequence[]

init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。

 

init_fnc_t *init_sequence[] = {

       cpu_init,             /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */

       board_init,           /* 基本的板级相关配置 -- board/smdk2410/smdk2410.c */

       interrupt_init,       /* 初始化例外处理 -- cpu/arm920t/s3c24x0/interrupt.c */

       env_init,             /* 初始化环境变量 -- common/cmd_flash.c */

       init_baudrate,        /* 初始化波特率设置 -- lib_arm/board.c */

       serial_init,          /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c */

       console_init_f,       /* 控制台初始化阶段1 -- common/console.c */

       display_banner,       /* 打印u-boot信息 -- lib_arm/board.c */

       dram_init,            /* 配置可用的RAM -- board/smdk2410/smdk2410.c */

       display_dram_config,  /* 显示RAM的配置大小 -- lib_arm/board.c */

       NULL,

};


6.3.4  U-Boot与内核的关系

U-Boot作为Bootloader,具备多种引导内核启动的方式。常用的go和bootm命令可以直接引导内核映像启动。U-Boot与内核的关系主要是内核启动过程中参数的传递。
1.go命令的实现

 

/* common/cmd_boot.c  */

int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       ulong addr, rc;

       int     rcode = 0;

       if (argc < 2) {

              printf ("Usage:\n%s\n", cmdtp->usage);

              return 1;

       }

       addr = simple_strtoul(argv[1], NULL, 16);

       printf ("## Starting application at 0x%08lX ...\n", addr);

       /*

        * pass address parameter as argv[0] (aka command name),

        * and all remaining args

        */

       rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);

       if (rc != 0) rcode = 1;

 

       printf ("## Application terminated, rc = 0x%lX\n", rc);

       return rcode;

}

 

go命令调用do_go()函数,跳转到某个地址执行的。如果在这个地址准备好了自引导的内核映像,就可以启动了。尽管go命令可以带变参,实际使用时一般不用来传递参数。
2.bootm命令的实现

 

/* common/cmd_bootm.c */

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])

{

       ulong iflag;

       ulong addr;

       ulong data, len, checksum;

       ulong  *len_ptr;

       uint  unc_len = 0x400000;

       int   i, verify;

       char  *name, *s;

       int   (*appl)(int, char *[]);

       image_header_t *hdr = &header;

 

       s = getenv ("verify");

       verify = (s && (*s == 'n')) ? 0 : 1;

       if (argc < 2) {

              addr = load_addr;

       } else {

              addr = simple_strtoul(argv[1], NULL, 16);

       }

       SHOW_BOOT_PROGRESS (1);

       printf ("## Booting image at %08lx ...\n", addr);

       /* Copy header so we can blank CRC field for re-calculation */

       memmove (&header, (char *)addr, sizeof(image_header_t));

       if (ntohl(hdr->ih_magic) != IH_MAGIC)

       {

              puts ("Bad Magic Number\n");

              SHOW_BOOT_PROGRESS (-1);

              return 1;

       }

       SHOW_BOOT_PROGRESS (2);

       data = (ulong)&header;

       len  = sizeof(image_header_t);

 

       checksum = ntohl(hdr->ih_hcrc);

       hdr->ih_hcrc = 0;

 

       if(crc32 (0, (char *)data, len) != checksum) {

              puts ("Bad Header Checksum\n");

              SHOW_BOOT_PROGRESS (-2);

              return 1;

       }

       SHOW_BOOT_PROGRESS (3);

       /* for multi-file images we need the data part, too */

       print_image_hdr ((image_header_t *)addr);

       data = addr + sizeof(image_header_t);

       len  = ntohl(hdr->ih_size);

       if(verify) {

              puts ("   Verifying Checksum ... ");

              if(crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {

                     printf ("Bad Data CRC\n");

                     SHOW_BOOT_PROGRESS (-3);

                     return 1;

              }

              puts ("OK\n");

       }

       SHOW_BOOT_PROGRESS (4);

       len_ptr = (ulong *)data;

……

       switch (hdr->ih_os) {

       default:                /* handled by (original) Linux case */

       case IH_OS_LINUX:

             do_bootm_linux  (cmdtp, flag, argc, argv,

                         addr, len_ptr, verify);

             break;

       ……

}

 

bootm命令调用do_bootm函数。这个函数专门用来引导各种操作系统映像,可以支持引导Linux、vxWorks、QNX等操作系统。引导Linux的时候,调用do_bootm_linux()函数。
3.do_bootm_linux函数的实现

 

/* lib_arm/armlinux.c */

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],

                   ulong addr, ulong *len_ptr, int verify)

{

       DECLARE_GLOBAL_DATA_PTR;

       ulong len = 0, checksum;

       ulong initrd_start, initrd_end;

       ulong data;

       void (*theKernel)(int zero, int arch, uint params);

       image_header_t *hdr = &header;

       bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG

       char *commandline = getenv ("bootargs");

#endif

       theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

       /* Check if there is an initrd image */

       if(argc >= 3) {

              SHOW_BOOT_PROGRESS (9);

              addr = simple_strtoul (argv[2], NULL, 16);

              printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

              /* Copy header so we can blank CRC field for re-calculation */

              memcpy (&header, (char *) addr, sizeof (image_header_t));

              if (ntohl (hdr->ih_magic) != IH_MAGIC) {

                      printf ("Bad Magic Number\n");

                      SHOW_BOOT_PROGRESS (-10);

                      do_reset (cmdtp, flag, argc, argv);

              }

              data = (ulong) & header;

              len = sizeof (image_header_t);

              checksum = ntohl (hdr->ih_hcrc);

              hdr->ih_hcrc = 0;

              if(crc32 (0, (char *) data, len) != checksum) {

                     printf ("Bad Header Checksum\n");

                     SHOW_BOOT_PROGRESS (-11);

                     do_reset (cmdtp, flag, argc, argv);

              }

              SHOW_BOOT_PROGRESS (10);

              print_image_hdr (hdr);

              data = addr + sizeof (image_header_t);

              len = ntohl (hdr->ih_size);

              if(verify) {

                     ulong csum = 0;

                     printf ("   Verifying Checksum ... ");

                     csum = crc32 (0, (char *) data, len);

                     if (csum != ntohl (hdr->ih_dcrc)) {

                            printf ("Bad Data CRC\n");

                            SHOW_BOOT_PROGRESS (-12);

                            do_reset (cmdtp, flag, argc, argv);

                     }

                     printf ("OK\n");

              }

              SHOW_BOOT_PROGRESS (11);

              if ((hdr->ih_os != IH_OS_LINUX) ||

                     (hdr->ih_arch != IH_CPU_ARM) ||

                     (hdr->ih_type != IH_TYPE_RAMDISK)) {

                     printf ("No Linux ARM Ramdisk Image\n");

                     SHOW_BOOT_PROGRESS (-13);

                     do_reset (cmdtp, flag, argc, argv);

              }

              /* Now check if we have a multifile image */

       } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {

               ulong tail = ntohl (len_ptr[0]) % 4;

               int i;

               SHOW_BOOT_PROGRESS (13);

               /* skip kernel length and terminator */

               data = (ulong) (&len_ptr[2]);

               /* skip any additional image length fields */

               for (i = 1; len_ptr[i]; ++i)

                       data += 4;

              /* add kernel length, and align */

              data += ntohl (len_ptr[0]);

              if (tail) {

                       data += 4 - tail;

              }

              len = ntohl (len_ptr[1]);

       } else {

               /* no initrd image */

              SHOW_BOOT_PROGRESS (14);

              len = data = 0;

       }

       if (data) {

               initrd_start = data;

               initrd_end = initrd_start + len;

       } else {

               initrd_start = 0;

               initrd_end = 0;

       }

       SHOW_BOOT_PROGRESS (15);

       debug ("## Transferring control to Linux (at address %08lx) ...\n",

               (ulong) theKernel);

#if defined (CONFIG_SETUP_MEMORY_TAGS) || \

      defined (CONFIG_CMDLINE_TAG) || \

      defined (CONFIG_INITRD_TAG) || \

      defined (CONFIG_SERIAL_TAG) || \

      defined (CONFIG_REVISION_TAG) || \

      defined (CONFIG_LCD) || \

      defined (CONFIG_VFD)

      setup_start_tag (bd);

#ifdef CONFIG_SERIAL_TAG

      setup_serial_tag (¶ms);

#endif

#ifdef CONFIG_REVISION_TAG

      setup_revision_tag (¶ms);

#endif

#ifdef CONFIG_SETUP_MEMORY_TAGS

      setup_memory_tags (bd);

#endif

#ifdef CONFIG_CMDLINE_TAG

      setup_commandline_tag (bd, commandline);

#endif

#ifdef CONFIG_INITRD_TAG

      if (initrd_start && initrd_end)

               setup_initrd_tag (bd, initrd_start, initrd_end);

#endif

      setup_end_tag (bd);

#endif

      /* we assume that the kernel is in place */

      printf ("\nStarting kernel ...\n\n");

      cleanup_before_linux ();

 

      theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

}

 

do_bootm_linux()函数是专门引导Linux映像的函数,它还可以处理ramdisk文件系统的映像。这里引导的内核映像和ramdisk映像,必须是U-Boot格式的。U-Boot格式的映像可以通过mkimage工具来转换,其中包含了U-Boot可以识别的符号。


6.4  使用U-Boot

U-Boot是“Monitor”。除了Bootloader的系统引导功能,它还有用户命令接口,提供了一些复杂的调试、读写内存、烧写Flash、
配置环境变量等功能。掌握U-Boot的使用,将极大地方便嵌入式系统的开发。

6.4.1  烧写U-Boot到Flash

新开发的电路板没有任何程序可以执行,也就不能启动,需要先将U-Boot烧写到Flash中。

如果主板上的EPROM或者Flash能够取下来,就可以通过编程器烧写。例如:计算机BIOS就存储在一块256KB的Flash上,通过插座与主板连接。

但是多数嵌入式单板使用贴片的Flash,不能取下来烧写。这种情况可以通过处理器的调试接口,直接对板上的Flash编程。

处理器调试接口是为处理器芯片设计的标准调试接口,包含BDM、JTAG和EJTAG 3种接口标准。JTAG接口在第4章已经介绍过;BDM(Background Debug Mode)主要应用在PowerPC8xx系列处理器上;EJTAG主要应用在MIPS处理器上。这3种硬件接口标准定义有所不同,但是功能基本相同,下面都统称为JTAG接口。

JTAG接口需要专用的硬件工具来连接。无论从功能、性能角度,还是从价格角度,这些工具都有很大差异。关于这些工具的选择,将在第6.4.1节详细介绍。

最简单方式就是通过JTAG电缆,转接到计算机并口连接。这需要在主机端开发烧写程序,还需要有并口设备驱动程序。开发板上电或者复位的时候,烧写程序探测到处理器并且开始通信,然后把Bootloader下载并烧写到Flash中。这种方式速率很慢,可是价格非常便宜。一般来说,平均每秒钟可以烧写100~200个字节。

烧写完成后,复位实验板,串口终端应该显示U-Boot的启动信息。

6.4.2  U-Boot的常用命令

U-Boot上电启动后,敲任意键可以退出自动启动状态,进入命令行。

 

U-Boot 1.1.2 (Apr 26 2005 - 12:27:13)

U-Boot code: 11080000 -> 1109614C  BSS: -> 1109A91C

RAM Configuration:

Bank #0: 10000000 32 MB

Micron StrataFlash MT28F128J3 device initialized

Flash: 32 MB

In:    serial

Out:   serial

Err:   serial

Hit any key to stop autoboot:  0

U-Boot>

 

在命令行提示符下,可以输入U-Boot的命令并执行。U-Boot可以支持几十个常用命令,通过这些命令,可以对开发板进行调试,可以引导Linux内核,还可以擦写Flash完成系统部署等功能。掌握这些命令的使用,才能够顺利地进行嵌入式系统的开发。

输入help命令,可以得到当前U-Boot的所有命令列表。每一条命令后面是简单的命令说明。

 

=> help

?       - 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'

bootm   - boot application image from memory

bootp   - boot image via network using BootP/TFTP protocol

cmp     - memory compare

coninfo  - print console devices and information

cp      - memory copy

crc32   - checksum calculation

dhcp    - invoke DHCP client to obtain IP/boot params

echo    - echo args to console

erase   - erase FLASH memory

flinfo  - print FLASH memory information

go      - start application at address 'addr'

help    - print online help

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

loop   - infinite loop on address range

md    - memory display

mm    - memory modify (auto-incrementing)

mtest   - simple RAM test

mw      - memory write (fill)

nfs     - boot image via network using NFS protocol

nm      - memory modify (constant address)

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

version - print monitor version

=>

 

U-Boot还提供了更加详细的命令帮助,通过help命令还可以查看每个命令的参数说明。由于开发过程的需要,有必要先把U-Boot命令的用法弄清楚。接下来,根据每一条命令的帮助信息,解释一下这些命令的功能和参数。

 

=> help bootm

bootm [addr [arg ...]]

    - boot application image stored in memory

          passing arguments 'arg ...'; when booting a Linux kernel,

          'arg' can be the address of an initrd image

 

bootm命令可以引导启动存储在内存中的程序映像。这些内存包括RAM和可以永久保存的Flash。

第1个参数addr是程序映像的地址,这个程序映像必须转换成U-Boot的格式。

第2个参数对于引导Linux内核有用,通常作为U-Boot格式的RAMDISK映像存储地址;也可以是传递给Linux内核的参数(缺省情况下传递bootargs环境变量给内核)。

 

=> help bootp

bootp [loadAddress] [bootfilename]


6.4.3  U-Boot的环境变量

有点类似Shell,U-Boot也使用环境变量。可以通过printenv命令查看环境变量的设置。

 

U-Boot> printenv

bootdelay=3

baudrate=115200

netmask=255.255.0.0

ethaddr=12:34:56:78:90:ab

bootfile=uImage

bootargs=console=ttyS0,115200 root=/dev/ram rw initrd=0x30800000,8M

bootcmd=tftp 0x30008000 zImage;go 0x30008000

serverip=192.168.1.1

ipaddr=192.168.1.100

stdin=serial

stdout=serial

stderr=serial

 

Environment size: 337/131068 bytes

U-Boot>

 

表6.5是常用环境变量的含义解释。通过printenv命令可以打印出这些变量的值。

表6.5                                                  U-Boot环境变量的解释说明

环 境 变 量
   

解 释 说 明

bootdelay
   

定义执行自动启动的等候秒数

baudrate
   

定义串口控制台的波特率

netmask
   

定义以太网接口的掩码

ethaddr
   

定义以太网接口的MAC地址

bootfile
   

定义缺省的下载文件

bootargs
   

定义传递给Linux内核的命令行参数

bootcmd
   

定义自动启动时执行的几条命令

serverip
   

定义tftp服务器端的IP地址

ipaddr
   

定义本地的IP地址

stdin
   

定义标准输入设备,一般是串口

stdout
   

定义标准输出设备,一般是串口

stderr
   

定义标准出错信息输出设备,一般是串口

 

U-Boot的环境变量都可以有缺省值,也可以修改并且保存在参数区。U-Boot的参数区一般有EEPROM和Flash两种设备。

环境变量的设置命令为setenv,在6.2.2节有命令的解释。

举例说明环境变量的使用。

 

=>setenv serverip  192.168.1.1

=>setenv ipaddr  192.168.1.100

=>setenv rootpath  "/usr/local/arm/3.3.2/rootfs"

=>setenv bootargs  "root=/dev/nfs rw nfsroot=\$(serverip):\$(rootpath) ip=
\$(ipaddr) "

=>setenv kernel_addr 30000000

=>setenv nfscmd  "tftp \$(kernel_addr) uImage; bootm \$(kernel_addr) "

=>run nfscmd

 

上面定义的环境变量有serverip ipaddr rootpath bootargs kernel_addr。环境变量bootargs中还使用了环境变量,bootargs定义命令行参数,通过bootm命令传递给内核。环境变量nfscmd中也使用了环境变量,功能是把uImage下载到指定的地址并且引导起来。可以通过run命令执行nfscmd脚本。


====================================================================================


====================================================================================


====================================================================================


====================================================================================


====================================================================================


====================================================================================


====================================================================================


====================================================================================



阅读(1587) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~