VxWorks启动之romStart剖析

引言

在VxWorks BSP中,从romInit.s跳转到romStart()那一刻起,我们便开始从汇编乾坤大挪移到C的世界。作为VxWorks BSP中的第一个C函数,它的主要任务是清空内存(BOOT_COLD and BOOT_CLEAR),并将bootloader或VxWorks(如果压缩则先解压)拷贝重定位到RAM,最后将CPU执行权交给usrInit()。对于bootloader,将进入bootCmdLoop串口交互命令界面;对于VxWorks则直接启动内核。
可以说对于romStart()这个承上启下函数的把握,决定我们是否真正理解了VxWorks bootloader的启动机制。继《VxWorks BSP宏梳理》、《VxWorks引导启动过程》梳理了BSP相关宏和各类映像的引导启动流程后,本文将结合各类映像的编译链接生成规则系统地剖析各类romStart()的执行逻辑。

本文涉及的相关代码请到《VxWorks BSP for ixp425》处下载。

1 两类romStart

1.1 可引导型vxWorks映像(vxWorks_rom*)的romStart()

使用target\config\comps\src\romStart.c中定义的romStart(),其中定义了三种类型的romStart()函数原型。那么区分类型的宏ROM_COMPRESS、ROM_COPYROM_RESIDENT到底是在哪里定义的呢?

target\h\make\rules.vxWorks中根据映像类型是vxWorks_romCompress(压缩)、vxWorks_rom(非压缩)或vxWorks_romResident(驻留),将ROM_FLAGS_EXTRA定义为CC_ROM_CMP_FLAGS、CC_ROM_CPY_FLAGS或CC_ROM_RES_FLAGS(target\h\make\defs.vxWorks中定义),其中分别定义了ROM_COMPRESS、ROM_COPYROM_RESIDENT宏。项目wpj文件在romInit.o和romStart.o的BUILDRULE中引入了ROM_FLAGS_EXTRA宏。

1.2 引导加载映像bootloader(bootrom*)的romStart()

使用bootInit.c中定义的romStart()。其中根据宏ROM_RESIDENT和UNCOMPRESS宏区分是bootrom_res(驻留)还是bootorm_uncmp(非压缩)类型;如果两者都没定义,则为bootrom(压缩)类型。

2 链接脚本定义的特殊符号

无论是在romStart.c还是在bootInit.c中定义的romStart(),都牵涉到对binArrayStart、binArrayEnd 、etext、end、   wrs_kernel_data_start、wrs_kernel_data_end等变量的导入引用,从它们的原型看都是char指针类型,可见它们是用于界定地址的边界符号。那么这些变量到底是在哪里定义的呢?

--------------------------------------------------------------
/*romStart.c*/
IMPORTSTATUS   UNCMP_RTN ();
IMPORTvoid     relocEntry ();
IMPORTUCHAR    binArrayStart [];       /* compressed binary image */
IMPORTUCHAR    binArrayEnd [];    /* end of compressed binary image */
IMPORTchar     etext [];               /* defined by the loader */
IMPORTchar     end [];                 /* defined by the loader */
IMPORTchar     wrs_kernel_data_end []; /*defined by the loader */
--------------------------------------------------------------

--------------------------------------------------------------
/*bootInit.c*/
IMPORTUCHAR binArrayStart [];  /* compressed binary image */
IMPORTUCHAR binArrayEnd [];    /* end of compressed binary image */
IMPORTchar etext [];   /* defined by the loader */
IMPORTchar end [];         /* defined by the loader */
IMPORTUCHAR wrs_kernel_data_start [];  /* defined by the loader */
IMPORTUCHAR wrs_kernel_data_end [];    /* defined by the loader */
--------------------------------------------------------------
2.1 链接脚本

在target\h\tool\gnu\defs.gnu中定义了链接生成可引导映像(vxWorks_rom*、bootrom*)和可加载映像(VxWorks)的链接脚本。

LD_SCRIPT_RAM  = -T $(TGT_DIR)/h/tool/gnu/ldscripts/link.RAM

LD_SCRIPT_ROM  = -T $(TGT_DIR)/h/tool/gnu/ldscripts/link.ROM

/*link.ROM*/
SECTIONS
{
    .text     :
    {
        wrs_kernel_text_start = .;_wrs_kernel_text_start = .;
        *(.text) *(.text.*) *(.stub)*(.gnu.warning) *(.gnu.linkonce.t*)

        /*此处省略若干行。。。*/

        . = ALIGN(16);
    }

    . = ALIGN(16);
    wrs_kernel_text_end = .;_wrs_kernel_text_end = .;
    etext = .; _etext = .;
    .data   : AT(etext)
    {
        wrs_kernel_data_start = .;_wrs_kernel_data_start = .;
        *(.data) *(.data.*) *(.gnu.linkonce.d*)SORT(CONSTRUCTORS) *(.data1)

        /*此处省略若干行。。。*/

        . = ALIGN(16);
    }

    . = ALIGN(16);
    edata = .; _edata = .;
    wrs_kernel_data_end = .;_wrs_kernel_data_end = .;
    .bss      :
    {
        wrs_kernel_bss_start = .;_wrs_kernel_bss_start = .;
        *(.sbss) *(.scommon) *(.dynbss) *(.bss)*(COMMON)

        . = ALIGN(16);

    }

    . = ALIGN(16);
    end = .; _end = .;
    wrs_kernel_bss_end = .; _wrs_kernel_bss_end= .;   

    /*此处省略若干行。。。*/

}
通过Merge Diff发现link.ROM和link.RAM仅仅一处不同,link.ROM中定义.data的冒号后面多了一个AT指令:在link.RAM中是“.data    :”,而在link.ROM中则是“.data    :AT(etext)”。

烧写的data和text都在ROM/Flash中,运行后再将data自ROM搬运到RAM中。为节省内存,最好bin文件中两个段紧挨着,以保持烧录文件尽可能小。link.ROM中增加了AT指令来描述其LMA,这样表示.data段的LMA紧接在.text段的后面。这主要针对ld的-Ttext和-Tdata参数不一致的情形

bootrom.Z.s\bootrom(rules.bsp)和bootrom_uncmp(rules.bsp)使用的链接脚本是LD_SCRIPT_RAM。在链接时,这些映像的.data段紧跟在.text段之后,ld时只指定了-Ttext。

在链接时,vxWorks_rom(rules.vxWorks)、vxWorks_romCompress(rules.vxWorks)指定-Ttext和-Tdata不同的地址;驻留型vxWorks_romResident和bootorm_res的.text段驻留在ROM中运行,.data段需要拷贝到RAM中。ld时指定了-Ttext和-Tdata,烧在ROM/Flash中需要紧凑存放,故vxWorks_rom(rules.vxWorks)、vxWorks_romCompress(rules.vxWorks)、vxWorks_romResident(rules.vxWorks)、bootrom_res(rules.bsp)使用的链接脚本是LD_SCRIPT_ROM

2.2 特殊符号

当我们使用ld作为链接器来链接产生可执行文件时(vxWorks_romCompress、bootrom)时,它会为我们定义很多特殊的符号。这些符号并没有在你的程序代码中定义,但是你可以直接声明并且引用它,我们称之为特殊符号

生成vxWorks_romCompress、bootrom,根据链接脚本target\h\tool\gnu\ldscripts\link.ROM(link.RAM)的定义,最后ld出来的vxWorks_rom*映像主要由代码段.text、数据段.data和.bss段构成。同时定义了各个段的起始边界的特殊符号:

--------------------------------------------------------------

.text段起始:wrs_kernel_text_start/_wrs_kernel_text_start;

.text段结束:wrs_kernel_text_end/_wrs_kernel_text_end、etext/_etext;

.data段开始:wrs_kernel_data_start/_wrs_kernel_data_start;

.data段结束:wrs_kernel_data_end/_wrs_kernel_data_end、edata/_edata;

.bss段开始:wrs_kernel_bss_start/_wrs_kernel_bss_start

.bss段结束:wrs_kernel_bss_end/_wrs_kernel_bss_end、end/_end。

--------------------------------------------------------------

其中end为程序结束地址。

可通过“readelf –a”命令查看符号表Symbol table '.symtab',查看以上各符号的值(链接地址,运行域VMA)。

3  C函数入口——romStart

3.1 关于RELOC对romStart的重定位

如《虚拟内存地址VMA、装载内存地址LMA和位置无关代码PIC》所分析,RELOC通过“符号romStart的链接地址 +(标号9的flash地址-标号9的链接地址)”计算出romStart在ROM/Flash中的物理地址。但有时候紧跟在RELOC后还有两句“and t0, ~0xFFF00000; or t0, ROM_TEXT_ADRS;”又折算回RAM中的地址,这个取决于具体映像的启动策略。

3.2 romStart运行位置:ROM or RAM?

对于vxWorks_romCompress映像,romInit.s中在调用romStart()前,可能已经将bootstrap program(包含romInit()、romStart(),压缩/解压函数)若干字节(ROM_COPY_SIZE)拷贝到RAM的ROM_TEXT_ADRS(基于RAM的链接地址,一般比RAM_HIGH_ADRS还高的内存末尾处几十KB)地址处,此时在romInit.s中可以直接“jal romStart”跳转到RAM中执行romStart(),似乎用不着RELOC、and/or这样一波三折?对于压缩型bootrom映像,其romInit.s中可能并没有将包括romStart在内的bootstrap program拷贝到RAM中,此时romStart()仍然驻留在ROM/Flash中执行,在跳转之前必须先RELOC计算出romStart()在ROM/Flash中的地址。

无论如何,要结合具体映像的romInit.s代码分析romStart()到底是运行在ROM还是RAM里面,因为这个运行上下文环境对romStart()内里分析至关重要。例如在bootrom_uncmp和bootrom的romStart()中,为什么调用copyLongs时先进行ROM_OFFSET重定位,后面调用fillLongs和absUncompresss时却无需重定位?欲知其中蹊跷,请见下文分晓。

3.3 romStart的栈空间——sp

这里需要提醒一下的是romStart()在ROM/Flash中执行时的栈空间问题,反正我曾经在这个地方感到过疑惑。MIPS预留了a0~a3四个寄存器参数槽,romInit.s将romStart()的参数startType存储到a0中,romStart()中调用copyLongs()的三个参数可以存储到a0~a2中,但是romStart()和copyLongs()中的局部自动变量还是得在栈中开辟,copyLongs子函数嵌套调用也得使用栈保存恢复ra。无论如何,在进入C函数调用前,需要建立好堆栈。那么堆栈到底是在哪里建立的呢?

跟踪romInit.s代码,在进入romStart()调用前的“la sp,STACK_ADRS-(4*_RTypeSize)“指令即是建立堆栈,sp将指向RAM中的某块空间,例如从RAM_DATA_ADRS开始grow down。也就是说,即使romStart()等函数在ROM/Flash执行,但函数调用仍然是在RAM栈空间中做道场。

关于_STACK_DIR和“STACK_ADRS-(4*_RTypeSize)”,请参考《VxWorks BSP宏梳理》。关于MIPS软件标准(ABI),可参考《MIPS体系结构透视》的11.2 参数传递以及MIPS ABI中的堆栈约定。

3.4 GOT索引——gp

说完sp,再说一说gpgp寄存器作为GOT的指针用来维护全局偏移量表(Global Offset Table,GOT),以便快速访问全局静态变量。相关概念详情可参考《MIPS体系结构透视》的相关章节以及《程序员的自我修养——链接、装载与库》的7.3.3地址无关代码的类型三。

在romInit.s中或许会有这么一句代码”la  gp, _gp",这里的_gp是由编译器定义的,具体来说GOT是在链接时期建立的,隶属于.data数据段,显然gp也是基于RAM的链接地址。bootstrap program中的romStart()等函数调用不涉及全局变量的引用,此时也就未涉及gp的引用,所以即使运行在ROM/Flash也没有什么问题。但是调用解压缩函数UNCMP_RTN()时,涉及全局变量的访问,所以所有类型的romStart()到这一步时已经调用copyLongs完成了重定位,届时gp已经正确地指向了RAM中的GOT。

关于下文提及的usrEntry()和compressedEntry()这两个桩函数以及gp重定位问题,详情参考后文《VxWorks/MIPS运行期的gp重定位》。

3.5 romStart核心机能

首先,romStart()到底运行在哪里?在bootloader启动之初,必须明确每个调用的函数所在的位置(ROM or RAM),这对上下文分析尤为重要。

其次,romStart()中涉及链接特殊符号的引用和大量的宏。这些符号和宏的定义往往隐藏较深,我们需要跟踪整个make->compile->link流程,在代码和脚本中追根溯源。如《Understanding the bootrom image - Debugging tips for vxWorks Bootrom images》所述,我们可以通过重定向手段“make ADDED_CFLAGS=-E romStart.o > romStart.i”来查看预编译生成的*.i文件,观察验证宏替换后的数值。

最后,romStart()的核心功能是对相关段实行拷贝重定位,如果段是压缩的则需要解压缩重定位,如此铺垫启动下一步的bootloader或vxWorks内核。romStart() copies the appropriate sections  of ROM into RAM. If the code in ROM is compressed, it is decompressed during the copy. Next, the processor jumps to the bootloader orVxWorks entrypoint in RAM.

拷贝时需要明确特殊符号对段(section)的边界界定,解压缩时需要明确压缩前的链接地址以便解压到正确的地址。那么,各个段的边界在哪里?各个段需要被重定位到哪里?解压缩到哪里?这些涉及到编译链接和地址重定位问题。在下文的分析中,我将首先挖掘编译链接脚本中的相关宏定义,然后厘清链接生成各个映像各部分时的-Ttext/-Tdata指定以及映像组成,最后顺理成章地阐述函数的执行逻辑。

3.6 romStart()->usrInit()

第一类romStart()对应的vxWorks_rom*中的VxWorks内核入口点为usrConfig.c或prjConfig.c中的usrInit(),如果是非驻留型vxWorks_rom(Compress)则通过usrEntry.c中的桩函数usrEntry()导入usrInit()。

第二类romStart()对应的bootloader的入口点为bootConfig.c中的usrInit(),如果是压缩型bootrom,则通过桩函数compressedEntry()导入usrInit()。至于bootload进来的loadable vxWorks内核入口点同样为usrConfig.c或prjConfig.c中的usrInit(),不过是由sysALib.s中的sysInit()跳转过来的。

4 第一类romStart(romStart.c)

4.1 vxWorks_romCompress(ROM_COMPRESS)

在这种映像中,romInit.s中在跳转到romStart()时已经将包括romStart()、copyLongs()、解压函数等在内的bootstrap program拷贝到RAM中,所以romStart()->copyLongs()/UNCMP_RTN()都是直接在RAM中的调用。但是,bootstrap和压缩/解压缩函数的数据段、以及压缩映像(由binArrayStart/binArrayEnd界定)一般还没有拷贝到RAM中。因此,romStart()在调用解压函数UNCMP_RTN()之前需要将相关数据段拷贝重定位到RAM中,UNCMP_RTN()的解压源(第一个参数)也需要确定在ROM/Flash中的地址。

#ifdef	ROM_COMPRESS
/*******************************************************************************
*
* romStart - generic ROM initialization for compressed images
*
* This is the first C code executed after reset.
*
* This routine is called by the assembly start-up code in romInit().
* It clears memory, copies ROM to RAM, and invokes the uncompressor.
* It then jumps to the entry point of the uncompressed object code.
*
* RETURNS: N/A
*/

void romStart
    (
    FAST int startType		/* start type */
    )
    {
    volatile FUNCPTR absEntry = (volatile FUNCPTR)RAM_DST_ADRS;

    /* relocate the data segment of the decompression stub */

    copyLongs (ROM_DATA_ADRS, (UINT *)UNCACHED(RAM_DATA_ADRS),
              ((UINT)binArrayStart - (UINT)RAM_DATA_ADRS) / sizeof (long));

    copyLongs ((UINT *)((UINT)ROM_DATA_ADRS + ((UINT)BINARRAYEND_ROUNDOFF -  
        (UINT)RAM_DATA_ADRS)), (UINT *)UNCACHED(BINARRAYEND_ROUNDOFF),
	((UINT)wrs_kernel_data_end - (UINT)binArrayEnd) / sizeof (long));

    /* If cold booting, clear memory to avoid parity errors */

#ifdef	ROMSTART_BOOT_CLEAR
    if (startType & BOOT_CLEAR)
	bootClear();
#endif

    /* decompress the main image */

    if (UNCMP_RTN (ROM_DATA(binArrayStart),
		UNCACHED(RAM_DST_ADRS),
		binArrayEnd - binArrayStart) != OK)
	return;

    /* and jump to it */

    absEntry (startType);
    }
#endif	/* ROM_COMPRESS */
(1)链接-Ttext和-Tdata

rules.vxWorks中ld vxWorks_romCompress时指定了$(LD_ROM_CMP_FLAGS):

LD_ROM_CMP_FLAGS= $(ROM_LDFLAGS) $(RES_LDFLAGS)-Ttext $(ROM_LINK_ADRS) -Tdata $(RAM_HIGH_ADRS)

其中ROM_LINK_ADRS默认为ROM_TEXT_ADRS(下同)。

rules.vxWorks中ld vxWorks(需要压缩部分)时指定了$(LD_RAM_FLAGS),defs.gnu中定义了LD_RAM_FLAGS:

LD_RAM_FLAGS    = -Ttext $(RAM_LOW_ADRS)

在rules.vxWorks中有两句注释:

#    The relocation stub is linked to ROM_TEXT_ADRS and RAM_HIGH_ADRS.
#    The main image is linked to RAM_LOW_ADRS.

“The relocation stub”即未压缩的bootstrap program,“The main image”此处指vxWorks_romCompress中的vxWorks,在下文的bootrom语境中则是指除bootstrap program外的bootloader部分和后续的Loadable VxWorks映像。

(2)ROM_DATA_ADRS

/*toolchain dependant location of the data segmentwithin ROM */

#ifndef  ROM_DATA_ADRS

#   define   ROM_DATA_ADRS   (UINT *)(etext)

#endif

ROM_DATA_ADRS为基于ROM/Flash的地址?

(3)RAM_DST_ADRS/RAM_DATA_ADRS

在defs.vxWorks中定义了CC_ROM_CMP_FLAGS宏:

CC_ROM_CMP_FLAGS=-DROM_COMPRESS -DRAM_DATA_ADRS=0x$(RAM_HIGH_ADRS)-DRAM_DST_ADRS=0x$(RAM_LOW_ADRS)

(4)函数解析

在不熟悉具体的编译链接过程时,我曾在romStart两个copyLongs这里纠结了很久,不知道它们到底要拷贝啥。实际上所有类型的romStart()中调用的copyLongs()无一例外都是将代码或数据从ROM/Flash(source)拷贝到RAM(destination)实现重定位,至于具体拷贝多少(nLongs)则由链接时产生的那些特殊符号变量来界定欲拷贝重定位的代码段和数据段的长度。下面具体来分析一下romStart()中的两个copyLongs()到底拷贝了啥。

第一个copyLongs()将bootstrap的数据段拷贝到RAM_DATA_ADRS(RAM_HIGH_ADRS)。实际上romStart.o中并无数据段,readelf或objdump查看符号可知wrs_kernel_data_start=binArrayStart在一个地址上,即压缩的subimage构成数据段的第一部分,data1中并无内容。

紧随subimage(由binArrayStart/binArrayEnd界定)之后的是压缩/解压函数(decompression stub)的数据段,存放inflate()/deflate()等函数涉及到的初始化了的全局静态变量和局部静态变量,即图示的data2部分。

此时,压缩/解压函数(decompression stub,属于bootstrap的一部分)的代码已经在romInit.s中拷贝到RAM中。其对应的数据段data2必须从ROM拷贝到对应的RAM位置(运行期VMA地址),压缩/解压函数才能正确运行,否则可能跑飞。第二个copyLongs()即实现了这种重定位(relocation)。

紧随数据段.data之后的是.bss段,存放inflate()/deflate()等函数涉及到的未初始化的全局静态变量和局部静态变量(link.ROM中已经将COMMON块合并到.bss段),边界衔接表现为wrs_kernel_bss_start=wrs_kernel_data_end。

   ROM

    --------------   0xffA00000  = ROM_BASE_ADRS+ROM_SIZE

    |                 |

    |--------------|  0xff8xxxxx  ->(wrs_kernel_data_end)

    |   data2      |

    |--------------|  0xff8xxxxx  ->binArrayEnd

    | subImage  |

    |--------------|  0xff8xxxxx  ->binArrayStart

    |   data1      |

    |--------------|  0xff8xxxxx -> ROM_DATA_ADRS (wrs_kernel_data_start)

    |   text         |

    |                 |  0xff800008  -> ROM_TEXT_ADRS

    --------------   0xff800000 -> ROM_BASE_ADRS

UNCMP_RTN()函数实现将ROM中从binArrayStart开始到binArrayEnd的压缩映像subimage解压缩到RAM_DST_ADRS。为什么要解压到RAM_DST_ADRS呢?结合上下文,RAM_DST_ADRS即ld链接生成VxWorks时由-Ttext指定的RAM_LOW_ADRS,解压到这里也算是一种重定位。接下来跳转到该处启动VxWorks(usrEntry()->usrInit())。

我们前面提到,在此类映像(包括后面的vxWorks_rom)的romInit.s在调用romStart()前,已经将bootstrap program(包含romInit()、romStart(),压缩/解压函数)若干字节(ROM_COPY_SIZE)拷贝到RAM的ROM_TEXT_ADRS(基于RAM的链接地址,一般比RAM_HIGH_ADRS还高的内存末尾处几十KB)地址处。vxWorks内核启动运行后,引导部分使命结束,这段高端内存空间可被覆盖回收利用。当然,暂存数据段的RAM_HIGH_ADRS内存空间也可被覆盖回收利用。

宏ROM_DATA定位了binArrayStart在ROM/Flash中的位置,如《VxWorks BSP宏梳理》,如果ROM_BASE_ADRS/ROM_TEXT_ADRS/ROM_DATA_ADRS为基于RAM的linked address,可能要做相应的地址调整!

完成了这种映像的romStart()分析之后,下面其他类型的romStart()调用流程大同小异。

4.2 vxWorks_rom(ROM_COPY)

这种映像同vxWorks_romCompress相比,唯一少了个compress,即binArrayStart/binArrayEnd界定的是未压缩的vxWorks映像。当然,这里的romStart()同样运行在RAM中。

#ifdef	ROM_COPY
/*******************************************************************************
*
* romStart - generic ROM initialization for uncompressed ROM images
*
* This is the first C code executed after reset.
*
* This routine is called by the assembly start-up code in romInit().
* It clears memory, copies ROM to RAM, and then jumps to the entry
* point of the copied object code.
*
* RETURNS: N/A
*/

void romStart
    (
    FAST int startType		/* start type */
    )
    {
    volatile FUNCPTR absEntry = (volatile FUNCPTR)RAM_DST_ADRS;

    /* If cold booting, clear memory to avoid parity errors */

#ifdef ROMSTART_BOOT_CLEAR
    if (startType & BOOT_CLEAR)
        bootClear();
#endif

    /* copy the main image into RAM */

    copyLongs ((UINT *)ROM_DATA(binArrayStart),
		(UINT *)UNCACHED(RAM_DST_ADRS),
		(binArrayEnd - binArrayStart) / sizeof (long));

    /* and jump to it */

    absEntry (startType);
    }
#endif	/* ROM_COPY */

(1)链接-Ttext和-Tdata

同vxWorks_romCompress。

(2)RAM_DST_ADRS/RAM_DATA_ADRS

同vxWorks_romCompress。

(3)函数解析

分析rules.vxWorks中vxWorks_rom映像的编译链接规则可知,同vxWorks_romCompress的做法,binToAsm还是将bootloader中除bootstrap外的代码(即未压缩的VxWorks)界定到binArrayStart/binArrayEnd中作为数据段。

没有了解压缩等过程,copyLongs()直接将ROM中从binArrayStart开始到binArrayEnd的VxWorks映像拷贝到RAM_DST_ADRS(基于RAM的link address,ld生成vxWorks时由-Ttext指定为RAM_LOW_ADRS),然后跳转到该处启动VxWorks(usrEntry()->usrInit())。同vxWorks_romCompress映像,vxWorks内核启动运行之后,高端内存空间可被覆盖回收利用。

疑问:这里也许由于bootstrap代码(copyLongs函数)没有代码段,所以没有执行像vxWorks_romCompress中那样的数据段拷贝到RAM_HIGH_ADRS的过程?

4.3 vxWorks_romResident(ROM_RESIDENT)

既然是驻留,所有的代码(函数调用)都是在ROM/Flash中运行的。如下文分析在链接时-Ttext指定的即时ROM_BASE_ADRS(0x9fc00000),即代码段的LMA和VMA一致。代码段中涉及的函数符号地址已经是基于ROM/Flash的了,无需再RELOC/ROM_OFFSET定位各个函数在ROM/Flash中的地址。当然这里的bootstrap非常干净,没有数据段,所以在bootstrap中(进入vxWorks内核调用之前)不涉及数据段重定位。但是,romStart()在即将调用usrInit()启动内核之前,必须将内核代码的数据段(.data和.bss)重定位到RAM中。

#ifdef	ROM_RESIDENT
/*******************************************************************************
*
* romStart - generic ROM initialization for ROM resident images
*
* This is the first C code executed after reset.
*
* This routine is called by the assembly start-up code in romInit().
* It clears memory, copies ROM to RAM, and invokes the uncompressor.
* It then jumps to the entry point of the uncompressed object code.
*
* RETURNS: N/A
*/

void romStart
    (
    FAST int startType		/* start type */
    )
    {
    /* relocate the data segment into RAM */

    copyLongs ((UINT *)ROM_DATA_ADRS, (UINT *)UNCACHED(RAM_DATA_ADRS),
		((UINT)end - (UINT)RAM_DATA_ADRS) / sizeof (long));

    /* If cold booting, clear memory to avoid parity errors */

#ifdef ROMSTART_BOOT_CLEAR
    if (startType & BOOT_CLEAR)
        bootClear();
#endif

    /* and jump to the entry */

    usrInit (startType);
    }
#endif	/* ROM_RESIDENT */
(1)链接-Ttext和-Tdata

rules.vxWorks中ld vxWorks_romResident时指定了$(LD_ROM_RES_FLAGS):

LD_ROM_RES_FLAGS= $(ROM_LDFLAGS) $(RES_LDFLAGS) -Ttext $(ROM_LINK_ADRS) -Tdata $(RAM_LOW_ADRS)

(2)RAM_DST_ADRS/RAM_DATA_ADRS

在defs.vxWorks中定义了CC_ROM_RES_FLAGS宏:

CC_ROM_RES_FLAGS= -DROM_RESIDENT-DRAM_DATA_ADRS=0x$(RAM_LOW_ADRS)  -DRAM_DST_ADRS=0x$(RAM_LOW_ADRS)

(3)函数解析

代码段驻留在ROM中运行,copyLongs()将ROM中从ROM_DATA_ADRS处开始的.data数据段和.bss段(参数三从-Tdata RAM_DATA_ADRS开始,到end结束)拷贝到RAM_DATA_ADRS(RAM_LOW_ADRS)处,然后跳转到ROM中的usrInit()处启动VxWorks。

5 第二类romStart(bootInit.c)

由于涉及到bootrom类型和CPU体系架构两层宏包围,所以romStart代码看起来非常混乱。以下对代码做了简单整理,注释中分别标出了bootrom_res、bootrom_uncmp和bootrom三种类型。

如无特殊说明,本文示例代码中涉及体系架构的分流均针对MIPS,即CPU_FAMILY==MIPS,具体来说是指MIPS32。下面分析时以MIPS体系架构为准。

/*如若未定义ROM_RESIDENT或ROM_UNCOMPRESS,即为压缩型bootloader(bootrom.bin)*/
void romStart
    (
    FAST int startType		/* start type */
    )
{
#ifdef ROM_RESIDENT /*bootrom_res*/
    /* If ROM resident code, then copy only data segment
     * from ROM to RAM, initialize memory and jump
     * to usrInit.
     */
    #if  (CPU_FAMILY == SPARC)
	    copyLongs ((UINT *)(etext + 8), (UINT *) RESIDENT_DATA,
	#else /*MIPS*/
	    copyLongs ((UINT *)etext, (UINT *) RESIDENT_DATA,
	#endif
	        ((UINT) wrs_kernel_data_end - (UINT) RESIDENT_DATA) / sizeof (long));
#else	/* ROM_RESIDENT */
	#ifdef UNCOMPRESS /*bootrom_uncmp*/
		#if	(CPU_FAMILY == MIPS)
		    /*
		     * copy text to uncached locations to avoid problems with
		     * copy back caches
		     */
		    ((FUNCPTR)ROM_OFFSET(copyLongs)) (ROM_TEXT_ADRS, (UINT)K0_TO_K1(romInit),
				ROM_COPY_SIZE / sizeof (long));
		#else	/* CPU_FAMILY == MIPS */
		    ((FUNCPTR)ROM_OFFSET(copyLongs)) (ROM_TEXT_ADRS, (UINT)romInit,
				ROM_COPY_SIZE / sizeof (long));
		#endif	/* CPU_FAMILY == MIPS */
	#else	/* UNCOMPRESS */  /*bootrom*/
		#if	(CPU_FAMILY == MIPS)
	    /*
	     * copy text to uncached locations to avoid problems with
	     * copy back caches
	     * copy the entire data segment because there is no way to ensure that
	     * binArray is the last thing in the data segment because of GP relative
	     * addressing
	     */
	    ((FUNCPTR)ROM_OFFSET(copyLongs)) (ROM_TEXT_ADRS, (UINT)K0_TO_K1(romInit),
			((UINT)wrs_kernel_data_end - (UINT)romInit) / sizeof (long));
		#else	/* CPU_FAMILY == MIPS */
			/*此处省略若干行...*/
			#if (CPU==XSCALE)
			#endif
		#endif	/* CPU_FAMILY == MIPS */
	#endif	/* UNCOMPRESS */
#endif	/* ROM_RESIDENT */

#if	(CPU_FAMILY != MIPS) && (!defined (BOOTCODE_IN_RAM))
    /* clear all memory if cold booting */
    if (startType & BOOT_CLEAR)
	{
		/*此处省略若干行...*/
	}
#endif	/* (CPU_FAMILY != MIPS) && (!defined (BOOTCODE_IN_RAM)) */

    /* jump to VxWorks entry point (after uncompressing) */

#if	defined (UNCOMPRESS) || defined (ROM_RESIDENT) /*bootrom_uncmp or bootrom_res*/
	#if	(CPU_FAMILY == I960)
	    absEntry = (FUNCPTR)sysInitAlt;			/* reinit proc tbl */
	#else
	    absEntry = (FUNCPTR)usrInit;			/* on to bootConfig */
	#endif	/* CPU_FAMILY == I960 */
#else /*bootrom*/
    {
	#if	(CPU_FAMILY == MIPS)
	    volatile FUNCPTR absUncompress = (FUNCPTR) UNCMP_RTN; /*先执行解压*/
	    if ((absUncompress) ((UCHAR *)ROM_OFFSET(binArrayStart),
				 (UCHAR *)K0_TO_K1(RAM_DST_ADRS),
				 (int)((UINT)binArrayEnd - (UINT)binArrayStart)) != OK)
	#elif	(CPU_FAMILY == I80X86) || (CPU_FAMILY == ARM)
	    volatile FUNCPTR absUncompress = (FUNCPTR) UNCMP_RTN;
	    if ((absUncompress) ((UCHAR *)ROM_OFFSET(binArrayStart),
		            (UCHAR *)RAM_DST_ADRS, binArrayEnd - binArrayStart) != OK)
	#else
	    if (UNCMP_RTN ((UCHAR *)ROM_OFFSET(binArrayStart),
		            (UCHAR *)RAM_DST_ADRS, binArrayEnd - binArrayStart) != OK)
	#endif	/* (CPU_FAMILY == MIPS) */
		return;		/* if we return then ROM's will halt */
	
	    absEntry = (FUNCPTR)RAM_DST_ADRS;			/* compressedEntry () */
	    }
#endif	/* defined UNCOMPRESS || defined ROM_RESIDENT */

#if	((CPU_FAMILY == ARM) && ARM_THUMB)
    absEntry = (FUNCPTR)((UINT32)absEntry | 1);		/* force Thumb state */
#endif	/* CPU_FAMILY == ARM */

    CLED(0x7);
    (absEntry) (startType);
}
5.1 bootorm_res(ROM_RESIDENT)

同vxWorks_romResident映像,这里的romStart及其他函数调用都是存放并运行于ROM/Flash,代码段的LMA和VMA一致。

(1)链接-Ttext和-Tdata

rules.bsp中ld bootrom_res时指定了$(RES_HIGH_FLAGS),defs.gnu中定义了RES_HIGH_FLAGS:

RES_HIGH_FLAGS      = -Ttext $(ROM_LINK_ADRS) -Tdata $(RAM_HIGH_ADRS)

(2)函数解析

此时,链接地址ROM_TEXT_ADRS与实际的ROM_PHY_ADDR(0x1FC00000)重合,代码驻留在ROM/Flash中运行,故copyLongs函数符号地址本身基于ROM/Flash,无需ROM_OFFSET重定位。

#define    RESIDENT_DATA    RAM_DST_ADRS /*RAM_HIGH_ADRS*/

copyLongs函数将从ROM/Flash中etext处开始存放的.data数据段拷贝到RAM中的RESIDENT_DATA(RAM_HIGH_ADRS)处。RESIDENT_DATA即ld时指定的-Tdata(RAM_HIGH_ADRS),wrs_kernel_data_end为数据段结束,它们都是基于RAM的link address,相差即为数据段的实际大小。

后面absEntry = (FUNCPTR)usrInit;指定了入口,最后调用(absEntry) (startType);跳转到该入口处执行。bootloader的Stage2产生一个命令行交互任务bootCmdLoop,autoboot或者bootload完成时bootloader的使命结束,vxWorks内核被加载到RAM_LOW_ADRS处启动运行之后,RAM_HIGH_ADRS可被覆盖回收利用。

5.2 bootrom_uncmp(UNCOMPRESS)

bootrom_uncmp与vxWorks_rom不同之处在于,ld链接bootrom_uncmp时,没有使用binToAsm工具,romStart()拷贝的是整个bootloader。

(1)链接-Ttext和-Tdata

rules.bsp中ld bootrom_uncmp时指定了$(LD_HIGH_FLAGS),defs.gnu中定义了LD_HIGH_FLAGS:
LD_HIGH_FLAGS       = -Ttext $(RAM_HIGH_ADRS)

这里没有特别指明-Tdata,就说明.data数据段紧随.text之后。

(2)函数解析

#define    ROM_COPY_SIZE    (ROM_SIZE - (ROM_TEXT_ADRS - ROM_BASE_ADRS))

行为过程可类比第一类第二种vxWorks_rom的romStart()。将ROM/Flash中从ROM_TEXT_ADRS开始到末尾的全部内容(长度为ROM_COPY_SIZE)拷贝到romInit处(基于RAM的link address,ld生成bootrom_uncmp时由-Ttext指定为RAM_HIGH_ADRS)。

同vxWorks_rom映像,这里的romStart及其他函数-Ttext指定链接到RAM,但是romStart()及copyLongs()在ROM/Flash中存放运行,因此需要romInit.s/RELOC、romStart()/ROM_OFFSET分别重定位romStart和copyLongs在ROM/Flash中的地址。ROM_OFFSET(copyLongs)调用之后,fillLongs、absUncompress等函数已经重定位到RAM,因此后面直接调用fillLongs()等函数。

后面absEntry = (FUNCPTR)usrInit;指定了入口,最后调用(absEntry) (startType);跳转到该入口处执行。bootloader的Stage2产生一个命令行交互任务bootCmdLoop,autoboot或者bootload完成时bootloader的使命结束,vxWorks内核被加载到RAM_LOW_ADRS处启动运行之后,RAM_HIGH_ADRS可被覆盖回收利用。

5.3 bootrom(not ROM_RESIDENT or UNCOMPRESS

这种映像同bootrom_uncmp相比,唯一少了个uncmp,即是压缩的。bootloader除bootstrap program部分外被压缩到binArrayStart/binArrayEnd定界的数据段中。当然,这里的romStart()同样运行在ROM/Flash中。

(1)链接-Ttext和-Tdata

rules.bsp中ld bootrom时指定了$(LD_LOW_FLAGS),defs.gnu中定义了LD_LOW_FLAGS:

LD_LOW_FLAGS        = -Ttext $(RAM_LOW_ADRS)

这里没有特别指明-Tdata,就说明.data数据段紧随.text之后。
rules.bsp中ld bootrom.Z.s时指定$(LD_HIGH_FLAGS) bootConfig.o,这正是bootrom.bin解压缩重定位到RAM中的地址,也即compressedEntry的地址。

(2)函数解析

行为过程可类比第一类第一种vxWorks_romCompress的romStart()。

等到ROM_OFFSET(copyLongs)在ROM中执行完,将ROM/Flash中从ROM_TEXT_ADRS开始到数据段结束(wrs_kernel_data_end)的内容拷贝到romInit处(基于RAM中的link address地址,ld生成bootrom时由-Ttext指定为RAM_LOW_ADRS。此时还没有加载vxWorks映像,可先借用此空间)。

ROM中的非压缩部分及其数据段已经完全拷贝到RAM中了,接下来直接调用RAM中的fillLongs(if BOOT_CLEAR)和解压缩函数absUncompress(UNCMP_RTN),将bootrom中由binArrayStart/binArrayEnd界定的压缩部分解压到RAM_DST_ADRS处(ld生成bootrom.Z.s时-Ttext指定为RAM_HIGH_ADRS)。

后面absEntry = (FUNCPTR)RAM_DST_ADRS;指定了入口,最后调用(absEntry) (startType);跳转到该入口处执行compressedEntry()->usrInit()。bootloader的Stage2产生一个命令行交互任务bootCmdLoop,autoboot或者bootload完成时bootloader的使命结束,vxWorks内核被加载到RAM_LOW_ADRS处,覆盖掉了原来此处暂存的bootstrap program。同时,vxWorks内核启动运行之后,原来存放bootloader的RAM_HIGH_ADRS处可被覆盖回收利用。


说明:

本文中涉及的target底下的bootInit.c/romStart.c代码和make/ld脚本文件,均源自《VxWorks BSP for ixp425》。

 

参考:

《VxWorks程序开发实践》

2.4 编译器

3.5 Linker和Loader

《VxWorks内核、设备驱动与BSP开发详解》

2.3 了解MakeFile

4.3 编译选项

《Tornado BSP Training Workshop》

04_preKernelInit & 05_preKernelBoot

《程序员的自我修养——链接、装载与库》

3.5 链接的接口——符号

4.6 链接过程控制

7.3.3 地址无关代码(GOT)

《MIPS体系结构透视》


《VxWorks架构》

《ROM_OFFSET的纳闷》

《ROM_OFFSET的疑问》


《romStart代码的小记》

《VxWorks启动代码romStart()函数分析》

《试论bootrom的拷贝地址》

《vxWorks bsp分析之bootInit.c》

《romInit和romStart两个函数编译和链接后的地址》

《Tornado下的Make文件简述——脱离Tornado编译vxWorks》


《源代码级调试romStart() in bootInit.c》

《DRAM test in romStart() routine in bootInit.c》

《external functions not visible in romMipsInit.s》

《What is macro RELOC(defined in romMipsInit.s) used for?》

《Understanding the bootrom image - Debugging tips for vxWorks Bootrom images》

你可能感兴趣的:(romStart,bootInit.c,rules.VxWorks,link.RAM,romStart.c)