DM6467开发之U-Boot移植(1)——U-Boot学习

来源:互联网 发布:英国约克大学商科 知乎 编辑:程序博客网 时间:2024/05/01 13:01

 

1        DM6467启动流程

在移植U-Boot之前,首先需要了解DM6467的启动流程,知道U-Boot在系统启动过程中的位置及作用。DM6467标准的启动过程如图 1‑1所示,主要包括四个步骤:RBL(ROM BootLoader)→UBL(User Boot Loader)→U-Boot →Linux内核。



图1‑1 DM6467启动流程

系统上电时,首先运行的是RBL。RBL是固化在片内ROM的,所以用户不能修改,并且功能比较弱。RBL根据开发板上拨码开关的值来确定下一阶段的启动方式,它支持NAND、UART和HPI三种启动方式。我们开发板上使用的是NAND启动,所以RBL负责将NAND Flash中的UBL复制到片内RAM,然后转到UBL执行。当RBL启动之后,其实就可以直接运行应用程序了,但是如果要进行嵌入式系统开发,也即要移植嵌入式操作系统,那么就需要下一阶段的bootloader,这里使用的是UBL。

UBL又称为2nd stagebootloader(第二阶段的bootloader),它主要负责初始化串口、PLL和DDR2 SDRAM,然后根据用户设定来选择下一阶段启动方式。UBL支持NOR、NAND、PCI和UART启动模式,我们选用的是NAND启动,所以UBL负责将NAND中的U-Boot复制到DDR2中,然后转到U-Boot执行。由于RBL没有初始化DDR2,UBL是复制到片内RAM执行的,而DM6467的片内RAM只有32K,但U-Boot的bin文件一般都是100K以上,所以系统需要使用第二阶段的UBL来初始化DDR2,然后将U-Boot复制到DDR2上运行。

TI官方提供了标准的UBL源文件和bin文件,兼容性很强,可以直接用于我们的开发板,不过,也可以根据我们的实际需要在其基础上进行一定修改。

第三阶段的U-Boot主要用于建立内存映射以及堆栈、初始化主要功能模块和复制Linux内核到DDR2,然后传递一些参数给内核,最后转到内核运行。由于这部分与实际的开发板联系紧密,而不同开发板总有些差别,所以需要根据实际情况来修改标准的U-Boot源文件以适应硬件参数。

2        U-Boot简介

U-Boot(UniversalBootloader)是一种广泛用于嵌入式设备的开源bootloader。U-Boot支持许多系统架构,包括PPC、ARM、MIPS、AVR32、X86、68K、Nios和MicroBlaze等。U-Boot也支持各种类型的文件系统,包括Cramfs、ext2、FAT、FDOS、JFFS2、RegisFS和UBIFS等。U-Boot还支持不同类型操作系统,包括NetBSD,、VxWorks、QNX、RTEMS、ARTOS、LynxOS和Linux等。

U-Boot源码可以在其官网http://www.denx.de/wiki/U-Boot/下载,U-Boot版本更新比较快,在U-Boot-2009.08版本及之前的版本中不支持DM646x系列SOC,只有对TI的DaVinci系列开发板的通用支持,在U-Boot-2009.08之后的所有版本都有专门的对DM646x系列SOC的支持,使用这些版本的U-Boot进行移植时可以减少一定工作量。在本文的以下部分,所有的分析及移植过程都是基于U-Boot-2009.08。

2.1             U-Boot源码结构

U-Boot源码解压后所得的目录结构见图 2‑1。从图中可以看到,文件和文件夹很多,并且有些文件夹下面还有很多子文件夹或者有很多文件,但是,由于U-Boot是支持不同架构、不同文件系统、不同开发板,而我们自己的开发板是ARM926ejs核的CPU,移植Linux操作系统,所以U-Boot源码中的大部分是与我们移植任务不相关的。从后面也可以看到,我们移植时对源码修改的地方非常少,并且只局限于几个文件。


图2‑1 U-Boot目录结构

为了使U-Boot更简洁,可以删除顶层目录中的doc、lib_avr32、lib_blackfin、lib_generic、lib_i386、lib_m68k、lib_microblaze、lib_mips、lib_nios、lib_nios2、lib_ppc、lib_sh、lib_sparc、nand_spl、onenand_spl这些文件夹,还有board/中除davinci之外的所有文件夹、cpu/中除arm926ejs之外的所有文件夹。当然,其实还有很多文件与文件夹是与我们开发板无关的,不过比较琐碎,就不用再一一删除了。

对于U-Boot源码顶层目录中各主要文件夹的内容和功能,表 2‑1给出了简单的介绍。另外,在顶层目录中还有一个很重要的文件Makefile,这是整个U-Boot编译时依据的规则,在移植时也可能需要修改。

表 2‑1 U-Boot顶层目录结构

文件夹名

包含内容

api

独立于硬件的一些API

board

与开发板相关的文件

common

独立于处理器结构的通用代码,如内存大小探测、U-Boot命令

cpu

与cpu相关的文件,每个子文件夹对应一种架构的cpu

disk

一些磁盘操作函数

drivers

通用的驱动程序

doc

参考文档

fs

U-Boot支持的文件系统

include

头文件及系统配置文件

lib_xxx

与具体cpu架构相关的库文件

net

网络功能相关

post

上电自检

tools

生成U-Boot的工具

2.2             U-Boot启动流程

对于嵌入式操作系统移植,比较常见的是只使用两个阶段的bootloader,即RBL和U-boot,但是对于DaVinci系列开发板则不同,它使用了三个阶段的bootloader。如第一章所述,第二阶段的UBL对PLL、DDR2以及其他一些较底层的部分进行了初始化,并且把U-Boot复制到了DDR2,所以第三阶段的U-Boot就不需要再次进行底层初始化和代码重定位,因而减少了许多工作量,在移植U-Boot时也更简单。

对于我们的开发板,一个完整、正常的U-Boot启动流程主要包括以下这几个步骤:

(1)     首先需要由UBL在NAND中找到U-Boot的文件头(Header),然后根据header之后的数据将U-Boot复制到内存中的指定位置,然后转到U-Boot运行。

(2)     由cpu/arm926ejs/davinci/u-boot.lds文件设置系统入口,分配各个段(数据段、指令段、bss等)的地址。

(3)     由cpu/arm926ejs/davinci/start.S文件中的汇编程序初始化CPU,清理内存,设置堆栈和中断向量表,然后转到C语言函数入口。

(4)     由lib_arm/board.c文件中的start_armboot()函数分配堆栈和内存空间,初始化各种外设,例如NAND、EMAC、DDR2等,然后进入主循环,等待用户命令。

(5)     用户如果不输入命令,U-Boot就根据预先设置的启动命令来运行,如果用户修改了环境变量和启动参数,则保存数据,然后按照用户指令来启动Linux。

另外,整个U-Boot代码的执行是受include/configs/中的头文件的宏定义来控制的,它决定系统需要使用哪些模块、需要编译哪些代码。

3        U-Boot源码分析

在U-Boot源码中,与硬件平台相关的文件是最重要的,需要详细分析其实现过程,即使在移植时不需要修改,但如果能熟悉这部分的程序那么能对U-Boot移植有更清晰的理解。而其他一些文件,比如U-Boot命令和各种驱动程序,这些是已经写好可以直接拿来用的,一般情况下是不需要修改的。

根据2.2 节中的启动流程,下面就对其中主要的几个文件进行详细分析。

3.1             cpu/arm926ejs/u-boot.lds

u-boot.lds是系统的链接文件,决定了一个可执行程序各个段的存储地址和程序的入口地址。u-boot.lds一般是不需要修改,不过,分析其代码可以更加深刻地理解内存中程序和数据的存储情况。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

/*  指定输出可执行文件是elf格式,32 位ARM指令,,小端 */

OUTPUT_ARCH(arm)       /* 指定输出可执行文件的平台是ARM */

ENTRY(_start)            /* 指定输出可执行文件的起始代码段为_start*/

SECTIONS

{

         . = 0x00000000;      /* 定位当前地址为0 */

 

         . = ALIGN(4);        /* 四字节对齐 */

         .text :              /* text 段 */

         {

                   cpu/arm926ejs/start.o    (.text)

                   *(.text)

         }

 

         . = ALIGN(4);

         .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }  /* 只读数据段 */

 

         . = ALIGN(4);

         .data : { *(.data) }      /* 读写数据段 */

 

         . = ALIGN(4);

         .got : { *(.got) }       /* got段,非标准段 */

 

         . = .;

         __u_boot_cmd_start = .;

         .u_boot_cmd : { *(.u_boot_cmd) }

         __u_boot_cmd_end = .;

 

         . = ALIGN(4);

         __bss_start = .;       /* bss段,用来存储未初始化的全局变量和静态变量 */

 

         .bss (NOLOAD) : { *(.bss) . = ALIGN(4); }

         _end = .;

}

3.2             cpu/arm926ejs/start.S

start.S是U-Boot启动后执行的第一个文件,主要负责初始化CPU、设置堆栈和中断向量表等任务,执行完成之后就跳转到lib_arm/board.c中的函数start_armboot()。start.S是ARM926EJS系列CPU通用的初始化文件,所以在移植时也不用修改。

.globl _start                              /* 系统起始位置 */

_start:

         b       reset                            /* 系统跳到reset处执行 */

         ldr    pc, _undefined_instruction          /* 中断向量表 */

 

_TEXT_BASE:                             /* 定义U-Boot使用的映射区的标号 */

         .word        TEXT_BASE

.globl _armboot_start

_armboot_start:

         .word _start

.globl _bss_start

_bss_start:

         .word __bss_start

.globl _bss_end

_bss_end:

         .word _end

 

reset:                           /* 复位操作,设置ARM为SVC32工作模式 */

         mrs  r0,cpsr

         bic    r0,r0,#0x1f

         orr    r0,r0,#0xd3

         msr  cpsr,r0

 

stack_setup:                               /* 初始化栈空间 */

         ldr    r0, _TEXT_BASE                        /*  upper 128 KiB: relocated uboot */

         sub   r0, r0, #CONFIG_SYS_MALLOC_LEN        /*  malloc area   */

         sub   r0, r0, #CONFIG_SYS_GBL_DATA_SIZE   /*  board info    */

 

clear_bss:                     /* 清理bss区,全部置0    */

         ldr    r0, _bss_start                /* find start of bss segment    */

         ldr    r1, _bss_end                  /* stop here                */

         mov r2, #0x00000000               /* clear                   */

clbss_l:str         r2, [r0]                    /* clear loop...              */

         add  r0, r0, #4

         cmp r0, r1

         ble    clbss_l

 

         ldr    pc, _start_armboot

_start_armboot:                 /* 跳转到stage2的star_armboot() C语言函数 */

         .word start_armboot

 

/* 由于DM6467的U-Boot由UBL引导,UBL进行了底层的初始化并将U-Boot复制到了内存,所以start.S不进行lowlevel_init、relocate、关闭cache、关闭看门狗等操作 */

3.3             lib_arm/board.c

start.S执行完之后就跳到board.c文件中的start_armboot()函数,该函数是整个U-Boot的主函数,它初始化系统的各种外设,然后转到主循环,根据用户指令来执行对应操作。start_armboot()函数是对ARM平台通用的主函数,在移植时可以不修改,也可以根据实际需要修改一些。

void start_armboot (void)

{

         init_fnc_t **init_fnc_ptr;

         char *s;

 

     /* gd是全局变量指针,用于保存一些全局参数,例如IP、波特率、开发板ID、DDR2参数、系统初始化函数列表等。 全局变量存储在DDR2当中,U-Boot分配一个寄存器作为指针指向全局变量的存储地址 */

         gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));

   

    /* 将分配给U-Boot全局变量的内存区域全部清零 */

         memset ((void*)gd, 0, sizeof (gd_t));

         gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

         memset (gd->bd, 0, sizeof (bd_t));

 

    /* 表示U-Boot被复制到RAM */

         gd->flags |= GD_FLG_RELOC;

 

    /* 计算U-Boot代码段的长度 */

         monitor_flash_len = _bss_start - _armboot_start;

 

    /* 初始化时钟、环境变量、波特率、串口、控制台、输出控制、I2C、DRAM

如果某个函数初始化失败,即返回值不是0,那么U-Boot会输出提示信息,进入死循环,等待用户重启系统 */

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

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

                            hang ();

                   }

         }

 

         /* 初始化malloc区域(全部置零),其地址是在U-Boot代码区域之前*/

         mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN);

 

#if defined(CONFIG_CMD_NAND)

         puts ("NAND:  ");

         nand_init();               

#endif

 

         /* 初始化环境参数 */

         env_relocate ();

 

#ifdef CONFIG_SERIAL_MULTI

         serial_initialize();

#endif

 

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

 

    /* 初始化标准输入输出设备 */

         stdio_init ();    

 

    /* 一些全局函数的初始化 */

         jumptable_init ();

 

         console_init_r ();     

 

         enable_interrupts ();

 

/* 初始化EMAC */

#ifdef CONFIG_DRIVER_TI_EMAC

extern void davinci_eth_set_mac_addr (const u_int8_t *addr);

         if (getenv ("ethaddr")) {

                   uchar enetaddr[6];

                   eth_getenv_enetaddr("ethaddr", enetaddr);

                   davinci_eth_set_mac_addr(enetaddr);

         }

#endif

 

         /* 获得U-Boot在DDR2中的加载地址,即0x8070 0000 */

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

                   load_addr = simple_strtoul (s, NULL, 16);

         }

 

/* 复制启动文件的文件名,支持TFTP协议 */

#if defined(CONFIG_CMD_NET)

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

                   copy_filename (BootFile, s, sizeof (BootFile));

         }

#endif

 

/* 初始化以太网 */

#if defined(CONFIG_CMD_NET)

#if defined(CONFIG_NET_MULTI)

         puts ("Net:   ");

#endif

         eth_initialize(gd->bd);

#endif

 

         /* 主循环main_loop(),只有系统启动才能退出此循环 */

         for (;;) {

                   main_loop ();

         }

}

原创粉丝点击