BSP用于初始化硬件、引导操作系统并提供软件和硬件之间的设备驱动接口,针对某类体系结构的处理器开发BSP时,了解其基本的体系结构和指令系统是必要的。一般来说,BSP的设计与开发可分为几个步骤:
1、 建立开发环境,这个不用说了,就是装集成环境;
2、 选择合适的BSP模板,要尽可能的与硬件平台相近;
3、 修改或添加wind内核激活前的初始化代码,例如初始化CPU内核、MMU、Cache禁止/使能等;
4、 内核激活后,连接系统中断、系统时钟,修改或添加所需的设备驱动程序;
5、 测试与验证,BSP的正确性与稳定性对上层软件和整个系统的稳定起着至关重要的作用,因此BSP完成后要经过测试验证。
开发BSP过程中最主要的三个程序分别为:1、romInit.s中的romInit()函数,用于初始化CPU及内存;2、sysLib.c中的sysHwInit2()函数,用于将所有板上硬件初始化为静止状态;3、sysLib.c中的sysHwInit()函数,进一步初始化板件以使用vxWorks程序。
下面根据代码详细分析vxWorks的启动顺序:
一、执行romInit():
系统启动时,处理器首先会跳到ROM中的入口点_romInit()(位于romInit.s中),该处汇编代码如下:
_romInit:
Cold:
MOV r0, #BOOT_COLD
warm:
B start
.ascii "Copyright …"
.balign 4
start:
/*此处添加延迟可以有效解决部分板件在复位后无法启动的问题*/
TEQ r0, #BOOT_COLD
MOVEQ r1, #INTEGRATOR_DELAY_VALUE
MOVNE r1, #1
delay_loop:
SUBS r1, r1, #1
BNE delay_loop
...
此处即为vxWorks在ROM中的入口点,在此之前,sysLib.c中的函数sysToMonitor将检查指令的前三行,看他们是否发生改变,进而可判断是否为热启动,具体代码如下:
if (p[0] == 0xE3A00002 && p[2] == 0x79706F43)
pRom = (FUNCPTR)(ROM_TEXT_ADRS + 4); /* warm boot address */
else
pRom = (FUNCPTR)ROM_TEXT_ADRS; /* start of ROM */
由上面代码可知,对于热启动,处理器在执行函数romInit的同时会先增加一个小的偏移量, romInit中的代码的需要用汇编语言实现,主要用于分配内存,初始化处理器状态字以及创建临时堆栈,另外,它还会初始化尽量最少的必需硬件、屏蔽中断、清空cache。如果romInit函数执行正确,从LOCAL_MEM_LOCAL_ADRS到LOCAL_MEM_LOCAL_ADRS+ LOCAL_MEM_SIZE的内存空间都将是可读写的,否则,说明此函数执行失败。romInit执行成功后,会确定启动类型并作为参数跳转到bootInit.c中的C函数romStart()中,跳转代码如下:
LDR r12, L$_rStrtInRom
ORR r12, r12, #1 /* in Thumb case */
BX r12
而L$_rStrtInRom就是函数romStart的地址:
L$_HiPosn:
.long ROM_TEXT_ADRS + HiPosn - FUNC(romInit)
L$_rStrtInRom:
.long ROM_TEXT_ADRS + FUNC(romStart) - FUNC(romInit)
romInit传递的参数(即上面定义的BOOT_COLD)决定了在romStart中是否清空(内存冷启动)。
二、执行romStart()
romStart的主要功能就是将ROM中适当位置的内存段拷贝到RAM中,如果ROM中的代码被压缩,拷贝过程中还会增加解压缩过程。然后,如果需要的话,处理器会跳转到RAM中的vxWorks入口点,也就是sysALib.s中的_sysInit()函数,但是一般情况下,则会跳到usrConfig.c中的usrInit()函数。这两种跳转的区分与vxWorks镜像文件类型有关,下面的图将会形象展示。该函数的功能相对简单,主要就是拷贝代码和必要时解压。其中解压代码如下:
# ifdef UNCOMPRESSED
((FUNCPTR)ROM_OFFSET(copyLongs))(ROM_TEXT_ADRS,(UINT)K0_TO_K1(romInit),ROM_COPY_SIZE / sizeof (long));
# else
((FUNCPTR)ROM_OFFSET(copyLongs))(ROM_TEXT_ADRS,(UINT)K0_TO_K1(romInit),((UINT)wrs_kernel_data_end - (UINT)romInit) / sizeof (long));
而实现拷贝的代码由copylongs函数实现,比较简单,此处略过。完成这些功能后,程序跳到RAM中的入口点sysALib.s的sysInite函数中。
三、执行sysInit()
它是启动后系统执行的第一段代码,主要功能为初始化处理器寄存器以及系统错误表,同时,关闭中断、允许追踪(tracing),最后跳转到usrInit函数。相关操作的源码及说明如下:
MOV r1, #0
MCR CP_MMU, 0, r1, c13, c0, 0/*初始化Processor寄存器*/
/*关闭CPU及外部中断*/
MRS r1, cpsr
BIC r1, r1, #MASK_MODE
ORR r1, r1, #MODE_SVC32 | I_BIT | F_BIT
MSR cpsr, r1
MOV r2, #IC_BASE /*R2->interrupt controller*/
MVN r1, #0 /*&FFFFFFFF*/
STR r1, [r2, #FIQ_DISABLE-IC_BASE] /*disable all FIQ sources*/
STR r1, [r2, #IRQ_DISABLE-IC_BASE] /*disable all IRQ sources*/
四、执行usrInit()
该函数位于useconfig.c中,它是从vxWorks镜像启动的第一个C程序,这段程序的主要任务是完全初始化CPU,关闭其他硬件,进而为内核自启动创造条件,一般情况下,不需要对该函数做改动,但是它调用的函数如sysHwInit则需要。useInit函数在执行时会屏蔽所有硬件中断,它的初始化工作有如下几个:1、初始化Cache:函数的开始段代码初始化了cache(调用函数cachelibInit),在函数结束时,指令及数据寄存器将被默认使能;2、将系统BSS段初始化为0:代码行为bzero(edata,end-edata),很简单,因为C语言将变量初始化的方法就是置0。3、初始化中断向量:异常向量必须在使能中断和启动kernel之前初始化,首先intVecBaseSet函数被调用来确定向量表起始地址,然后,函数excVecInit将所有异常向量初始化为默认句柄,这些句柄就可以捕捉并处理由程序错误引起的异常了;4、初始化系统硬件为安静状态:通过调用sysHwInit初始化硬件。5、调用kernelInit,该函数初始化多任务环境,而且从不返回。它所需要的参数较多,统一放在结构体中,如下:
typedef struct kernelInit_params
{
FUNCPTR rootRtn; /* function to run as tRootTask */
unsigned int rootMemSize; /* memory for TCB and root stack */
char *pMemPoolStart; /* beginning of memory pool */
char *pMemPoolEnd; /* end of memory pool */
unsigned int intStackSize; /* interrupt stack size */
int lockOutLevel; /* interrupt lock-out level (1-7) */
unsigned int vmPageSize; /* configuration's VM_PAGE_SIZE */
unsigned int intStackOverflowSize; /* stack overflow region size */
unsigned int intStackUnderflowSize; /* stack underflow region size */
unsigned int idleTaskExcepStkSize; /* SMP idle task exception stack size */
unsigned int idleTaskExcepStkOverflowSize; /*... overflow region size */
unsigned int idleTaskExcepStkUnderflowSize;/*... underflow region size */
} _KERNEL_INIT_PARAMS;