PCI配置空间简介
来源:互联网 发布:破壁机骗局知乎 编辑:程序博客网 时间:2024/06/05 02:38
一、PCI配置空间简介
PCI有三个相互独立的物理地址空间:设备存储器地址空间、I/O地址空间和配置空间。配置空间是PCI所特有的一个物理空间。由于PCI支持设备即插即用,所以PCI设备不占用固定的内存地址空间或I/O地址空间,而是由操作系统决定其映射的基址。
系统加电时,BIOS检测PCI总线,确定所有连接在PCI总线上的设备以及它们的配置要求,并进行系统配置。所以,所有的PCI设备必须实现配置空间,从而能够实现参数的自动配置,实现真正的即插即用。
PCI总线规范定义的配置空间总长度为256个字节,配置信息按一定的顺序和大小依次存放。前64个字节的配置空间称为配置头,对于所有的设备都一样,配置头的主要功能是用来识别设备、定义主机访问PCI卡的方式(I/O访问或者存储器访问,还有中断信息)。其余的192个字节称为本地配置空间(设备有关区),主要定义卡上局部总线的特性、本地空间基地址及范围等。
PCI设备有三个空间——内存地址空间、IO地址空间和配置空间。由于PCI支持即插即用,所以PCI设备不是占用固定的内存地址空间或I/O地址空间,而是可以由操作系统决定其映射的基址。怎么配置呢?这就是配置空间的作用。
配置空间中最重要的有:
Vendor ID:厂商ID。知名的设备厂商的ID。FFFFh是一个非法厂商ID,可它来判断PCI设备是否存在。
Device ID:设备ID。某厂商生产的设备的ID。操作系统就是凭着 Vendor ID和Device ID 找到对应驱动程序的。
Class Code:类代码。共三字节,分别是 类代码、子类代码、编程接口。类代码不仅用于区分设备类型,还是编程接口的规范,这就是为什么会有通用驱动程序。
IRQ Line:IRQ编号。PC机以前是靠两片8259芯片来管理16个硬件中断。现在为了支持对称多处理器,有了APIC(高级可编程中断控制器),它支持管理24个中断。
IRQ Pin:中断引脚。PCI有4个中断引脚,该寄存器表明该设备连接的是哪个引脚。
二、如何访问配置空间
如何访问配置空间呢?可通过访问0xCF8h、0xCFCh端口来实现。
- 0xCF8h: CONFIG_ADDRESS。PCI配置空间地址端口。
- 0xCFCh: CONFIG_DATA。PCI配置空间数据端口。
CONFIG_ADDRESS寄存器格式:
31 位: Enabled位。
23:16 位: 总线编号。
15:11 位: 设备编号。
10: 8 位:功能编号。
7: 2 位:配置空间寄存器编号。
1: 0 位:恒为“00”。这是因为CF8h、CFCh端口是32位端口。
现在有个难题——CF8h、CFCh端口是32位端口,可像Turbo C之类的16位C语言编译器都不支持32位端口访问。怎么办?我们可以使用_ _ emit _ 在程序中插入机器码。每次都 _ emit _ _一下肯定很麻烦,所以我们应该将它封装成函数。代码如下(注意66h是32位指令前缀):
/* 读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;}
三、遍历PCI设备
怎么枚举PCI设备呢?我们可以尝试所有的 bus/dev/func 组合,然后判断得到的厂商ID是否为FFFFh。下面这个程序就是使用该方法枚举PCI设备的。同时为了便于分析数据,将每个设备的配置空间信息保存到文件,这样可以慢慢分析。
Windows下代码如下:
#include <stdio.h>#include <conio.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;}
总线编号为0的都是主板上固有的芯片(主要是南桥),非主板设备的典型是——显卡。WindowsXP的设备管理器中也可以看到PCI信息。启动“设备管理器”,最好将查看方式设为“依连接查看设备(V)”。找到我的显卡,双击查看属性。切换到“详细信息”页,定位组合框为“硬件Id”。可看到其中一行为“PCI/VEN_10DE&DEV_0110&CC_030000”,表示厂商ID为“10DE”、设备ID为“0110”、类代码为“030000”,与程序得到的结果一致。
linux下代码如下
#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/io.h>#define PCI_MAX_BUS 255#define PCI_MAX_DEV 31#define PCI_MAX_FUN 7#define PCI_BASE_ADDR 0x80000000L#define CONFIG_ADDR 0xcf8#define CONFIG_DATA 0xcfctypedef unsigned long DWORD;typedef unsigned int WORD;int main(){ WORD bus, dev, fun; DWORD addr, data; printf("\nbus#\tdev#\tfun#\tvendor#\t\tdevice#\n"); if ( iopl(3) < 0 ) { printf("iopl set error\n"); return -1; } for (bus = 0; bus <= PCI_MAX_BUS; bus++) for (dev = 0; dev <= PCI_MAX_DEV; dev++) for (fun = 0; fun <= PCI_MAX_FUN; fun++) { addr = PCI_BASE_ADDR | (bus << 16) | (dev << 11) | (fun << 8); outl(addr, CONFIG_ADDR); data = inl(CONFIG_DATA); if (((data & 0xFFFF) != 0xFFFF) && (data != 0)) { // bus, dev, fun printf("%02d \t%02d \t%02d \t", bus, dev, fun); // vendorID、deviceID printf("%04x \t\t%04x", (data & 0xFFFF), (data & 0xFFFF0000) >> 16); printf("\n--------------------------------------------\n"); } } if (iopl(0) < 0 ) { printf("iopl set error\n"); return -1; } return 0;}
- PCI配置空间简介
- PCI配置空间简介
- PCI配置空间简介
- PCI配置空间简介
- PCI配置空间简介
- PCI配置空间简介
- PCI配置空间简介
- PCI配置空间简介
- PCI配置空间简介
- PCI配置空间简介
- PCI 配置空间简介
- 一、PCI配置空间简介
- PCI配置空间
- PCI的配置空间
- PCI配置空间
- PCI配置空间
- PCI配置空间
- 浅析PCI配置空间
- android EventBus 3.0 官方的混淆配置
- CTL控制文件的小感悟
- Tomcat-JavaWeb
- thinkPHP中{$Think }用法
- [原创] 删繁就简--1 bit的查找,看了白版主的0bit查找的感想
- PCI配置空间简介
- 服务启动问题总结
- 第九章
- C语言,用EGE图形库实现推箱子小游戏
- 下划线字符转换为驼峰式字符
- 先锋机器人激光建图配置方法(一)
- ubuntu完美搭建git服务器 客户端
- 文章标题
- C语言编译过程