PCI总线协议
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配置空间简介
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
空间。
通过I/O端口直接读取PCI配置空间
访问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位)。
#include <ntddk.h>#ifdef _AMD64_
ULONG
HalGetBusData(
_In_ BUS_DATA_TYPE BusDataType,
_In_ ULONG BusNumber,
_In_ ULONG SlotNumber,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length
);
#endif
VOID Unload(IN PDRIVER_OBJECT DriverObject)
{KdPrint(("驱动卸载\n"));}VOID DisplayConfig(int bus,int dev,int func)
{PCI_COMMON_CONFIG Config;PPCI_COMMON_HEADER pConfigHeader;PCI_SLOT_NUMBER SlotNumber;ULONG Addr = 0;ULONG Data;ULONG i;SlotNumber.u.AsULONG = 0;SlotNumber.u.bits.DeviceNumber = dev;SlotNumber.u.bits.FunctionNumber = func;Addr = 0x80000000 | (bus << 16) | (SlotNumber.u.AsULONG << 8);for (i = 0; i < 40; i += 4){Addr |= i;WRITE_PORT_ULONG((PULONG)0xcf8, Addr|i);Data = READ_PORT_ULONG((PULONG)0xcfc);((PULONG)&Config)[i>>2] = Data;}pConfigHeader = (PPCI_COMMON_HEADER)&Config;if (pConfigHeader->VendorID == 0xffff || pConfigHeader->VendorID == 0){return;}KdPrint(("VendorID:%x\n", pConfigHeader->VendorID));KdPrint(("DeviceID:%x\n", pConfigHeader->DeviceID));KdPrint(("Command:%x\n", pConfigHeader->Command));KdPrint(("Status:%x\n", pConfigHeader->Status));KdPrint(("RevisionID:%x\n", pConfigHeader->RevisionID));KdPrint(("ProgIf:%x\n", pConfigHeader->ProgIf));KdPrint(("SubClass:%x\n", pConfigHeader->SubClass));KdPrint(("BaseClass:%x\n", pConfigHeader->BaseClass));KdPrint(("CacheLineSize:%x\n", pConfigHeader->CacheLineSize));KdPrint(("LatencyTimer:%x\n", pConfigHeader->LatencyTimer));KdPrint(("HeaderType:%x\n", pConfigHeader->HeaderType));KdPrint(("BIST:%x\n", pConfigHeader->BIST));for (i = 0; i < 6;i++){KdPrint(("Bar:%x\n", pConfigHeader->u.type0.BaseAddresses[i]));}KdPrint(("VendorID:%x\n", pConfigHeader->u.type0.InterruptLine));KdPrint(("VendorID:%x\n", pConfigHeader->u.type0.InterruptPin));
}VOID DisplayConfig11111(int bus, int dev, int func)
{PCI_SLOT_NUMBER SlotNumber;PCI_COMMON_CONFIG Config;PPCI_COMMON_HEADER pConfigHeader;ULONG Size;ULONG i;SlotNumber.u.AsULONG = 0;SlotNumber.u.bits.DeviceNumber = dev;SlotNumber.u.bits.FunctionNumber = func;Size = HalGetBusData(PCIConfiguration, bus, SlotNumber.u.AsULONG, &Config, PCI_COMMON_HDR_LENGTH);if (Size == PCI_COMMON_HDR_LENGTH){pConfigHeader = (PPCI_COMMON_HEADER)&Config;KdPrint(("VendorID:%x\n", pConfigHeader->VendorID));KdPrint(("DeviceID:%x\n", pConfigHeader->DeviceID));KdPrint(("Command:%x\n", pConfigHeader->Command));KdPrint(("Status:%x\n", pConfigHeader->Status));KdPrint(("RevisionID:%x\n", pConfigHeader->RevisionID));KdPrint(("ProgIf:%x\n", pConfigHeader->ProgIf));KdPrint(("SubClass:%x\n", pConfigHeader->SubClass));KdPrint(("BaseClass:%x\n", pConfigHeader->BaseClass));KdPrint(("CacheLineSize:%x\n", pConfigHeader->CacheLineSize));KdPrint(("LatencyTimer:%x\n", pConfigHeader->LatencyTimer));KdPrint(("HeaderType:%x\n", pConfigHeader->HeaderType));KdPrint(("BIST:%x\n", pConfigHeader->BIST));for (i = 0; i < 6; i++){KdPrint(("Bar:%x\n", pConfigHeader->u.type0.BaseAddresses[i]));}KdPrint(("VendorID:%x\n", pConfigHeader->u.type0.InterruptLine));KdPrint(("VendorID:%x\n", pConfigHeader->u.type0.InterruptPin));}else{}}VOID GetAllPciDevice()
{PCI_SLOT_NUMBER slotData;PCI_COMMON_CONFIG pciData;PCI_COMMON_CONFIG MyPciData = {0};ULONG pciBus;ULONG slotNumber;ULONG functionNumber;PPCI_COMMON_HEADER pConfigHeader = NULL;ULONG length;slotData.u.AsULONG = 0;for (pciBus = 0; pciBus < PCI_MAX_BRIDGE_NUMBER; pciBus++){for (slotNumber = 0; slotNumber < PCI_MAX_DEVICES; slotNumber++){slotData.u.bits.DeviceNumber = slotNumber;for (functionNumber = 0; functionNumber < PCI_MAX_FUNCTION; functionNumber++){slotData.u.bits.FunctionNumber = functionNumber;RtlZeroMemory(&pciData,sizeof(PCI_COMMON_CONFIG));RtlZeroMemory(&MyPciData, sizeof(PCI_COMMON_CONFIG));//和函数HalGetBusData功能差不多,HalGetBusDataByOffset可以控制读取的起始位置length = HalGetBusDataByOffset(PCIConfiguration,pciBus,slotData.u.AsULONG,&pciData.VendorID,FIELD_OFFSET(PCI_COMMON_CONFIG, VendorID), //读取的起始位置PCI_COMMON_HDR_LENGTH); //读取的大小if (length == 0){continue;}pConfigHeader = (PPCI_COMMON_HEADER)&pciData;if (PCI_INVALID_VENDORID == pConfigHeader->VendorID){continue;}KdPrint(("VendorID:%x\n", pConfigHeader->VendorID));KdPrint(("DeviceID:%x\n", pConfigHeader->DeviceID));KdPrint(("HeaderType:%x\n", pConfigHeader->HeaderType));KdPrint(("Command:%x\n", pConfigHeader->Command));KdPrint(("Status:%x\n", pConfigHeader->Status));KdPrint(("RevisionID:%x\n", pConfigHeader->RevisionID));KdPrint(("ProgIf:%x\n", pConfigHeader->ProgIf));KdPrint(("SubClass:%x\n", pConfigHeader->SubClass));KdPrint(("BaseClass:%x\n", pConfigHeader->BaseClass));KdPrint(("CacheLineSize:%x\n", pConfigHeader->CacheLineSize));KdPrint(("LatencyTimer:%x\n", pConfigHeader->LatencyTimer));KdPrint(("BIST:%x\n", pConfigHeader->BIST));for (int i = 0; i < 6; i++){KdPrint(("Bar:%x\n", pConfigHeader->u.type0.BaseAddresses[i]));}KdPrint(("InterruptLine:%x\n", pConfigHeader->u.type0.InterruptLine));KdPrint(("InterruptPin:%x\n\n\n", pConfigHeader->u.type0.InterruptPin));}}}
}NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{int bus, dev, func;KdPrint(("驱动加载\n"));DriverObject->DriverUnload = Unload;for (bus = 0; bus < PCI_MAX_BRIDGE_NUMBER;bus++){for (dev = 0; dev < PCI_MAX_DEVICES;dev++){for (func = 0; func < PCI_MAX_FUNCTION;func++){DisplayConfig(bus, dev, func);}}}return STATUS_SUCCESS;
}
WDM 方式三:转发IRP_MN_START_DEVICE
与枚举设备资源代码一样.
使用PCI配置空间
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;
}