一、概述
引导加载程序(bootloader)就是在操作系统内核运行前执行的一段小程序。通过这段小程序,我们可以初始化必要的硬件设备,创建内核需要的一些信息并将这些信息通过相关机制传递给内核,从而将系统的软硬件环境带到一个合适的状态,最终调用操作系统内核,真正起到引导和加载内核的作用。
在DDR系统中,该部分的代码位于IDR400-2_SMDK6410/SRC/BOOTLOADER文件夹下。该文件夹下有五个子文件夹NBL1.LSB,NBL2,EBOOT.WHIMORY,Eboot.SDFuser和NBL1.IROM_SD,其中Eboot.SDFuser和NBL1.IROM_SD一起所生成的引导程序用于烧写,也就是说把NBOOT、EBOOT和内核镜像从SD卡中烧写到NAND FLASH(相当于DDR系统的硬盘)中去,以后系统正常使用的时候都是从NAND FLASH中读入引导程序,然后引导程序加载内核到内存开始执行;其中NBL1.LSB,NBL2,EBOOT.WHIMORY所生成的引导程序,则是系统正常运行时的bootloader,该文档主要介绍的就是这个bootloader
二、NBL1.LSB
Bootloader最先被执行的就是这个文件夹下的代码,入口是startup.s。这部分代码应该是在CPU片内ROM里面,无需加载,在系统上电的时候自动运行,然后它初始化一些硬件,再从NAND FLASH第一个块(块号为0)中读取NBOOT到SDRAM(即内存)中,即规定了NBOOT大小最大只能是一个块大小,其实NBOOT编译完后生成的镜像只有70k左右。
首先在startup.s汇编代码中设置一些硬件环境,如关中断、关看门狗等等,然后跳到main.c文件中的main函数中。
这里有个重要的地址:
#define LOAD_ADDRESS_PHYSICAL (0x50000000)
这个地址即SDRAM的起始地址
Main函数中流程:
l 在main函数中首先通过NAND_Init();初始化NAND FLASH
l pBuf = (unsigned char *)LOAD_ADDRESS_PHYSICAL;因为这个时候还没有开启MMU,虚拟地址pBuf也就是物理地址,可以直接赋值而无需转换
l 从FLASH中读取相应数据到这个地址处
l ((PFN_IMAGE_LAUNCH)(LOAD_ADDRESS_PHYSICAL))();最后跳转到这个地址处执行
它的功能:
l 开启指令缓存Icache,设置了一些堆栈,以及设置其它硬件坏境
l 加载NBOOT镜像(保存在NAND FLASH第一个BLOCK中)进入RAM中,并启动
三、NBL2
首先被执行的依然是startup.s中汇编代码,然后跳转到main.c的main函数中。Main函数的流程如下:
l OEMInitDebugSerial();初始化串口,此后就可以使用OEMWriteDebugString输出调试信息了
l FIL_Init();初始化Flash Interface Layer,按我的理解是驱动NAND FLASH,跟进去看这个函数的代码,只是给一个结构体变量赋值NAND操作的一些函数(如NAND_Read_Retry),
l ShadowEboot();就是加载EBOOT镜像到SDRAM中特定地址了,这个地址就是:
#define EBOOT_VIRTUAL_BASEADDR 0x80030000
用过这个函数pLowFuncTbl=FIL_GetFuncTbl()获取之前FIL_Init()设置好的那个结构体变量,然后通过pLowFuncTbl->Read从NAND_FLASH中读取EBOOT Image
l OEMLaunchImage(EBOOT_VIRTUAL_BASEADDR);最后用这个函数跳转到EBOOT执行,因为已经开始了MMU,代码中使用的都是虚拟地址,所以要转换成物理地址进行跳转
NBOOT2的功能:
l 开启MMU和数据缓存Dcache,设置其它硬件坏境
l This routine will initialize the first-level page table based up the contents of the MemoryMap array(这一步不太理解)
l 加载EBOOT镜像(保存在NAND FLASH第三个BLOCK(块号为2)中,最大大小为512K)进入RAM中,并启动
四、EBOOT.WHIMORY
4-1、执行流程
Bootloader最主要的工作都在这部分完成,首先被执行的依然是startup.s汇编代码,除了一些例行的初始化硬件坏境设置外,一个重要的功能是建立了虚拟地址的映射机制。然后依然是跳转到main.c文件中的main函数里。
Main函数调用BootloaderMain();BootloaderMain()的执行流程如下:
l KernelRelocate:重定位全局变量到RAM中
l OEMDebugInit:初始化调试端口
l OEMPlatformInit:初始化目标板上的设备
l OEMPreDownload:下载映像之前
l DownloadImage:下载映像
l OEMLaunch:启动映像
在EBOOT中,OEMPlatformInit是其中的主体,以上的执行流程在OEMPlatformInit之后就跳到内核执行了,下面描述一下这个函数体的整体流程:
1. OALArgsInit(pBSPArgs); //初始化BSP_ARGS结构体
2. BP_Init;// initialize the boot media block driver and BinFS partition
3. FMD_GetInfo(&flashInfo) //获取FLASH信息
4. TOC_Read( ) //从NAND FLASH TOC分区中读取数据
5. Boot_Keys_Scan();// 检测用户是否进入烧写模式,如果不是则跳到第8步
6. InitializeUSB() //初始化USB,准备烧写
7. StateMachine3();//启动烧写,烧写完以后,调用SpinForever()终止
8. ReadOSImageFromBootMedia();//从NAND FLASH中读取内核到指定地址
9. Launch(0x50101000);//从这个地址处进入内核,os开始运行,bootloader完成使命
4-2、其它
bootloader如何存放在NAND FLASH:
NAND上首先存放着NBOOT,大小为一个块
然后存放着TOC,大小也为一个块,但是预留了一个块
然后存放着EBOOT,大小为五个块,预留了二个块
然后才存放着内核镜像
BINFS到底是什么?
BINFS就是MS给CE做的一种存放系统镜像的一个文件系统,但不知如何实现的
一些结构体
UCHAR g_TOC[SECTOR_SIZE];
const PTOC g_pTOC = (PTOC)&g_TOC;
上面变量是定义在main.c文件中的全局变量,它用来存储从NAND FLASH TOC分区中读取出来的数据,然后根据这些数据来决定从哪里加载内核镜像到哪里以及其大小。
typedef struct _TOC
{
//只用来验证接下去内容的合法
DWORD dwSignature;
//包含image的索引、启动delay时间、ip地址、MAC地址和掩码等
BOOT_CFG BootCfg;
//用来描述ce内核image数组
IMAGE_DESCRIPTOR id[MAX_TOC_DESCRIPTORS];
CHAININFO chainInfo;
} TOC, *PTOC; // 512 字节
其中IMAGE_DESCRIPTOR结构体的定义如下:
typedef struct _IMAGE_DESCRIPTOR
{
DWORD dwVersion; //编译时的版本号
DWORD dwSignature; //“EBOOT”或“CFSH”等
UCHAR ucString[IMAGE_STRING_LEN]; //描述字符串:如"eboot.nb0"之类
DWORD dwImageType; //image的类型
DWORD dwTtlSectors; //image文件用到的NAND的扇区总数
DWORD dwLoadAddress; //image加载时的虚拟地址
DWORD dwJumpAddress; //image加载完成后的跳转地址
SG_SECTOR sgList[MAX_SG_SECTORS];
//image的段描述,包括起始扇区号和所需扇区数目
ULONG dwStoreOffset;
} IMAGE_DESCRIPTOR, *PIMAGE_DESCRIPTOR;
五、总结
简而言之,bootloader就是在系统开机的时候运行的一段代码,这段代码用来初始化硬件环境、加载操作系统到内存并转入执行。初始化硬件坏境一般都是由汇编代码(如startup.s)完成,这些代码与硬件高度关联,深入了解和改变这些代码以进行调试和配置都需要参考芯片文档(如datasheet)。而加载镜像到SDRAM,都是由c语言代码实现的,其过程在main函数中都比较明朗,其实质都是通过初始化FLASH,然后由NAND_READ()之类的函数把存在于NAND FLASH上的镜像加载到特定的内存地址,然后跳转到这个地址处转到下一步执行。