• PCIe驱动开发接口函数

    2015-05-21 15:51:52
    Realtek8168网卡时pci接口的网卡,其驱动程序就是一个PCI设备的驱动程序实例,我们一起看看其流程。 1. 首先,初始化模块调用static inline int pci_register_driver(struct pci_driver *driver)函数来注册设备...

    Realtek8168网卡时pci接口的网卡,其驱动程序就是一个PCI设备的驱动程序实例,我们一起看看其流程。

    1.  首先,初始化模块调用static inline int pci_register_driver(struct pci_driver *driver)函数来注册设备驱动,这个函数的参数是struct pci_driver *driver,对应于r8168,就是

    static struct pci_driver rtl8168_pci_driver = {

           .name             = MODULENAME,

           .id_table  = rtl8168_pci_tbl,

           .probe            = rtl8168_init_one,

           .remove          = __devexit_p(rtl8168_remove_one),

    #if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,11)

           .shutdown       = rtl8168_shutdown,

    #endif

    #ifdef CONFIG_PM

           .suspend  = rtl8168_suspend,

           .resume          = rtl8168_resume,

    #endif

    };

    这个结构体把这个设备驱动所支持的设备(rtl8168_pci_tbl),探测函数(rtl8168_init_one)等都定义好,后面我们将需要用到rtl8168_pci_tbl,rtl8168_init_one两部分内容来匹配,是否系统中的设备,看是否有设备可以跟这个驱动匹配

    2.  pci_register_driver 函数调用__pci_register_driver来完成任务,而__pci_register_driver则重新封装了要注册的驱动为PCI总线的,即

    int __pci_register_driver(struct pci_driver *drv, struct module *owner)

    {

           ……

           drv->driver.bus = &pci_bus_type;

        ……

           drv->driver.kobj.ktype = &pci_driver_kobj_type;

        ……

    }

    接下来就是调用设备驱动模型的函数,把我们要注册的驱动挂载到PCI总线的设备队列上,并扫描PCI总线的设备队列,查看是否有设备可以匹配这个驱动,这跟usb设备驱动的挂载是一致的,只是这里挂载的是PCI总线,usb挂载的是USB总线,大致的流程是

    driver_register()---àbus_add_driver()----àdriver_attach()--à__driver_attach()--àdriver_probe_device()---àdev->bus->probe(),即最后还是调用了2.中的pci_bus_type结构体中的probe成员函数,即static int pci_device_probe(struct device * dev)

    3.  static int pci_device_probe(struct device * dev)函数的参数dev就是遍历了PCI总线上的设备链表,一一进行匹配来完成的,因为我们调用__driver_attach()的方式是bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

    4.  static int pci_device_probe(struct device * dev)通过两个宏转换to_pci_driver,to_pci_dev,获得需要匹配的设备和驱动,调用static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)函数进行匹配

    5.  __pci_device_probe函数首先通过设备驱动中的rtl8168_pci_tbl表,跟从设备获得vendorID,productID进行比较,看是否一致,如果一致,就返回这个表的地址;如果没有一致的,就表明,这个设备跟这个驱动不匹配,就不需要继续进行下面的操作了,直接退出

    6.  如果第5步发现了一致的设备表,就表明有设备ID一致,需要进一步探测,接下来就要调用我们设备驱动程序中的探测函数,进行更具体的探测了,即pci_call_probe(drv, pci_dev, id)---à drv->probe(dev, id),到这里,就开始调用我们的设备驱动中的探测函数了。

    7.  static int __devinit rtl8168_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)函数是r8168的探测函数,其调用rtl8168_init_board(pdev, &dev, &ioaddr)来完成跟PCI设备驱动相关的探测。

    8.  static int __devinit rtl8168_init_board(struct pci_dev *pdev, struct net_device **dev_out, void __iomem **ioaddr_out)函数调用pci_enable_device函数来使能PCI设备,只有使能成功的PCI设备,才能正常使用。

    9.  调用pci_set_mwi函数判断设备是否支持memory-write-invalidate 功能

    10.              调用pci_find_capability函数来判断设备是否有电源管理功能.

    11.              调用pci_resource_flags函数来判断PCI是内存映射模式,还是IO模式

    12.              调用pci_resource_len函数来判断内存空间是否小于设备所需要的内存空间,如果小于,明显出错

    13.              调用pci_request_regions函数通知内核,当前PCI将使用这些内存地址,其他设备不能再使用了

    14.              调用pci_set_master(pdev)函数,设置设备具有获得总线的能力,即调用这个函数,使设备具备申请使用PCI总线的能力。

    15.              调用ioremap函数把刚刚申请的物理内存,映射成虚拟内存,因为进程使用的都是虚拟内存地址,而不是物理内存地址。

    16.              把ioremap映射的虚拟内存返回给调用函数。

    17.              到此,跟PCI相关的初始化都完成了,设备即可正常工作了

    转载地址:http://www.cnblogs.com/image-eye/archive/2012/02/15/2352912.html

    展开全文
  • PCI/PCIe接口卡Windows驱动程序(4)- 驱动程序代码(源文件) http://www.cnblogs.com/jacklu/p/4687325.html 本篇文章将对PCIe驱动程序的源文件代码作详细解释与说明。整个WDF驱动程序工程共包含4个头...
    
    

    本篇文章将对PCIe驱动程序的源文件代码作详细解释与说明。整个WDF驱动程序工程共包含4个头文件(已经在上篇文章中讲解)和3个.c文件(Driver.c  Device.c   Queue.c)

    Driver.c

    在看复杂的代码前,先给出程序流程图

     

    复制代码
      1 #include "driver.h"
      2 #include "driver.tmh"
      3 
      4 #ifdef ALLOC_PRAGMA
      5 #pragma alloc_text (INIT, DriverEntry)
      6 #pragma alloc_text (PAGE, Spw_PCIeEvtDeviceAdd)
      7 #pragma alloc_text (PAGE, Spw_PCIeEvtDriverContextCleanup)
      8 #endif
      9 
     10 
     11 NTSTATUS
     12 DriverEntry(
     13    IN PDRIVER_OBJECT  DriverObject,
     14    IN PUNICODE_STRING RegistryPath
     15     )
     16 {
     17     WDF_DRIVER_CONFIG config;
     18     //WDFDRIVER   driver;//????
     19     NTSTATUS status = STATUS_SUCCESS;
     20     WDF_OBJECT_ATTRIBUTES attributes;
     21 
     22     //
     23     // Initialize WPP Tracing
     24     //
     25     WPP_INIT_TRACING( DriverObject, RegistryPath );
     26 
     27     TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
     28 
     29     //
     30     // Register a cleanup callback so that we can call WPP_CLEANUP when
     31     // the framework driver object is deleted during driver unload.
     32     //
     33     
     34     WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
     35 
     36     attributes.EvtCleanupCallback = Spw_PCIeEvtDriverContextCleanup;
     37     
     38     WDF_DRIVER_CONFIG_INIT(&config,
     39         Spw_PCIeEvtDeviceAdd
     40         );
     41 
     42     status = WdfDriverCreate(DriverObject,
     43                              RegistryPath,
     44                              &attributes,
     45                              &config,
     46                              WDF_NO_HANDLE
     47                              );
     48 
     49     if (!NT_SUCCESS(status)) {
     50         TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
     51         WPP_CLEANUP(DriverObject);
     52         return status;
     53     }
     54 
     55     TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
     56 
     57     return status;
     58 }
     59 
     60 
     61 NTSTATUS
     62 Spw_PCIeEvtDeviceAdd(
     63     _In_    WDFDRIVER       Driver,
     64     _Inout_ PWDFDEVICE_INIT DeviceInit
     65     )
     66 {
     67     NTSTATUS status = STATUS_SUCCESS;
     68     WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
     69     WDF_OBJECT_ATTRIBUTES   deviceAttributes;
     70     WDFDEVICE device;
     71     PDEVICE_CONTEXT deviceContext;
     72 
     73     WDFQUEUE queue;
     74     WDF_IO_QUEUE_CONFIG    queueConfig;
     75 
     76     /*+++++Interrupt
     77     WDF_INTERRUPT_CONFIG    interruptConfig;
     78     -----*/
     79     //    WDF_IO_QUEUE_CONFIG        ioQueueConfig;
     80 
     81     UNREFERENCED_PARAMETER(Driver);
     82 
     83     PAGED_CODE();
     84 
     85     //采用WdfDeviceIoDirect方式
     86     WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect);//WdfDeviceIoBuffered???重要吗?
     87     //When the I/O manager sends a request for buffered I/O, the IRP contains an internal copy of the caller's buffer
     88     //rather than the caller's buffer itself. The I/O manager copies data from the caller's buffer to the internal buffer
     89     //during a write request or from the internal buffer to the caller's buffer when the driver completes a read
     90     //request.
     91     //The WDF driver receives a WDF request object, which in turn contains an embedded WDF memory object.
     92     //The memory object contains the address of the buffer on which the driver should operate.
     93 
     94 
     95 
     96    // status = Spw_PCIeCreateDevice(DeviceInit);
     97 
     98     //初始化即插即用和电源管理例程配置结构
     99     WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
    100 
    101     //设置即插即用基本例程
    102     pnpPowerCallbacks.EvtDevicePrepareHardware = Spw_PCIeEvtDevicePrepareHardware;
    103     pnpPowerCallbacks.EvtDeviceReleaseHardware = Spw_PCIeEvtDeviceReleaseHardware;
    104     pnpPowerCallbacks.EvtDeviceD0Entry = Spw_PCIeEvtDeviceD0Entry;
    105     pnpPowerCallbacks.EvtDeviceD0Exit = Spw_PCIeEvtDeviceD0Exit;
    106 
    107     //注册即插即用和电源管理例程
    108     WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
    109 
    110     
    111     WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
    112 
    113 
    114     //deviceAttributes.EvtCleanupCallback = Spw_PCIeEvtDriverContextCleanup;
    115     //
    116     // Set WDFDEVICE synchronization scope. By opting for device level
    117     // synchronization scope, all the queue and timer callbacks are
    118     // synchronized with the device-level spinlock.
    119     //
    120     deviceAttributes.SynchronizationScope = WdfSynchronizationScopeDevice;
    121 
    122     status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
    123     if (!NT_SUCCESS(status)) {
    124         return status;
    125     }
    126     deviceContext = GetDeviceContext(device);///????
    127     //deviceContext->Device = device;
    128     //
    129     // 初始化Context这个结构里的所有成员.
    130     //
    131     //deviceContext->PrivateDeviceData = 0;
    132     /*++++++Interrupt & DMA
    133     //设置中断服务例程和延迟过程调用
    134     WDF_INTERRUPT_CONFIG_INIT(&interruptConfig,
    135     PCISample_EvtInterruptIsr,
    136     PCISample_EvtInterruptDpc);
    137 
    138     //创建中断对象
    139     status = WdfInterruptCreate(device,
    140     &interruptConfig,
    141     WDF_NO_OBJECT_ATTRIBUTES,
    142     &pDeviceContext->Interrupt);
    143     if (!NT_SUCCESS (status)) {
    144     return status;
    145     }
    146 
    147     status = InitializeDMA(device);
    148 
    149     if (!NT_SUCCESS(status)) {
    150     return status;
    151     }
    152     -----*/
    153     //WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(&queueConfig, WdfIoQueueDispatchSequential);
    154     //Initialize the Queue
    155     //        queueConfig.EvtIoDefault = Spw_PCIeEvtIoDefault;
    156     //        queueConfig.EvtIoWrite = Spw_PCIeEvtIoWrite;
    157     //queueConfig.EvtIoRead = Spw_PCIeEvtIoRead;
    158     //        queueConfig.EvtIoStop = Spw_PCIeEvtIoStop;
    159     //The driver must initialize the WDF_IO_QUEUE_CONFIG structure 
    160     //by calling WDF_IO_QUEUE_CONFIG_INIT or WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE.
    161     //用default初始化default 队列,用另一个初始化非default队列
    162     WDF_IO_QUEUE_CONFIG_INIT(
    163         &queueConfig,
    164         WdfIoQueueDispatchSequential
    165         );
    166 
    167     queueConfig.EvtIoDeviceControl = Spw_PCIeEvtIoDeviceControl;
    168 
    169 
    170     status = WdfIoQueueCreate(device, &queueConfig, WDF_NO_OBJECT_ATTRIBUTES, &queue);
    171     if (!NT_SUCCESS(status)) {
    172         return status;
    173     }
    174 
    175     //对于非默认队列,必须指定要分发的I/O请求类型
    176     //The WdfDeviceConfigureRequestDispatching method causes the framework to queue a specified type of I/O requests to a specified I/O queue.
    177     status = WdfDeviceConfigureRequestDispatching(
    178         device,
    179         queue,
    180         WdfRequestTypeDeviceControl
    181         );
    182     if (!NT_SUCCESS(status)) {
    183         return status;
    184     }
    185     //创建驱动程序接口与应用程序通信
    186     status = WdfDeviceCreateDeviceInterface(
    187         device,
    188         (LPGUID)&GUID_DEVINTERFACE_Spw_PCIe,
    189         NULL // ReferenceString
    190         );
    191     if (!NT_SUCCESS(status)) {
    192         return status;
    193     }
    194     /*
    195     if (NT_SUCCESS(status)) {
    196     //
    197     // Initialize the I/O Package and any Queues
    198     //
    199     status = Spw_PCIeQueueInitialize(device);
    200     }
    201     */
    202     //deviceContext->MemLength = MAXNLEN;
    203 
    204     //TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
    205 
    206     return status;
    207 }
    208 
    209 VOID
    210 Spw_PCIeEvtDriverContextCleanup(
    211     _In_ WDFOBJECT DriverObject
    212     )
    213 /*++
    214 Routine Description:
    215 
    216     Free all the resources allocated in DriverEntry.
    217 
    218 Arguments:
    219 
    220     DriverObject - handle to a WDF Driver object.
    221 
    222 Return Value:
    223 
    224     VOID.
    225 
    226 --*/
    227 {
    228     UNREFERENCED_PARAMETER(DriverObject);
    229 
    230     PAGED_CODE ();
    231 
    232     TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
    233 
    234     //没有必要清除WDFINTERRUPT对象,因为框架会自动清除
    235     // Stop WPP Tracing
    236     //
    237     WPP_CLEANUP( WdfDriverWdmGetDriverObject(DriverObject) );
    238 
    239 }
    复制代码

    4-8行是做一些预处理,驱动程序开发中,需要为每个函数指定位于分页内存还是非分页内存。INIT标识是指此函数为入口函数,驱动成功加载后可以从内存删除。PAGE标识是指此函数可以在驱动运行时被交换到硬盘上,如果不指定,将被编译器默认为非分页内存。

    11-58行定义了DriverEntry函数,每个 KMDF 驱动程序必须有一个 DriverEntry 例程,当操作系统检测到有新硬 件设备插入后,会查找它对应的驱动程序,找到这个驱动程序中的 DriverEntry 程。DriverEntry 是驱动程序的入口,它相当于 C 语言程序里的 main 函数。 DriverEntry 例程的原型声明如下:

    1 NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) ;

    函数返回类型 NTSTATUS  WDF 中的一个宏,它实际上是一个 32 位的二进制数,不同的数值表示不同的状态,在 PCIe 设备驱动程序开发中,需要用到的状态有: STATUS_SUCCESS STATUS_PENDING STATUS_UNSUCCESSFUL  别表示例程回调成功、 例程回调未完成、 例程回调失败。在传入参数里, IN 是一 个宏, 代表这个参数为入口参数,这与例程编写无关,只是为了让开发者能够更 容易的知道参数特性,其中 OUT 表示出口参数。关于参数标识, 还有另一种写法, _In__Out_ 两种写法对回调例程的编写都没影响。

    DriverEntry 的第一个参数是一个指向驱动程序对象的指针, 该对象就代表驱 动程序。 在 DriverEntry 例程中, 应该完成对这个对象的初始化并返回。 DriverEntry 的第二个参数是设备驱动对应服务键在注册表中的路径。DriverEntry 例程需要完成的任务主要包括:

    • 激活 WPP( Windows software trace preprocessor)软件调试,为可选任务;(对应代码25-27行)
    • 注册驱动程序的 EvtDriverDeviceAdd 回调函数;(对应代码38-40行)
    • 创建一个驱动程序对象, 向框架“注册”驱动程序;(对应代码42-53行)

    61-206行定义了EvtDriverDeviceAdd函数。每个支持即插即用的 KMDF 驱动程序必须有 EvtDriverDeviceAdd 回调例程, 每次操作系统枚举设备时, PnP 管理器就调用这个回调例程。 EvtDriverDeviceAdd 例程的主要任务包括:

    • 创建并初始化设备对象和相应的上下文区(122-126行);
    • 设置传输方式(86行)、 初始化即插即用和电源管理配置结构(99行), 注册即插即用和电源管理例程(101-108行);
    • 初始化队列配置结构(162-165行), 注册 I/O 处理例程(167行), 创建 I/O 队列(170行), 指定要分发的 I/O 请求类型(177-184行), 创建 GUID 接口(185-194行)。

    EvtDriverDeviceAdd 例程的原型声明如下:

    EvtDriverDeviceAdd( IN WDFDRIVER Driver, IN PWDFDEVICE_INIT DeviceInit ) ; 

    DeviceInit 指向 KMDF 自定义的一个结构体, 它在设置传输方式、 注册即插即 用和电源管理例程、 创建设备对象这些任务中起着传递重要数据的作用。

    209-239行定义了EvtDriverContextCleanup函数。EvtDriverContextCleanup 回调例程用来删除设备和回收操作系统分配给设备 的资源。对于即插即用设备,当手动拔出设备后, PnP 管理器会自动识别并删除设     Windows           EvtDriverContextCleanup 例程。

     

    Device.c

    复制代码
      1 #include "driver.h"
      2 #include "device.tmh"
      3 
      4 #pragma warning(disable:4013)  // assuming extern returning int
      5 #ifdef ALLOC_PRAGMA
      6 
      7 #pragma alloc_text(PAGE, Spw_PCIeEvtDevicePrepareHardware)
      8 #pragma alloc_text(PAGE, Spw_PCIeEvtDeviceReleaseHardware)
      9 #pragma alloc_text(PAGE, Spw_PCIeEvtDeviceD0Entry)
     10 #pragma alloc_text(PAGE, Spw_PCIeEvtDeviceD0Exit)
     11 
     12 #endif
     13 
     14 NTSTATUS
     15 Spw_PCIeEvtDevicePrepareHardware(
     16 IN WDFDEVICE Device,
     17 IN WDFCMRESLIST ResourceList,
     18 IN WDFCMRESLIST ResourceListTranslated
     19 )
     20 {
     21     ULONG            i;
     22     NTSTATUS        status = STATUS_SUCCESS;
     23     PDEVICE_CONTEXT pDeviceContext;
     24 
     25     PCM_PARTIAL_RESOURCE_DESCRIPTOR descriptor;//record the Hareware resource that OS dispatched to PCIe
     26     /*
     27     在Windows驱动开发中,PCM_PARTIAL_RESOURCE_DESCRIPTOR记录了为PCI设备分配的硬件资源,
     28     可能有CmResourceTypePort, CmResourceTypeMemory等,
     29     后者表示一段memory地址空间,顾名思义,是通过memory space访问的,
     30     前者表示一段I/O地址空间,但其flag有CM_RESOURCE_PORT_MEMORY和CM_RESOURCE_PORT_IO两种,
     31     分别表示通过memory space访问以及通过I/O space访问,这就是PCI请求与实际分配的差异,
     32     在x86下,CmResourceTypePort的flag都是CM_RESOURCE_PORT_IO,即表明PCI设备请求的是I/O地址空间,分配的也是I/O地址空间,
     33     而在ARM或Alpha等下,flag是CM_RESOURCE_PORT_MEMORY,表明即使PCI请求的I/O地址空间,但分配在了memory space,
     34     我们需要通过memory space访问I/O设备(通过MmMapIoSpace映射物理地址空间到虚拟地址空间,当然,是内核的虚拟地址空间,这样驱动就可以正常访问设备了)。
     35     */
     36     PAGED_CODE();
     37 
     38 //    UNREFERENCED_PARAMETER(Resources);//告诉编译器不要发出Resources没有被引用的警告
     39 
     40     pDeviceContext = GetDeviceContext(Device);
     41     pDeviceContext->MemBaseAddress = NULL;
     42     pDeviceContext->Counter_i = 0;
     43     //get resource
     44     for (i = 0; i < WdfCmResourceListGetCount(ResourceListTranslated); i++) {
     45 
     46         descriptor = WdfCmResourceListGetDescriptor(ResourceListTranslated, i);
     47         //if failed:
     48         if (!descriptor) {
     49             return STATUS_DEVICE_CONFIGURATION_ERROR;
     50         }
     51 
     52         switch (descriptor->Type) {
     53 
     54         case CmResourceTypeMemory:
     55             //MmMapIoSpace将物理地址转换成系统内核模式地址
     56             if (i == 0){
     57                 pDeviceContext->PhysicalAddressRegister = descriptor->u.Memory.Start.LowPart;
     58                 pDeviceContext->BAR0_VirtualAddress = MmMapIoSpace(
     59                     descriptor->u.Memory.Start,
     60                     descriptor->u.Memory.Length,
     61                     MmNonCached);
     62             }
     63             
     64             pDeviceContext->MemBaseAddress = MmMapIoSpace(
     65                 descriptor->u.Memory.Start,
     66                 descriptor->u.Memory.Length,
     67                 MmNonCached);
     68             pDeviceContext->MemLength = descriptor->u.Memory.Length;
     69 
     70             break;
     71 
     72         default:
     73             break;
     74         }
     75         if (!pDeviceContext->MemBaseAddress){
     76             return STATUS_INSUFFICIENT_RESOURCES;
     77         }
     78     }
     79     pDeviceContext->Counter_i = i;
     80     DbgPrint("EvtDevicePrepareHardware - ends\n");
     81 
     82     return STATUS_SUCCESS;
     83 }
     84 
     85 NTSTATUS
     86 Spw_PCIeEvtDeviceReleaseHardware(
     87 IN WDFDEVICE Device,
     88 IN WDFCMRESLIST ResourceListTranslated
     89 )
     90 {
     91     PDEVICE_CONTEXT    pDeviceContext = NULL;
     92 
     93     PAGED_CODE();
     94 
     95     DbgPrint("EvtDeviceReleaseHardware - begins\n");
     96 
     97     pDeviceContext = GetDeviceContext(Device);
     98 
     99     if (pDeviceContext->MemBaseAddress) {
    100         //MmUnmapIoSpace解除物理地址与系统内核模式地址的关联
    101         MmUnmapIoSpace(pDeviceContext->MemBaseAddress, pDeviceContext->MemLength);
    102         pDeviceContext->MemBaseAddress = NULL;
    103     }
    104 
    105     DbgPrint("EvtDeviceReleaseHardware - ends\n");
    106 
    107     return STATUS_SUCCESS;
    108 }
    109 
    110 NTSTATUS
    111 Spw_PCIeEvtDeviceD0Entry(
    112 IN  WDFDEVICE Device,
    113 IN  WDF_POWER_DEVICE_STATE PreviousState
    114 )
    115 {
    116     UNREFERENCED_PARAMETER(Device);
    117     UNREFERENCED_PARAMETER(PreviousState);
    118 
    119     return STATUS_SUCCESS;
    120 }
    121 
    122 
    123 NTSTATUS
    124 Spw_PCIeEvtDeviceD0Exit(
    125 IN  WDFDEVICE Device,
    126 IN  WDF_POWER_DEVICE_STATE TargetState
    127 )
    128 {
    129     UNREFERENCED_PARAMETER(Device);
    130     UNREFERENCED_PARAMETER(TargetState);
    131 
    132     PAGED_CODE();
    133 
    134     return STATUS_SUCCESS;
    135 }
    复制代码

    13-83行定义了EvtDevicePrepareHardware例程。EvtDevicePrepareHardwareEvtDeviceReleaseHardware两个例程对硬件设备能否获得Windows操作系统分配的资源起着至关重要的作用。EvtDevicePrepareHardware的任务主要包括获得内存资源、内存物理地址与虚拟地址的映射、I/O端口映射和中断资源分配。

    EvtDevicePrepareHardware例程的原型声明如下:

    1 NTSTATUS EvtDevicePrepareHardware(
    2 IN WDFDEVICE Device,
    3 IN WDFCMRESLIST ResourceList,
    4 IN WDFCMRESLIST ResourceListTranslated
    5 ) ;

    传入函数的三个参数,Device是在EvtDriverDeviceAdd创建的设备对象,另外两个参数是两个硬件资源列表,这两个硬件资源列表实际上代表了不同版本的同一份硬件资源集。ResourceList代表的硬件资源是通过总线地址描述的;ResourceListTranslated代表的硬件资源是通过内存物理地址描述的。

    WDF框架分配给硬件资源的具体过程如下:

    (1)用户插入PnP设备,总线驱动识别设备并枚举;

    (2)WDF框架调用总线驱动的EvtDeviceResourcesQuery,创建资源列表;

    (3)WDF框架调用总线驱动的EvtDeviceResourcesRequirementQuery,创建资源需求列表;

    (4)PnP管理器决定设备需要什么驱动程序;

    (5)PnP管理器创建设备资源列表并发送给驱动程序;

    (6)如果驱动程序调用WdfInterruptCreate例程,WDF框架就会在资源列表中分配给中断资源给驱动程序;

    (7)设备进入工作状态后,KMDF调用EvtDevicePrepareHardware例程传递两个资源列表,驱动程序保存这两个资源列表,直到WDF框架调用了EvtDeviceReleaseHardware例程。

    驱动程序通过EvtDevicePrepareHardware获得内存资源后,需要用MmMapIoSpace函数将物理地址映射成虚拟地址。

    85-108行定义了EvtDeviceReleaseHardware回调例程,其调用过程是EvtDevicePrepareHardware的逆过程,即获得虚拟地址后,利用MmUnMapIoSpace 函数将虚拟地址解映射成物理地址,然后再交给WDF框架释放,这里不再赘述。

    当 PCIe-SpaceWire接口卡设备被移除时,WDF框架会自动调用Spw_PCIeEvtDeviceReleaseHardware 函数释放设备和驱动程序的内存空间。由于系统每次检测到PCIe接口卡,会自动调用Spw_PCIeEvtDevicePrepareHardware函数提供内存资源,因此,断电或移除设备时,必须调用Spw_PCIeEvtDeviceReleaseHardware函数必须释放所分配的内存空间,否则,有可能导致内存溢出甚至操作系统崩溃。

    110-135定义了EvtDeviceD0Entry和EvtDeviceD0Exit例程,WDF框架会在设备进入工作状态后调用EvtDeviceD0Entry回调例程,设备进入工作状态会在以下几种情况下发生:

    • 即插即用设备被系统发现;
    • 操作系统和设备从睡眠状态被唤醒;
    • (如果设备支持低电压闲置状态)设备从低电压闲置状态被唤醒;
    • PnP管理器重新为设备分配资源。

    由于设备进入工作状态后,WDF框架就会根据事件调用各种回调例程,所以EvtDeviceD0Entry例程里一般不需要处理任何任务。设备离开工作状态后,WDF调EvtDeviceD0Exit回调例程,通常EvtDeviceD0Exit例程也不需要处理任何任务。需要注意的是,在注册这两个例程的时候,必须调用WdfDeviceInitSetPnpPowerEventCallbacks来注册设备即插即用和电源管理回调例程。

     

    Queue.c

    复制代码
      1 #include "driver.h"
      2 #include "queue.tmh"
      3 
      4 #pragma warning(disable:4013)  // assuming extern returning int
      5 
      6 #ifdef ALLOC_PRAGMA
      7 #pragma alloc_text (PAGE, Spw_PCIeEvtIoDeviceControl)
      8 
      9 #endif
     10 /*
     11 单一的默认I/O队列和单一的请求处理函数,EvtIoDefault。KMDF将会将设备所有的请求发送到默认I/O队列,
     12 然后它会调用驱动程序的EvtIoDefault来将每一个请求递交给驱动程序。
     13 
     14 *单一的默认I/O队列和多个请求处理函数,例如EvtIoRead、EvtIoWrite和EvtIoDeviceControl。KMDF会将设备所有的请求发送到默认I/O队列。
     15 然后会调用驱动程序的EvtIoRead处理函数来递交读请求、调用EvtIoWrite处理函数来递交写请求、调用EvtIoDeviceControl处理函数来递交设备I/O控制请求。
     16 */
     17 
     18 
     19 VOID
     20 Spw_PCIeEvtIoDeviceControl(
     21     IN WDFQUEUE Queue,
     22     IN WDFREQUEST Request,
     23     IN size_t OutputBufferLength,
     24     IN size_t InputBufferLength,
     25     IN ULONG IoControlCode
     26     )
     27 {
     28     WDFDEVICE device;
     29     PDEVICE_CONTEXT pDevContext;
     30 
     31     NTSTATUS  status;
     32 
     33     PVOID      inBuffer;
     34     PVOID     outBuffer;
     35     ULONG      AddressOffset;
     36 
     37     //PAGED_CODE(); do not uncomment this sentence
     38     device = WdfIoQueueGetDevice(Queue);
     39     pDevContext = GetDeviceContext(device);
     40 
     41     switch (IoControlCode) {
     42 //根据CTL_CODE请求码作相应的处理
     43     case Spw_PCIe_IOCTL_WRITE_OFFSETADDRESS:
     44         status = WdfRequestRetrieveInputBuffer(
     45             Request,
     46             sizeof(ULONG),
     47             &inBuffer,
     48             NULL
     49             );
     50         pDevContext->OffsetAddressFromApp = *(ULONG*)inBuffer;
     51         WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
     52         if (!NT_SUCCESS(status)){
     53             goto Exit;
     54         }
     55         break;
     56 
     57     case Spw_PCIe_IOCTL_IN_BUFFERED:
     58             status = WdfRequestRetrieveInputBuffer(
     59                 Request,
     60                 sizeof(ULONG),
     61                 &inBuffer,
     62                 NULL
     63                 );
     64             AddressOffset = PCIE_WRITE_MEMORY_OFFSET + pDevContext->OffsetAddressFromApp;
     65             *(ULONG*)WDF_PTR_ADD_OFFSET(pDevContext->BAR0_VirtualAddress, AddressOffset) = *(ULONG*)inBuffer;
     66             WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
     67             if (!NT_SUCCESS(status)){
     68                 goto Exit;
     69             }
     70         break;
     71         
     72     case Spw_PCIe_IOCTL_OUT_BUFFERED:
     73         status = WdfRequestRetrieveOutputBuffer(
     74             Request,
     75             sizeof(ULONG),
     76             &outBuffer,
     77             NULL
     78             );
     79         AddressOffset = PCIE_WRITE_MEMORY_OFFSET + pDevContext->OffsetAddressFromApp;
     80         //--------------------------------------------------------------------------
     81         *(ULONG*)outBuffer = *(ULONG*)WDF_PTR_ADD_OFFSET(pDevContext->BAR0_VirtualAddress, AddressOffset);
     82         //--------------------------------------------------------------------------
     83         //*(ULONG*)outBuffer = pDevContext->Counter_i;
     84         //--------------------------------------------------------------------------
     85         WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
     86         if (!NT_SUCCESS(status)){
     87             goto Exit;
     88         }
     89         break;
     90     case Spw_PCIe_IOCTL_READ_PADDRESS:
     91             //Just think about the size of the data when you are choosing the METHOD.  
     92             //METHOD_BUFFERED is typically the fastest for small (less the 16KB) buffers, 
     93             //and METHOD_IN_DIRECT and METHOD_OUT_DIRECT should be used for larger buffers than that.
     94             //METHOD_BUFFERED,METHOD_OUT_DIRECT,METHOD_IN_DIRECT三种方式,
     95             //输入缓冲区地址可通过调用WdfRequestRetrieveInputBuffer函数获得
     96             //输出缓冲区地址可通过调用WdfRequestRetrieveOutputBuffer函数获得
     97     
     98             status = WdfRequestRetrieveOutputBuffer(
     99             Request,
    100             sizeof(ULONG),
    101             &outBuffer,
    102             NULL
    103             );
    104 
    105             *(ULONG*)outBuffer = pDevContext->PhysicalAddressRegister;//read BAR0 pysical address
    106 
    107             WdfRequestCompleteWithInformation(Request, status, sizeof(ULONG));
    108             if (!NT_SUCCESS(status)){
    109                 goto Exit;
    110             }
    111         break;
    112 
    113     default:
    114         status = STATUS_INVALID_DEVICE_REQUEST;
    115         WdfRequestCompleteWithInformation(Request, status, 0);
    116         break;
    117     }
    118 
    119 Exit:
    120     if (!NT_SUCCESS(status)) {
    121         WdfRequestCompleteWithInformation(
    122             Request,
    123             status,
    124             0
    125             );
    126     }
    127     return;
    128 }
    复制代码

    整个源代码文件只定义了一个例程EvtIoDeviceControl,当WDF框架处理I/O请求时,根据I/O 请求的副功能码执行相应的操作,I/O 请求处理结束后,需要通过一个例程完成I/O请求,以通知应用程序处理结束。否则,会因为应用程序无法正常退出而导致系统挂起。接口卡驱动程序中处理I/O请求的例程为Spw_PCIeEvtIoDeviceControl,它根据应用程序传入控制字的不同会执行不同的任务,包括读BAR0物理起始地址、读寄存器、写寄存器、写入偏移地址。

    Windows 2000及其以后的操作系统都是以I/O请求包的形式与驱动程序进行通信的。在WDF驱动程序中,处理I/O请求的关键判断哪些类型的I/O请求由驱动程序处理,哪些类型的I/O请求由WDF框架自动处理。当Windows操作系统收到一个从应用程序传送过来的I/O请求后,I/O管理器将它封装成I/O请求包发送给设备驱动程序。常见的I/O请求包括:create, close, read, write, 和 device I/O control,分别表示创建设备、关闭设备、读操作、写操作和控制命令字传输。

    应用程序执行I/O操作时,向I/O管理器提供了一个数据缓冲区。WDF框架提供三种数据传输方式:

    •  buffered方式:I/O管理器会创建与应用程序数据缓冲区完全相同的系统缓冲区,驱动程序在这个缓冲区工作,由I/O管理器完成复制数据任务;
    •  direct方式:I/O管理器锁定应用程序缓冲区的物理内存页,并创建一个MDL(内存描述符表)来描述该页,驱动程序将使用MDL工作;
    •  neither方式:I/O管理器把应用程序缓冲区的虚拟地址传递给驱动程序,一般不采用这种方式。

    在I/O请求处理中,WDF规定驱动程序必须包括以下一个或多个I/O回调例程,来处理从队列调度的I/O请求:

    •  EvtIoRead
    •  EvtIoWrite
    •  EvtIoDeviceIoControl
    •  EvtIoInternalDeeviceControl
    •  EvtIoDefault

     下面以完成一个读请求为例,描述WDF框架处理I/O请求的全过程

    第1步,应用程序调用Win32 API函数ReadFile进行读操作;第2步,ReadFile函数调用NTDLL.dll中的原生函数NtReadFile,从而进入内核服务,I/O管理器将接管读操作。第3步,I/O管理器为读请求构造类型为IRP_MJ_READ的请求包;第4步,I/O管理器找到由WDF框架创建的设备对象,并将请求包发送到它的读派遣函数;第5步,WDF框架收到请求包后,查看WDF驱动是否注册了读回调例程,如果注册了,就将请求包封装成一个I/O请求对象把它放到WDF驱动的某个指定队列中;第6步,队列将I/O请求对象发送给WDF驱动处理,WDF驱动注册的读回调例程被执行。

    现代操作系统比如Windows、Linux在内存管理上均采用分页机制。分页内存可被交换到硬盘,而非分页内存则不会交换到硬盘上。运行的程序代码中断请求优先级高于DISPATCH_LEVEL(包括DISPATCH_LEVEL)的,必须保证程序所在内存页为非分页内存,否则会造成系统挂起。在WDF驱动程序开发中,使用宏PAGE_CODE来标记某例程应在分页内存上。因此在驱动程序开发过程中要特别注意PAGE_CODE的使用。

    对于PCIe设备驱动开发,开发者还注意读写映射内存不能越界。比如在本次毕业设计中,BAR2为配置寄存器,编写程序时由于误写入BAR2映射的内存地址,造成操作系统一执行写操作就发生蓝屏。

     

    在看完这几篇文章后,将源代码通过VS2013+WDK8.1编译就能生成相应PCI/PCIe硬件板卡的Windows驱动程序(.sys文件),为了实现对驱动程序的安装与验证,还需要编写INF文件和应用程序文件,这部分将在下一篇文章中讲述。

     

    参考资料:

    武安河. Windows设备驱动程序WDF开发

    孔鹏. 基于WDF的光纤传输卡PCIe接口驱动的研究和实现

    杨阿锋基于WDF的PCIe接口高速数据传输卡的驱动程序开发

     

    展开全文
  • PCIE(PCI Express)是INTEL提出的新一代的总线接口,目前普及的PCIE 3.0的传输速率为8GT/s,下一代PCIE 4.0将翻番为16GT/S,因为传输速率快广泛应用于数据中心、云计算、人工智能、机器学习、视觉计算、显卡、存储和...
    PCIE(PCI Express)是INTEL提出的新一代的总线接口,目前普及的PCIE 3.0的传输速率为8GT/s,下一代PCIE 4.0将翻番为16GT/S,因为传输速率快广泛应用于数据中心、云计算、人工智能、机器学习、视觉计算、显卡、存储和网络等领域。PCIE插槽是可以向下兼容的,比如PCIE 1X接口可以插4X、8X、16X的插槽上。

    实现基本的PCIE驱动程序,实现以下模块:初始化设备、设备打开、数据读写和控制、中断处理、设备释放、设备卸载。本程序适合PCIE驱动开发通用调试的基本框架,对于具体PCIE设备,需要配置相关寄存器才可以使用!

    源代码

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/signal.h>
    #include <linux/init.h>
    #include <linux/cdev.h>
    #include <linux/delay.h>
    #include <linux/poll.h>
    #include <linux/device.h>
    #include <linux/pci.h>
    #include <linux/interrupt.h> 
    #include <asm/uaccess.h> 
    
    MODULE_LICENSE("Dual BSD/GPL");
    MODULE_DESCRIPTION("pcie device driver");
    
    #define DEV_NAME "hello_pcie"
    #define DEBUG 
    
    #ifdef DEBUG
    	#define DEBUG_ERR(format,args...) \
    	do{  \
    		printk("[%s:%d] ",__FUNCTION__,__LINE__); \
    		printk(format,##args); \
    	}while(0)
    #else
    	#define DEBUG_PRINT(format,args...) 
    #endif
    
    //1M 
    #define DMA_BUFFER_SIZE 1*1024*1024 
    #define FASYNC_MINOR 1
    #define FASYNC_MAJOR 244
    #define DEVICE_NUMBER 1
     
    static struct class * hello_class;
    static struct device * hello_class_dev;
    
    struct hello_device
    {
    	struct pci_dev* pci_dev;
    	struct cdev cdev;
    	dev_t devno;
    }my_device;
    
    //barn(n=0,1,2或者0,1,2,3,4,5) 空间的物理地址,长度,虚拟地址
    unsigned long bar0_phy;
    unsigned long bar0_vir;
    unsigned long bar0_length;
    unsigned long bar1_phy;
    unsigned long bar1_vir;
    unsigned long bar1_length;
    
    //进行DMA转换时,dma的源地址和目的地址
    dma_addr_t dma_src_phy;
    dma_addr_t dma_src_vir;
    dma_addr_t dma_dst_phy;
    dma_addr_t dma_dst_vir;
    
    //根据设备的id填写,这里假设厂商id和设备id
    #define HELLO_VENDOR_ID 0x666
    #define HELLO_DEVICE_ID 0x999
    static struct pci_device_id hello_ids[] = {
        {HELLO_VENDOR_ID,HELLO_DEVICE_ID,PCI_ANY_ID,PCI_ANY_ID,0,0,0},
        {0,}
    };
    MODULE_DEVICE_TABLE(pci,hello_ids);
    
    static int hello_probe(struct pci_dev *pdev, const struct pci_device_id *id);
    static void hello_remove(struct pci_dev *pdev);
    static irqreturn_t hello_interrupt(int irq, void * dev);
    
    //往iATU写数据的函数
    void iATU_write_config_dword(struct pci_dev *pdev,int offset,int value)
    {
    	
    }
    
    //假设需要将bar0映射到内存
    static void iATU_bar0(void)
    {
    	//下面几步,在手册中有example
    	//iATU_write_config_dword(my_device.pci_dev,iATU Lower Target Address ,xxx);//xxx表示内存中的地址,将bar0映射到这块内存
    	//iATU_write_config_dword(my_device.pci_dev,iATU Upper Target Address ,xxx);//xxx表示内存中的地址,将bar0映射到这块内存
    
    	//iATU_write_config_dword(my_device.pci_dev,iATU Control 1,0x0);//映射的时内存,所以写0x0
    	//iATU_write_config_dword(my_device.pci_dev,iATU Control 2,xxx);//使能某个region,开始地址转换
    }
    
    
    //往dma配置寄存器中读写数据的函数,这是难点一:dma寄存器的寻址。
    int dma_read_config_dword(struct pci_dev *pdev,int offset)
    {
    	int value =0;
    	return value;
    }
    
    void dma_write_config_dword(struct pci_dev *pdev,int offset,int value)
    {
    	
    }
    
    void dma_init(void)
    {
    	int pos;
    	u16 msi_control;
    	u32 msi_addr_l;
    	u32 msi_addr_h;
    	u32 msi_data;
    	
    	//1.dma 通道0 写初始化 。如何访问DMA global register 寄存器组需要根据具体的硬件,可以通过pci_write/read_config_word/dword,
    	//也可以通过某个bar,比如通过bar0+偏移量访问。
    	//1.1 DMA write engine enable =0x1,这里请根据自己的芯片填写
    	//dma_write_config_dword(->pci_dev,DMA write engine enable,0x1);	
    	//1.2 获取msi能力寄存器的地址
    	pos =pci_find_capability(my_device.pci_dev,PCI_CAP_ID_MSI);
    	//1.3 读取msi的协议部分,得到pci设备是32位还是64位,不同的架构msi data寄存器地址同
    	pci_read_config_word(my_device.pci_dev,pos+2,&msi_control);
    	//1.4 读取msi能力寄存器组中的地址寄存器的值
    	pci_read_config_dword(my_device.pci_dev,pos+4,&msi_addr_l);	
    	//1.5 设置 DMA write done IMWr Address Low.这里请根据自己的芯片填写
    	//dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address Low,msi_addr_l);
    	//1.6 设置 DMA write abort IMWr Address Low.这里请根据自己的芯片填写
    	//dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address Low,msi_addr_l);
    	
    	if(msi_control&0x80){
    		//64位的
    		//1.7 读取msi能力寄存器组中的高32位地址寄存器的值
    		pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_addr_h);
    		//1.8 读取msi能力寄存器组中的数据寄存器的值
    		pci_read_config_dword(my_device.pci_dev,pos+0xc,&msi_data);
    		
    		//1.9 设置 DMA write done IMWr Address High.这里请根据自己的芯片填写
    		//dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address High,msi_addr_h);
    		//1.10 设置 DMA write abort IMWr Address High.这里请根据自己的芯片填写
    		//dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address High,msi_addr_h);
    		
    	} else {
    		//1.11 读取msi能力寄存器组中的数据寄存器的值
    		pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_data);
    	}
    	
    	//1.12 把数据寄存器的值写入到dma的控制寄存器组中的 DMA write channel 0 IMWr data中
    	//dma_write_config_dword(my_device.pci_dev,DMA write channel 0 IMWr data,msi_data);
    	
    	//1.13 DMA channel 0 control register 1 = 0x4000010
    	//dma_write_config_dword(my_device.pci_dev,DMA channel 0 control register 1,0x4000010);
    	
    	//2.dma 通道0 读初始化 和上述操作类似,不再叙述。
    }
    
    static int hello_probe(struct pci_dev *pdev, const struct pci_device_id *id)
    {
    	int i;
    	int result;
    	//使能pci设备
    	if (pci_enable_device(pdev)){
            result = -EIO;
    		goto end;
    	}
    	
    	pci_set_master(pdev);	
    	my_device.pci_dev=pdev;
    
    	if(unlikely(pci_request_regions(pdev,DEV_NAME))){
    		DEBUG_ERR("failed:pci_request_regions\n");
    		result = -EIO;
    		goto enable_device_err;
    	}
    	
    	//获得bar0的物理地址和虚拟地址
    	bar0_phy = pci_resource_start(pdev,0);
    	if(bar0_phy<0){
    		DEBUG_ERR("failed:pci_resource_start\n");
    		result =-EIO;
    		goto request_regions_err;
    	}
    	
    	//假设bar0是作为内存,流程是这样的,但是在本程序中不对bar0进行任何操作。
    	bar0_length = pci_resource_len(pdev,0);
    	if(bar0_length!=0){
    		bar0_vir = (unsigned long)ioremap(bar0_phy,bar0_length);
    	}
    	
    	//申请一块DMA内存,作为源地址,在进行DMA读写的时候会用到。
    	dma_src_vir=(dma_addr_t)pci_alloc_consistent(pdev,DMA_BUFFER_SIZE,&dma_src_phy);
    	if(dma_src_vir != 0){
    		for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    			SetPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
    		}
    	} else {
    		goto free_bar0;
    	}
    	
    	//申请一块DMA内存,作为目的地址,在进行DMA读写的时候会用到。
    	dma_dst_vir=(dma_addr_t)pci_alloc_consistent(pdev,DMA_BUFFER_SIZE,&dma_dst_phy);
    	if(dma_dst_vir!=0){
    		for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    			SetPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
    		}
    	} else {
    		goto alloc_dma_src_err;
    	}
    	//使能msi,然后才能得到pdev->irq
    	 result = pci_enable_msi(pdev);
    	 if (unlikely(result)){
    		DEBUG_ERR("failed:pci_enable_msi\n");
    		goto alloc_dma_dst_err;
        }
    	
    	result = request_irq(pdev->irq, hello_interrupt, 0, DEV_NAME, my_device.pci_dev);
        if (unlikely(result)){
           DEBUG_ERR("failed:request_irq\n");
    	   goto enable_msi_error;
        }
    	
    	//DMA 的读写初始化
    	dma_init();
    	
    enable_msi_error:
    		pci_disable_msi(pdev);
    alloc_dma_dst_err:
    	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    		ClearPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
    	}
    	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_dst_vir,dma_dst_phy);
    alloc_dma_src_err:
    	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    		ClearPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
    	}
    	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_src_vir,dma_src_phy);
    free_bar0:
    	iounmap((void *)bar0_vir);
    request_regions_err:
    	pci_release_regions(pdev);
    	
    enable_device_err:
    	pci_disable_device(pdev);
    end:
    	return result;
    }
    
    static void hello_remove(struct pci_dev *pdev)
    {
    	int i;
    	
    	free_irq(pdev->irq,my_device.pci_dev);
    	pci_disable_msi(pdev);
    
    	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    		ClearPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
    	}
    	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_dst_vir,dma_dst_phy);
    
    	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    		ClearPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
    	}
    	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_src_vir,dma_src_phy);
    
    	iounmap((void *)bar0_vir);
    	pci_release_regions(pdev);
    	pci_disable_device(pdev);
    }
    
    //难点三:中断响应设置
    static irqreturn_t hello_interrupt(int irq, void * dev)
    {  
        //1.该中断调用时机:当DMA完成的时候,会往msi_addr中写入msi_data,从而产生中断调用这个函数
    	//2.根据DMA Channel control 1 register寄存器的状态,判断读写状态,读失败,写失败,读成功,写成功,做出不同的处理。
    	return 0;
    }
    static struct pci_driver hello_driver = {
        .name = DEV_NAME,
        .id_table = hello_ids,
        .probe = hello_probe,
        .remove = hello_remove,
    };
    
    static int hello_open(struct inode *inode, struct file *file)
    {
    	printk("driver: hello_open\n");
    	//填写产品的逻辑
    	return 0;
    }
    
    int hello_close(struct inode *inode, struct file *file)
    {
    	printk("driver: hello_close\n");
    	//填写产品的逻辑
    	return 0;
    }
    
    long hello_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
    	//填写产品的逻辑
    	//为应用层提供的函数接口,通过解析cmd,在switch中做出不同的处理。 
    	iATU_bar0();//某个合适的地方调用
    	return 0;
    	
    }
    
    //难点二:启动dma的读写(read和write函数).
    static struct file_operations hello_fops = {
    	.owner   		=  THIS_MODULE,    
    	.open   		=  hello_open,     
    	.release 		=  hello_close,
    	.unlocked_ioctl =  hello_unlocked_ioctl,
    };
    
    static int hello_drv_init(void)
    {
    	int ret;
    	ret = pci_register_driver(&hello_driver);
    	if (ret < 0) {
    		printk("failed: pci_register_driver\n");
    		return ret;
    	}
    	
    	ret=alloc_chrdev_region(&my_device.devno,0,DEVICE_NUMBER,"hello");
    	if (ret < 0) {
    		printk("failed: register_chrdev_region\n");
    		return ret;
    	}
    
    	cdev_init(&my_device.cdev, &hello_fops);
    	ret = cdev_add(&my_device.cdev, my_device.devno, DEVICE_NUMBER);
    	if (ret < 0) {
    		printk("faield: cdev_add\n");
    		return ret;
    	}
    	
    	hello_class = class_create(THIS_MODULE, "hello_class");
    	hello_class_dev = device_create(hello_class, NULL, my_device.devno, NULL, "hello_device"); 
    
    	return 0;
    }
    
    static void hello_drv_exit(void)
    {
    	device_destroy(hello_class,my_device.devno);
    	class_destroy(hello_class);
    		
    	cdev_del(&(my_device.cdev));
    	unregister_chrdev_region(my_device.devno,DEVICE_NUMBER);
    	pci_unregister_driver(&hello_driver);
    }
    
    module_init(hello_drv_init);
    module_exit(hello_drv_exit);

    运行结果

    程序运行后,在linux内核注册PCIE设备,内容如下

    下载

    PCIE驱动开发(内含Makefile,直接编译即可使用)
    http://download.csdn.net/download/u010872301/10116259

    展开全文
  • 2、根据PCIe的WDF驱动程序的功能需求,设计和实现了驱动程序的初始化、读写基址寄存器、获得寄存器的基地址、读写配置空间、中断处理、申请和释放DMA空间等功能。针对光纤通信中海量数据高速传输的特点,基于链表机制,...
  • pcie接口电路及驱动

    2020-07-14 23:31:14
    花钱买的PCIE接口电路图及驱动程序开发,现在免费共享,由于等级不够无法上传大文件
  • 近期在64位Win7下开发一款PCIe接口的多串口卡驱动程序,做个小结: 1. 因为在Win下对WDF不熟悉,加上市面上DDK、WDM书籍较多,故选用WDM框架; 2. 多串口卡的硬件接口为PCIe,因为在软件驱动层面上,PCIe和PCI...

    近期在64位Win7下开发一款PCIe接口的多串口卡驱动程序,做个小结:


    1. 因为在Win下对WDF不熟悉,加上市面上DDK、WDM书籍较多,故选用WDM框架;


    2. 多串口卡的硬件接口为PCIe,因为在软件驱动层面上,PCIe和PCI兼容,直接借用常用的WDM即插即用框架。这里用《Windows驱动开发技术详解(张帆等编写)》第16章Test5中的InitMyPCI函数。该函数枚举了PCI总线的各种资源,如中断、IO口、内存等,直接原封不动的拿来即可用;

    ① PCI工具PciScope、RW,多多使用;

    ② WinDriver开发框架虽然简单,但效率不高,做初期验证不错;但随着对PCI等总线的深入了解,发现还不如自己控制来的直接;DriverStudio也是如此;

    ② 访问一个LONG型的reg,注意内存边界访问方式,:WRITE_REGISTER_ULONG((PULONG)(g_MemBar0+(i * 4) + UART_TX_DATA), dwVal);【这里g_MemBar0是一个unsigned char类型的


    3. 该串口卡没有使用中断,底层数据交换都是读写reg实现的。这种机制,驱动开发难度一下子减少了不少,故在内核驱动中启动一个线程,轮询PCIe定义的reg资源,调度线程每次Sleep为1ms(但经测试DebugView测试,实际在15ms左右)。这里和硬件设计人员沟通,每次休息的15ms中,串口卡FPGA中数据缓存足够大,不会丢失。但在串口使用层面看,写入或读出的数据会带来约15ms的延时(忽略其他延时),这是一个需注意的问题;

    //延时函数,单位为ms
    void MySleep(LONG msec)
    {
    #define DELAY_ONE_MICROSECOND		(-10)							//1微秒
    #define DELAY_ONE_MILLISECOND		(DELAY_ONE_MICROSECOND*1000)	//1毫秒
    	
    	LARGE_INTEGER	my_interval;
    	my_interval.QuadPart = DELAY_ONE_MILLISECOND;
    	my_interval.QuadPart *= msec;
    	
    	KeDelayExecutionThread(KernelMode, 0, &my_interval);
    }
    

    由此看来,内核层的1ms睡眠,和Win32的(1)精度一样,不加高精度多媒体库时,都是约15ms。

    (关于在Win32下测量Sleep的精度问题,参见我的另一篇文章:http://blog.csdn.net/dijkstar/article/details/23092747,里面有详细的测试方法和测试数据)


    4. 驱动层面暴露给Win32的串口COM口,使用《Windows驱动开发技术详解(张帆等编写)》第19章的Virtual_COM示例。

    借用这个框架,能看出市面上最常用的串口调试助手“sscom32.exe”,在读取数据时,调用了IOCTL_SERIAL_GET_COMMSTATUS,又调用了IOCTL_SERIAL_WAIT_ON_MASK(对应Win32的WaitCommEvent),所以,自己写的驱动里,必须要处理这两个请求:对第一个IOCTL_SERIAL_GET_COMMSTATUS,简单点处理,要将当前可读的数据个数赋值给AmountInInQueue成员;第二个请求,还用原框架内容:立刻Pending该请求,但要在轮询的线程里,轮询Fifo(后面介绍)发现有数据时,释放到这个请求。这个逻辑查看函数DriverCheckEvent怎么被调用的即可;


    5. 如何多个设备dev的创建?传统的WDM是创建一个fdo,附加在底层的pdo之上。灵活改变一下,将第一个fdo创建时,调用IoAttachDeviceToDeviceStack,但第二个、第三个....,不要调用该函数,这样创建后,用DeviceTree查看创建的设备列表,发现他们都是“平行”关系,而不是“Att”关系(只有第一个是);

    如此,在Win32应用层CreateFile、ReadFile、WriteFile时,访问的都是“当前”的那个dev,所以,要在驱动层里,自己维护一个全局的设备dev[n]数组;


    6. WDM的初始化PCI设备函数是放在IRP_MN_START_DEVICE完成的,而IRP_MN_START_DEVICE又依赖于“某一个dev”的CreateFile完成的,怎么办?使用一个全局变量,当应用层CreateFile时第一个设备时(下标不一定是0),调用初始化PCI设备函数,将全局变量置false,下次再有应用层CreateFile不会调用InitMyPCI;

    由此看出,“WDM即插即用”和“传统的NTDriver”并没有区分的那么明显,像上面的思路,就是把两者结合起来使用了;


    7. 64位inf生成:只需要将张帆示例的inf添加个NTamd64即可,如下:

    [Manufacturer]
    %MfgName%=Mfg0,NTamd64
    
    [Mfg0.NTamd64]
    
    ; PCI hardware Ids use the form
    ; PCI\VEN_aaaa&DEV_bbbb&SUBSYS_cccccccc&REV_dd
    %DeviceDesc%=YouMark_DDI, PCI\VEN_9999&DEV_9999
    

    8. 64位Win7的驱动需要数字签名,目前没有找到彻底解决办法,暂时:

    ① 每次启动按F8进入“禁用驱动签名”;

    ② 进入TestMode:bcdedit/set testsigning on(一定用管理员身份运行),但这样结果导致会出现桌面右下角水印;

    ③ 使用工具dseo13b.exe(http://www.ngohq.com/?page=dseo);

    ④ 找网上的“去水印”工具(Windows 7 Watermark Remover);

    ⑤ 张佩写的签名工具:64Signer V1.2.exe,使用文档、签名原理及下载处:http://www.yiiyee.cn/Blog/64signer/


    9. IRP同步问题,做一个假设:假设用户应用程序不会开启两个线程同时调用ReadFile、WriteFile,因此在IRP_MJ_READ、IRP_MJ_WRITE处理里,不考虑STATUS_PENDING的情况,而是直接读写Fifo缓冲区,即为一个非阻塞的操作,无论有无数据都立刻返回。由上层用户启动线程时在调用ReadFile类似的函数时,自己Sleep操作;

    如果假设应用层开启两个线程同时ReadFile操作,那么应该使用“串行化处理IRP”(StartIO例程)技术,基本思路是IRP_MJ_READ请求进来后,先Pending挂起该请求,将该请求入队,在StartIO例程里去处理该请求,自己做取舍;


    10. 硬件Reg数据的使用:驱动中一个线程高速轮询Reg,不断的读写,读出或写入的数据不是和IRP_MJ_READ、IRP_MJ_WRITE交互,而是首先放到一个Fifo中再交互。这个Fifo要在驱动里自己实现,参见我的另一篇文章:http://blog.csdn.net/dijkstar/article/details/42361805(推荐一个VC下的FIFO实现源码CCircularFifo,附带测试程序),这个程序稍加改造,就可以在驱动层面中使用,记住在内核里创建内存干脆直接用NonPagedPool(非分页内存)吧,现在的机器内存都不少于4G了,没必要为区区一点“交换内存”而引起蓝屏崩溃。



    展开全文
  • PCIE(PCI Express)是INTEL提出的新一代的总线接口,目前普及的PCIE 3.0的传输速率为8GT/s,下一代PCIE 4.0将翻番为16GT/S, 因为传输速率快广泛应用于数据中心、云计算、人工智能、机器学习、视觉计算、显卡、存储和...

    PCIE(PCI Express)是INTEL提出的新一代的总线接口,目前普及的PCIE 3.0的传输速率为8GT/s,下一代PCIE 4.0将翻番为16GT/S,
    因为传输速率快广泛应用于数据中心、云计算、人工智能、机器学习、视觉计算、显卡、存储和网络等领域。

    PCIE插槽是可以向下兼容的,比如PCIE 1X接口可以插4X、8X、16X的插槽上。
    在这里插入图片描述
    实现基本的PCIE驱动程序,实现以下模块:
    初始化设备、设备打开、数据读写和控制、中断处理、设备释放、设备卸载。

    本程序适合PCIE驱动开发通用调试的基本框架,对于具体PCIE设备,需要配置相关寄存器才可以使用!

    源码:

    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <linux/fs.h>
    #include <linux/signal.h>
    #include <linux/init.h>
    #include <linux/cdev.h>
    #include <linux/delay.h>
    #include <linux/poll.h>
    #include <linux/device.h>
    #include <linux/pci.h>
    #include <linux/interrupt.h> 
    #include <asm/uaccess.h> 
     
    MODULE_LICENSE("Dual BSD/GPL");
    MODULE_DESCRIPTION("pcie device driver");
     
    #define DEV_NAME "hello_pcie"
    #define DEBUG 
     
    #ifdef DEBUG
    	#define DEBUG_ERR(format,args...) \
    	do{  \
    		printk("[%s:%d] ",__FUNCTION__,__LINE__); \
    		printk(format,##args); \
    	}while(0)
    #else
    	#define DEBUG_PRINT(format,args...) 
    #endif
     
    //1M 
    #define DMA_BUFFER_SIZE 1*1024*1024 
    #define FASYNC_MINOR 1
    #define FASYNC_MAJOR 244
    #define DEVICE_NUMBER 1
     
    static struct class * hello_class;
    static struct device * hello_class_dev;
     
    struct hello_device
    {
    	struct pci_dev* pci_dev;
    	struct cdev cdev;
    	dev_t devno;
    }my_device;
     
    //barn(n=0,1,2或者012345) 空间的物理地址,长度,虚拟地址
    unsigned long bar0_phy;
    unsigned long bar0_vir;
    unsigned long bar0_length;
    unsigned long bar1_phy;
    unsigned long bar1_vir;
    unsigned long bar1_length;
     
    //进行DMA转换时,dma的源地址和目的地址
    dma_addr_t dma_src_phy;
    dma_addr_t dma_src_vir;
    dma_addr_t dma_dst_phy;
    dma_addr_t dma_dst_vir;
     
    //根据设备的id填写,这里假设厂商id和设备id
    #define HELLO_VENDOR_ID 0x666
    #define HELLO_DEVICE_ID 0x999
    static struct pci_device_id hello_ids[] = {
        {HELLO_VENDOR_ID,HELLO_DEVICE_ID,PCI_ANY_ID,PCI_ANY_ID,0,0,0},
        {0,}
    };
    MODULE_DEVICE_TABLE(pci,hello_ids);
     
    static int hello_probe(struct pci_dev *pdev, const struct pci_device_id *id);
    static void hello_remove(struct pci_dev *pdev);
    static irqreturn_t hello_interrupt(int irq, void * dev);
     
    //往iATU写数据的函数
    void iATU_write_config_dword(struct pci_dev *pdev,int offset,int value)
    {
    	
    }
     
    //假设需要将bar0映射到内存
    static void iATU_bar0(void)
    {
    	//下面几步,在手册中有example
    	//iATU_write_config_dword(my_device.pci_dev,iATU Lower Target Address ,xxx);//xxx表示内存中的地址,将bar0映射到这块内存
    	//iATU_write_config_dword(my_device.pci_dev,iATU Upper Target Address ,xxx);//xxx表示内存中的地址,将bar0映射到这块内存
     
    	//iATU_write_config_dword(my_device.pci_dev,iATU Control 1,0x0);//映射的时内存,所以写0x0
    	//iATU_write_config_dword(my_device.pci_dev,iATU Control 2,xxx);//使能某个region,开始地址转换
    }
     
     
    //往dma配置寄存器中读写数据的函数,这是难点一:dma寄存器的寻址。
    int dma_read_config_dword(struct pci_dev *pdev,int offset)
    {
    	int value =0;
    	return value;
    }
     
    void dma_write_config_dword(struct pci_dev *pdev,int offset,int value)
    {
    	
    }
     
    void dma_init(void)
    {
    	int pos;
    	u16 msi_control;
    	u32 msi_addr_l;
    	u32 msi_addr_h;
    	u32 msi_data;
    	
    	//1.dma 通道0 写初始化 。如何访问DMA global register 寄存器组需要根据具体的硬件,可以通过pci_write/read_config_word/dword,
    	//也可以通过某个bar,比如通过bar0+偏移量访问。
    	//1.1 DMA write engine enable =0x1,这里请根据自己的芯片填写
    	//dma_write_config_dword(->pci_dev,DMA write engine enable,0x1);	
    	//1.2 获取msi能力寄存器的地址
    	pos =pci_find_capability(my_device.pci_dev,PCI_CAP_ID_MSI);
    	//1.3 读取msi的协议部分,得到pci设备是32位还是64位,不同的架构msi data寄存器地址同
    	pci_read_config_word(my_device.pci_dev,pos+2,&msi_control);
    	//1.4 读取msi能力寄存器组中的地址寄存器的值
    	pci_read_config_dword(my_device.pci_dev,pos+4,&msi_addr_l);	
    	//1.5 设置 DMA write done IMWr Address Low.这里请根据自己的芯片填写
    	//dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address Low,msi_addr_l);
    	//1.6 设置 DMA write abort IMWr Address Low.这里请根据自己的芯片填写
    	//dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address Low,msi_addr_l);
    	
    	if(msi_control&0x80){
    		//64位的
    		//1.7 读取msi能力寄存器组中的高32位地址寄存器的值
    		pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_addr_h);
    		//1.8 读取msi能力寄存器组中的数据寄存器的值
    		pci_read_config_dword(my_device.pci_dev,pos+0xc,&msi_data);
    		
    		//1.9 设置 DMA write done IMWr Address High.这里请根据自己的芯片填写
    		//dma_write_config_dword(my_device.pci_dev,DMA write done IMWr Address High,msi_addr_h);
    		//1.10 设置 DMA write abort IMWr Address High.这里请根据自己的芯片填写
    		//dma_write_config_dword(my_device.pci_dev,DMA write abort IMWr Address High,msi_addr_h);
    		
    	} else {
    		//1.11 读取msi能力寄存器组中的数据寄存器的值
    		pci_read_config_dword(my_device.pci_dev,pos+0x8,&msi_data);
    	}
    	
    	//1.12 把数据寄存器的值写入到dma的控制寄存器组中的 DMA write channel 0 IMWr data中
    	//dma_write_config_dword(my_device.pci_dev,DMA write channel 0 IMWr data,msi_data);
    	
    	//1.13 DMA channel 0 control register 1 = 0x4000010
    	//dma_write_config_dword(my_device.pci_dev,DMA channel 0 control register 1,0x4000010);
    	
    	//2.dma 通道0 读初始化 和上述操作类似,不再叙述。
    }
     
    static int hello_probe(struct pci_dev *pdev, const struct pci_device_id *id)
    {
    	int i;
    	int result;
    	//使能pci设备
    	if (pci_enable_device(pdev)){
            result = -EIO;
    		goto end;
    	}
    	
    	pci_set_master(pdev);	
    	my_device.pci_dev=pdev;
     
    	if(unlikely(pci_request_regions(pdev,DEV_NAME))){
    		DEBUG_ERR("failed:pci_request_regions\n");
    		result = -EIO;
    		goto enable_device_err;
    	}
    	
    	//获得bar0的物理地址和虚拟地址
    	bar0_phy = pci_resource_start(pdev,0);
    	if(bar0_phy<0){
    		DEBUG_ERR("failed:pci_resource_start\n");
    		result =-EIO;
    		goto request_regions_err;
    	}
    	
    	//假设bar0是作为内存,流程是这样的,但是在本程序中不对bar0进行任何操作。
    	bar0_length = pci_resource_len(pdev,0);
    	if(bar0_length!=0){
    		bar0_vir = (unsigned long)ioremap(bar0_phy,bar0_length);
    	}
    	
    	//申请一块DMA内存,作为源地址,在进行DMA读写的时候会用到。
    	dma_src_vir=(dma_addr_t)pci_alloc_consistent(pdev,DMA_BUFFER_SIZE,&dma_src_phy);
    	if(dma_src_vir != 0){
    		for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    			SetPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
    		}
    	} else {
    		goto free_bar0;
    	}
    	
    	//申请一块DMA内存,作为目的地址,在进行DMA读写的时候会用到。
    	dma_dst_vir=(dma_addr_t)pci_alloc_consistent(pdev,DMA_BUFFER_SIZE,&dma_dst_phy);
    	if(dma_dst_vir!=0){
    		for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    			SetPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
    		}
    	} else {
    		goto alloc_dma_src_err;
    	}
    	//使能msi,然后才能得到pdev->irq
    	 result = pci_enable_msi(pdev);
    	 if (unlikely(result)){
    		DEBUG_ERR("failed:pci_enable_msi\n");
    		goto alloc_dma_dst_err;
        }
    	
    	result = request_irq(pdev->irq, hello_interrupt, 0, DEV_NAME, my_device.pci_dev);
        if (unlikely(result)){
           DEBUG_ERR("failed:request_irq\n");
    	   goto enable_msi_error;
        }
    	
    	//DMA 的读写初始化
    	dma_init();
    	
    enable_msi_error:
    		pci_disable_msi(pdev);
    alloc_dma_dst_err:
    	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    		ClearPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
    	}
    	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_dst_vir,dma_dst_phy);
    alloc_dma_src_err:
    	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    		ClearPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
    	}
    	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_src_vir,dma_src_phy);
    free_bar0:
    	iounmap((void *)bar0_vir);
    request_regions_err:
    	pci_release_regions(pdev);
    	
    enable_device_err:
    	pci_disable_device(pdev);
    end:
    	return result;
    }
     
    static void hello_remove(struct pci_dev *pdev)
    {
    	int i;
    	
    	free_irq(pdev->irq,my_device.pci_dev);
    	pci_disable_msi(pdev);
     
    	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    		ClearPageReserved(virt_to_page(dma_dst_phy+i*PAGE_SIZE));
    	}
    	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_dst_vir,dma_dst_phy);
     
    	for(i=0;i<DMA_BUFFER_SIZE/PAGE_SIZE;i++){
    		ClearPageReserved(virt_to_page(dma_src_phy+i*PAGE_SIZE));
    	}
    	pci_free_consistent(pdev,DMA_BUFFER_SIZE,(void *)dma_src_vir,dma_src_phy);
     
    	iounmap((void *)bar0_vir);
    	pci_release_regions(pdev);
    	pci_disable_device(pdev);
    }
     
    //难点三:中断响应设置
    static irqreturn_t hello_interrupt(int irq, void * dev)
    {  
        //1.该中断调用时机:当DMA完成的时候,会往msi_addr中写入msi_data,从而产生中断调用这个函数
    	//2.根据DMA Channel control 1 register寄存器的状态,判断读写状态,读失败,写失败,读成功,写成功,做出不同的处理。
    	return 0;
    }
    static struct pci_driver hello_driver = {
        .name = DEV_NAME,
        .id_table = hello_ids,
        .probe = hello_probe,
        .remove = hello_remove,
    };
     
    static int hello_open(struct inode *inode, struct file *file)
    {
    	printk("driver: hello_open\n");
    	//填写产品的逻辑
    	return 0;
    }
     
    int hello_close(struct inode *inode, struct file *file)
    {
    	printk("driver: hello_close\n");
    	//填写产品的逻辑
    	return 0;
    }
     
    long hello_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    {
    	//填写产品的逻辑
    	//为应用层提供的函数接口,通过解析cmd,在switch中做出不同的处理。 
    	iATU_bar0();//某个合适的地方调用
    	return 0;
    	
    }
     
    //难点二:启动dma的读写(read和write函数).
    static struct file_operations hello_fops = {
    	.owner   		=  THIS_MODULE,    
    	.open   		=  hello_open,     
    	.release 		=  hello_close,
    	.unlocked_ioctl =  hello_unlocked_ioctl,
    };
     
    static int hello_drv_init(void)
    {
    	int ret;
    	ret = pci_register_driver(&hello_driver);
    	if (ret < 0) {
    		printk("failed: pci_register_driver\n");
    		return ret;
    	}
    	
    	ret=alloc_chrdev_region(&my_device.devno,0,DEVICE_NUMBER,"hello");
    	if (ret < 0) {
    		printk("failed: register_chrdev_region\n");
    		return ret;
    	}
     
    	cdev_init(&my_device.cdev, &hello_fops);
    	ret = cdev_add(&my_device.cdev, my_device.devno, DEVICE_NUMBER);
    	if (ret < 0) {
    		printk("faield: cdev_add\n");
    		return ret;
    	}
    	
    	hello_class = class_create(THIS_MODULE, "hello_class");
    	hello_class_dev = device_create(hello_class, NULL, my_device.devno, NULL, "hello_device"); 
     
    	return 0;
    }
     
    static void hello_drv_exit(void)
    {
    	device_destroy(hello_class,my_device.devno);
    	class_destroy(hello_class);
    		
    	cdev_del(&(my_device.cdev));
    	unregister_chrdev_region(my_device.devno,DEVICE_NUMBER);
    	pci_unregister_driver(&hello_driver);
    }
     
    module_init(hello_drv_init);
    module_exit(hello_drv_exit);
    

    转自: https://blog.csdn.net/u010872301/article/details/78519371

    展开全文
  • Windows平台下的设备驱动程序从Windows 2000开始都是以WDM ( Windows Driver Model) 框架为平台进行开发。以此模型开发,开发者需要一方面实现驱动程序与硬件的交互,另一方面要对操作系统内核进行操作,难度大。...
  • 使用Visual Studio 2017 + WDK 10 KMDF模版 开发驱动在Windows 7 上可以正常使用。在Windows 10 上可以正常安装驱动驱动安装完,使用事件查看器查看到设备有错误:来源Kernel-PnP、问题0x15、问题状态0x00。另外...
  • Linux平台PCIe驱动编写

    2019-03-21 10:45:33
    以前文章分析了PCIe整个系统知识,包括如何扫描PCIE树,这篇文章讲解一下当拿到一个PCIe设备时如何编写驱动程序。编写驱动程序在应用程序中编写,同样可以在内核层编写驱动。 从应用层编写驱动主要是使用pcilib库和...
  • PCIe设备驱动demo

    2018-08-21 17:35:30
    PCIE(PCI Express)是INTEL提出的新一代的总线接口,目前普及的PCIE 3.0的传输速率为8GT/s,下一代PCIE 4.0将翻番为16GT/S,因为传输速率快广泛应用于数据中心、云计算、人工智能、机器学习、视觉计算、显卡、存储和...
  • 深入剖析了 WDF 驱动程序模型的基本框架和运行机理,从驱动程序的初始化、IRP 的处理、中断响应、DMA 操作以及应用程序接口等方面详细讨论了高速数据传输卡驱动程序的开发过程。最后,针对高速数据传输卡的工作特点...
  • 基于xilinx--ML605的一个开发例程,对于刚入门PCIe开发人员来说,不失为一个很好的参考
  • 基于WDF的PCIe驱动开发

    2017-11-09 19:15:06
    由于第一次接触WDF驱动开发,因此底层驱动基于微软提供的PCI9056驱动例子(因PCIe和PCI配置空间基本一致,故对9056例子做适当修改便可直接安装使用)。 驱动层提供设备驱动的基本功能函数,包括但不限于设备打开...
  • minipcie接口CAN卡

    2019-07-15 19:33:26
    LCminiPCIe系列miniPCIe接口CAN卡,具有1~2路CAN通道和一路PCI Express mini接口,插到工控机或单板电脑的PCI Express mini卡槽上,快速扩展出1~2路CAN通道。CAN接口电气隔离高达2500VDC,具有优秀的EMC性能,可靠性...
  • 有好几个月没来更新博客啦,但是我并不是在偷懒,已经整理好好几篇的材料,后面陆续会发表出来,敬请期待!...关于基于FPGA的PCIe接口设计,我规划分3篇来阐述。第一篇:介绍PCIe的基本概念;第...
  • 1、WinDBG是唯一的内核驱动调试利器,但是开发PCIe的WDF驱动可以采用“黑盒”方式,所以windbg不是必须的; 2、WDF比WDM好,别再用WDM了; 3、驱动程序编译成的二进制文件是sys类型,和EXE一样都是Portable ...
  • linux PCIE驱动开发

    2019-06-16 16:18:15
    2019独角兽企业重金招聘Python工程师标准>>> ...
  • 最近几天,在为公司自研的PCI板卡写
1 2 3 4 5 ... 20
收藏数 2,518
精华内容 1,007
关键字:

pcie 接口驱动开发