X86硬件之dos下的PCI device遍历及实例(BorlandC++)

一、PCI简介

PCI设备都有独立的配置空间,HOST主桥通过配置读写总线事务访问这段空间。PCI总线规定了三种类型的PCI配置空间,分别是PCI Agent设备使用的配置空间,PCI桥使用的配置空间和Cardbus桥片使用的配置空间。

1、 PCI桥

PCI桥的引入使PCI总线极具扩展性,也极大地增加了PCI总线的复杂度。PCI总线的电气特性决定了在一条PCI总线上挂接的负载有限,当PCI总线需要连接多个PCI设备时,需要使用PCI桥进行总线扩展,扩展出的PCI总线可以连接其他PCI设备,包括PCI桥。在一颗PCI总线树上,最多可以挂接256个PCI设备,包括PCI桥。
PCI桥在PCI总线树中的位置如图所示。
在这里插入图片描述

PCI桥作为一个特殊的PCI设备,具有独立的配置空间。但是PCI桥配置空间的定义与PCI Agent设备有所不同。PCI桥的配置空间可以管理其下PCI总线子树的PCI设备,并可以优化这些PCI设备通过PCI桥的数据访问。PCI桥的配置空间在系统软件遍历PCI总线树时配置,系统软件不需要专门的驱动程序设置PCI桥的使用方法,这也是PCI桥被称为透明桥的主要原因。
在某些处理器系统中,还有一类PCI桥,叫做非透明桥。非透明桥不是PCI总线定义的标准桥片,但是在使用PCI总线挂接另外一个处理器系统时非常有用,非透明桥片的主要作用是连接两个不同的PCI总线域,进而连接两个处理器系统。
使用PCI桥可以扩展出新的PCI总线,在这条PCI总线上还可以继续挂接多个PCI设备。PCI桥跨接在两个PCI总线之间,其中距离HOST主桥较近的PCI总线被称为该桥片上游总线(Primary Bus),距离HOST主桥较远的PCI总线被称为该桥片的下游总线(Secondary Bus)。如图2 8所示,PCI桥1的上游总线为PCI总线x0,而PCI桥1的下游总线为PCI总线x1。这两条总线间的数据通信需要通过PCI桥1。
通过PCI桥连接的PCI总线属于同一个PCI总线域,在图2 8中,PCI桥1、2和3连接的PCI总线都属于PCI总线x域。在这些PCI总线域上的设备可以通过PCI桥直接进行数据交换而不需要进行地址转换;而分属不同PCI总线域的设备间的通信需要进行地址转换,如与PCI非透明桥两端连接的设备之间的通信。
如图所示,每一个PCI总线的下方都可以挂接一个到多个PCI桥,每一个PCI桥都可以推出一条新的PCI总线。在同一条PCI总线上的设备之间的数据交换不会影响其他PCI总线。如PCI设备21与PCI设备22之间的数据通信仅占用PCI总线x2的带宽,而不会影响PCI总线x0、x1与x3,这也是引入PCI桥的另一个重要原因。
由图2 8我们还可以发现PCI总线可以通过PCI桥组成一个胖树结构,其中每一个桥片都是父节点,而PCI Agent设备只能是子节点。当PCI桥出现故障时,其下的设备不能将数据传递给上游总线,但是并不影响PCI桥下游设备间的通信。当PCI桥1出现故障时,PCI设备11、PCI设备21和PCI设备22将不能与PCI设备01和存储器进行通信,但是PCI设备21和PCI设备22之间的通信可以正常进行。
使用PCI桥可以扩展一条新的PCI总线,但是不能扩展新的PCI总线域。如果当前系统使用32位的PCI总线地址,那么这个系统的PCI总线域的地址空间为4GB大小,在这个总线域上的所有设备将共享这个4GB大小的空间。如在PCI总线x域上的PCI桥1、PCI设备01、PCI设备11、PCI桥2、PCI设备21和PCI设备22等都将共享一个4GB大小的空间。再次强调这个4GB空间是PCI总线x域的“PCI总线地址空间”,和存储器域地址空间和PCI总线y域没有直接联系。
处理器系统可以通过HOST主桥扩展出新的PCI总线域,如MPC8548处理器的HOST主桥x和y可以扩展出两个PCI总线域x和y。这两个PCI总线域x和y之间的PCI空间在正常情况下不能直接进行数据交换,但是PowerPC处理器可以通过设置PIWARn寄存器的TGI字段使得不同PCI总线域的设备直接通信。
许多处理器系统使用的PCI设备较少,因而并不需要使用PCI桥。因此在这些处理器系统中,PCI设备都是直接挂接在HOST主桥上,而不需要使用PCI桥扩展新的PCI总线。即便如此读者也需要深入理解PCI桥的知识。
PCI桥对于理解PCI和PCIe总线都非常重要。在PCIe总线中,虽然在物理结构上并不含有PCI桥,但是与PCI桥相关的知识在PCIe总线中无处不在,比如在PCIe总线的Switch中,每一个端口都与一个虚拟PCI桥对应,Switch使用这个虚拟PCI桥管理其下PCI总线子树的地址空间。
2、PCI Agent设备的配置空间
在一个具体的处理器应用中,PCI设备通常将PCI配置信息存放在E2PROM中。PCI设备进行上电初始化时,将E2PROM中的信息读到PCI设备的配置空间中作为初始值。这个过程由硬件逻辑完成,绝大多数PCI设备使用这种方式初始化其配置空间。
在x86处理器中,系统软件使用CONFIG_ADDR和CONFIG_DATA寄存器,读取PCI设备配置空间的这些初始化信息,然后根据处理器系统的实际情况使用DFS算法,初始化处理器系统中所有PCI设备的配置空间。
在PCI Agent设备的配置空间中包含了许多寄存器,这些寄存器决定了该设备在PCI总线中的使用方法。
PCI Agent设备使用的配置空间如图2 9所示。
在这里插入图片描述
在PCI Agent设备配置空间中包含的寄存器如下所示。
(1) Device ID和Vendor ID寄存器
这两个寄存器的值由PCISIG分配,只读。其中Vendor ID代表PCI设备的生产厂商,而Device ID代表这个厂商所生产的具体设备。如Intel公司的基于82571EB芯片的系列网卡,其Vendor ID为0x8086 [1],而Device ID为0x105E [2]。
(2) Revision ID和Class Code寄存器
这两个寄存器只读。其中Revision ID寄存器记载PCI设备的版本号。该寄存器可以被认为是Device ID寄存器的扩展。
(3) Header Type寄存器
该寄存器只读,由8位组成。
第7位为1表示当前PCI设备是多功能设备,为0表示为单功能设备。
第6~0位表示当前配置空间的类型,为0表示该设备使用PCI Agent设备的配置空间,普通PCI设备都使用这种配置头;为1表示使用PCI桥的配置空间,PCI桥使用这种配置头;为2表示使用Cardbus桥片的配置空间,Card Bus桥片使用这种配置头,本篇对这类配置头不感兴趣。
系统软件需要使用该寄存器区分不同类型的PCI配置空间,该寄存器的初始化必须与PCI设备的实际情况对应,而且必须为一个合法值。
(4) Cache Line Size寄存器
该寄存器记录HOST处理器使用的Cache行长度。在PCI总线中和Cache相关的总线事务,如存储器写并无效和Cache多行读等总线事务需要使用这个寄存器。值得注意的是,该寄存器由系统软件设置,但是在PCI设备的运行过程中,只有其硬件逻辑才会使用该寄存器,比如PCI设备的硬件逻辑需要得知处理器系统Cache行的大小,才能进行存储器写并无效总线事务,单行读和多行读总线事务。
如果PCI设备不支持与Cache相关的总线事务,系统软件可以不设置该寄存器,此时该寄存器为初始值0x00。对于PCIe设备,该寄存器的值无意义,因为PCIe设备在进行数据传送时,在其报文中含有一次数据传送的大小,PCIe总线控制器可以使用这个“大小”,判断数据区域与Cache行的对应关系。
(5) Subsystem ID和Subsystem Vendor ID寄存器
这两个寄存器和Device ID和Vendor ID类似,也是记录PCI设备的生产厂商和设备名称。但是这两个寄存器和Device ID与Vendor ID寄存器略有不同。下文以一个实例说明Subsystem ID和Subsystem Vendor ID的用途。
Xilinx公司在FGPA中集成了一个PCIe总线接口的IP核,即LogiCORE。用户可以使用LogiCORE设计各种各样基于PCIe总线的设备,但是这些设备的Device ID都是0x10EE,而Vendor ID为0x0007 [3]。
(6) Expansion ROM base address寄存器
有些PCI设备在处理器还没有运行操作系统之前,就需要完成基本的初始化设置,比如显卡、键盘和硬盘等设备。为了实现这个“预先执行”功能,PCI设备需要提供一段ROM程序,而处理器在初始化过程中将运行这段ROM程序,初始化这些PCI设备。Expansion ROM base address记载这段ROM程序的基地址。
(7) Capabilities Pointer寄存器
在PCI设备中,该寄存器是可选的,但是在PCI-X和PCIe设备中必须支持这个寄存器,Capabilities Pointer寄存器存放Capabilities寄存器组的基地址,PCI设备使用Capabilities寄存器组存放一些与PCI设备相关的扩展配置信息。该组寄存器的详细说明见第4.3节。
(8) Interrupt Line寄存器
这个寄存器是系统软件对PCI设备进行配置时写入的,该寄存器记录当前PCI设备使用的中断向量号,设备驱动程序可以通过这个寄存器,判断当前PCI设备使用处理器系统中的哪个中断向量号,并将驱动程序的中断服务例程注册到操作系统中 [4]。
该寄存器由系统软件初始化,其保存的值与8259A中断控制器相关,该寄存器的值也是由PCI设备与8259A中断控制器的连接关系决定的。如果在一个处理器系统中,没有使用8259A中断控制器管理PCI设备的中断,则该寄存器中的数据并没有意义。
在多数PowerPC处理器系统中,并不使用8259A中断控制器管理PCI设备的中断请求,因此该寄存器没有意义。即使在x86处理器系统中,如果使用I/O APIC中断控制器,该寄存器保存的内容仍然无效。目前在绝大多数处理器系统中,并没有使用该寄存器存放PCI设备使用的中断向量号。
(9) Interrupt Pin寄存器
这个寄存器保存PCI设备使用的中断引脚,PCI总线提供了四个中断引脚INTA#、INTB#、INTC#和INTD#。Interrupt Pin寄存器为1时表示使用INTA#引脚向中断控制器提交中断请求,为2表示使用INTB#,为3表示使用INTC#,为4表示使用INTD#。
如果PCI设备只有一个子设备时,该设备只能使用INTA#;如果有多个子设备时,可以使用INTBD#信号。如果PCI设备不使用这些中断引脚,向处理器提交中断请求时,该寄存器的值必须为0。值得注意的是,虽然在PCIe设备中并不含有INTAD#信号,但是依然可以使用该寄存器,因为PCIe设备可以使用INTx中断消息,模拟PCI设备的INTA~D#信号,详见第6.3.4节。
(10) Base Address Register 0~5寄存器
该组寄存器简称为BAR寄存器,BAR寄存器保存PCI设备使用的地址空间的基地址,该基地址保存的是该设备在PCI总线域中的地址。其中每一个设备最多可以有6个基址空间,但多数设备不会使用这么多组地址空间。
在PCI设备复位之后,该寄存器将存放PCI设备需要使用的基址空间大小,这段空间是I/O空间还是存储器空间 [5],如果是存储器空间该空间是否可预取,有关PCI总线预读机制的详细说明见第3.4.5节。
系统软件对PCI总线进行配置时,首先获得BAR寄存器中的初始化信息,之后根据处理器系统的配置,将合理的基地址写入相应的BAR寄存器中。系统软件还可以使用该寄存器,获得PCI设备使用的BAR空间的长度,其方法是向BAR寄存器写入0xFFFF-FFFF,之后再读取该寄存器。
处理器访问PCI设备的BAR空间时,需要使用BAR寄存器提供的基地址。值得注意的是,处理器使用存储器域的地址,而BAR寄存器存放PCI总线域的地址。因此处理器系统并不能直接使用“BAR寄存器+偏移”的方式访问PCI设备的寄存器空间,而需要将PCI总线域的地址转换为存储器域的地址。
如果x86处理器系统使能了IOMMU后,这两个地址也并不一定相等,因此处理器系统直接使用这个PCI总线域的物理地址,并不能确保访问PCI设备的BAR空间的正确性。除此之外在Linux系统中,ioremap函数的输入参数为存储器域的物理地址,而不能使用PCI总线域的物理地址。
而在pci_devresource[bar].start参数中保存的地址已经经过PCI总线域到存储器域的地址转换,因此在编写Linux系统的设备驱动程序时,需要使用pci_devresource[bar].start参数中的物理地址,然后再经过ioremap函数将物理地址转换为“存储器域”的虚拟地址。
(11) Command寄存器
该寄存器为PCI设备的命令寄存器,该寄存器在初始化时,其值为0,此时这个PCI设备除了能够接收配置请求总线事务之外,不能接收任何存储器或者I/O请求。系统软件需要合理设置该寄存器之后,才能访问该设备的存储器或者I/O空间。在Linux系统中,设备驱动程序调用pci_enable_device函数,使能该寄存器的I/O和Memory Space位之后,才能访问该设备的存储器或者I/O地址空间。
(12) Status寄存器
该寄存器的绝大多数位都是只读位,保存PCI设备的状态。
(13) Latency Timer寄存器
在PCI总线中,多个设备共享同一条总线带宽。该寄存器用来控制PCI设备占用PCI总线的时间,当PCI设备获得总线使用权,并使能Frame#信号后,Latency Timer寄存器将递减,当该寄存器归零后,该设备将使用超时机制停止 [6]对当前总线的使用。
如果当前总线事务为Memeory Write and Invalidate时,需要保证对一个完整Cache行的操作结束后才能停止当前总线事务。对于多数PCI设备而言,该寄存器的值为32或者64,以保证一次突发传送的基本单位为一个Cache行。
PCIe设备不需要使用该寄存器,该寄存器的值必须为0。因为PCIe总线的仲裁方法与PCI总线不同,使用的连接方法也与PCI总线不同。
3、 PCI桥的配置空间
PCI桥使用的配置空间的寄存器如错误!未找到引用源。PCI桥作为一个PCI设备,使用的许多配置寄存器与PCI Agent的寄存器是类似的,如Device ID、Vendor ID、Status、Command、Interrupt Pin、Interrupt Line寄存器等,本节不再重复介绍这些寄存器。下文将重点介绍在PCI桥中与PCI Agent的配置空间不相同的寄存器。
在这里插入图片描述

与PCI Agent设备不同,在PCI桥中只含有两组BAR寄存器,Base Address Register 0~1寄存器。这两组寄存器与PCI Agent设备配置空间的对应寄存器的含义一致。但是在PCI桥中,这两个寄存器是可选的。如果在PCI桥中不存在私有寄存器,那么可以不使用这组寄存器设置BAR空间。

在大多数PCI桥中都不存在私有寄存器,操作系统也不需要为PCI桥提供专门的驱动程序,这也是这类桥被称为透明桥的原因。如果在PCI桥中不存在私有空间时,PCI桥将这两个BAR寄存器初始化为0。在PCI桥的配置空间中使用两个BAR寄存器的目的是这两个32位的寄存器可以组成一个64位地址空间。
在PCI桥的配置空间中,有许多寄存器是PCI桥所特有的。PCI桥除了作为PCI设备之外,还需要管理其下连接的PCI总线子树使用的各类资源,即Secondary Bus所连接PCI总线子树使用的资源。这些资源包括存储器、I/O地址空间和总线号。
在PCI桥中,与Secondary bus相关的寄存器包括两大类。一类寄存器管理Secondary Bus之下PCI子树的总线号,如Secondary和Subordinate Bus Number寄存器;另一类寄存器管理下游PCI总线的I/O和存储器地址空间,如I/O和Memory Limit、I/O和Memory Base寄存器。在PCI桥中还使用Primary Bus寄存器保存上游的PCI总线号。
其中存储器地址空间还分为可预读空间和不可预读空间,Prefetchable Memory Limit和Prefetchable Memory Base寄存器管理可预读空间,而Memory Limit、Memory Base管理不可预读空间。在PCI体系结构中,除了了ROM地址空间之外,PCI设备使用的地址空间大多都是不可预读的。
(1) Subordinate Bus Number、Secondary Bus Number和Primary Bus Number寄存器
PCI桥可以管理其下的PCI总线子树。其中Subordinate Bus Number寄存器存放当前PCI子树中,编号最大的PCI总线号。而Secondary Bus Number寄存器存放当前PCI桥Secondary Bus使用的总线号,这个PCI总线号也是该PCI桥管理的PCI子树中编号最小的PCI总线号。因此一个PCI桥能够管理的PCI总线号在Secondary Bus Number~Subordinate Bus Number之间。这两个寄存器的值由系统软件遍历PCI总线树时设置。
Primary Bus Number寄存器存放该PCI桥上游的PCI总线号,该寄存器可读写。Primary Bus Number、Subordinate Bus Number和Secondary Bus Number寄存器在初始化时必须为0,系统软件将根据这几个寄存器是否为0,判断PCI桥是否被配置过。
不同的操作系统使用不同的Bootloader引导,有的Bootloader可能会对PCI总线树进行遍历,此时操作系统可以不再重新遍历PCI总线树。在x86处理器系统中,BIOS会遍历处理器系统中的所有PCI总线树,操作系统可以直接使用BIOS的结果,也可以重新遍历PCI总线树。而PowerPC处理器系统中的Bootloader,如U-Boot并没有完全遍历PCI总线树,此时操作系统必须重新遍历PCI总线树。
(2) Secondary Status寄存器
该寄存器的含义与PCI Agent配置空间的Status寄存器的含义相近,PCI桥的Secondary Status寄存器记录Secondary Bus的状态,而不是PCI桥作为PCI设备时使用的状态。在PCI桥配置空间中还存在一个Status寄存器,该寄存器保存PCI桥作为PCI设备时的状态。
(3) Secondary Latency Timer寄存器
该寄存器的含义与PCI Agent配置空间的Latency Timer寄存器的含义相近,PCI桥的Secondary Latency Timer寄存器管理Secondary Bus的超时机制,即PCI桥发向下游的总线事务;在PCI桥配置空间中还存在一个Latency Timer寄存器,该寄存器管理PCI桥发向上游的总线事务。
(4) I/O Limit和I/O Base寄存器
在PCI桥管理的PCI子树中包含许多PCI设备,而这些PCI设备可能会使用I/O地址空间。PCI桥使用这两个寄存器,存放PCI子树中所有设备使用的I/O地址空间集合的基地址和大小。
(5) Memory Limit和Memory Base寄存器
在PCI桥管理的PCI子树中有许多PCI设备,这些PCI设备可能会使用存储器地址空间。这两个寄存器存放所有这些PCI设备使用的,存储器地址空间集合的基地址和大小,PCI桥规定这个空间的大小至少为1MB。
(6) Prefetchable Memory Limit和Prefetchable Memory Base寄存器
在PCI桥管理的PCI子树中有许多PCI设备,如果这些PCI设备支持预读,则需要从PCI桥的可预读空间中获取地址空间。PCI桥的这两个寄存器存放这些PCI设备使用的,可预取存储器空间的基地址和大小。
如果PCI桥不支持预读,则其下支持预读的PCI设备需要从Memory Base寄存器为基地址的存储器空间中获取地址空间。如果PCI桥支持预读,其下的PCI设备需要根据情况,决定使用可预读空间,还是不可预读空间。PCI总线建议PCI设备支持预读,但是支持预读的PCI设备并不多见。
(7) I/O Base Upper 16 Bits and I/O Limit Upper 16寄存器
如果PCI桥仅支持16位的I/O端口,这组寄存器只读,且其值为0。如果PCI桥支持32位I/O端口,这组寄存器可以提供I/O端口的高16位地址。
(8) Bridge Control Register。
该寄存器用来管理PCI桥的Secondary Bus,其主要位的描述如下。
Secondary Bus Reset位,第6位,可读写。当该位为1时,将使用下游总线提供的RST#信号复位与PCI桥的下游总线连接的PCI设备。通常情况下与PCI桥下游总线连接的PCI设备,其复位信号需要与PCI桥提供的RST#信号连接,而不能与HOST主桥提供的RST#信号连接。
Primary Discard Timer位,第8位,可读写。PCI桥支持Delayed传送方式,当PCI桥的Primary总线上的主设备使用Delayed方式进行数据传递时,PCI桥使用Retry周期结束Primary总线的Non-Posted数据请求,并将这个Non-Posted数据请求转换为Delayed数据请求,之后主设备需要择时重试相同的Non-Posted数据请求。当该位为1时,表示在Primary Bus上的主设备需要在210个时钟周期之内重试这个数据请求,为0时,表示主设备需要在215个时钟周期之内重试这个数据请求,否则PCI桥将丢弃Delayed数据请求。
Secondary Discard Timer位,第9位,可读写。当该位为1时,表示在Secondary Bus上的主设备需要在210个时钟周期之内重试这个数据请求,为0时,表示主设备需要在215个时钟周期之内重试这个数据请求,如果主设备在规定的时间内没有进行重试时,PCI桥将丢弃Delayed数据请求。

[1] PCI SIG分配给Intel的Vendor ID号是0x8086,8086处理器也是Intel设计的第一个PC处理器。
[2] 这仅是Intel为82571的Copper口分配的Vendor ID.
[3] Xilinx使用的Device ID号为0x10EE,而LogiCORE的Vendor ID号为0x0007。
[4] Linux系统使用request_irq函数注册一个设备的中断服务例程。
[5] 一般来说PCI设备使用E2PROM保存BAR寄存器的初始值。
[6] 此时GNT#信号为无效。为提高仲裁效率,PCI设备在进行数据传送时,GNT#信号可能已经无效。

4、example 遍历pci设备(borland C)
4.1 获取PCI列表
方法一:
直接访问CF8、CFCH端口的方法:
1.定义PCI设备索引和配置空间寄存器
2.填充bus,dev,func的16位空间
3.读写32位端口CF8,CFC,16位编译器加入机器码
4.枚举pci设备(获取厂商,厂商ID,class code,IRQ等)
方法二:
使用pci BIOS 中断(中断号:1AH位B1,AL位功能号)
1.定义pci设备索引和使用WORD方式读取配置字来访问PCI配置空间
2.定义union来缓存pci信息
3.枚举PCI设备(获取厂商,厂商ID,class code,IRQ等)

注:
在这里插入图片描述

使用端口遍历PCI列表:

#include <stdio.h>
#include <conio.h>
#include <dos.h>
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;

/* PCI设备索引。bus/dev/func 共16位,为了方便处理可放在一个WORD中 */
#define PDI_BUS_SHIFT  8
#define PDI_BUS_SIZE  8
#define PDI_BUS_MAX  0xFF
#define PDI_BUS_MASK  0xFF00
#define PDI_DEVICE_SHIFT  3
#define PDI_DEVICE_SIZE  5
#define PDI_DEVICE_MAX   0x1F
#define PDI_DEVICE_MASK   0x00F8
#define PDI_FUNCTION_SHIFT   0
#define PDI_FUNCTION_SIZE   3
#define PDI_FUNCTION_MAX  0x7
#define PDI_FUNCTION_MASK  0x0007
#define MK_PDI(bus,dev,func)   (WORD)((bus&PDI_BUS_MAX)<<PDI_BUS_SHIFT |(dev&PDI_DEVICE_MAX)<<PDI_DEVICE_SHIFT | (func&PDI_FUNCTION_MAX) )

/* PCI配置空间寄存器 */
#define PCI_CONFIG_ADDRESS   0xCF8
#define PCI_CONFIG_DATA   0xCFC

/* 填充PCI_CONFIG_ADDRESS */
#define MK_PCICFGADDR(bus,dev,func)  (DWORD)(0x80000000L | (DWORD)MK_PDI(bus,dev,func)<<8)

/* 读32位端口 */
DWORD inpd(int portid)
{
 DWORD dwRet;
 asm mov dx, portid;
 asm lea bx, dwRet;
 __emit__(
  0x66,0x50, // push EAX
  0x66,0xED, // in EAX,DX
  0x66,0x89,0x07,// mov [BX],EAX
  0x66,0x58); // pop EAX
 return dwRet;
}
/* 写32位端口 */
void outpd(int portid, DWORD dwVal)
{
 asm mov dx, portid;
 asm lea bx, dwVal;
 __emit__(
  0x66,0x50, // push EAX
  0x66,0x8B,0x07, // mov EAX,[BX]
  0x66,0xEF, // out DX,EAX
  0x66,0x58); // pop EAX
 return;
}

int main(void)
{
 int bus, dev, func;
 int i;
 DWORD dwAddr;
 DWORD dwData;
 FILE* hF;
 char szFile[0x10];
 printf("\n");
 printf("Bus#\tDevice#\tFunc#\tVendor\tDevice\tClass\tIRQ\tIntPin\n");
 /* 枚举PCI设备 */
 for(bus = 0; bus <= PDI_BUS_MAX; ++bus) {
  for(dev = 0; dev <= PDI_DEVICE_MAX; ++dev) {
   for(func = 0; func <= PDI_FUNCTION_MAX; ++func) {
	 /* 计算地址 */
	dwAddr = MK_PCICFGADDR(bus, dev, func);

   /* 获取厂商ID */
	outpd(PCI_CONFIG_ADDRESS, dwAddr);
	dwData = inpd(PCI_CONFIG_DATA);
	/* 判断设备是否存在。FFFFh是非法厂商ID */
	if ((WORD)dwData != 0xFFFF) {
	 /* bus/dev/func */
	 printf("%2.2X\t%2.2X\t%1X\t", bus, dev, func);
	 /* Vendor/Device */
	 printf("%4.4X\t%4.4X\t", (WORD)dwData, dwData>>16);
	 /* Class Code */
	 outpd(PCI_CONFIG_ADDRESS, dwAddr | 0x8);
	 dwData = inpd(PCI_CONFIG_DATA);
	 printf("%6.6lX\t", dwData>>8);
	 /* IRQ/intPin */
	 outpd(PCI_CONFIG_ADDRESS, dwAddr | 0x3C);
	 dwData = inpd(PCI_CONFIG_DATA);
	 printf("%d\t", (BYTE)dwData);
	 printf("%d", (BYTE)(dwData>>8));
	 printf("\n");
	 /* 写文件 */
	 sprintf(szFile, "PCI%2.2X%2.2X%X.bin", bus, dev, func);
	 hF = fopen(szFile, "wb");
	 if (hF != NULL) {
	  /* 256字节的PCI配置空间 */
	  for (i = 0; i < 0x100; i += 4) {
	  /* Read */
	   outpd(PCI_CONFIG_ADDRESS, dwAddr | i);
	   dwData = inpd(PCI_CONFIG_DATA);
	   /* Write */
	   fwrite(&dwData, sizeof(dwData), 1, hF);
	   }
	   fclose(hF);
	 }
	}
   }
  }
 }
 return 0;
}

使用BIOS 中断遍历PCI列表

#include <stdio.h>
#include <conio.h>
#include <dos.h>
typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef unsigned long DWORD;

// PCI设备索引。bus/dev/func 共16位,为了方便处理可放在一个WORD中 
#define PDI_BUS_SHIFT  8
#define PDI_BUS_SIZE  8
#define PDI_BUS_MAX  0xFF
#define PDI_BUS_MASK  0xFF00
#define PDI_DEVICE_SHIFT  3
#define PDI_DEVICE_SIZE  5
#define PDI_DEVICE_MAX   0x1F
#define PDI_DEVICE_MASK   0x00F8
#define PDI_FUNCTION_SHIFT   0
#define PDI_FUNCTION_SIZE   3
#define PDI_FUNCTION_MAX  0x7
#define PDI_FUNCTION_MASK  0x0007
#define MK_PDI(bus,dev,func)   (WORD)((bus&PDI_BUS_MAX)<<PDI_BUS_SHIFT |(dev&PDI_DEVICE_MAX)<<PDI_DEVICE_SHIFT | (func&PDI_FUNCTION_MAX) )


int main(void)
{
  int bus,dev,func;
  int i;
  union REGS regs;
  WORD wAddr;
  FILE* fp;
  char szFile[0x10];
  printf("\n");
  printf("Bus#\tDevice#\tFunc#\tVendor\tDevice\tClass\tIRQ\tIntPin\n");
 / 枚举PCI设备 
 for(bus = 0; bus <= PDI_BUS_MAX; ++bus) {
  for(dev = 0; dev <= PDI_DEVICE_MAX; ++dev) {
   for(func = 0; func <= PDI_FUNCTION_MAX; ++func) {
   //计算地址 
   wAddr = MK_PDI(bus,dev,func);

   /获取厂商ID 
   regs.x.ax=0xB109;
   regs.x.bx=wAddr;
   regs.x.di=0;
   regs.x.cx=0xFFFF;//非法的vendor ID
   int86(0x1A,&regs,&regs);

   // 判断设备是否存在。FFFFh是非法厂商ID 
   if(regs.x.cx!=0xFFFF){
   // bus/dev/func 
   printf("%2.2X\t%2.2X\t%1X\t", bus, dev, func);
   //Vendor

   printf("%4.4X\t", regs.x.cx);
   /*
   switch(regs.x.cx)
   {
	case 1106: printf("%3d VIM %4.4X\t",regs.x.cx);
	break;
   //	case "10EC": printf("%3d Realtek" );
   //	break;
	default:printf("%3d nodefnal" );
   }
   */
   //Device 
   regs.x.ax=0xB109;
   regs.x.bx=wAddr;
   regs.x.di=2;//Debice ID
   int86(0x1A,&regs,&regs);
   printf("%4.4X\t",regs.x.cx);

   // Class Code 
   regs.x.ax=0xB109;
   regs.x.bx=wAddr;
   regs.x.di=0xA;//Debice ID
   int86(0x1A,&regs,&regs);
   printf("%4.4X\t",regs.x.cx);

   // IRQ/intPin 
   regs.x.ax=0xB109;
   regs.x.bx=wAddr;
   regs.x.di=0x3C;//Debice ID
   int86(0x1A,&regs,&regs);
   printf("%d\t",(BYTE)regs.x.cx);
   printf("%d",(BYTE)(regs.x.cx>>8));
   printf("\n");
   // 写文件 
   sprintf(szFile, "PCI%2.2X%2.2X%X.bin", bus, dev, func);
   fp = fopen(szFile, "wb");
   if (fp != NULL) {
    // 256字节的PCI配置空间
    for (i = 0; i < 0x100; i += 2) {
    // Read 
      regs.x.ax=0xB109;
      regs.x.bx=wAddr;
      regs.x.di=i;
      int86(0x1A,&regs,&regs);
      fwrite(&regs.x.cx,2,1,fp);
    }
	 fclose(fp);
   }
  }
   }
  }
 }
 return 0;
}

二、获取PCI speed 和version

1、在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

#include<string.h>
#include<stdio.h>
#include<dos.h>
#include<conio.h>

typedef unsigned char BYTE;//1byte--4bit
typedef unsigned int WORD;//2byte--8bit
typedef unsigned long DWORD;//4byte--16bit

#define PDI_BUS_MAX 0xFF
#define PDI_DEVICE_MAX 0x1F
#define PDI_FUNCTION_MAX 0x7

#define PCI_CONFIG_ADDRESS 0xCF8
#define PCI_CONFIG_DATA 0xCFC

/*filled with one WORD*/
#define MK_PDI(bus,dev,func) (WORD)((bus<<8)|(dev<<3)|func)
/*filled with PCI_CONFIG_ADDRESS*/
#define MK_PCIaddr(bus,dev,func) ((DWORD)0x80000000L|(DWORD)MK_PDI(bus,dev,func)<<8)

/*read 32bit port*/
DWORD inpd(int portid)
{
 DWORD dwRet;
 asm mov dx, portid;
 asm lea bx, dwRet;
 __emit__(
  0x66,0x50, // push EAX
  0x66,0xED, // in EAX,DX
  0x66,0x89,0x07,// mov [BX],EAX
  0x66,0x58); // pop EAX
 return dwRet;
}
/*write 32bit port*/
void outpd(int portid, DWORD dwVal)
{
 asm mov dx, portid;
 asm lea bx, dwVal;
 __emit__(
  0x66,0x50, // push EAX
  0x66,0x8B,0x07, // mov EAX,[BX]
  0x66,0xEF, // out DX,EAX
  0x66,0x58); // pop EAX
 return;
}
DWORD GetData(DWORD address)
{
  DWORD data;
  outpd(0xCF8,address);
  data=inpd(0xCFC);
  return data;
}
void main()
{
  int bus,dev,func;
  int i;
  DWORD address;
  DWORD data;

  printf("Bus#\tDevice#\tFunc#\tVersion#\tWidth\tWorkSpeed\tConfigSpeed\n");

  for (bus = 0; bus <= 0xFF; ++bus)
  {
	for (dev = 0; dev <= 0x1F; ++dev)
	{
	  for (func = 0; func <= 0x7; ++func)
	  {
			address =MK_PCIaddr(bus,dev,func);
		  data = GetData(address);
		if((WORD)data!=0xFFFF)
		{
		  /* bus/dev/func */
		  printf("%1X\t%1X\t%1X\t", bus, dev, func);

		  DWORD address1=address|0x34;
		  data=GetData(address1);
		if((BYTE)data==0x00)
		  {
			printf("-\t-\t-\t-\n");

		  }else //data!=0
	   {
		address1=address|(BYTE)data;
		data=GetData(address1);

		if ((BYTE)data==0x10)
		{
		  data=GetData(address1|0x0C);

		  switch(((WORD)data)&0x03ff)
				{
					case 0x0011:printf("1.0\t250MB/s\t");
			break;
			case 0x0021:printf("1.0\t1GB/s\t");
			break;
			case 0x0041:printf("1.0\t2GB/s\t");
			break;
			case 0x0101:printf("1.0\t4GB/s\t");
			break;
			case 0x0012:printf("2.0\t500MB/s\t");
			break;
			case 0x0022:printf("2.0\t2GB/s\t");
			break;
			case 0x0042:printf("2.0\t4GB/s\t");
			break;
			case 0x0102:printf("2.0\t8GB/s\t");
			break;
			case 0x0013:printf("3.0\t984.6MB/s\t");
			break;
			case 0x0023:printf("3.0\t3.938GB/s\t");
			break;
			case 0x0043:printf("3.0\t7.877GB/s\t");
			break;
			case 0x0103:printf("3.0\t15.754GB/s\t");
			break;
			default:    printf("-\t-\t");
			break;
		}
		data=GetData(address1|0x10);
		switch((BYTE)(data>>16))
		{
		  case 0x01:printf("2.5GT/s\t");
		  break;
		  case 0x02:printf("5GT/s\t");
		  break;
		  case 0x03:printf("8GT/s\t");
		  break;
		  case 0x11:printf("2.5GT/s\t");
		  break;
		  case 0x12:printf("5GT/s\t");
		  break;
		  case 0x13:printf("8GT/s\t");
		  break;
		  default:  printf("-\t");
		  break;
		}
		data=GetData(address1|0x30);
		switch((BYTE)data)
		{
		  case 0x01:printf("2.5GT/s\n");
		  break;
		  case 0x02:printf("5GT/s\n");
		  break;
		  case 0x03:printf("8GT/s\n");
		  break;
		  case 0x04:printf("16GT/s\n");
		  break;
		  case 0x05:printf("32GT/s\n");
		  break;
		  default:  printf("-\n");
		  break;
		}

		  }
	   else
		{
			address1=address|(BYTE)(data>>8);
				data=GetData(address1);
		}
	   printf("-\t-\t-\t-\n");

	   }
	   //printf("-\t-\t-\t-\n");
	   break;

		}
	  }
	}
  }
    return;
  }
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值