BSP用于初始化硬件、引导操作系统并提供软件和硬件之间的设备驱动接口,针对某类体系结构的处理器开发BSP时,了解其基本的体系结构和指令系统是必要的。一般来说,BSP的设计与开发可分为几个步骤:
- 建立开发环境,这个不用说了,就是装集成环境;
- 选择合适的BSP模板,要尽可能的与硬件平台相近;
- 修改或添加wind内核激活前的初始化代码,例如初始化CPU内核、MMU、Cache禁止/使能等;
- 内核激活后,连接系统中断、系统时钟,修改或添加所需的设备驱动程序;
- 测试与验证,BSP的正确性与稳定性对上层软件和整个系统的稳定起着至关重要的作用,因此BSP完成后要经过测试验证。
开发BSP过程中最主要的三个程序分别为:
- romInit.s中的romInit()函数,用于初始化CPU及内存;
- sysLib.c中的sysHwInit2()函数,用于将所有板上硬件初始化为静止状态;
- 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()
该函数位于usrConfig.c中,它是从vxWorks镜像启动的第一个C程序,这段程序的主要任务是完全初始化CPU,关闭其他硬件,进而为内核自启动创造条件,一般情况下,不需要对该函数做改动,但是它调用的函数如sysHwInit则需要。useInit函数在执行时会屏蔽所有硬件中断,它的初始化工作有如下几个:
- 初始化Cache:函数的开始段代码初始化了cache(调用函数cachelibInit),在函数结束时,指令及数据寄存器将被默认使能;
- 将系统BSS段初始化为0:代码行为bzero(edata,end-edata),很简单,因为C语言将变量初始化的方法就是置0。
- 初始化中断向量:异常向量必须在使能中断和启动kernel之前初始化,首先intVecBaseSet函数被调用来确定向量表起始地址,然后,函数excVecInit将所有异常向量初始化为默认句柄,这些句柄就可以捕捉并处理由程序错误引起的异常了;
- 初始化系统硬件为安静状态:通过调用sysHwInit初始化硬件。
- 调用kernelInit,该函数初始化多任务环境,而且从不返回。
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;
五、执行kernelInit()
该函数是由风河vxWorks库文件封装的,功能为提取kernel并运行,使得后期的初始化能够以kernel下运行的任务来完成。该任务名为tRootTask,它执行的方式类似于usrRoot。kernelInit先调用intLockLevelSet函数,屏蔽round-robin scheduling模式,并创建中断堆栈。然后从内存池顶端产生root堆栈及TCB(任务控制块),产生root线程usrRoot,并终止线程usrInit。至此,中断被使能。
六、以任务形式执行usrRoot
usrRoot将完成内核及所有硬件的初始化(网络、驱动、I/O等),然后执行其他程序代码。它首先会调用memInit函数(可以选择memShowInit或usrMmuInit),一旦系统开始多任务执行,BSP会调用第一个函数sysClkConnect,该函数马上又调用sysHwInit2,此函数完成sysHwInit中未完成的所有板件初始化工作,例如用intConnect建立中断源的连接。接着usrRoot继续时钟的初始化,它会修改SYS_CLK_RATE来设置时钟默认频率(通常为60),然后调用sysClkEnable使能时钟。
接下来通过图形简化介绍启动方式,由于vxWorks的有多种程序映像类型,主要可以分成两类:BootRom类型和vxWorks类型,其中,BootRom类型映像是一个最小化、专用的vxWorks引导映像,实现最少的系统初始化,主要用于启动装载vxWorks映像,其功能类似于PC机的BIOS。BootRom运行时也建立起多任务环境,包括usrRoot任务、网络任务、TFFS任务和FTP任务等。引导映像在运行时,可能在ROM/Flash中执行(例如ROM驻留型引导映像),也可能在RAM 中执行,在系统中其对应的编译规则文件是rules.bsp,它的启动流程图如下:
而vxWorks类型映像为系统的主映像,也即系统最终要运行的映像。该映像在运行时至少有一部分(如数据段和BSS段)需要在RAM中运行。在系统中其对应的编译规则文件是rules.vxWorks,它的启动流程图如下:
BSP文件中sysLib.c是最大的,在BSP开发初期建议只实现基本功能,包括sysModel()、sysBspRev()、sysHwInit()、sysHwInit2()和sysMemTop()。