戏说BIOS之PCI SCAN
1. Introduction
PCI由intel公司在1990年前后开发的,后续经过若干年的发展以及标准化,它已然成为server&pc上的标准总线。PCI以其出色的设计以及不错的通信速率在计算机领域攻城掠地,不断的取代诸如MCA,ISA,EISA,VESA,NuBus等传统总线。PCI相对于传统总线有非常多的优点,如:1.它是数据总线和地址总线是分时复用的,这样减少了pin脚节省了空间,而且这样也可以方便实现突发式数据传输。2.它是即插即用的(plug & play),当device插入系统时,系统会自动对device进行资源分配并加载对应driver,而传统的ISA device则需要做复杂的手工配置。3.中断共享,传统的总线有一个致命的缺陷就是它们是中断独占的,本来系统的中断就非常紧缺所以增加新的device会出现中断不够使用的麻烦,而pci irq routing机制使得不同的device irq共用成为现实。可是技术的发展总是长江后浪推前浪,前浪死在沙滩上J! PCI又逐渐被更好的总线PCIE所取代渐渐退出PC的历史舞台,后续我会再去研究一下PCIE。
2. PCI Arch
可能是软体背景的原因,因此我看PCI spec也会习惯性的使用软件设计的视角去理解PCI的设计(我觉得有关设计、架构的理论应该是相通的,正如软件中经典的design pattern的思想来源于建筑学一样)。我的视角里PCI同经典的接口编程或者插件式设计非常接近。接口本质上是一组规则的集合它是对同类事物行为上的表示,它的主要目标是实现相同类别的不同对象行为上的多态性。面向接口的编程是OO思想的精髓所在。它的好处体现在哪里呢?首先它增强了系统的灵活性,只要遵循接口定义的规则,系统的底层实现部分就可以灵活的替换、扩充如: PCI总线定出了设备的统一的硬件接口,这样遵循该接口pci device就可以方便的扩展入系统;另外相同的接口可以接入不同厂家的设备就像同样的sata接口可以接三星的光驱也可以接LG的。其次规则给出以后,实现该接口的部件就会有共通的接口但是不同的实现,如此系统端就可以通过接口灵活实现对部件的操作配置。PCI定义出了三种规格的配置空间,根据配置空间提供的信息系统端可以方便的识别设备的种类,功能甚至于厂商和版本号,获得非常丰富的系统端知识;而且该功能也使得设备可以动态的配置资源进而能够做到plug & play。
3. PCI Scan
PCI Configuration Space是大小为256字节的一块空间,它由header和device specific两个部分组成,其中header部分是固定的而device specific部分则是与device相关的,不同的device会有不同的layout。配置空间被用于配置,初始化以及灾难性错误处理的功能。下图1是type 00h Configura-tion Space Header:
图1
PCI Scan的重要任务就是读出该256bytes 配置空间,那么如何读取这部分的信息呢? 有下述两种方法:
1.使用0CF8-0CFB, 0CFC这两组IO port存取PCI Configuration Space。将总线号、设备号、功能号和寄存器号组合成一个双字送到配置地址端口 (CF8H-CFBH),然后读写配置数据端口 (CFCH)即可获得配置空间的数据,下图2是配置地址寄存器的格式定义:
图2
所以我们先要build一个config-address然后再去透过端口存取配置空间。下述代码用于build config-address:
;----------------------------------------------------------------------------
; build _pci_cfg_add:
; build pci config address
; used registers: eax,ebx
;-----------------------------------------------------------------------------
build_pci_cfg_add proc near
push eax
push ebx
xor eax,eax
xor ebx,ebx
mov PCI_CFG_ADDRESS,80000000h
mov al,PCI_BUS_NUM
shl ax,08h
mov bl,PCI_DEV_NUM
shl bx,03h
or ax,bx
or al,PCI_FUN_NUM
shl eax,08h
or PCI_CFG_ADDRESS,eax
pop ebx
pop eax
ret
build_pci_cfg_add endp
config-address准备好以后接下来就是透过IO port读取pci configuration space了,下述代码演示读取的过程:
;----------------------------------------------------------------------------
; read_cfg_space
; read pci config space use io port
; Called with: NULL
; used registers: eax,edx
; returned regs: eax
;-----------------------------------------------------------------------------
read_cfg_space proc near
mov eax,PCI_CFG_ADDRESS
or eax,edx
mov dx,PCI_CFG_APORT
out dx,eax
mov dx,PCI_CFG_DPORT
in eax,dx
ret
read_cfg_space endp
理论上PCI bus支持256条总线,每条总线支持32个device,每个device又支持8个function,所以我们组合出上面所有的可能就可以遍历出所有的PCI 设备了。可是实际上PC上面PCI 总线通常只有1条,最多也不会超过四条所以我们只扫4条总线就可以了,不用做太多的无用功。有了前面的准备,我们就来实现一个类似RU中的PCI scan吧,下图3就是我写的pciscan运行的状况了:
图 3
在该界面下按下esc就会退出该程序;移动↑↓键就可以选中device,然后敲enter就会看到该device的Configuration space如下图4所示
图 4
当前界面下如果想返回到上一个界面只需要按下F6就会回到图3的界面了。
2. Call PCI BIOS int1A同样也可以获取PCI device的信息。其中AH=B1h,AL=function id所有的function id如下所示:
01h: INSTALLATION CHECK
02h: FIND PCI DEVICE
03h: FIND PCI CLASS CODE
06h: PCI BUS-SPECIFIC OPERATIONS
08h: READ CONFIGURATION BYTE
09h: READ CONFIGURATION WORD
0Ah: READ CONFIGURATION DWORD
0Bh: WRITE CONFIGURATION BYTE
0Ch: WRITE CONFIGURATION WORD
0Dh: WRITE CONFIGURATION DWORD
0Eh: GET IRQ ROUTING INFORMATION
0Fh: SET PCI IRQ
81h: INSTALLATION CHECK (32-bit)
82h: FIND PCI DEVICE (32-bit)
83h: FIND PCI CLASS CODE (32-bit)
86h: PCI BUS-SPECIFIC OPERATIONS (32-bit)
88h: READ CONFIGURATION BYTE (32-bit)
89h: READ CONFIGURATION WORD (32-bit)
8Ah: READ CONFIGURATION DWORD (32-bit)
8Bh: WRITE CONFIGURATION BYTE (32-bit)
8Ch: WRITE CONFIGURATION WORD (32-bit)
8Dh: WRITE CONFIGURATION DWORD (32-bit)
8Eh: GET IRQ ROUTING INFORMATION (32-bit)
8Fh: SET PCI IRQ (32-bit)
我们使用function id 09h就可以从configuration space 中读取出一个字,这样的操作明显简单的多了,只需call 一次int1a中断即可。下述c代码演示了读取Vendor id的过程,如需读取其它部分只要算出具体config-address即可。
#include <stdio.h>
#include <conio.h>
#include <dos.h>
int main(int argc,char** argv)
{
union REGS reg;
argc = argc;
argv = argv;
reg.x.ax = 0xB109;
reg.x.bx = 0x80000000;
reg.x.di = 0;
int86(0x1A, ®, ®);
if(reg.x.cx != 0xffff)
{
printf("Vendor : %4.4X/n", reg.x.cx);
}
return 0;
}
程序运行结果如下图5所示:
图 5
最后依旧是开放完整的source code和可执行文件供有兴趣的朋友下载。
Enjoy it!
That’s all!
Peter