本文详细描述Blob的原理、实现方法及开发要点,作为对自己工作的总结。
一、Bootloader简介
Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。每种CPU体系结构都有不同的 BootLoader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。
常用的Bootloader有U-Boot,Vivi,Blob,Redboot等。他们通常存储于系统的ROM中。对于嵌入式系统而言,主要是Flash,E2ROM等。一个嵌入式系统的典型存储空间分配如图(1)所示。
图(1)嵌入式系统典型存储空间分配
二、Blob功能
Blob通过串口输出信息,接收从串口的输入,blob的控制台就是通过串口实现的。文件传输也可以通过网口或者USB口进行以提高传输速度。其主要功能如下:
● 使用xModem协议下载文件到内存
● Nor Flash在线烧写
● Nand Flash在线烧写
● Linux内核加载和引导
● 网络和简单协议(TFTP、PING)支持
● USB传输支持
● 在线调试BLOB
和其他Bootloader一样,blob支持两种工作模式:加载模式和下载模式。分别描述如下:
(1)加载模式
也称为“自主”(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。
(2)下载模式
在这种模式下,目标机上的 Boot Loader将通过串口连接或网络连接等通信手段从主机下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。
显然,两种模式的差别仅仅对于开发者才有意义的。在启动的stage 2,将kernel复制到RAM后,Blob在一定时间内会循环检查控制台有无输入,如果此时用户没有按任何按键,blob将自动引导操作系统启动,否则将进入下载模式,输出控制台信息。等待时间的长短在Main.c中设定。
三、Blob的文件组织结构
Blob->include ->blob ->arch:与硬件平台相关的头文件其它头文件,如xmodem.h等
->src ->blob: 大部分源代码在这个目录
->diag: 诊断测试代码,不常用
->lib : 库文件,如串口驱动、led驱动等
->tools:
->utils:
四、Blob空间分配
在../include/blob/arch/lubbock.h中,定义了系统的存储空间分配,包括Bootloader、Linux内核,文件系统等。主要内容如下:
/* the baseaddress were BLOB is loaded by the first stage loader */
#define BLOB_ABS_BASE_ADDR (0xA0000000)
/* where dovarious parts live in RAM */
#define BLOB_RAM_BASE (0xA0100000)
#define KERNEL_RAM_BASE (0xA0200000)
#define PARAM_RAM_BASE (0xA0180000)
#define RAMDISK_RAM_BASE (0xA0400000)
/* and wheredo they live in flash */
#define BLOB_FLASH_BASE (0x00000000)
#define BLOB_FLASH_LEN (64* 1024)
#define PARAM_FLASH_BASE (BLOB_FLASH_BASE+ BLOB_FLASH_LEN)
#define PARAM_FLASH_LEN (64*1024)
#define KERNEL_FLASH_BASE (0x00020000)
#define KERNEL_FLASH_LEN (1920*1024) //1920=2048-128
#define RAMDISK_FLASH_BASE (0x00200000)
#define RAMDISK_FLASH_LEN (14* 1024 * 1024) //for rootfs
#define JFFS2_FLASH_BASE (0x01000000) //17M--32M
#define JFFS2_FLASH_LEN (4* 1024 * 1024)
/* theposition of the kernel boot parameters */
#define BOOT_PARAMS (0xA0000100)
因此,整个32M的Flash空间分配如图(2)。
图(2)Blob对系统Flash的规划
五、Blob启动分析
Blob被放在Flash的0x0地址起始处,ARM微处理器复位后自动跳到0x0地址执行,因此系统复位时首先运行blob程序。Blob负责完成对系统硬件的初始化、对SDRAM进行测试,然后加载操作系统,并跳到操作系统处开始执行。Blob的启动流程如图(3)所示。
图(3) blob启动流程
Blob支持自烧写,即通过blob更新blob。对于一个空的目标系统,首先是通过JTAG或者其他手段将blob程序烧写到flash中去,之后,如果需要修改blob,则可以直接使用blob提供的工具来完成更新了。要实现这一功能,blob程序必须在SDRAM中运行。因此,blob键程序分成两个段:stage1和stage2,如图(4)所示。
图(4)Blob的两个阶段
在Stage 1,基本都是用汇编语言编写。通常要完成以下工作;
(1) 初始化硬件;
(2) 为Blob的Stage 2准备RAM空间;
(3) 复制Stage 2到RAM;
(4) 设置好堆栈;
(5) 跳到Stage 2 的C程序入口点;
当blob由stage1执行到Stage 2时,程序已经在SDRAM中运行了。下面看一下是如何跳转的。
在../include/blob/arch/lubbock.h中定义了很多宏变量:
/* the baseaddress were BLOB is loaded by the first stage loader */
#define BLOB_ABS_BASE_ADDR (0xA0000000)
/* where dovarious parts live in RAM */
#defineBLOB_RAM_BASE (0xA0100000)
/* and wheredo they live in flash */
#define BLOB_FLASH_BASE (0x00000000)
#define BLOB_FLASH_LEN (64* 1024)
在Start.S中,初始化硬件后进行了Stage 2的搬移:
BLOB_START: .word BLOB_ABS_BASE_ADDR
……
relocate:
adr r0, _start /* blob入口地址到r1,在此相当于基址 */
/* relocate the second stage loader */
add r2, r0, #(64 * 1024) /* blob maximumsize is 64kB */
add r0, r0, #0x1000 /* skip first 4k bytes,Only Stage 2 */
ldr r1, BLOB_START /* 目的地址 */
/* r0 =source address
* r1 = target address
* r2 = source end address
*/
copy_loop:
ldmia r0!, {r3-r10} /* ldmia批量加载,r0指向的地址上的多字数据,保存到r3-r10中,r0的值更新*/
stmia r1!, {r3-r10}/*将r3-r10中的数据存储到R1指向的地址上,R1值更新*/
cmp r0, r2 /*源开始地址是否=源结束地址*/
ble copy_loop /* 不等就继续复制数据 */
……
可以看到,Blob的Stage 1将flash地址0x1000开始的60K空间全部搬移到了BLOB_START定义的RAM空间。在搬移过程中,跳过了前4K的空间,因为前4K存放的blob的Stage 1。Blob如何将代码分割为前4k和后60K的,我们下面在讨论。搬移成功后,紧接着是下面的跳转语句:
/*blob is copied to ram, so jump to it */
ldr r0,BLOB_START
mov pc,r0 // 跳转
通过将PC指针指向BLOB_START即实现了跳转。
Stage 2 开始并不是Mani.c文件,而是trampoline.S,意为“跳板”。该文件内容如下:
_trampoline:
/*clear the BSS section */
ldr r1,bss_start
ldr r0,bss_end
sub r0,r0, r1
/* r1 = startaddress */
/* r0 =#number of bytes */
mov r2,#0
clear_bss:
stmia r1!,{r2}
subs r0,r0, #4
bne clear_bss
/*setup the stack pointer */ //设置堆栈
ldr r0,stack_end
sub sp,r0, #4
/*jump to C code */
bl main // 这里跳转到C程序,即Main.C的main()函数
/*if main ever returns we just call it again */
b _trampoline
bss_start: .word __bss_start
bss_end: .word __bss_end
stack_end: .word __stack_end
这是,Blob才真正开始执行C程序代码了。Blob的Stage 2一般使用C语言编写,便于实现更复杂的功能和取得更好的代码可读性和可移植性。Stage 2一般完成的功能是:
(1)初始化本阶段要使用到的硬件设备
(2)检测系统内存映射(memory map)
(3)将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中
(4)为内核设置启动参数
(5) 调用内核
Stage 2中程序的执行过程如图(5)所示。
图(5)Blob Main函数流程图
启动内核掉用parse_command("boot")完成得,该函数实际执行的函数是在../src/blob/linux.c中的boot_linux(),如下:
static int boot_linux(int argc, char *argv[])
{
int i,len;
struct param_struct *params;
void (*theKernel)(int zero, int arch) = (void (*)(int,int))KERNEL_RAM_BASE;
/* Initial kernel params */
params = (struct param_struct *)BOOT_PARAMS;
params->u1.s.page_size = LINUX_PAGE_SIZE;
params->u1.s.nr_pages = (0x04000000 >>LINUX_PAGE_SHIFT);
memcpy(params->commandline, boot_paramsm,strlen(boot_paramsm));
setup_start_tag(); //传递内核参数
setup_memory_tags();
setup_commandline_tag(argc, argv);
setup_initrd_tag();
setup_ramdisk_tag();
setup_end_tag();
/* we assume that the kernel is in place */
SerialOutputString("Starting kernel ...\n");
serial_flush_output();
/* disable subsystems that want to be disabled beforekernel boot */
exit_subsystems();
/* start kernel */
theKernel(0, ARCH_NUMBER); // 启动内核
SerialOutputString("Hey, the kernel returned! Thisshould not happen.\n");
return 0;
}
这里面非常重要的就是theKernel()函数指针。这是在C语言中实现程序跳转的很好的方法。theKernel()有两个参数,根据POSIX准则,第一个参数将传递给CPU的R0寄存器,第二个则是R1寄存器。在跳到内核是,必须要满足以下条件:
(1) CPU 寄存器的设置:
·R0=0;(theKernel的第一个参数)
·R1=机器类型 ID;关于 Machine Type Number,可以参见 linux/arch/arm/tools/mach-types。(theKernel的第二个参数)
·R2=启动参数标记列表在 RAM 中起始基地址;
(2) CPU 模式:
·必须禁止中断(IRQs和FIQs);
·CPU 必须 为Supervisor的保护模式;
(3)Cache 和 MMU 的设置:
·MMU 必须关闭;
·指令 Cache 可以打开也可以关闭;
·数据 Cache 必须关闭
至此,Blob交出CPU使用权了,一次正常的引导过程完成了。而进入Blob控制台后,blob在死循环中等待用户输入,并执行用户命令。Blob支持简单的命令集,比如,help命令打印帮助信息,xdownload命令下载文件,flash命令将下载的文件烧写到flash中,boot命令引导内核启动等。
六、移植到PXA255平台需要做的工作
Blob支持的平台比较多,因此移植blob到一个已有的平台是比较简单的事情。只需要修改为数不多的几个文件即可。移植到一个新的平台则相对复杂,具体可以参考../doc/porting.txt。下面以第一种情况为例说明需要修改的几个地方。
(1)在../include/blob/arch/lubbock.h修改存储空间分配方案及IO寄存器、Mem控制器的初始值;
(2)如果系统由LED,在../src/lib/led.c和../src/blob/ledasm.S中修改led的控制函数;
(3)在memsetup-pxa.S中设置CPU工作时钟、内存访问速度等;
(4)在Main.c中修改控制台参数;
(5)在../src/lib/serial-pxa.c中选择控制台串口,在../src/blob/initcalls.c中设置默认的控制台串口速率。
(6)在../src/blob/flash.c中修改FLASH读写函数(如果不是同一种Flash的话,这里用的是IntelE28F128J3A);
(7)对于不同的Linux内核,需要修改../src/blob/linux.c中的参数和boot_linux()函数。
(8)如果需要网卡功能和USB功能支持,则需要修改相应的文件,这里不再详述。
七、其它主要程序分析
1、 Blob链接时如何区分Stage 1 和 Stage 2?
分析Blob的Makefile可以得到答案。../src/blob/Makefile.in文件写得很清楚:
# ---- Blob first stage loader---------------------------------------
# WARNING: start.S *must* be the first file, otherwisethe target will
# be linked in the wrong order!
blob_start_elf32_SOURCES = \
start.S \
ledasm.S \
testmem.S
……
blob_start_elf32_DEPENDENCIES = \
memsetup-pxa.o \
start-ld-script
# ---- Blob second stage---------------------------------------------
# WARNING: trampoline.S *must* be the first file,otherwise the target
# will be linked in the wrong order!
blob_rest_elf32_SOURCES = \
trampoline.S \
flashasm.S \
stack.S \
testmem2.S \
bootldrpart.c \
commands.c \
flash.c \
initcalls.c \
linux.c \
main.c \
memory.c \
param_block.c \
partition.c \
reboot.c \
uucodec.c \
xmodem.c
……
在../src/blob/start-ld-script和../src/blob/rest-ld-script两个文件中定义了链接时各个代码段的地址。
2、 Blob使用的文件传输协议
Blob使用Xmodem协议传输文件,源代码可以参考../src/blob/xmodem.c。
八、Blob编译说明
使用arm-linux-gcc可以编译blob,这里使用的arm-linux-gcc版本为3.3.2。编译blob时必须指定Linux源码的安装路径,原因在于编译blob时需要寻找在linux.c程序中语句#include<asm-arm/setup.h>包含的头文件,该头文件包含了Linux内核启动参数结构体,不同版本的linux内核可能不一样。
./configure-pxa255可以自动编译生成blob可执行文件。该脚本实际执行如下指令:
make distclean
export CC=/usr/local/arm/3.3.2/bin/arm-linux-gcc
export OBJCOPY=/usr/local/arm/3.3.2/bin/arm-linux-objcopy
./configure --with-board=lubbock --with-cpu=pxa255--with-linux-prefix=/usr/src/linux-2.6.8.1-sniffer --enable-usb --with-eth=usb--enable-debug
Make
各个选项的说明可以参考configure.in文件。更为详细的说明参考blob的README文件。
九、Blob使用说明
参考blob的README文件。Blob的控制台界面如下:
图(6)Blob控制台
原文主要载于
http://blog.163.com/xd_zxw/blog/static/15473901200702101959175/
本人稍作编辑修改。