摘要: 本文对Boot Loader的功能、操作模式等作了简单的介绍,重点对基于S3C44B0X内核的UP-NetARM3000上移植uCOS所设计的Boot Loader代码进行了深入的分析和研究,详细的介绍了这款Boot Loader的运行流程和工作机理,为成功装载嵌入式操作系统建立良好的软硬件环境。
一个完整的嵌入式系统包括嵌入式微处理器和外围支撑硬件,以及嵌入式操作系统、系统软件和应用软件构成。在嵌入式系统中,移植一款合适的操作系统非常重要,而系统的引导程序就显得尤为重要。这段运行于操作系统内核运行之前,初始化硬件设备,将系统的软硬件环境带到一个合适状态的小程序就是Boot Loader。
Boot Loader概述
Boot Loader的概念及功能
在以ARM为代表的嵌入式系统中,操作系统内核运行前的硬件初始化、建立内核镜像等都是由Boot Loader来完成的。在PC机上,最先启动的是主板上的BIOS,BIOS负责对硬件初始化,给操作系统提供硬件的接口函数等等,但在嵌入式操作系统中并没有BIOS,因此整个嵌入式操作系统的加载启动任务就完全由Boot Loader来完成。
Boot Loader通常存放于目标平台的非易失存储介质中,主要用于完成由硬件启动到操作系统启动的过渡,能够在上电后对SDRAM、CACHE、FLASH等硬件部分进行检测,建立内存空间的映射图和内核镜像,建立通讯通道和调试通道等,还能够提供Shell Menu检测设置菜单和相应的检测程序,引导操作系统及应用程序,从而为最终调用操作系统内核准备好正确的环境。
目前,嵌入式系统中广泛应用的UBoot、vivi、blob、armboot等Boot Loader在原有功能的基础上,增加了更多的功能并大大增强了移植性。嵌入式系统中硬件的种类繁多,差距较大,而Boot Loader是严重依赖于硬件而实现的。不同的CPU体系需要不同的Boot Loader,即便是同一种体系结构,由于其它硬件设备配置的不同,如板卡硬件地址的分配、RAM芯片的型号等,也需要对Boot Loader作一定的修改才能使用。因此,开发人员需针对不同的处理器和开发板,对Boot Loader进行定制,来实现不同的功能。
Boot Loader的操作模式
Boot Loader通常包括“启动加载(Boot loading)”和“下载(Downloading)”两种模式。这两种操作模式仅对开发人员具有一定的意义。
启动加载模式:Boot Loader从目标机上的固态存储设备上将操作系统加载到RAM中运行,无需用户介入。此模式是Boot Loader正常的工作模式。
下载模式:在这种模式下,目标机上的Boot Loader通过串口连接或网络连接等手段从主机上下载文件到目标机的RAM中,然后再烧写到目标机上的固态存储设备中。通常在第一次安装内核与根文件系统以及系统的更新时使用此模式。
Boot Loader的具体实现
硬件配置
本文以UP-NetARM3000为例来介绍Boot Loader的工作机理和运行流程。UP-NetARM3000使用的是三星公司生产的S3C44B0X芯片,这是三星公司推出的一款高性价比和高性能的微控制器。它具有32位的ARM7TDMI内核,外部时钟为8MHz,内部倍频最高可达72MHz,工作频率为64MHz。S3C44B0X通过提供全面的、通用的片上外设,大大减少了系统中处理器以外的元器件配置,从而使系统的成本大大降低,它集成的各种片上功能包括:8KB Cache、扩展内存控制器、2通道UART带有握手协议、1通道SIO、2个通用DMA、2个外设用DMA、外部存储控制器、LCD控制器、 IIC/IIS总线接口、5个通道PWM定时器和一个内部定时器、看门狗定时器、71个通用I/O口、8个外部中断源、8通道10位ADC、片上PLL时钟产生器等。
Boot Loader的启动流程
大多数Boot Loader通常都分为Stage1和Stage2两大部分。Stage1通常由汇编语言编写,即Boot Loader的启动代码,旨在对部分硬件设备进行初始化。Stage2即Boot Loader的主代码,为了实现更加复杂的功能,使代码具有更好的可读性和可移植性,通常由C语言来实现,主要用来加载操作系统内核。具体启动流程如图1所示。
· Stage1部分
设置CPU的速度、时钟频率及中断控制寄存器
Boot Loader的启动代码首先定义了一个全局入口,然后对异常向量进行设置。由于Boot Loader严重的依赖于硬件而实现,因此根据CPU体系结构和具体的硬件配置来设置CPU的速度、时钟频率及中断控制寄存器。除完成上述功能,启动代码还需要实现禁止看门狗定时器、设置时钟控制寄存器、设置锁相环控制寄存器、使能所有功能单元块时钟等功能。另外,系统中断的设置也是在这部分实现的,主要是中断向量表和IRQ中断入口地址的设置。
//设置处理器时钟PLL的LOCKTIME
ldr r0,=LOCKTIME
ldr r1,=0xfff
str r1,[r0]
//设置时钟频率
ldr r0,=PLLCON ldr
r1,= ((M_DIV<<12)+(P_DIV<<4)+ S_DIV)
str r1,[r0]
存储器的分配
S3C44B0X 的存储系统具有一些主要特征,如:支持数据存储的大、小端选择;地址空间具有8个存储体,每个存储体可达32MB;对所有存储体的访问大小均可以进行改变;7个存储器的起始地址固定,1个存储器的起始地址可变。在本文介绍的这款Boot Loader中,启动代码通过对BUSWIDTH的赋值来使能各个存储器。其具体对应情况如右面所示:
启动代码还有一个重要的任务—初始化内存控制寄存器,它主要通过设置13 个从0x01c80000开始的寄存器来实现,包括BWSCON总线宽度与等待状态控制寄存器、BANKCONn块控制寄存器。标号SMRDATA处为将要赋予内存控制寄存器的具体值。以ResetHandler标号地址为参照,根据其偏移地址推算出SMRDATA标号地址的实际位置,然后读取该处的数据对内存控制寄存器进行赋值。
adr r0, ResetHandler //将ResetHandler在代码中的实际地址加载到r0
ldr r1,=Resethandler //将ResetHandler链接时决定的值放入r1
sub r0, r1, r0,
ldr r0,=SMRDATA //设定存储器控制寄存器
ldmia r0,{r1-r13}
ldr r0,=0x01c80000 //内存寄存器组第一个寄存器基地址
stmia r0,{r1-r13}
形成可执行文件
在嵌入式系统应用程序中,可执行文件通常包括RO(Read_Only)段、RW(Read_Write)段和BSS段。当需要烧写内存中的映像文件到FLASH中时,通常都会把Boot Loader代码先移到FLASH的高地址空间中,因为通常RO的地址都是0x0,防止在烧写时覆盖本来在FLASH中已有的Boot Loader代码。程序编译、链接时要求编译器设置的Read_Only地址要和最终代码下载的地址相同,如图2所示。
Boot Loader映像文件最终运行的地址空间是0Bank,因此将RO Base设置为0x0,RW Base设置为0x0c60000,经编译后生成bin格式的可执行文件烧写到FLASH0地址处。在程序运行之前,RO段和RW段全部放在FLASH 中,RO段可以直接在FLASH中运行,而RW必须调入SDRAM中才可以运行,因此,程序运行过程中RO段保持已设置完毕的0x0地址不变,而必须将 RW段拷贝到RW Base即0x0c60000地址处,并将ZI段进行零初始化。
- LDR r0, =|Image$$RO$$Limit|
- LDR r1, =|Image$$RW$$Base|
- LDR r3, =|Image$$ZI$$Base|
- CMP r0, r1 //对R0和R1进行比较
- BEQ %F1
- 0 CMP r1, r3 //拷贝RW段的数据到RW Base
- LDRCC r2, [r0], #4 //LDRCC r2, [r0] + ADD r0, r0, #4
- STRCC r2, [r1], #4 //STRCC r2, [r1] + ADD r1, r1, #4
- BCC %B0
- 1 LDR r1, =|Image$$ZI$$Limit| //ZI段的存储区域界限
- MOV r2, #0
- 2 CMP r3, r1 //ZI段0初始化
- STRCC r2, [r3], #4
- BCC %B2
·Stage2部分
初始化硬件
Boot Loader主程序对串口进行调试并对本阶段所涉及的硬件进行检测后,通过串口下载镜像到目标机中。主代码中定义了LCD_Test()、 LED_Test()、ADTest()、KeyTest()、BootSystem()等函数测试部分硬件的功能。以AD的检测函数为例简要介绍检测函数功能具体的实现。ADTest_Loop()是针对AD硬件的操作函数,而Set_UartLoopFunc()函数是把ADTest_Loop()设置到串口轮询函数数组中(串口轮询函数数组中还包括其他的检测函数),AD检测函数在对目标进行操作的同时查询是否有停止命令,如果Uart_Getch ()函数没有查询到串口有输入的停止命令,调用串口轮询数组中的其他函数,否则立刻返回。
- void ADTest()
- {
- int index;
- Uart_Printf("\nTest AD from channel 0 to 3. Press any key return\n");
- init_ADdevice();
- index=Set_UartLoopFunc(ADTest_Loop);
- Uart_Getch(0);
- Clear_UartLoopFunc(index);
- void ADTest_Loop()
- {static int count=0; int i; count++;
- if(count<50000)
- return;
- count=0;
- for(i=0;i<4;i++)
- {
- Uart_Printf("%8AD%d=%4.4d",i,GetADresult(i));
- }
- }
检测内存及引导系统
在Boot Loader的主程序中需要检测重要的硬件——内存,检测完毕后会分别通过串口和LCD输出提示信息。接下来会等待查询是否有键按下,当没有键按下时,正常引导操作系统,否则显示Shell Menu。
Boot Loader程序在获得系统的控制权之后,对关键硬件检测并且没有发现故障,控制台也没有发出启用Shell Menu检测菜单的情况下,根据文件系统的管理和支持,从NAND FLASH中读取操作系统或应用程序的代码到SDRAM的指定位置,然后把程序指针转移到该位置,从而使操作系统获得控制权,完成引导过程。
static void(*run)(void)=(void(*)(void))DOWNLOAD _ADDRESS,在程序中定义得此函数指针能够把指定位置的地址强制转换为函数指针类型,然后使用run()函数即可运行该地址处的指令。在实现引导功能的代码中,打开并读取指定的系统文件到指定的位置DOWNLOAD_ADDRESS(0xc080000)处即完成了装载,将程序指针指向已指定的这个位置,使用run()函数运行该地址处的指令,即可实现控制的转移。当看到屏幕上显示操作系统的启动信息后,Boot Loader完成任务,成功的引导了操作系统。
- int LoadFile(char *filename, unsigned char *pbuffer)
- {
- __FILE *file;
- int i;
- file=OpenOSFile(filename,FILEMODE_READ);
- if(file==NULL)
- return FALSE;
- ReadOSFile(file, pbuffer, file->filesize);
- CloseOSFile(file);
- return TRUE;
- }
- void Boot(char *pbootfile)
- {
- if(pbootfile==NULL)
- pbootfile=SYSTEM_CODE;
- rINTMSK=0xffffffff;
- initOSFile();
- if(!LoadFile(pbootfile,(unsigned char *)(DOWNLOAD_ADDRESS)))
- {
- Uart_Printf("\nCan't find file %s",pbootfile);
- return;
- }
- run();
- }
结语
本文介绍了在基于S3C44B0X的UP-NETARM3000的实验板上移植uCOS系统的Boot Loader。不同的开发板具有不同的CPU体系结构和外围硬件设备,但Boot Loader的工作机理都是类似的,明确开发板的硬件资源和具体即将移植的操作系统后,对Boot Loader进行具体裁减和修改。
参考文献:
1. 张仑编著.32位嵌入式系统硬件设计与调试.北京:机械工业出版社,2005.5
2. 金鑫. S3C44B0X下的Boot Loader开发.工业控制计算机,2005(8)
3. 严国清. S3C44B0X中Boot Loader的实现.数据采集与嵌入式系统,2004(6)