獲取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驅動代碼進行說明。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM