PCI(外设部件互连标准)总线标准是一种将系统外部设备连接起来的总线标准,它是PC中最重要的总线。
其他总线如ISA总线、USB等总线都挂在PCI总线之上。
PCI ( Peripheral Component Interconnect) 总线是当前最流行的总线之一,是由Intel
公司首先推出的一种局部总线。它定义了32位数据地址总线,并且可扩展为64位,其支
持突发读写操作,也同时可以支持多组外围设备。
PCI局部总线不能兼容在其之前出现的ISA、EISA、MCA (Micro Channel Architecture)
等总线。
但是大多数基于上述总线的设备可以通过自行设计PCI桥接电路挂接在PCI局部
总线之上。实际上在当前的PC体系结构内,几乎所有外部设备采用的各种各样的接口总
线,均是通过桥接电路挂接在PCI系统内的。
在这种PCI系统中,Host/PCI 桥称为北桥,连接主处理器总线到基础PCI局部总线。使用北桥来连接CPU是为了CPU的安全,其他外设如果直接连接CPU,电压的不稳定容易烧毁CPU。
PCI-ISA 桥称为南桥,连接基础PCI总线到ISA总线。其中南桥通常还含有中断控制器、IDE控制器、USB控制器和DMA控制器等设备。
南桥和北桥组成主板的芯片组。通过芯片组的扩展,实现了多种总线与基础PCI局部总线的桥接,从而实现了多种总线对整个PC系统的挂接。在基础PCI局部总线或PCI插入卡上,可以嵌入一个或多个PCI-PCI桥,从而实现在基础PCI局部总线上挂接多个PCI设备。如图16-1所示为PCI总线、扩展总线、处理器和存储器总线间的基本关系。
PCI有三个相互独立的物理地址空间:设备存储器地址空间、I/O 地址空间和配置空
间。配置空间是PCI所特有的一个物理空间。由于PCI支持设备即插即用,所以PCI设备
不占用固定的内存地址空间或IO地址空间,而是可以由操作系统决定其映射的基址。
系统加电时,BIOS 检测PCI总线,确定所有连接在PCI总线上的设备以及它们的配
置要求,并进行系统配置。所以,所有的PCI设备必须实现配置空间,从而能够实现参数
自动配置,实现真正的即插即用。
PCI总线规范定义的配置空间总长度为256个字节,配置信息按一定的顺序和大小依
次存放。前64个字节的配置空间称为配置头,对于所有的设备都-一样, 配置头的功能主
要是用来识别设备,定义主机访问PCI卡的方式(I/O访问或者存储器访问,还有中断信
息)。其余的192 个字节空间称为本地配置空间,主要定义卡上局部总线的特性、本地空间基地址及范围等。
PC12.2规范定义了三种配置头格式:类型0、类型1和类型2。其中类型1适用于
PCI-to-PCI桥设备,用于将两条PCI总线进行连接;类型2适用于PCI-CardBus (主要用
于笔记本的插卡式总线)桥,在PC Card规范中进行定义:类型0用于除类型1和类型2
以外所有的PCI设备。一般设备都采用类型0的配置头。
PCI大大地提高了系统的易配置性,这些是通过由PCI设备提供相应的功能来实现的。
虽然寄存器的具体位的设置因设备而异,但是所有的寄存器必须能够被读写。这些寄存器
的操作要能反映以下的信息:
(1) VendorID:厂商ID。知名的设备厂商的ID。 FFFFh是一个非法厂商ID, 它判断
PCI设备是否存在。
(2) DeviceID:设备ID。某厂商生产的设备的ID.操作系统就是凭着Vendor ID和
Device ID找到对应驱动程序的。
(3) ClassCode: 类代码。共三个字节,分别是类代码、子类代码及编程接口。类代
码不仅用于区分设备类型,还是编程接口的规范,这就是为什么会有通用驱动程序的原因。
(4) IRQLine: IRQ编号。PC机以前是靠两片8259芯片来管理16个硬件中断。现在
为了支持对称多处理器,有了APIC (高级可编程中断控制器),它支持管理24个中断。
(5) IRQ Pin:中断引脚。PCI 有4个中断引脚,该寄存器表明该设备连接的是哪个引脚。
(6)设备控制:这是由Command 寄存器来实现的,提供了控制设备对于PCI访问的
响应以及执行的能力,Command寄存器的具体各位的信息可以通过PCI规范查得。
(7)设备状态: Status寄存器用来记录一个PCI设备当前的状态,Status寄存器的具
体各位的信息也可以通过PCI规范查到。
(8)基地址:基地址寄存器用来存放PCI设备映射的存储器地址或者使用的I/O空间
的首地址。PCI规范提供一种机制使得I/O和存储器分开,即在基地址寄存器的最低位上,
如果是0,表示该基地址寄存器指向的是一个存储器空间,而如果是1,就是指向一个I/O
空间。
访问PCI配置空间可通过访问两个寄存器,CONFIG_ADDRESS寄存器和CONFIG_
DATA寄存器。这两个寄存器在PC中分别对应着CF8h和CFCh端口,并且是32位端口,
即读写要用32位的IN和OUT汇编指令。其中CONFIG_ ADDRESS寄存器的内容如图16-3
所示。
每个PCI设备可应用三个信息进行定位,即Bus Number、Device Number及Function
Number.另外,PCI配置空间- -共是256个字节,被分割成64个4字节的寄存器,从0-63
编号。
每次要访问PCI配置空间时,先设置CONFIG_ADDRESS寄存器.这时CONFIG_DATA
寄存器中的内容就对应着该PCI配置空间中的相应寄存器。
例如,要访问Bus Number=0,Device Number=1, Function Number=2的PCI配置空间
中的Status和Command。
首先查看Status 和Command在PCI配置空间位于第1号寄存器(最开始的寄存器是
0号),然后将Bus Number(总线号), Device Number(设备号), Function Number(功能号)按照图16-3中的格式填好,
即0x80000A04 (二进制是1000 0000 0000 0000 0000 1010 0000 0100)。2-7位的值代表寄存器号,这里是1,说明就是04h寄存器(Status和Command)
然后设置CONFIG ADDRESS寄存器(0xCF8 端口)为0x80000A04,然后查看CONFIG_ DATA寄
存器(0xCFC端口),得出的内容即Status和Command (Status 在高16位,Command 在
低16位)。
VOID DisplayPCIConfiguation(int bus,int dev,int func)
{
ULONG Addr;
ULONG Data;
PCI_COMMON_HEADER pch;
PCI_SLOT_NUMBER SlotNumber;
SlotNumber.u.AsULONG = 0;
//设置设备号
SlotNumber.u.bits.DeviceNumber = dev;
//设置功能号
SlotNumber.u.bits.FunctionNumber = func;
//0x80000000 判断VendorId是0XFFFF 就直接返回 代表是无效的ID
Addr = 0x80000000 | (bus << 16) | (SlotNumber.u.AsULONG << 8);
WRITE_PORT_ULONG((PULONG)0xcf8, Addr);
ULONG sc = READ_PORT_ULONG((PULONG)0xcfc);
if ((sc&0xffff)==0xffff)
{
return;
}
//这里可以少读一次 懒得写.
//2-7位代表寄存器好 对应CONFIG_ADDRESS的第几个寄存器,0代表第一个 4代表第二个
Addr = 0x80000000 | (bus << 16) | (SlotNumber.u.AsULONG << 8);
//头部复制40字节就够了 后面的都是保留的
for (ULONG i = 0; i < 40;i+=4)
{
WRITE_PORT_ULONG((PULONG)0xcf8, Addr|i);
ULONG sc = READ_PORT_ULONG((PULONG)0xcfc);
ULONG des = (ULONG)&pch + i;
RtlCopyMemory((PULONG)des, &sc, 4);
}
KdPrint(("-----------------------\n\n\n"));
KdPrint(("总线=%x 设备号=%x 功能号=%x \n",bus,dev,func));
KdPrint(("VendorID=%x\n DeviceID=%x\n Command=%x\n Status=%x\n ", pch.VendorID,pch.DeviceID,pch.Command,pch.Status));
DriverEntry中调用
for (int bus = 0; bus < PCI_MAX_BRIDGE_NUMBER;bus++)
{
for (int dev = 0; dev < PCI_MAX_DEVICES;dev++)
{
for (int func = 0; func < PCI_MAX_FUNCTION;func++)
{
DisplayPCIConfiguation(bus,dev,func);
}
}
}
//注意该方式在64位系统下需要做如下申明
#ifdef _AMD64_
NTHALAPI
ULONG
HalGetBusData(
_In_ BUS_DATA_TYPE BusDataType,
_In_ ULONG BusNumber,
_In_ ULONG SlotNumber,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length
);
#endif
VOID DisplayPCIConfiguation2(int bus, int dev, int func)
{
//用来设置CONFIG_ADDRESS 的值 用的位域定义PCI_SLOT_NUMBER
PCI_SLOT_NUMBER SlotNumber;
PCI_COMMON_CONFIG Config;
PPCI_COMMON_HEADER ConfigHeader;
ULONG Size = 0;
SlotNumber.u.AsULONG = 0;
SlotNumber.u.bits.DeviceNumber = dev;
SlotNumber.u.bits.FunctionNumber = func;
Size=HalGetBusData(PCIConfiguration,bus,SlotNumber.u.AsULONG,&Config,sizeof(PCI_COMMON_HEADER));
if (Size==PCI_COMMON_HDR_LENGTH)
{
PPCI_COMMON_HEADER pch = (PPCI_COMMON_HEADER)&Config;
KdPrint(("-----------------------\n\n\n"));
KdPrint(("总线=%x 设备号=%x 功能号=%x \n", bus, dev, func));
KdPrint(("VendorID=%x\n DeviceID=%x\n Command=%x\n Status=%x\n ", pch->VendorID, pch->DeviceID, pch->Command, pch->Status));
}
}
与枚举设备资源代码一样.
typedef struct _DEVICE_EXTENSION
{
PDEVICE_OBJECT fdo;
PDEVICE_OBJECT NextStackDevice;
UNICODE_STRING interfaceName; // 接口名
//中断对象
PKINTERRUPT InterruptObject;
PUCHAR BarMem0;
PUCHAR BarMem1;
PUCHAR PortBase;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
BOOLEAN
InterruptService(
_In_ struct _KINTERRUPT *Interrupt,
_In_opt_ PVOID ServiceContext
)
{
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)ServiceContext;
char Buffer[1024] = { 0 };
READ_PORT_BUFFER_UCHAR(pdx->BarMem0, Buffer,1024);
WRITE_PORT_BUFFER_UCHAR(pdx->BarMem0, Buffer, 1024);
UCHAR c=READ_PORT_UCHAR(pdx->PortBase);
return TRUE;
}
NTSTATUS InitMyPci(PDEVICE_EXTENSION pdx,PCM_PARTIAL_RESOURCE_LIST resource)
{
NTSTATUS status = STATUS_SUCCESS;
KdPrint(("------------------------\n"));
//枚举资源
ULONG nres = resource->Count;
ULONG i;
BOOLEAN bFirst = TRUE;
for (ULONG i = 0; i < resource->Count;i++)
{
switch (resource->PartialDescriptors[i].Type)
{
case CmResourceTypeMemory:
KdPrint(("内存%11x:%x", resource->PartialDescriptors[i].u.Memory.Start.QuadPart,
resource->PartialDescriptors[i].u.Memory.Length));
if (bFirst)
{
pdx->BarMem0 = (PUCHAR)MmMapIoSpace(
resource->PartialDescriptors[i].u.Memory.Start,//要映射的物理地址
resource->PartialDescriptors[i].u.Memory.Length,//映射的长度
MmNonCached);
bFirst = FALSE;
}
else
{
pdx->BarMem1 = (PUCHAR)MmMapIoSpace(
resource->PartialDescriptors[i].u.Memory.Start,//要映射的物理地址
resource->PartialDescriptors[i].u.Memory.Length,//映射的长度
MmNonCached);
}
break;
case CmResourceTypePort:
KdPrint(("端口%11x:%x", resource->PartialDescriptors[i].u.Port.Start.QuadPart,
resource->PartialDescriptors[i].u.Port.Length));
//判断是否需要映射 端口一般不需要映射
if (resource->PartialDescriptors[i].Flags==CM_RESOURCE_PORT_IO)
{
pdx->PortBase =(PUCHAR)resource->PartialDescriptors[i].u.Port.Start.LowPart;
}
else
{
pdx->PortBase = (PUCHAR)MmMapIoSpace(
resource->PartialDescriptors[i].u.Port.Start,//要映射的物理地址
resource->PartialDescriptors[i].u.Port.Length,//映射的长度
MmNonCached);
}
break;
case CmResourceTypeInterrupt:
KdPrint(("中断亲缘性:%11x 中断等级:%x 中断向量:%x\n", resource->PartialDescriptors[i].u.Interrupt.Affinity,
resource->PartialDescriptors[i].u.Interrupt.Level,
resource->PartialDescriptors[i].u.Interrupt.Vector));
IoConnectInterrupt(
&pdx->InterruptObject, InterruptService, pdx, NULL,
resource->PartialDescriptors[i].u.Interrupt.Vector,
(KIRQL)resource->PartialDescriptors[i].u.Interrupt.Level,
(KIRQL)resource->PartialDescriptors[i].u.Interrupt.Level,
resource->PartialDescriptors[i].Flags == CM_RESOURCE_INTERRUPT_LATCHED ? Latched : LevelSensitive,
TRUE,
resource->PartialDescriptors[i].u.Interrupt.Affinity,
FALSE
);
break;
default:
DbgPrint("unknown resource\n");
break;
}
}
return status;
}