zoukankan      html  css  js  c++  java
  • 获取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驱动代码进行说明。

    make it simple, make it happen
  • 相关阅读:
    实战SQL Server 2005镜像配置全过程
    Sql Server 主从数据库配置
    Windows server2003 + sql server2005 集群配置安装
    windows配置nginx实现负载均衡集群
    Nginx 在windows下配合iis搭建负载均衡过程 [转]
    服务器后端开发系列——《实战Memcached内存缓存系统》
    一步一步配置NLB(续)之深入测试
    一步一步配置NLB
    Hadoop MapReduceV2(Yarn) 框架简介[转]
    亿级Web系统搭建——单机到分布式集群[转]
  • 原文地址:https://www.cnblogs.com/zhuyp1015/p/2396619.html
Copyright © 2011-2022 走看看