不同的CPU具有不同的启动方式,其系统外设等均具有较大的差异。CPU上电启动后,并不具有相应的内核启动环境,需要bootloader先初始化CPU及相应系统外设,加载内核,使之具备内核启动的必要条件。
bootloader一般应有下载固化以及加载启动这两个功能,笔者此处就s3c2416基于yaffs文件系统的Linux下载固化以及加载启动作一个简单的介绍。
1、基于Linux的bootloader
bootloader的实现流程前面章节有详细的介绍,此处不再细述。bootloader为了能启动Linux,往往需要跟内核彼此协调约定,才能联合工作。
Linux系统为了精简以及便于维护,分成了内核空间以及用户空间。Linux内核由内存管理、进程管理、设备驱动程序、网络管理等组成,它是操作系统的核心,具有很多最基本的功能,决定了系统的性能和稳定性。用户空间的文件系统用来提供管理系统的各种配置,提供相应的应用程序、服务、数据交换等。文件系统作为一种载体,它是用来实现用户与操作系统内核的交互。因此,一个可启动的Linux系统必须包含Linux内核以及一个根文件系统。
bootloader应该具有把自身、Linux内核、根文件系统下载固化进相应的非易失存性储器中,以便目标机能脱离宿主机,单独加载启动运行。bootloader可以通过多种通讯方式,如usb、网络、sd卡、u盘等,把宿主机上开发好的内核、根文件系统等下载进目标机的内存,再固化进相应的存储器中。目标机单独运行时,bootloader首先自启动,然后从固化存储器中加载Linux内核到内存,如果根文件系统在ram disk里,bootloader还需加载根文件系统到内存,否则,根文件系统由Linux内核根据约定进行挂载。最后,bootloader跳转到内核,把CPU控制器全权交付内核处理。
SofTool.CN Notes:
在NandFlash写入象棋小子的 LinuxBoot.bin,先按下空格,然后上电进入启动界面:
需要在SD卡建目录image,放入对应的文件即可。
文件名含义:
bootloader.bin - 启动程序
kernel.bin - 内核
rootfs.bin - 根文件系统
2、下载固化
此处假设已经做好了bootloader、Linux内核、基于yaffs的根文件系统,把宿主机里的这些镜像文件拷贝到sd卡,bootloader从sd卡读取这些镜像文件到内存,然后再烧录固化进nand flash里,实现从sd卡自更新nand flash里面的bootloader、内核、文件系统。bootloader采用fatfs来支持sd卡的fat32文件系统的访问。
2.1. bootloader固化
bootloader从/image/bootloader.bin读取文件到内存,然后调用nand写函数把代码下载固化进nand flash。对于s3c2416,此时的bootloader在nand flash最开始的位置,如果是nand boot,启动后CPU不通过任何校验直接把nand flash最前面的8k加载进内部ram(stepping stone),如果是irom nand启动,则CPU需要通过ecc校验成功后才会把nand flash里面的8k代码加载进stepping stone。此处bootloader采用ecc检验烧写方式用于同时支持nand boot以及irom nand启动,同时,bootloader从nand flash加载自身时需采用一致的读取方式。
2.2. Linux内核固化
bootloader从/image/kernel.bin读取文件到内存,Linux内核由bootloader安装以及加载,因此Linux内核的储存位置本身与内核无关,由bootloader决定。bootloader可以决定把内核储存进如spi flash、sd卡等存储器,如果储存进nand flash,只需注意Linux内核储存位置不与bootloader、根文件系统位置冲突即可。bootloader加载内核时需从安装内核的位置读取即可。
2.3. 根文件系统固化
bootloader从/image/rootfs.bin读取文件到内存,如果根文件系统为initrd,则根文件系统储存位置也与内核无关,由bootloader决定存储及加载。如果根文件系统为initramfs,并且跟Linux内核链接在一起,那么bootloader无需再处理根文件系统了,因为Linux内核包含了根文件系统,固化加载内核也同时处理了根文件系统。
如果根文件系统在nand flash、sd/mmc卡等存储设备中,首先Linux内核必须支持相应的设备驱动以及文件系统。此处以nandflash为例说明,bootloader应该与Linux内核协调约定nand flash的分区位置,因为此时根文件系统由Linux内核挂载,与bootloader无关。bootloader把根文件系统下载固化进相应的分区位置,然后在启动内核时告之内核所在的分区。除此之外,nand flash还有oob区,对于yaffs文件系统,oob区用来存储yaffs的tag数据,坏块标记,ecc校验数据,这需要宿主机制作yaffs镜像工具mkyaffs2image、bootloader、Linux内核协调约定,确保bootloader与Linux内核有一致的nand layout,即bootloader的坏块标记位置、yaffs的tag数据位置应该与Linux内核完全一致,如果Linux内核采用了相应的nand ecc,那么bootloader在下载固化根文件系统时也应采用相同的nandecc方式进行填充oob区。任何一点bootloader与Linux内核的不一致,将造成Linux内核无法挂载根文件系统,引起内核panic。
3、加载启动
3.1. bootloader硬件初始化
上电启动后,bootloader首先初始化必要的硬件,如内存控制器,整个系统时钟等,包括Linux内核默认不初始化直接使用的部分,如外部总线时序、usb时钟等。
3.2. bootloader加载内核
bootloader从内核安装位置加载内核到内存,这个内存位置并没有需要特别注意的地方,一般默认是内存基址+0x8000处,Linux内核会自行重定位到执行地址执行。如果采用initrd根文件系统,也由bootloader加载根文件系统到内存。
3.3. Linux启动参数
bootloader可以给Linux内核传递启动参数以控制其行为。Linux内核启动参数位置并没有需要特别注意的地方,一般默认是内存基址+0x100处,bootloader通过标记列表的形式来传递启动参数。
标记列表以标记ATAG_CORE开始
params->hdr.tag =ATAG_CORE;
params->hdr.size =tag_size (tag_core);
params->u.core.flags =0;
params->u.core.pagesize= 0;
params->u.core.rootdev= 0;
params = tag_next(params);
设置内存标记ATAG_MEM,标记内存的位置及大小
params->hdr.tag =ATAG_MEM;
params->hdr.size =tag_size (tag_mem32);
params->u.mem.start =DRAM_BASE;
params->u.mem.size =DRAM_SIZE; // 64M
params = tag_next(params);
设置命令行标记ATAG_CMDLINE,这是一个字符串,用来控制内核的行为。如”noinitrd root=/dev/mtdblock3 rootfstype=yaffs2init=/Linuxrc console=ttySAC0”,表示根文件系统在MTD3分区上,文件系统为yaffs2,系统启动后执行的第一个程序为Linuxrc,控制台为ttySAC0。
params->hdr.tag =ATAG_CMDLINE;
params->hdr.size =(sizeof(struct tag_header)+strlen(NandBootCmd)+1+4) >> 2;
strcpy(params->u.cmdline.cmdline,NandBootCmd);
params = tag_next(params);
标记列表以标记ATAG_NONE结束
params->hdr.tag =ATAG_NONE;
params->hdr.size = 0;
3.4. 内核启动环境
在交出CPU控制权之前,bootloader需要把CPU恢复成初始的环境,CPU必须禁止IRQ以及FIQ中断,处于特权模式(supervisor mode),关闭MMU,使无效并且写回DCache数据到主存,禁止DCache,推荐ICache打开。
IRQ_Disable();
CP15_DisableDCache();
CP15_DisableICache();
CP15_CleanDCache();
CP15_DrainWriteBuffer();
CP15_InvalidateDCache();
CP15_InvalidateICache();
CP15_DisableMMU();
CP15_InvalidateTLB();
CP15_EnableICache();
3.5. 跳转到内核
bootloader跳转到内核时,将传递三个参数给内核,对于ARM为R0、R1、R2这三个寄存器。第一个参数R0必须为0,第二个参数R1为机器码,必须与Linux内核一致才能启动,第三个参数为先前设置的内核启动参数地址位置。跳转到内核后,bootloader完全退出,由Linux内核全权处理。
Kernel(0, MACH_TYPE,(uint32_t)params);
4、附录
附录为arm交叉编译工具链下基于newlib的s3c2416 Linux启动 bootloader工程,附带了newlib库,sd卡windows下烧录工具,工程直接make即可。
http://pan.baidu.com/s/1czo6N8
mdk下s3c2416 Linux启动bootloader工程。
http://pan.baidu.com/s/1dFrGU7R