【摘要】本节介绍了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() 函数调用应该不会返回,如果该调用返回,则说明出错。
×××××××××××××××××××××××××××××