Chinaunix首页 | 论坛 | 博客
  • 博客访问: 853565
  • 博文数量: 286
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1980
  • 用 户 组: 普通用户
  • 注册时间: 2014-05-04 16:41
文章分类

全部博文(286)

文章存档

2020年(2)

2018年(5)

2017年(95)

2016年(69)

2015年(15)

2014年(100)

我的朋友

分类: LINUX

2014-08-15 13:07:33

原文地址:u-boot分析(1) 作者:charming2440

14.2  U-Boot分析

Bootloader代码是嵌入式系统复位后进入操作系统前执行的一段代码。通过Bootloader的代码初始化处理器的各寄存器以及其他外部设备,建立存储器映射图以及初始化堆栈,为操作系统提供基本的运行环境。由于嵌入式系统的硬件的多样性,不可能有通用的Bootloader,因此需要根据具体硬件特点移植。本节以目前应用比较广泛的U-Boot为例讲解嵌入式系统Bootloader移植的方法。

14.2.1  获取U-Boot

U-Boot的源代码可以从ftp://ftp.denx.de/pub/u-boot/上获得。使用匿名用户身份登录到U-Boot的FTP服务器后,进入pub/u-boot目录,该目录包含了U-Boot所有代码。本书使用U-Boot 1.1.6版本代码作为分析的样本。

14.2.2  U-Boot工程结构分析

学习一个软件,尤其是开源软件,首先应该从分析软件的工程结构开始。一个好的软件有良好的工程结构,对于读者学习和理解软件的架构以及工作流程都有很好的帮助。

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

    

    

   

board

平台依赖

存放电路板相关的目录文件,例如:RPXlite(mpc8xx)smdk2410(arm920t)sc520_cdp(x86)等目录

cpu

平台依赖

存放CPU相关的目录文件,例如:mpc8xxppc4xxarm720tarm920t xscalei386等目录

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的源码是通过GCCMakefile组织编译的。顶层目录下的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文件定义了ARCHCPUBOARDSOC这些变量。这样硬件平台依赖的目录文件可以根据这些定义来确定。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文件中定义。各种体系结构通用的规则直接在这个文件中定义。通过ARCHCPUBOARDSOC等变量为不同硬件平台定义不同选项。不同体系结构的规则分别包含在ppc_config.mkarm_config.mkmips_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.srecu-boot.binSystem.mapu-boot.srecu-boot.bin又依赖于U-BootU-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-Boot3种映像格式都可以烧写到Flash中,但需要看加载器能否识别这些格式。一般u-boot.bin最为常用,直接按照二进制格式下载,并且按照绝对地址烧写到Flash中就可以了。U-Bootu-boot.srec格式映像都自带定位信息。

4U-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开发板,SMDK2410U-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_CMDinclude/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命令。

14.2.3  U-Boot总体工作流程

与大多数Bootloader类似,U-Boot的启动分成stage1和stage2两个阶段。stage1使用汇编语言编写,通常与CPU体系紧密相关,如处理器初始化和设备初始化代码等,该阶段在start.S文件中实现。图14-1展示了U-Boot中Stage1阶段的启动过程。

图14-1  U-Boot中Stage1工作流程

图14-1是U-Boot中Stage1工作流程。Stage1的代码都是与平台相关的,使用汇编语言编写占用空间小而且执行速度快。以ARM920为例,Stage1阶段主要是设置各模式程序异常向量表,初始化处理器相关的关键寄存器以及系统内存。Stage1负责建立Stage1阶段使用的堆栈和代码段,然后复制Stage2阶段的代码到内存。

Stage2阶段一般包括:初始化Flash器件、检测系统内存映射、初始化网络设备、进入命令循环,接收用户从串口发送的命令然后进行相应的处理。Stage2使用C语言编写,用于加载操作系统内核,该阶段主要是board.c中的start_armboot()函数实现。图14-2给出了U-Boot的Stage1和Stage2在Flash和RAM中的分配。

图14-2  U-Boot Stage2阶段内存映射

从图14-2中可以看出,U-Boot在加载到内存后,使用了操作系统空余的内存空间。

阅读(795) | 评论(0) | 转发(0) |
0

上一篇:bootloader简介

下一篇:u-boot分析(2)

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