1 总体概述
romInit.s初始化程序的目标是使CPU按照用户要求工作并使得板子的FLASH(ROM)可读,RAM可读可写,从而可用把镜像文件从FLASH中拷贝到RAM中运行。
romInit.s 文件为一个汇编语言文件,所以具有汇编程序的分段式特点。在学习单片机时,汇编程序一般都有数据段、程序段及堆栈段,同样对于POWERPC的汇编程序而言也分为为这几段。
在romInit.s中可用发现共定义了两个段分别为数据段和程序段。数据段从.data伪指令开始,主要做的工作有:包含头文件;定义常量;声明一些符号等。程序段从.text开始,这是初始化程序的起始部分。
2 数据段(.data)
/* romInit.s - Motorola 860mbx ROM initialization module */
/* Copyright 1984-1998 Wind River Systems, Inc. */
/* Copyright 1997,1998 Motorola, Inc. All Rights Reserved */
.data
// 定义数据段。
.globl copyright_wind_river
.long copyright_wind_river
// 申明(declare)全局变量_copyright_wind_river并使用它定义一个新变量。
// 注意“.globl”是申明而非定义。“_copyright_wind_river”变量在Tornado的库(对于MPC860,为[Tornado]/target/lib/libPPC860gnuvx.a)中的一个模块copyright.o中定义。
// “.long”定义一个32-bit的全局变量(没有变量名),变量的初始值为_copyright_wind_river的值(还是地址?)。由于在Makefile中规定了romInit.o为第一个链接的模块,所以这个无名变量将出现在数据段的最开始。
// 不清楚_copyright_wind_river的用途,以及这个无名变量的用途。
#define _ASMLANGUAGE
// 定义_ASMLANGUAGE。GNU汇编器GAS看到这个定义后,会按照C的语法进行预处理,所以GAS预处理器能够认识C头文件中定义的类型和宏。如果不定义_ASMLANGUAGE,以下的#include语句将无法编译。
#include "vxWorks.h"
#include "asm.h"
#include "cacheLib.h"
#include "config.h"
#include "regs.h"
#include "sysLib.h"
#include "drv/multi/ppc860Siu.h"
.globl _romInit
.globl romInit
.extern romStart
.extern mbxI2cConfigParamsGet
.extern mbxI2cMemcConfig
3 程序段(.text)
3.1 程序入口
.text
.align 2
/*
* romInit - entry point for VxWorks in ROM
*/
_romInit:
romInit:
// 同时定义_romInit和romInit的原因是,有些编译器产生的对外部符号的调用不加下划线,而有些加。
bl cold
bl start
// 若是热启动,则跳转到start。激活热启动的代码将跳转到romInit+4处。注意PowerPC的所有指令都占4字节(32位)。
/* copyright notice appears at beginning of ROM (in TEXT segment) */
.ascii "Copyright 1984-1997 Wind River Systems, Inc."
// 以上定义的版权申明字符串将出现在代码段中。
// 不清楚这个字符串的作用。
.align 2
cold:
li r3, BOOT_COLD /* r3 = BOOT_COLD */
lis r4, HIADJ(start)
addi r4, r4, LO(start) /* r4 = @start */
lis r5, HIADJ(romInit)
addi r5, r5, LO(romInit) /* r5 = @romInit */
lis r6, HIADJ(ROM_TEXT_ADRS)
addi r6, r6, LO(ROM_TEXT_ADRS) /* r6 = ROM_TEXT_ADRS */
sub r4, r4, r5 /* r4 = r4 – r5 */
add r4, r4, r6 /* r4 = r4 + r6 */
mtspr LR, r4
blr /* jump to flash mem adress */
// 计算start在Flash中的位置,为:start-romInit+ROM_TEXT_ADRS。并跳转到这个位置(下面)。为什么要这样?
// 由附录1可知,CPU从0xFFF00100或0x00000100启动,而在BR0和OR0未被初始化之前,0xFFF00100或0x00000100就指向Flash的偏移0x100处。这是起始状态,但不可能真的就把全部4GB空间都给Flash,在以后的初始化中,将会把4G空间分配给ROM,RAM,IMMR和外设等也要映射进来。分配地址的方法是写ORx和BRx寄存器。当然一般首先确定ROM的地址分配。但是此时会出现问题:如果在写BR0或OR0完毕时,CPU的程序计数器(PC)还在从0xFFF00100+offset或0x00000100+offset取指令,而Flash已经被映射到0xFE000000,那么程序必定跑飞掉。所以在写BRO和OR0之前,就应把程序计数器(PC)调整到准备分配的地址空间0xFE000000范围内。然而PC指针对程序员是不可见的,但用跳转指令可以修改它。此时跳转的目标地址就是start-romInit+ROM_TEXT_ADRS。ROM_TEXT_ADRS在config.h中定义,它就是FLASH被映射到的位置。通过跳转到这个地址,我们可以实现PC程序计数器指向FLASH真正的地址空间。
3.2 POWERPC CORE初始化
POWERPC CORE的初始化主要是对其一些与POWERPC CORE 有关的寄存器进行赋值,主要涉及到的寄存器如下:
MSR machine state register =>清零
DER debugger enable register =>清零
ICR intrupt control register =>清零
ICTRL instruction support control register =>0x00000007
IC_CST instruction cache configure state register
=>CACHE_CMD_DISABLE
=>CACHE_CMD_UNLOCK_ALL
=>CACHE_CMD_INVALIDATE
DC_CST dada cache configure state register
=>同上
3.3 SIU初始化
SIU是时钟,扩展外部存储器,系统保护与配置等控制中心。
1 IMMR
internal memory map register =>INTERNAL_MEM_MAP_ADDR
2 SIU自身
SIUMCR siu module configure register
3 时钟部分
PLPRCR PLL and Reset Control Register
SCCR System Clock Control Register
TBSCR time base state and control register
PISCR periodic interrupt status and control register
4 系统保护配置相关
SYPCR system protection and configure register
5 扩展外部存贮器控制
1)MSTAT memory status register
2)ROM/FLASH扩展使用GPCM
BR0 base register
OR0 option register
3)SDRAM扩展使用UPMA
(1)MPTPR Memory Periodic Timer Prescaler Register
(2)MAMR Machine A mode register (for UPMA)
(3)初始化UPM RAM WORD
利用MCR,MAR,MDR复制UPMA table到UPMA的64个32位的RAM WORD。
(4)DRAM在Normal Read/Write之前需要先通过MCR执行三个时序
A: precharge
B: auto-refresh
C: Load Mode Register (通过MDR传入参数)
(5) BR1 OR1
3.4 初始化栈
/*initialize the stack pointer */
lis sp, HIADJ(STACK_ADRS)
addi sp, sp, LO(STACK_ADRS)
//看附录2的说明
addi sp, sp, -FRAMEBASESZ /* get frame stack */
//这一句是干什么的,没搞懂,不过好像跳转到外部函数时需要
3.5 跳转到c语言入口
/*
* calculate C entry point: routine - entry point + ROM base
* routine = romStart
* entry point = romInit = R7
* ROM base = ROM_TEXT_ADRS = R8
* C entry point: romStart - R7 + R8
*/
lis r8, HIADJ(ROM_TEXT_ADRS)
addi r8, r8, LO(ROM_TEXT_ADRS)
lis r6, HIADJ(romStart)
addi r6, r6, LO(romStart) /* load R6 with C entry point */
lis r7, HIADJ(romInit)
addi r7, r7, LO(romInit)
sub r6, r6, r7 /* routine - entry point */
add r6, r6, r8 /* + ROM base */
mtlr r6 /* move C entry point to LR */
blr /* jump to the C entry point */
// 计算romstart在FLASH中的地址:romStart-romInit+ROM_TEXT_ADRS并跳转。为什么此处跳转需要计算相对地址呢?
// 根据windriver AN237-Understanding the bootrom image 文档,在生成bootrom镜像时rominit.s的链接地址并不一定是ROM_TEXT_ADRS。Bootrom_res、bootrom、bootrom_uncompress三种镜像对应的rominit.s的连接地址分别是ROM_TEXT_ADRS、RAM_LOW_ADRS、RAM_HIGH_ADRS。RAM_LOW_ADRS和RAM_HIGH_ADRS所指的地址是RAM地址空间的一部分而不是FLASH地址空间。那么bootrom或bootrom_uncompress镜像中的符号romstart的地址就会是RAM_LOW_ADRS+offset或RAM_HIGH_ADRS+offset而不是ROM_TEXT_ADRS+offset。此时如果直接跳转到符号romstart,那么PC就将指向RAM_LOW_ADRS+offset或RAM_HIGH_ADRS+offset。所以直接跳转到符号romstart并不能真正的调用函数romstart,反而会使程序跑飞。所以应该通过romStart-romInit+ROM_TEXT_ADRS计算出romstart在FLASH中的地址并跳转,此时才能正确的调用函数romstart。
// 以上代码叫PIC(Position Independent Code)。在程序将自身拷贝到RAM之前,需要注意。
附录
1 为什么PPC复位后会指向FLASH?
1 复位向量
860复位后,执行的第一条指令位于4GB地址空间的什么地方?
对于PowerPC,复位也是一种异常(你可以理解为CPU自己产生的中断),向量号为0x100。异常向量表的基地址加上复位向量号即为复位向量,也就是CPU开始执行指令的地方。异常向量表在内存空间的可能位置有2个:0x00000000和0xFFF00000。具体是哪一个由MSR寄存器的IP位指定:若IP位为0,则异常向量表在0x0处,否则在0xFFF00000处。860有多种复位方式,对于hard reset,MSR的IP位由硬件配置字的IIP(Initial Interrupt Prefix)位决定。所以PowerPC的复位向量为0x00000100或者0xFFF00100。当PowerPC复位时,它会从复位向量所值的地址取指令。
2 从Flash偏移0x100出开始执行指令
假设有128K字节的Flash,并打算把它映射到CPU地址空间的0xFE000000开始的地方。但由于复位向量为0xFFF00100,那么为什么CPU会从Flash中偏移0x100的地方开始执行指令呢?
860内嵌的memory controller有个引脚,#CS0,称为global boot chip select,一般把它连接到Flash或EPROM的片选上。假定这128K Flash是8位数据宽度,地址线A16..0分别连接到860的A15..A31(注意A31为860地址线的最低位),数据线D0..7分别和860的D0..7相连。再看控制#CS0的BR0和OR0寄存器,CPU复位后,BR0.V=1(Valid)且BR0.BA[0..16]=0,OR0.AM[0..16]=0。由于地址掩码AM位全为0,意味内存控制器会忽略所有参与片选逻辑的地址线A0..A16,所以产生的#CS0总是有效的。这样,上电后Flash总会被选中。CPU从Flash偏移0x100的地方取指令。这个结果和复位向量表基地址,以及以后Flash映射到CPU内存空间的什么位置都是无关的。
2 栈
栈是一种只能在叫做栈的一段进行进栈或者出栈操作的线性数据结构。栈的主要特点是”后进先出”,即后进栈的元素先处理。栈的操作主要是入栈和出栈。有两种功能的实现需要用到栈:一是函数调用二是中断处理。
栈有栈顶和栈底。栈顶的地址总是大于栈底。这两者之差就是栈的大小。SP是栈指针,它指向使用栈的当前位置。在初始化栈时,sp应该指向栈顶还是栈底栈要根据栈是向下生长栈和向上生长栈而定。向下生长栈当入栈时,首先将数据入栈,然后sp要减一,所以初始化时,sp应指向栈顶;而向下生长栈入栈时,首先数据入栈,然后sp要加一,所以初始化时sp应指向栈底。
_STACK_DIR用于指明栈的增长方向,定义在vxArch.h中,
#ifndef _STACK_DIR
#define _STACK_DIR _STACK_GROWS_DOWN
#endif /* _STACK_DIR */
但是根据这个宏,说明前面还有一次宏定义,发现,在这句之前,有根据各个CPU而定义的头文件,例如
#if (CPU_FAMILY==PPC)
#include "arch/ppc/archPpc.h"
#endif /* (CPU_FAMILY==PPC) */
也就是说,在每个cpu的arch中都会定义自己的增长方向,但是如果没有定义的话,就默认在vxArch.h中定义为向上增长。因为在archPpc.h中没有找到_STACK_DIR的定义,所以ppc是向上增长栈。
在rominit.s中,这个栈是用来干什么的呢?
是用于下面的函数跳转的,跳到romstart,并且应该是只有这么一次作用。
而在3.4节中,就是对sp指针进行初始化,sp= STACK_ADRS。STACK_ADRS宏定义在congfigAll.h文件中。
//首先定义栈的大小
#if ((CPU_FAMILY==I960) || (CPU_FAMILY==ARM))
#define STACK_SAVE 512 /* maximum stack used to preserve */
#else /* sparc or others */
#if ((CPU_FAMILY==SPARC) || (CPU_FAMILY==PPC))
#define STACK_SAVE 0x1000
#else /* all other architecutes */
#define STACK_SAVE 0x40 /* maximum stack used to preserve */
#endif /* mips cpp no elif */
#endif
//第二步,定义宏STACK_RESIDENT
#if ((CPU_FAMILY == MIPS) || (CPU_FAMILY == PPC))
#define STACK_RESIDENT RAM_DST_ADRS
#else
#define STACK_RESIDENT _sdata
#endif
//第三步,根据栈的生长方向确定sp的初始值STACK_ADRS
#if (_STACK_DIR == _STACK_GROWS_DOWN)
#ifdef ROM_RESIDENT
#define STACK_ADRS STACK_RESIDENT
#else
#define STACK_ADRS _romInit //注意rominit的链接地址
#endif /* ROM_RESIDENT */
#else /* _STACK_DIR == _STACK_GROWS_UP */
#ifdef ROM_RESIDENT
#define STACK_ADRS (STACK_RESIDENT-STACK_SAVE)
#else
#define STACK_ADRS (_romInit-STACK_SAVE)
#endif /* ROM_RESIDENT */
#endif /* _STACK_DIR == _STACK_GROWS_UP */