获取PCI设备并初始化

PCI有三个相互独立的物理地址空间:设备存储器地址空间、I/O地址空间和配置空间。配置空间是PCI所特有的一个物理空间。由于PCI支持设备即插即用,所以PCI设备不占用固定的内存地址空间或I/O地址空间,而是可以由操作系统决定映射的基址。

系统加电时,BIOS检测PCI总线,确定所有连接在PCI总线上的设备以及它们配置要求,并进行系统配置。所以,所有PCI设备必须实现配置空间,从而能实现参数自动配置,实现真正的即插即用。

  

前面简单介绍了一下PCI设备的特性。现在来介绍一种方位PCI设备配置空间的常用方式:通过即插即用IRP获得PCI配置空间。

在WDM驱动中,总线驱动会为每个设备提供一个PDO设备,当开发者缩写的功能驱动挂载在PDO之上的时候。就可以将IRP_MN_START_DEVICE传递给底层的PDO去处理。PCI总线的PDO就会得到PCI配置空间,并从中得到有用信息,如中断号、设备物理内存及IO端口信息等。

在处理完IRP_MN_START_DEVICE后,驱动程序会将处理结果存储在IRP的设备堆栈中,从I/O堆栈可以取出CM_FULL_RESOURCE_DESCRIPTOR数据结构,从CM_FULL_RESOURCE_DESCRIPTOR中取出CM_PARTIAL_RESOURCE_LIST数据结构,而在CM_PARTIAL_RESOURCE_LIST中又可以取出CM_PARTIAL_RESOURCE_DESCRIPTOR数据结构。

CM_PARTIAL_RESOURCE_DESCRIPTOR数据结构就是PDO帮主程序员从256字节的PCI配置空间中获取的有用信息。

 

 

下面来具体讨论一下这个过程。首先是当开发者将自己写的FDO挂载到PDO上去之后,就可以将Pnp的IRP_MN_START_DEVICE传递给底层的PDO去处理。

   在Driver_Entry()函数中将我们自己的Pnp处理函数指针填充到对应的MajorFunction中去:

pDriverObject->MajorFunction[IRP_MJ_PNP] = xxxPnp;

 

然后是定义该函数:

   NTSTATUS xxxPnp(IN PDEVICE_OBJECT fdo, IN PIRP Irp)//注意函数的参数

{

         PAGED_CODE();

 

         //      KdPrint(("Enter HBAPnp\n"));

         NTSTATUS status = STATUS_SUCCESS;

         PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION) fdo->DeviceExtension;

         PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

         static NTSTATUS (*fcntab[])(PDEVICE_EXTENSION pdx, PIRP Irp) =

         {

                   HandleStartDevice,          // IRP_MN_START_DEVICE

                   //               DefaultPnpHandler,

                   DefaultPnpHandler,                   // IRP_MN_QUERY_REMOVE_DEVICE

                   HandleRemoveDevice,             // IRP_MN_REMOVE_DEVICE

                   DefaultPnpHandler,                   // IRP_MN_CANCEL_REMOVE_DEVICE

                   DefaultPnpHandler,                   // IRP_MN_STOP_DEVICE

                   DefaultPnpHandler,                   // IRP_MN_QUERY_STOP_DEVICE

                   DefaultPnpHandler,                   // IRP_MN_CANCEL_STOP_DEVICE

                   DefaultPnpHandler,                   // IRP_MN_QUERY_DEVICE_RELATIONS

                   DefaultPnpHandler,                   // IRP_MN_QUERY_INTERFACE

                   DefaultPnpHandler,                   // IRP_MN_QUERY_CAPABILITIES

                   DefaultPnpHandler,                   // IRP_MN_QUERY_RESOURCES

                   DefaultPnpHandler,                   // IRP_MN_QUERY_RESOURCE_REQUIREMENTS

                   DefaultPnpHandler,                   // IRP_MN_QUERY_DEVICE_TEXT

                   DefaultPnpHandler,                   // IRP_MN_FILTER_RESOURCE_REQUIREMENTS

                   DefaultPnpHandler,                   //

                   DefaultPnpHandler,                   // IRP_MN_READ_CONFIG

                   DefaultPnpHandler,                   // IRP_MN_WRITE_CONFIG

                   DefaultPnpHandler,                   // IRP_MN_EJECT

                   DefaultPnpHandler,                   // IRP_MN_SET_LOCK

                   DefaultPnpHandler,                   // IRP_MN_QUERY_ID

                   DefaultPnpHandler,                   // IRP_MN_QUERY_PNP_DEVICE_STATE

                   DefaultPnpHandler,                   // IRP_MN_QUERY_BUS_INFORMATION

                   DefaultPnpHandler,                   // IRP_MN_DEVICE_USAGE_NOTIFICATION

                   DefaultPnpHandler,                   // IRP_MN_SURPRISE_REMOVAL

         };

 

         ULONG fcn = stack->MinorFunction;

         if (fcn >= arraysize(fcntab))

         {                                                       // 未知的子功能代码

                   status = DefaultPnpHandler(pdx, Irp); // some function we don't know about

                   return status;

         }                                            

status = (*fcntab[fcn])(pdx, Irp);

         KdPrint(("Leave HBAPnp\n"));

         return status;

}

在该函数中,首先通过函数IoGetCurrentIrpStackLocation()获得当前堆栈的指针,接着定义了一个函数指针数组static NTSTATUS  (*fcntab[])(PDEVICE_EXTENSION pdx, PIRP Irp) 用于处理不同的Pnp IRP。这里主要针对IRP_MN_START_DEVICE进行说明,其他的说明可以参见上面代码中的简单注释。从上述代码可以看出,如果传到该层驱动的IRP包类型为IRP_MN_START_DEVICE则会调用HandleStartDevice()进行处理。通过HandleStartDevice()函数我们将获得PCI的配置空间信息。接着看一下HandleStartDevice()函数该如何定义:

NTSTATUS HandleStartDevice(PDEVICE_EXTENSION pdx, PIRP Irp)

{

         PAGED_CODE();

 

         //      KdPrint(("Enter HandleStartDevice\n"));

 

         //转发IRP并等待返回

         NTSTATUS status = ForwardAndWait(pdx,Irp);

         if (!NT_SUCCESS(status))

         {

                   Irp->IoStatus.Status = status;

                   IoCompleteRequest(Irp, IO_NO_INCREMENT);

                   return status;

         }

 

         //得到当前堆栈

         PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

 

         //从当前堆栈得到翻译信息

         PCM_PARTIAL_RESOURCE_LIST translated;

         if (stack->Parameters.StartDevice.AllocatedResourcesTranslated)

                   translate= &stack->Parameters.StartDevice.AllocatedResourcesTranslated->List[0].PartialResourceList;

         else

                   translated = NULL;

 

         //      KdPrint(("Init the PCI card!\n"));

         status=InitDevice(pdx,translated);

         if(!NT_SUCCESS(status))

         {

                   KdPrint(("Initialize device failed!%\n"));

                   IoSetDeviceInterfaceState(&pdx->interfaceName, FALSE);

                   RtlFreeUnicodeString(&pdx->interfaceName);

 

                   //调用IoDetachDevice()把fdo从设备栈中脱开:

                   if (pdx->NextStackDevice)

                            IoDetachDevice(pdx->NextStackDevice);

 

                   //删除fdo:

                   IoDeleteDevice(pdx->fdo);

                   RtlFreeUnicodeString(&pdx->devName);

         }

 

         //完成IRP

         Irp->IoStatus.Status = status;

         IoCompleteRequest(Irp, IO_NO_INCREMENT);

 

         //      KdPrint(("Leave HandleStartDevice\n"));

         return status;

}

一进入该函数,首先调用了另一个函数:ForwardAndWait(pdx, Irp);

注意这个函数的参数中包含Irp,结合前面讲解,此时我们是要将即插即用的IRP_MN_START_DEVICE类型的IRP包发送给PDO来处理。这里就涉及到一个处理的同步还是异步问题。

       为了得到底层PDO处理IRP的结果,需要调用PDO后,能够查询IRP的结果。这就面临两个问题。

  1. 不知道PDO是基于同步完成还是异步完成。如果同步完成,即IoCallDrive()

函数返回就标志着PDO处理IRP完成。如果异步完成,IoCallDriver()的返回并不能代表PDO处理IRP完成。

  1. IRP一旦处理完成(即IoCompleteRequest(Irp, IO_NO_INCREMENT)),就不能再对IRP进行操作。但我们需要获得底层设备PDO对IRP的设置情况。

 

解决上述两个问题,需要采用完成例程,并归结为以下几个步骤:

①  插即用IRP进入WDM派遣函数

②  派遣函数初始化一个事件,这个事件作为与PDO同步之用

③  设置完成例程,并将事件作为参数传递给完成例程

④  调用底层驱动,即PDO,并紧接着等待这个同步事件

⑤  底层驱动完成IRP时,触发完成例程

⑥  在完成例程中,将事件设为有效。

⑦  事件有效后,等待停止。

 

ForwardAndWait(pdx, Irp)函数就是将上面的几个步骤封装而得。由于这里设置了完成例程(通过IoSetCompletionRoutine()),所以需要使用IoCopyCurrentIrpStackLocaction()函数。具体原因见之前文章的讨论。

ForWardAndWait()函数具体定义为如下:

NTSTATUS ForwardAndWait(PDEVICE_EXTENSION pdx, PIRP Irp)

{        // ForwardAndWait

         PAGED_CODE();

 

         //      KdPrint(("Entry ForwardAndWait!\n"));

         KEVENT event;

         //初始化事件

         KeInitializeEvent(&event, NotificationEvent, FALSE);

 

         //将本层堆栈拷贝到下一层堆栈

         IoCopyCurrentIrpStackLocationToNext(Irp);

         //设置完成例程

         IoSetCompletionRoutine(Irp, (PIO_COMPLETION_ROUTINE) OnRequestComplete,

                   (PVOID) &event, TRUE, TRUE, TRUE);

 

         //调用底层驱动,即PDO

         IoCallDriver(pdx->NextStackDevice, Irp);

         //等待PDO完成

         KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);

         //      KdPrint(("Leave ForwardAndWait!\n"));

         return Irp->IoStatus.Status;

}                                   

正如前面所说的:在处理完IRP_MN_START_DEVICE后,驱动程序会将处理结果存储在IRP的设备堆栈中,从I/O堆栈可以取出CM_FULL_RESOURCE_DESCRIPTOR数据结构,从CM_FULL_RESOURCE_DESCRIPTOR中取出CM_PARTIAL_RESOURCE_LIST数据结构,而在CM_PARTIAL_RESOURCE_LIST中又可以取出CM_PARTIAL_RESOURCE_DESCRIPTOR数据结构。从HandleStartDevice()函数中的:

translate= &stack->Parameters.StartDevice.AllocatedResourcesTranslated->List[0].PartialResourceList;

可以看出,通过translate变量获得了配置空间的基本信息,这是一个CM_PARTIAL_RESOURCE_LIST类型,CM_PARTIAL_RESOURCE_LIST定义为:

typedef struct _CM_PARTIAL_RESOURCE_LIST {

  USHORT                         Version;

  USHORT                         Revision;

  ULONG                          Count;

  CM_PARTIAL_RESOURCE_DESCRIPTOR PartialDescriptors[1];

} CM_PARTIAL_RESOURCE_LIST, *PCM_PARTIAL_RESOURCE_LIST;

但是其实具体的信息存储来PartialDescriptors中,该结构在内核中的定义为:

typedef struct _CM_PARTIAL_RESOURCE_DESCRIPTOR {

    UCHAR  Type;

    UCHAR  ShareDisposition;

    USHORT  Flags;

    union {

        struct {

            PHYSICAL_ADDRESS  Start;

            ULONG  Length;

        } Generic;

        struct {

            PHYSICAL_ADDRESS  Start;

            ULONG  Length;

        } Port;

        struct {

#if defined(NT_PROCESSOR_GROUPS)

            USHORT  Level;

            USHORT  Group;

#else

            ULONG  Level;

#endif

            ULONG  Vector;

            KAFFINITY Affinity;

        } Interrupt;

 

        // This member exists only on Windows Vista and later

        struct {

            union {

               struct {

#if defined(NT_PROCESSOR_GROUPS)

                   USHORT  Group;

#else

                   USHORT  Reserved;

#endif

                   USHORT  MessageCount;

                   ULONG  Vector;

                   KAFFINITY  Affinity;

               } Raw;

 

               struct {

#if defined(NT_PROCESSOR_GROUPS)

                   USHORT  Level;

                   USHORT  Group;

#else

                   ULONG  Level;

#endif

                   ULONG  Vector;

                   KAFFINITY  Affinity;

               } Translated;       

            };

        } MessageInterrupt;

        struct {

            PHYSICAL_ADDRESS  Start;

            ULONG  Length;

        } Memory;

        struct {

            ULONG  Channel;

            ULONG  Port;

            ULONG  Reserved1;

        } Dma;

        struct { 

            ULONG Channel; 

            ULONG RequestLine; 

            UCHAR TransferWidth; 

            UCHAR Reserved1; 

            UCHAR Reserved2; 

            UCHAR Reserved3; 

        } DmaV3;

        struct {

            ULONG  Data[3];

        } DevicePrivate;

        struct {

            ULONG  Start;

            ULONG  Length;

            ULONG  Reserved;

        } BusNumber;

        struct {

            ULONG  DataSize;

            ULONG  Reserved1;

            ULONG  Reserved2;

        } DeviceSpecificData;

        // The following structures provide support for memory-mapped

        // IO resources greater than MAXULONG

        struct {

            PHYSICAL_ADDRESS  Start;

            ULONG  Length40;

        } Memory40;

        struct {

            PHYSICAL_ADDRESS  Start;

            ULONG  Length48;

        } Memory48;

        struct {

            PHYSICAL_ADDRESS  Start;

            ULONG  Length64;

        } Memory64;

        struct {

            UCHAR Class;

            UCHAR Type;

            UCHAR Reserved1;

            UCHAR Reserved2;

            ULONG IdLowPart;

            ULONG IdHighPart;

        } Connection;             

    } u;

} CM_PARTIAL_RESOURCE_DESCRIPTOR, *PCM_PARTIAL_RESOURCE_DESCRIPTOR;

 

 

 

 

 

 

CM_PARTIAL_RESOURCE_DESCRIPTOR 的第一个参数 Type的实际意义:

Type value

u member substructure

CmResourceTypePort

u.Port

CmResourceTypeInterrupt

u.Interrupt or u.MessageInterrupt.

If the CM_RESOURCE_INTERRUPT_MESSAGE flag of Flags is set, use u.MessageInterrupt; otherwise, use u.Interrupt.

CmResourceTypeMemory

u.Memory

CmResourceTypeMemoryLarge

One of u.Memory40, u.Memory48, or u.Memory64.

The CM_RESOURCE_MEMORY_LARGE_XXX flags set in the Flags member determines which structure is used.

CmResourceTypeDma

u.Dma (if CM_RESOURCE_DMA_V3 is not set) or u.DmaV3 (if CM_RESOURCE_DMA_V3 flag is set)

CmResourceTypeDevicePrivate

u.DevicePrivate

CmResourceTypeBusNumber

u.BusNumber

CmResourceTypeDeviceSpecific

u.DeviceSpecificData

(Not used within IO_RESOURCE_DESCRIPTOR.)

CmResourceTypePcCardConfig

u.DevicePrivate

CmResourceTypeMfCardConfig

u.DevicePrivate

CmResourceTypeConnection

u.Connection

CmResourceTypeConfigData

Reserved for system use.

CmResourceTypeNonArbitrated

Not used.

 

 

当获取了PCI设备空间之后,开发者就可以对PCI设备进行具体的操作。这里通过InitDevice()函数来实现,InitDevice()函数定义如下:

 

NTSTATUS InitDevice(IN PDEVICE_EXTENSION pdx,  IN PCM_PARTIAL_RESOURCE_LIST list)

{

         PAGED_CODE();

         KdPrint(("Enter InitDevice!\n"));

         PDEVICE_OBJECT fdo = pdx->fdo;

         … //各种初始化

 

         PCM_PARTIAL_RESOURCE_DESCRIPTOR   resource = &list->PartialDescriptors[0];

 

         ULONG nres = list->Count;

        

         //获取PCI资源

         for (ULONG i = 0; i < nres; ++i, ++resource)

         {                                   

                   switch (resource->Type)

 

                   {

                            //设备物理内存资源

                   case  CmResourceTypeMemory:

                     pdx->MemBar0 = (PUCHAR)MmMapIoSpace(resource->u.Memory.Start,  

                                     resource->u.Memory.Length,

                                     MmNonCached); //将获得的物理地址映射为系统空间赋给内存基地址0

                            pdx->nMem0 = resource->u.Memory.Length;                  //基地址BAR0占用字节数

                            pdx->RegsPhyBase=resource->u.Memory.Start;     //寄存器物理地址首地址

                            //KdPrint(("pdx->RegsPhyBase = 0x%x\n",pdx->RegsPhyBase));

                            pdx->RegsBase = pdx->MemBar0;             //寄存器虚拟地址首地址

                            pdx->pHBARegs=(PHBA_REGS)pdx->RegsBase; //寄存器地址==寄存器虚拟首址

                            continue;

                            //中断资源

                   case  CmResourceTypeInterrupt:

                            irql = (KIRQL) resource->u.Interrupt.Level;              //中断级别

                            vector = resource->u.Interrupt.Vector;                     //

                            affinity = resource->u.Interrupt.Affinity;

                            mode = (resource->Flags == CM_RESOURCE_INTERRUPT_LATCHED)

                                     ? Latched : LevelSensitive;

                            irqshare = resource->ShareDisposition == CmResourceShareShared;

                            gotinterrupt = TRUE;

                            KdPrint(("i=%u,irqvector= %u\n",i,vector));

                            continue;

                   default:

                            continue;

                   }                           //switch on resource type

         }                                    //for each resource

 

         if (!(gotinterrupt))

         {

                   KdPrint((" Didn't get expected I/O  interrupt resources\n"));

                   return STATUS_UNSUCCESSFUL;

         }

 

         //注册中断

         status = IoConnectInterrupt(&pdx->InterruptObject, (PKSERVICE_ROUTINE) ISRInterrupt,

                   (PVOID) pdx, NULL, vector, irql, irql, LevelSensitive, irqshare, affinity, FALSE);

 

 

         if (!NT_SUCCESS(status))

         {

                   KdPrint(("IoConnectInterrupt failed - %X\n", status));

                   if (pdx->MemBar0)

                            MmUnmapIoSpace(pdx->MemBar0,pdx->nMem0);

                   return status;

         }

 

         //初始化DPC例程

         KeInitializeDpc(&pdx->fdo->Dpc,DPCForISR,NULL);

        

         KeInitializeSpinLock(&pdx->spinLock);

         PDEVICE_DESCRIPTION DeviceDescription=(PDEVICE_DESCRIPTION)ExAllocatePool(PagedPool, sizeof(DEVICE_DESCRIPTION));

         //这里需要设置DeviceDescription,代码略

         ULONG  NumberOfMapRegisters=100;

 

         //创建一个DMA适配器

         pdx->DmaAdapter=IoGetDmaAdapter(pdx->NextStackDevice,DeviceDescription,&NumberOfMapRegisters);

         if(!pdx->DmaAdapter)

         {

                   KdPrint(("Create DmaAdapter failed!\n"));

                   ExFreePool(DeviceDescription);

                   if (pdx->MemBar0)

                            MmUnmapIoSpace(pdx->MemBar0,pdx->nMem0);

                   IoDisconnectInterrupt(pdx->InterruptObject);

                   status=STATUS_UNSUCCESSFUL;

                   return status;

         }

         //      KdPrint(("DMANumberOfMapRegisters=%u,DMAChannel=%u,DMAPort=%u\n",NumberOfMapRegisters,DeviceDescription->DmaChannel,DeviceDescription->DmaPort));

 

pdx->allocateCommonBuffer=*pdx->DmaAdapter->DmaOperations->AllocateCommonBuffer;  //分配连续的物理内存DMA函数

pdx->freeCommonBuffer = *pdx->DmaAdapter->DmaOperations->FreeCommonBuffer;        

//释放连续的物理内存DMA函数

pdx->putDmaAdapter=*pdx->DmaAdapter->DmaOperations->PutDmaAdapter;                           

//释放DMA Adapter对象

 

pdx->descAddress=pdx->allocateCommonBuffer(pdx->DmaAdapter,(ULONG)DESC_ADDRESS*PORT_NUM,&pdx->DescLogicalAddress,FALSE);  //分配寄存器范围的基本虚拟地址

         if(!pdx->descAddress)      KdPrint(("descAddress failed"));

         for(int i=0;i<PORT_NUM;i++)

         {

         pdx->frameAddress[i]=pdx->allocateCommonBuffer(pdx->DmaAdapter,(ULONG)FRAME_ADDRESS,&pdx->FrameLogicalAddress[i],FALSE);//分配寄存器范围的基本虚拟地址

                  

                   if(!pdx->frameAddress[i])         {ret=0;KdPrint(("frameAddress[%d] failed\n",i));}

         }

         //代码省略一部分,调用allocateCommonBuffer()继续进行分配

         ExFreePool(DeviceDescription);

         //KdPrint(("Allocate first address is  0x%0x",memAddress));

 

         //初始化DMA内存缓冲区

         RtlZeroMemory(pdx->descAddress,DESC_ADDRESS*PORT_NUM);

         //复位,代码略

        

        

         //获得物理地址与虚拟地址的方法 , 都是用类似的方法来获取

         pdx->RxDescVirBase=(PCHAR)pdx->descAddress;

         pdx->RxDescPhyBase=(ULONG)(pdx->DescLogicalAddress.LowPart);

        

         pdx->InfBufferVirBase=(PCHAR)pdx->debugAddress;

         pdx->InfBufferPhyBase=(ULONG)(pdx->DebugLogicalAddress.LowPart);

        

        

         InitRecvAddr(pdx);

 

        

         //注:写寄存器都是用物理地址

         WRITE_REGISTER_ULONG((PULONG) &pdx->pHBARegs->RxAddr_des_0,pdx->rx_fc_desc_buf_phy[0]+16);

         WRITE_REGISTER_ULONG((PULONG) &pdx->pHBARegs->RxAddr_des_addr0_ptr,pdx->rx_fc_desc_buf_phy[0]+4);

         //初始化寄存器

         InitReg(pdx);

        

         KdPrint(("Leave InitHBA!\n"));

         return status; 

}

 

 

 

注:这一部分主要参考了张帆大哥的“Windows 驱动开发技术详解”,自己将一些分开的内容做了简单的总结,并结合自己已经在工作的PCI驱动代码进行说明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值