7.2 总线接口初始化

在使用同一款处理器的不同系统上,TLB、Cache这些体系结构紧密相关的芯片组成部分的初始化方法是基本一致的,不需要进行特别的改动。与此不同的是,内存和IO设备的具体组成在计算机系统中则可以比较灵活地搭配,不同系统间的差异可能会比较大。

在计算机系统硬件设计中,内存可以使用不同的总线类型,例如DDR、DDR2、DDR3或者DDR4。在主板上使用时,也可以灵活地增加或者减小内存容量,甚至改变内存条种类,例如将非缓冲型内存模组(Unbuffered DIMM,简称UDIMM)改为寄存型内存模组(Registered DIMM,简称RDIMM)

IO总线的情况也比较类似,在计算机系统硬件设计中可以搭配不同的桥片,也可以在主板上根据实际情况改变某些接口上连接的设备,例如增加PCIE网卡或者增加硬盘数量等。

这些不同的配置情况要求在计算机系统启动时能够进行有针对性的初始化。前面提到过,初始化就是将某些寄存器通过load/store指令或是其他的方法设置为期望数值的过程。以下代码段就是龙芯3A处理器上对内存控制器进行初始化的程序片段。


ddr2_config:
add.d     a2, a2, s0               # a2、s0为调用该程序时传入的参数,它们的和表示初始化参数在
                                   # Flash中的基地址
dli       t1, DDR_PARAM_NUM        # t1用于表示内存参数的个数
addi.d    v0, t8, 0x0              # t8为调用该程序时传入的参数,用于表示内存控制器的寄存器基地址
1:
ld.d      a1, 0x0(a2)              # 可以看到,初始化的过程就是从Flash中取数再写入内存控制器中的
st.d      a1, 0x0(v0)              # 寄存器的过程
addi.d    t1, t1, -1
addi.d    a2, a2, 0x8
addi.d    v0, v0, 0x8
bnez      t1, 1b

7.2.1 内存初始化

内存是计算机系统的重要组成部分。冯·诺依曼结构下,计算机运行时的程序和数据都被保存在内存之中。相对复位时用于取指的ROM或是Flash器件来说,内存的读写性能大幅提高。以SPI接口的Flash为例,即使不考虑传输效率的损失,当SPI接口运行在50MHz时,其带宽最高也只有50MHz×2b=100Mb/s,而一个DDR3-1600的接口在内存宽度为64位时,其带宽可以达到最高1600MHz×64b=102400Mb/s。由此可见内存的使用对系统性能的重要程度。

越来越多的处理器已经集成内存控制器。因为内存的使用和设置与外接内存芯片的种类、配置相关,所以在计算机系统启动的过程中需要先获取内存的配置信息,再根据该信息对内存控制器进行配置。正如上一章对内存总线的介绍,这些信息包含内存条的类型、容量、行列地址数、运行频率等。

获取这些信息是程序通过I2C总线对外部内存条的SPD芯片进行读操作来完成的。SPD芯片也相当于一个Flash芯片,专门用于存储内存条的配置信息。

如上面的程序片段所示,对内存的初始化实际上就是根据内存配置信息对内存控制器进行初始化。与Cache初始化类似的是,内存初始化并不涉及其存储的初始数据。与Cache又有所不同的地方在于,Cache有专门的硬件控制位来表示Cache块是否有效,而内存却并不需要这样的硬件控制位。内存的使用完全是由软件控制的,软件知道其访问的每一个地址是否存在有效的数据。而Cache是一个硬件加速部件,大多数情况下软件并不需要真正知道而且也不希望知道其存在,Cache的硬件控制位正是为了掩盖内存访问延迟,保证访存操作的正确性。因此内存初始化仅仅需要对内存控制器进行初始化,并通过控制器对内存的状态进行初始化。在初始化完成以后,如果是休眠唤醒,程序可以使用内存中已有的数据来恢复系统状态;如果是普通开机,则程序可以完全不关心内存数据而随意使用。

内存控制器的初始化包括两个相对独立的部分,一是根据内存的行地址、列地址等对内存地址映射进行配置,二是根据协议对内存信号调整的支持方式对内存读写信号相关的寄存器进行训练,以保证传输时的数据完整性。

在内存初始化完成后,可能还需要根据内存的大小对系统可用的物理地址空间进行相应的调整和设置。

7.2.2 IO总线初始化

前面提到,受外围桥片搭配及可插拔设备变化的影响,系统每次启动时需要对IO总线进行有针对性的初始化操作。

对于龙芯3A5000处理器,对应的IO总线主要为HyperTransport总线。在IO总线初始化时,做了三件事:一是对IO总线的访问地址空间进行设置,划定设备的配置访问空间、IO访问空间和Memory访问空间;二是对IO设备的DMA访问空间进行规定,对处理器能接收的DMA内存地址进行设置;三是对HyperTransport总线进行升频,从复位时1.0模式的200MHz升频到了3.0模式的2.0GHz,并将总线宽度由8位升至16位。

完成了这三件事,对IO总线的初始化就基本完成了。接着还将设置一些和桥片特性相关的配置寄存器以使桥片正常工作。

IO总线初始化的主要目的是将非通用的软硬件设置都预先配置好,将与桥片特性相关的部分与后面的标准驱动加载程序完全分离出来,以保证接下来的通用设备管理程序的运行。

如果把CPU比作一个大房间,至此,房间灯火通明,门窗均已打开,但门窗外还是漆黑一片。

完成内存与IO总线初始化后,BIOS中基本硬件初始化的功能目标已经达到。但是为了加载操作系统,还必须对系统中的一些设备进行配置和驱动。操作系统所需要的存储空间比较大,通常无法保存在Flash这样的存储设备中,一般保存在硬盘中并在使用时加载,或者也可以通过网口、U盘等设备进行加载。为此就需要使用更复杂的软件协议来驱动系统中的各种设备,以达到加载操作系统的最终目标。

在此之前的程序运行基本没有使用内存进行存数取数操作,程序也是存放在Flash空间之中的,只不过是经过了Cache的缓存加速。在此之后的程序使用的复杂数据结构和算法才会对内存进行大量的读写操作。为了进一步提高程序的运行效率,先将程序复制到内存中,再跳转到内存空间开始执行。

此后,还需要对处理器的运行频率进行测量,对BIOS中的计时函数进行校准,以便在需要等待的位置进行精确的时间同步。在经过对各种软件结构必要的初始化之后,BIOS将开始一个比较通用的设备枚举和驱动加载的过程。下一节将对这个标准的设备枚举和加载过程进行专门的说明。