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.ldsu-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.Sstart.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.cstart.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_标志寄存器_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 ();
}
}
|
|