PCI设备驱动开发

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设备驱动开发_第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设备驱动开发_第2张图片
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设备驱动开发_第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);
			}
		}
	}
	

方式二: 使用提供的函数HalGetBusData

//注意该方式在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));

	}

}

WDM 方式三:转发IRP_MN_START_DEVICE

与枚举设备资源代码一样.

使用PCI配置空间

PCI设备驱动开发_第4张图片

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;
}

你可能感兴趣的:(驱动编程)