U-BOOT全线移植分析系列之二
――U-boot基础
Sailor_forever [email protected]转载请注明
http://blog.csdn.net/sailor_8318/archive/2008/08/04/2768049.aspx
【摘要】本节介绍了U-boot的基本概念。首先介绍了U-boot源代码的目录结构,并给出了一个实例。接着简单介绍了U-boot支持的基本功能、常见命令和环境变量。最后详细分析了U-boot启动的两个阶段,重点介绍了加载拷贝代码至RAM中的过程。
【关键词】bootloader,U-boot,环境变量, stage1,位置无关,代码搬移
二 U-boot基础
现在为Linux开放源代码Bootloader有很多,blob、 redboot及U-BOOT等,其中U-BOOT是目前用来开发嵌入式系统引导代码使用最为广泛的Bootloader。它支持POWERPC、ARM、MIPS和 X86等处理器,支持嵌入式操作系统有Linux、Vxworks及NetBSD等。
|-- board 平台依赖,存放电路板相关的目录文件
|-- common 通用多功能函数的实现
|-- cpu 平台依赖,存放cpu相关的目录文件
|-- disk 通用。硬盘接口程序
|-- doc 文档
|-- drivers 通用的设备驱动程序,如以太网接口驱动
|-- dtt
|-- examples 应用例子
|-- fs 通用存放文件系统的程序
|-- include 头文件和开发板配置文件,所有开发板配置文件放在其configs里
|-- lib_arm 平台依赖,存放arm架构通用文件
|-- lib_generic 通用的库函数
|-- lib_i386 平台依赖,存放x86架构通用文件
|-- lib_m68k 平台依赖
|-- lib_microblaze 平台依赖
|-- lib_mips 平台依赖
|-- lib_nios 平台依赖
|-- lib_ppc平台依赖,存放ppc架构通用文件
|-- net 存放网络的程序
|-- post 存放上电自检程序
|-- rtc rtc的驱动程序
`-- tools 工具
详细实例:
² board:开发板相关的源码,不同的板子对应一个子目录,内部放着主板相关代码。 Board/at91rm9200dk/at91rm9200.c, config.mk, Makefile, flash.c ,u-boot.lds等都和具体开发板的硬件和地址分配有关。
² common:与体系结构无关的代码文件,实现了u-boot所有命令,其中内置了一个shell脚本解释器(hush.c, a prototype Bourne shell grammar parser), busybox中也使用了它。
² cpu:与cpu相关代码文件,其中的所有子目录都是以u-boot所支持的cpu命名。
cpu/at91rm9200/at45.c, at91rm9200_ether.c, cpu.c, interrupts.c serial.c, start.S, config.mk, Makefile等。其中:
cpu.c负责初始化CPU、设置指令Cache和数据Cache等;
interrupt.c负责设置系统的各种中断和异常,比如快速中断、开关中断、时钟中断、软件中断、预取中止和未定义指令等;
start.S负责u-boot启动时执行的第一个文件,它主要是设置系统堆栈和工作方式,为跳转到C程序入口点做准备;
at91rm9200_ether.c和serial.c很重要,这是系统能够下载资源的前提。
² disk:设备分区处理代码。
² doc:u-boot相关文档。
² drivers:u-boot所支持的设备驱动代码, 网卡、支持CFI的Flash、串口和USB总线等。
² fs: u-boot所支持文件系统访问存取代码, 如jffs2。
² include:u-boot head文件,主要是与各种硬件平台相关的头文件,如include/asm-arm/arch-at91rm9200/AT91RM9200.h(硬件寄存器名称及地址的定义), hardware.h (内存及flash地址以及IO物理地址和虚拟地址的定义),include/asm-arm/proc-armv(与具体的CPU无关,无需移植)。
² net:与网络有关的代码,BOOTP协议、TFTP协议、RARP协议代码实现. 无需移植。
² lib_arm:与arm体系相关的代码。
² tools:编译后会生成mkimage工具,用来对生成的raw bin文件加入u-boot特定的image_header.
主要功能如下:
² 系统引导,支持NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统;
² 支持NFS挂载、从FLASH中引导压缩或非压缩系统内核;
² 基本辅助功能,强大的操作系统接口功能;可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与产品发布,尤对Linux支持最为强劲;
² 支持目标板环境参数多种存储方式,如FLASH、NVRAM、EEPROM;
² CRC32校验,可校验FLASH中内核、RAMDISK镜像文件是否完好;
² 设备驱动,串口、SDRAM、FLASH、以太网、LCD、NVRAM、EEPROM、键盘、USB、PCMCIA、PCI、RTC等驱动支持;
² 上电自检功能 SDRAM、FLASH大小自动检测;SDRAM故障检测;CPU型号;
² 特殊功能,XIP内核引导。
² ?得到所有命令列表
² Help:help usb, 列出USB功能的使用说明
² ping:注意只能开发板PING别的机器(AT91RM9200不支持,需要进行配置)
² setenv: 设置环境变量
Ø setenv serverip 192.168.0.1
Ø setenv ipaddr 192.168.0.56
Ø setenv bootcmd ‘tftp 32000000 vmlinux; kgo 32000000’
² saveenv:保存环境变量。在设置好环境变量以后,保存变量值
² tftp:tftp 32000000 vmlinux, 把server(IP=环境变量中设置的serverip)中/tftpboot/下的vmlinux通过TFTP读入到物理内存32000000处
² bootp- 通过网络用 BootP/TFTP 协议来启动映象
² tftpboot- 通过网络用 TFTP 协议、设置服务器和客户机的 IP 地址进行映象文件传送
² kgo: 起动没有压缩的linux内核,kgo 32000000(AT91RM9200不支持)
² bootm:起动UBOOT TOOLS制作的压缩LINUX内核, bootm 3200000
² protect: 对FLASH进行写保护或取消写保护,protect on 1:0-3(就是对第一块FLASH的0-3扇区进行保护),protect off 1:0-3取消写保护
² erase:删除FLASH的扇区, erase 1:0-2(就是对每一块FLASH的0-2扇区进行删除)
² cp: 在内存中复制内容, cp 32000000 0 40000(把内存中0x32000000开始的0x40000字节复制到0x0处)
² mw: 对RAM中的内容写操作, mw 32000000 ff 10000(把内存0x32000000开始的0x10000字节设为0xFF)
² md: 修改RAM中的内容, md 32000000(内存的起始地址)
² flinfo: 列出flash的信息
² loadb: 准备用KERMIT协议接收来自kermit或超级终端传送的文件。
² nfs: nfs 32000000 192.168.0.12:aa.txt,把192.168.0.12(LINUX 的NFS文件系统)中的NFS文件系统中的aa.txt 读入内存0x32000000处。
最常用的几个命令如下:
² go- 在地址 'addr' 处开始程序执行
² run- 运行一个环境变量所定义的命令
² bootm- 从内存中进行运行经过mkimage加工的程序映象
² loadb- 通过串口线(kermit mode) 来装载二进制文件
² printenv- 打印环境变量
² setenv- 设置环境变量
² saveenv保存环境变量到内存
² tftp-通过网络下载文件
² protect,erase,flash读写
下面是 U-BOOT 中的简单环境变量
² baudrate波特率
² bootdelay boot 延迟
² bootcmd Boot 命令
² bootargs Boot 参数,传递给内核
² bootfile 默认下载启动的内核映象
² ipaddr 客户机 IP 地址
² serverip 服务器地址
² loadaddr 装载地址
² ethaddr 网卡 MAC 地址
和大多数的Bootloader一样,U-BOOT的启动分为两个阶段两个部分,依赖于CPU体系结构的代码主要放在stage1,且用汇编来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且具有更好的可读性和可移植性。
下面分别分析一下这两个阶段的启动流程:
第一阶段:基本的硬件初始化,为第二阶段程序运行建立环境(cpu/ at91rm9200/start.s文件的代码部分):
××××××××××××××××××××××××××××
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start) // 程序的入口在/cpu/××××/start.s中定义
SECTIONS
{
. = 0x00000000; // 程序链接的地址
. = ALIGN(4);
.text :
{
cpu/at91rm9200/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
armboot_end_data = .; //代码段结束地址
. = ALIGN(4);
.bss : { *(.bss) }
armboot_end = .; //整个U-boot印象的结束地址
}
××××××××××××××××××××××××××××
在此需要定义程序入口,由于一个可执行的Image必须要有一个入口点,并且只能有一个全局入口,通常这个入口就在ROM (flash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本u-boot.lds来完成,该阶段需要依次完成的工作一般包括:
² CPU自身的初始化,它包括:CPU运行模式的设置(管理模式)、设置异常的入口地址和异常处理函数、运行时钟频率的设置等工作。
² 初始化GPIO和内存控制器。
² 为拷贝Stage2准备RAM空间。
² 进行自拷贝,将U-BOOT的Stage2拷贝到RAM中。
² 设置好堆栈。
² 跳转到Stage2的入口,从而转到RAM中执行,该工作是调用指令ldr pc, start armboot来完成的。
××××××××××××××××××××××××××
从1.1.2开始,u-boot有初始化SDRAM并拷贝自己到SDRAM运行的代码,而之前的版本就没有这个功能(详细查看下代码??的确如此,因此对于以前的版本TEXT_BASE没有起作用?实际测试下??)。board/ at91rm9200dk中config.mk文件(TEXT_BASE = 0x21f00000,32M RAM的设置,为第二阶段程序在RAM中的运行地址)用于设置程序编译连接的起始地址,在程序中要特别注意与地址相关指令的使用。
Board/at91rm9200dk/config.mk
TEXT_BASE = 0x21f00000(u-boot将被载入SDRAM的高端部分)
注意,对于不同的系统RAM大小可能不一样,要根据实际情况调整。
在/config.mk中
ifdef BOARD
sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific rules
endif
CPPFLAGS := $(DBGFLAGS) $(OPTFLAGS) $(RELFLAGS) /
-D__KERNEL__ -DTEXT_BASE=$(TEXT_BASE)
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS)
export TEXT_BASE PLATFORM_CPPFLAGS PLATFORM_RELFLAGS CPPFLAGS CFLAGS AFLAGS
对于U-Boot 1.1.2以前的版本,并没有自拷贝的部分,若flash中首地址存放的是非压缩的u-boot.bin的,则启动部分是一直运行在flash中的;但对于AT91RM9200来说,他通常有三个文件loader.bin, boot.bin, u-boot.bin,flash中首先运行的是boot.bin其将压缩的u-boot.bin.gz解压拷贝到TEXT_BASE处运行,此时已经在RAM中了,因此也无需实现自拷贝了。
对于U-Boot 1.1.2以后的版本,若像AT91RM9200有boot.bin这样的过渡程序,则将u-boot.bin.gz解压到RAM中,此时运行地址和链接地址相同,无需拷贝;若没有,则u-boot.bin将在flash中执行初始化部分,然后将自身拷贝到RAM中执行。
// 比较运行地址和链接地址,如果当前已经在RAM中运行了,则无需拷贝到RAM中
relocate: /* relocate U-Boot to RAM */
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 */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
当程序在Flash中运行时,执行程序跳转时必须要使用相对跳转指令,而不能使用绝对地址的跳转(即直接对PC操作)。如果使用绝对地址,那么,程序的取指是相对于当前PC位置向前或者向后的32MB空间内,而不会跳入SDRAM中。
×××××××××××××××××××××××××××××
在上述操作运行完成后,就进入到/lib_arm/board.c 中的start_armboot()函数运行,并建立起了一个基本的环境,此时的物理内存空间的分布就变成了如图所示的情况。
转入boatloader stage2的系统内存布局
第二阶段:运行U-BOOT的主体部分
该阶段以程序跳转到lib_arm/board.c中的start_armboot函数为标志,该函数同时也是C语言的开始函数,是整个启动代码的主体函数,同时还是整个U-BOOT的主体函数,该函数主要完成以下工作:
² 调用一系列的初始化函数,初始化本阶段使用到的硬件,如:
×××××××××××××××××××
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
board_init, /* basic board dependent setup */
interrupt_init, /* set up exceptions */
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
dram_init, /* configure available RAM banks */
display_dram_config,
#if defined(CONFIG_VCMA9)
checkboard,
#endif
NULL,
};
×××××××××××××××××××××
env_init:设置环境变量,初始化环境;
init_baudrate:设置串口的波特率;
serial_init:设置串口的工作方式;
dram_init:设置SDRAM的起始地址和大小;
² 检查存储器分配和使用情况:获取flash的bank分区情况、是否擦除、是否上锁等信息为以后flash相关命令使用;初始化系统内存分配函数,供后面的代码使用malloc等函数,U-BOOT没有使用其他现成的库所有函数的实现均在文件中),如果系统有液晶等显示设备,一并在此分配显示内存。
² 打印内存,flash、环境变量设置等信息。
² 等待几秒时间,如果有键盘输入,则进入命令模式,接收用户输入的命令并解释执行(如启动操作系统,更新flash内容)。
² 如果在给定的时间内没有用户输入或者在执行命令操作时收到了用户要求启动内核的命令(boot),则将把操作系统内核和根文件系统映像文件从flash中拷贝到RAM中相应位置,并在设置内核启动参数后跳转到内核映像的首地址处执行。
×××××××××××××××××××××××××××××
U-BOOT调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到MEM_START+0x8000地址处。在跳转时,要满足下列条件:
a) CPU寄存器的设置:R0=0;R1=机器类型 ID,本系统的机器类型ID=193。R2=启动参数标记列表在RAM中的起始基地址;
b) CPU模式:必须禁止中断(IRQs和FIQs);CPU必须工作在SVC模式;
c) Cache和MMU的设置:MMU 必须关闭;指令Cache可以打开也可以关闭;数据Cache必须关闭。
系统采用下列代码来进入内核函数:
void (*theKernel)(int zero, int arch);
theKernel = (void (*)(int, int))ntohl(hdr->ih_ep);
theKernel(0, bd->bi_arch_number);其中,hdr是image_header_t类型的结构体,hdr->ih_ep为entry point,指向内核的第一条指令地址,即Linux操作系统下的/kernel/arch/arm/boot/compressed/head.S汇编程序。theKernel()函数调用应该不会返回,如果该调用返回,则说明出错。
×××××××××××××××××××××××××××××