PCI 扩展 ROM
1. ROM 映像
在 PCI 规范中提供了一种机制,使 PCI 设备可以带一个扩展 ROM 。通过执行 ROM 中存放的代码来完成与设备有关的初始化,同时也有可能完成系统的引导功能。该机制允许扩展 ROM 包含有几种为不同系统和处理器结构而设计的映像(如图 16 )。
每个映像必须开始于一个 512 字节边界并含有 PCI 扩展 ROM 头标,每个映像的开始位置取决于前一个映像的长度。 ROM 的最后一个映像的头标中要有一个特殊的编码以表示它是最后一个。
每个 ROM 映像中所要求的信息分为两个不同的区域:一个区域是 ROM 头标,应该放在 ROM 映像的开始处;另一个区域是 PCI 数据结构,必须放置在映像的第一个 64KB 处。图 17 表示了扩展 ROM 中一个映像的典型布局。
图 17 ROM 映像的典型布局
下面来详细说说它的各个结构。
2. ROM 头标
ROM 头标格式如下:
偏移 | 值 | 字节大小 | 说明 |
+0 | 55 AA | 2 | ROM 标识 |
+2 | 66 | 1 | 初始化长度 : 512 字节为单位 |
+3 | EB xxxx | 3 | Jmp near xxxx, POST 代码会直接远程调用此位置。所以返回需要 RETF |
+6 | 0 | 18 | 保留 |
+18H | 2 | 指向 PCI 数据结构 的指针。以本 rom image 开始为起始值。一般为 1AH 。 |
ROM 标识为 55H 0AAH , POST 程序在检测 ROM 开头两个字节,只要检测到 55 AA 就知道知道有扩展 ROM 了。加电自检程序( POST )当检测到合适的 ROM 后,会直接调用偏移 +3 处的指令,故在此位置上(偏移 +3 处)总是放置一个跳转指令。
3. PCI 数据结构
PCI 数据结构格式如下:
偏移 | 值 | 字节大小 | 说明 |
+1A | ‘PCIR’ | 4 | 标识: 50H 43H 49H 52H |
+1E | 2 | 供应商识别码 | |
+20 | 2 | 设备识别码 | |
+22 | 2 | VPD | |
+24 | 2 | 本 PCI 数据结构长度,一般为: 18h | |
+26 | 1 | 本 PCI 数据结构的版本号,一般为 0 | |
+27 | 3 | 设备分类代码 | |
+2A | 2 | IMAGE LENGTH ,也是以 512 字节为单位。一般和上面的初始化长度一样。 | |
+2C | 2 | 代码版本 | |
+2E | 1 | 代码类型。在 PC 机上该值为 0 。当有多个 ROM 映像时, POST 会检测本字段,直到找到合适的映像。 | |
+2F | 1 | 标识。最后一个 IMAGE ,该值为 80H ,否则为 0 。 | |
+30 | 2 | 保留。 |
供应商识别码和设备识别码必须和 PCI 配置空间中的相应字段一样,否则也不会执行映像中指令的。 设备分类代码虽然也要和 PCI 配置空间中数据一致,但是 POST 不会进行强行检测,也就是说:这不是 ROM 映像是否执行的必须条件。 POST 主要根据该字段来判断设备类型,以进行一些特别处理,比如,当为 VGA 设备时, POST 就会将 ROM 中的 BIOS 拷贝至 RAM 的 0C 0000H 处。
4. POST 代码处理 ROM 流程
在大多数情况下,系统的上电自测试( POST )代码对插接的 PCI 设备的处理与对那些焊接在母板上的设备的处理相同。但对扩展 ROM 的处理是不同的。 POST 代码分两步检测一个选项 ROM 是否存在:(1)其中第一步确定设备是否在配置空间中实现了一个扩展 ROM 基址寄存器,如果该寄存器存在, POST 就必须将 ROM 映射到地址空间中未用的部分并将使能位置 1 ;(2)第二步是检查前两个字节是否为标签值 AA55H ,如果是,就表示存在一个 ROM ,否则就说明该设备上没有 ROM 。
如果检测到设备上有一个 ROM , POST 就必须在该 ROM 中寻找一个具有合适的代码类型的映象,同时该映象中的供应商识别和设备识别字段也要与设备配置空间的相应字段吻合。当这个合适的映象找到之后, POST 就从 ROM 中拷贝适当数量的数据到 RAM 中,然后执行该设备的初时化代码。至于拷贝多少数据量,以及怎样执行设备初始化代码,要由代码类型来决定。
系统中的 POST 代码根据初始化长度字段所指定的字节数,将数据从 ROM 拷贝到 RAM 中,然后以 03H 处的值作为入口点调用 INIT 功能。在 INIT 返回之前, POST 代码应将上述拷贝过来的 RAM 区保持为可写状态,以使 INIT 代码能在该区存放一些静态数据,并调整运行存储分配,使得系统在运行时耗费较少的空间。
当涉及到扩展 ROM 是,在系统 POST 代码中有一套为 PC 兼容而设的特别步骤,它们是:
1. 映象并使能扩展 ROM 到存储器地址空间的一个空区域。
2. 在 ROM 中寻找一个适当的映像区,并根据初始化长度指定的字节数从 ROM 中拷贝数据到 RAM 兼容区。
3. 使扩展 ROM 基址寄存器实效。
4. 保持 RAM 区为可写状态并远程调用( FAR CALL ) INIT 功能。
5. 从 INIT 返回之后,利用偏移地址 02H 处的的值决定运行是需要多少存储器空间。
在系统引导之前, POST 代码必须把含有扩展 ROM 代码的 RAM 区变为只读区。
POST 代码必须用特殊的方法去处理带有扩展 ROM 的 VGA 设备, VGA 设备的扩展 BIOS 必须拷贝到 0C 0000H 处。 VGA 设备的识别是通过检查设备配置空间中的分类代码字段来实现的。
5. ROM 的初始化( INIT )代码
在 PC 兼容的扩展 ROM 中,含有一个 INIT 功能,用来负责 I/O 设备的初始化以及为运行操作做准备。由于在执行 INIT 功能时代码所驻留的 RAM 区被保持为可写状态,因此,允许 PCI 扩展 ROM 中的 INIT 功能具有某种扩展能力。
在 INIT 功能执行期间, INIT 功能可以在它的 RAM 区中存放静态参数。运行 BIOS 或设备驱动程序便可以调用这些参数。但是该 RAM 区在运行期间是不能写入的。
INIT 功能还可以调整它在运行期间所消耗的 RAM 总量,具体方法是修改映象中位于偏移地址 02H 处的长度字段,从而能够保持扩展 ROM 区( 0C 0000H~0DFFFFH )占用有限的存储器资源。例如,一个设备的扩展 ROM 为其初始化和运行代码要求 24KB 的存储空间,但运行代码只要求 8KB 。 ROM 中的映象所表现出的长度是 24KB 。所以, POST 代码将全部数据拷贝到 RAM 中。然后,当运行到 INIT 功能时,它可以调整长度字段使其减为 8KB 。当退出 INIT 功能时, POST 代码看到的运行长度为 8KB 并且可以将下一个扩展 BIOS 拷贝到最佳位置。
INIT 功能负责保证整个映象长度的检查的校验和是正确的。如果它以任何方式修改了 RAM 区,就必须计算出新的校验和并存入映象中。 INIT 功能不能以任何方式修改系统存储器(除 RAM 区域的 INIT 功能),除非它对配置存储器采用适当的协议或 BIOS 服务。
如果要将 INIT 功能从扩展 ROM 中移去,只要向初始化长度字段(便宜地址 02H )写入 0 即可。在这种情况下,不产生检查和。
INIT 从入口处可以得到三个参数:设备的总线编号、设备编号和功能编号。这些参数可以用来访问正在被初始化的设备。它们被传送到 X86 的寄存器 AX 中,其中 AH 包含总线编号, AL 的高 5 位为设备编号, AL 的低 3 位是功能编号。
6. 映像结构的三个长度
一个 PC 兼容的映像有三个长度与映像结构有关:映像长度、初始化长度和运行长度。
映像长度是映像的总长度,它必须大于或等于初始化长度。
初始化长度表示映像中所含的初始化代码和运行代码两部分的总和,这也是在执行初始化例行程序之前, POST 代码要拷贝到 RAM 区中的数据总量。初始化长度必须大于或等于运行长度。拷贝到 RAM 区中的初始化数据的检查和必须为 0 。
运行长度是指映像中所包含运行代码的总量。这也是系统运行时 POST 代码保留在 RAM 区中的数据量。这部分映像的检查和必须为 0.
PCI 数据结构必须包含与映像的运行分配部分中,否则他必须包含于初始化部分。
水到渠成:写一个扩展 ROM 程序
有了这么多理论知识,自己动手写一个可以启动的扩展 ROM 程序就不怎么难了(源代码 ROM.ASM 和 TROM.ASM 见附件)。
ROM.ASM 程序分成两部分:前一部分编译后便是 ROM 映像;后一部分是则是将前面部分烧写到 27C256 ( ROM )中去。
ROM 映像分成 3 部分:
1) ROM 头标和 PCI 数据结构
2) 初始化后继续保留在 RAM 中的代码。这段代码主要是中断 98H 的服务代码。
3) INIT 初始化代码,这部分代码完成 4 项工作:显示一段字符串,以表示初始化代码运行正常;修改 IVT 中断向量表 98H ,将地址指向上面的正确位置;修改初始化代码长度,以便保留一定大小的代码在 RAM 中;进行计算和的重新计算。
系统启动后进入 DOS ,然后可以运行 TROM ——该程序仅仅调用中断 98H ——来检查保留在 RAM 中的代码是否正常。