DSP开发之PCI驱动
发布时间:2021-05-02 18:46:11.425 文章来源:AiSoftCloud 浏览次数:2354 下载次数:1 

初步了解Windows驱动

微软技术文档:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/

示例KMDF驱动程序:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/wdf/sample-kmdf-drivers

示例UMDF驱动程序:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/wdf/sample-umdf-drivers

编译示例程序

此处以微软提供的最简单的KMDF驱动程序(ECHO,该驱动程序演示了如何使用框架的队列并请求对象和自动同步)为例:

从github上下载微软驱动程序示例:

  1. git clone https://github.com/microsoft/Windows-driver-samples.git

(1) 用Visual Studio 2019打开工程目录Windows-driver-samples\general\echo\kmdf\kmdfecho.sln(安装了Windows SDK及WDK驱动开发工具:

如何安装参考:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/download-the-wdk

在Windows 10下这个仓库生成的驱动安装有问题,未找到原因,可以使用Windows-driver-samples\general\echo\umdf2\umdf2echo.sln,这个工程测试可用。

(2) 64位系统下需要设置编译平台为x64,仓库默认x86,安装会出现失败:
编译平台设置

(3) 64位系统下需要对驱动进行签名,不然会无法安装,设置测试签名:
签名设置

(4) 用Visual Studio 2019打开工程目录Windows-driver-samples\setup\devcon\devcon.sln,编译生成devcon可执行程序。

(5) 管理员权限打开命令行,输入以下命令,即可安装驱动:

  1. devcon.exe install echo.inf root\ECHO

(6) 打开设备管理器,可以看到安装的驱动了:

下图为内核态驱动:
设备管理器
下图为用户态驱动:
用户态

KMDF驱动框架

一个即插即用的驱动程序主要包含:

1、一个DriverEntry例程

2、一个EvtDriverDeviceAdd例程

3、一个或多个I/O队列

4、一个或多个I/O事件回调例程

5、支持即插即用及电源管理的例程

6、支持的WMI回调例程,用于管理计算机系统

7、其他回调例程,如对象的清除、终端处理、DMA等例程

DriverEntry例程

DriverEntry例程负责驱动程序的初始化,是驱动程序的入口,就像可执行程序的main函数一样。所有的驱动程序都包含DriverEntry例程。

DriverEntry例程主要用来创建驱动对象及设置EvtDriverDeviceAdd例程地址:

  1. NTSTATUS
  2. DriverEntry(
  3. _In_ PDRIVER_OBJECT DriverObject,
  4. _In_ PUNICODE_STRING RegistryPath
  5. )
  6. {
  7. WDF_DRIVER_CONFIG config;
  8. NTSTATUS status;
  9. WDF_OBJECT_ATTRIBUTES attributes;
  10. WDFDRIVER driver;
  11. PDRIVER_CONTEXT driverContext;
  12. WPP_INIT_TRACING(DriverObject, RegistryPath);
  13. TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
  14. WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
  15. attributes.EvtCleanupCallback = ZhouZhenPciDriverKmdfEvtDriverContextCleanup;
  16. WDF_DRIVER_CONFIG_INIT(&config,
  17. ZhouZhenPciDriverKmdfEvtDeviceAdd
  18. );
  19. status = WdfDriverCreate(DriverObject,
  20. RegistryPath,
  21. &attributes,
  22. &config,
  23. WDF_NO_HANDLE
  24. );
  25. if (!NT_SUCCESS(status)) {
  26. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
  27. WPP_CLEANUP(DriverObject);
  28. return status;
  29. }
  30. TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
  31. return status;
  32. }

EvtDriverDeviceAdd 例程

驱动程序初始化后,PnP管理器调用驱动程序的 DeviceAdd 例程来初始化由该驱动程序所控制的设备。在 EvtDriverDeviceAdd 例程中,驱动程序创建一个设备对象作为目标I/O设备,并将设备对象附着在设备堆栈中。

在设备被首次枚举时,DeviceAdd 例程在系统初始化时被调用。当系统运行时,任何时候新设备被枚举,系统都将调用 DeviceAdd 例程。

在KMDF中,DeviceAdd 例程的职责是:创建设备对象,一个或多个I/O队列和设备GUID接口,设置各种事件的回调例程,如即插即用、电源管理、I/O处理等例程。

  1. NTSTATUS
  2. KmdfEvtDeviceAdd(
  3. _In_ WDFDRIVER Driver,
  4. _Inout_ PWDFDEVICE_INIT DeviceInit
  5. )
  6. /*++
  7. Routine Description:
  8. EvtDeviceAdd is called by the framework in response to AddDevice
  9. call from the PnP manager. We create and initialize a device object to
  10. represent a new instance of the device.
  11. Arguments:
  12. Driver - Handle to a framework driver object created in DriverEntry
  13. DeviceInit - Pointer to a framework-allocated WDFDEVICE_INIT structure.
  14. Return Value:
  15. NTSTATUS
  16. --*/
  17. {
  18. NTSTATUS status;
  19. WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
  20. WDF_POWER_POLICY_EVENT_CALLBACKS powerPolicyCallbacks;
  21. WDF_OBJECT_ATTRIBUTES deviceAttributes;
  22. WDFDEVICE device;
  23. PFDO_DATA fdoData = NULL;
  24. WDFQUEUE queue;
  25. WDF_IO_QUEUE_CONFIG queueConfig;
  26. WDF_INTERRUPT_CONFIG interruptConfig;
  27. UNREFERENCED_PARAMETER(Driver);
  28. PAGED_CODE();
  29. TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
  30. //
  31. // I/O type is Buffered by default. If required to use something else,
  32. // call WdfDeviceInitSetIoType with the appropriate type.
  33. //
  34. // 采用WdfDeviceIoDirect方式
  35. WdfDeviceInitSetIoType(DeviceInit, WdfDeviceIoDirect);
  36. //
  37. // Zero out the PnpPowerCallbacks structure.
  38. //
  39. // 初始化即插即用和电源管理例程配置结构
  40. WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
  41. //
  42. // Set Callbacks for any of the functions we are interested in.
  43. // If no callback is set, Framework will take the default action
  44. // by itself. This sample provides many of the possible callbacks,
  45. // mostly because it's a fairly complex sample that drives full-featured
  46. // hardware. Drivers derived from this sample will often be able to
  47. // provide only some of these.
  48. //
  49. //
  50. // These callback is invoked to tear down all the driver-managed state
  51. // that is set up in this function. Many times, this callback won't do
  52. // much of anything, since many of the things that are set up here will
  53. // have their lifetimes automatically managed by the Framework.
  54. //
  55. //
  56. // These two callbacks set up and tear down hardware state,
  57. // specifically that which only has to be done once.
  58. //
  59. // 设置即插即用基本例程
  60. // EvtDevicePrepareHardware和EvtDeviceReleaseHardware两个例程
  61. // 对硬件设备能否获得Windows操作系统分配的资源起着至关重要的作用。
  62. // EvtDevicePrepareHardware的任务主要包括获得内存资源、内存物理地址与虚拟地址的映射、I/O端口映射和中断资源分配。
  63. pnpPowerCallbacks.EvtDevicePrepareHardware = PciDrvEvtDevicePrepareHardware;
  64. pnpPowerCallbacks.EvtDeviceReleaseHardware = PciDrvEvtDeviceReleaseHardware;
  65. //
  66. // These two callbacks set up and tear down hardware state that must be
  67. // done every time the device moves in and out of the D0-working state.
  68. //
  69. pnpPowerCallbacks.EvtDeviceD0Entry = PciDrvEvtDeviceD0Entry;
  70. pnpPowerCallbacks.EvtDeviceD0Exit = PciDrvEvtDeviceD0Exit;
  71. //
  72. // These next two callbacks are for doing work at PASSIVE_LEVEL (low IRQL)
  73. // after all the interrupts are connected and before they are disconnected.
  74. //
  75. // Some drivers need to do device initialization and tear-down while the
  76. // interrupt is connected. (This is a problem for these devices, since
  77. // it opens them up to taking interrupts before they are actually ready
  78. // to handle them, or to taking them after they have torn down too much
  79. // to be able to handle them.) While this hardware design pattern is to
  80. // be discouraged, it is possible to handle it by doing device init and
  81. // tear down in these routines rather than in EvtDeviceD0Entry and
  82. // EvtDeviceD0Exit.
  83. //
  84. // In this sample these callbacks don't do anything.
  85. //
  86. // pnpPowerCallbacks.EvtDeviceD0EntryPostInterruptsEnabled = NICEvtDeviceD0EntryPostInterruptsEnabled;
  87. // pnpPowerCallbacks.EvtDeviceD0ExitPreInterruptsDisabled = NICEvtDeviceD0ExitPreInterruptsDisabled;
  88. //
  89. // This next group of five callbacks allow a driver to become involved in
  90. // starting and stopping operations within a driver as the driver moves
  91. // through various PnP/Power states. These functions are not necessary
  92. // if the Framework is managing all the device's queues and there is no
  93. // activity going on that isn't queue-based. This sample provides these
  94. // callbacks because it uses watchdog timer to monitor whether the device
  95. // is working or not and it needs to start and stop the timer when the device
  96. // is started or removed. It cannot start and stop the timers in the D0Entry
  97. // and D0Exit callbacks because if the device is surprise-removed, D0Exit
  98. // will not be called.
  99. //
  100. pnpPowerCallbacks.EvtDeviceSelfManagedIoInit = PciDrvEvtDeviceSelfManagedIoInit;
  101. pnpPowerCallbacks.EvtDeviceSelfManagedIoCleanup = PciDrvEvtDeviceSelfManagedIoCleanup;
  102. pnpPowerCallbacks.EvtDeviceSelfManagedIoSuspend = PciDrvEvtDeviceSelfManagedIoSuspend;
  103. pnpPowerCallbacks.EvtDeviceSelfManagedIoRestart = PciDrvEvtDeviceSelfManagedIoRestart;
  104. //
  105. // Register the PnP and power callbacks. Power policy related callbacks will be registered
  106. // later.
  107. //
  108. // 注册即插即用和电源管理例程
  109. WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
  110. //
  111. // Init the power policy callbacks
  112. //
  113. WDF_POWER_POLICY_EVENT_CALLBACKS_INIT(&powerPolicyCallbacks);
  114. //
  115. // This group of three callbacks allows this sample driver to manage
  116. // arming the device for wake from the S0 state. Networking devices can
  117. // optionally be put into a low-power state when there is no networking
  118. // cable plugged into them. This sample implements this feature.
  119. //
  120. powerPolicyCallbacks.EvtDeviceArmWakeFromS0 = PciDrvEvtDeviceWakeArmS0;
  121. powerPolicyCallbacks.EvtDeviceDisarmWakeFromS0 = PciDrvEvtDeviceWakeDisarmS0;
  122. powerPolicyCallbacks.EvtDeviceWakeFromS0Triggered = PciDrvEvtDeviceWakeTriggeredS0;
  123. //
  124. // This group of three callbacks allows the device to be armed for wake
  125. // from Sx (S1, S2, S3 or S4.) Networking devices can optionally be put
  126. // into a state where a packet sent to them will cause the device's wake
  127. // signal to be triggered, which causes the machine to wake, moving back
  128. // into the S0 state.
  129. //
  130. powerPolicyCallbacks.EvtDeviceArmWakeFromSx = PciDrvEvtDeviceWakeArmSx;
  131. powerPolicyCallbacks.EvtDeviceDisarmWakeFromSx = PciDrvEvtDeviceWakeDisarmSx;
  132. powerPolicyCallbacks.EvtDeviceWakeFromSxTriggered = PciDrvEvtDeviceWakeTriggeredSx;
  133. //
  134. // Register the power policy callbacks.
  135. //
  136. WdfDeviceInitSetPowerPolicyEventCallbacks(DeviceInit, &powerPolicyCallbacks);
  137. // Since we are the function driver, we are now the power policy owner
  138. // for the device according to the default framework rule. We will register
  139. // our power policy callbacks after finding the wakeup capability of the device.
  140. //
  141. // Specify the context type and size for the device we are about to create.
  142. //
  143. WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, FDO_DATA);
  144. deviceAttributes.SynchronizationScope = WdfSynchronizationScopeDevice;
  145. status = ZhouZhenPciDriverKmdfCreateDevice(DeviceInit);
  146. if (!NT_SUCCESS(status)) {
  147. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER,
  148. "WdfDeviceInitialize failed %!STATUS!\n", status);
  149. return status;
  150. }
  151. //
  152. // If our device supports wait-wake then we will set our power-policy and
  153. // update S0-Idle policy.
  154. //
  155. /*
  156. if (IsPoMgmtSupported(fdoData)) {
  157. TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER,
  158. "Device has wait-wake capability\n");
  159. status = PciDrvSetPowerPolicy(fdoData);
  160. if (!NT_SUCCESS(status)) {
  161. TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER,
  162. "PciDrvSetPowerPolicy failed %!STATUS!\n", status);
  163. return status;
  164. }
  165. }
  166. */
  167. TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
  168. return status;
  169. }

I/O处理例程

I/O处理例程处理应用程序与驱动程序之间的通信,包括Create、Close、CleanUp、Read、Write、DeviceContrl等。

对于Read、Write、DeviceControl的I/O请求,是由队列来管理的,可以是默认队列,也可以是自己创建的队列,队列可以是一个,也可以是多个,可以串行处理,也可以并行处理。

  1. NTSTATUS
  2. ZhouZhenPciDriverKmdfQueueInitialize(
  3. _In_ WDFDEVICE Device
  4. )
  5. /*++
  6. Routine Description:
  7. The I/O dispatch callbacks for the frameworks device object
  8. are configured in this function.
  9. A single default I/O Queue is configured for parallel request
  10. processing, and a driver context memory allocation is created
  11. to hold our structure QUEUE_CONTEXT.
  12. Arguments:
  13. Device - Handle to a framework device object.
  14. Return Value:
  15. VOID
  16. --*/
  17. {
  18. WDFQUEUE queue;
  19. NTSTATUS status;
  20. WDF_IO_QUEUE_CONFIG queueConfig;
  21. PAGED_CODE();
  22. //
  23. // Configure a default queue so that requests that are not
  24. // configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
  25. // other queues get dispatched here.
  26. //
  27. WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
  28. &queueConfig,
  29. WdfIoQueueDispatchParallel
  30. );
  31. queueConfig.EvtIoDeviceControl = ZhouZhenPciDriverKmdfEvtIoDeviceControl;
  32. queueConfig.EvtIoStop = ZhouZhenPciDriverKmdfEvtIoStop;
  33. queueConfig.EvtIoRead = ZhouZhenPciDriverKmdfEvtIoRead;
  34. queueConfig.EvtIoWrite = ZhouZhenPciDriverKmdfEvtIoWrite;
  35. status = WdfIoQueueCreate(
  36. Device,
  37. &queueConfig,
  38. WDF_NO_OBJECT_ATTRIBUTES,
  39. &queue
  40. );
  41. if(!NT_SUCCESS(status)) {
  42. TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
  43. return status;
  44. }
  45. return status;
  46. }

驱动程序调试

微软提供了可以调试驱动程序的工具 TraceView,该工具安装在WDK的安装路径下(默认路径:C:\Program Files (x86)\Windows Kits\10\Tools\x64),具体调试步骤如下:

1、以管理员权限打开 TraceView:界面如下:
TraceView

2、点击“File/Create new log session”,选择“PDB(Debug Infomation)File”,选择驱动程序的符号文件(.pdb后缀的文件):
TraceView
3、然后点击“OK”、“下一步”,创建一个调试session,如下:
Session
4、这时候就可以调试了,如执行安装驱动命令“devcon install echo.inf root\ECHO”,可以看到调试日志:
调试日志

生成测试证书

1、打开系统的testsigning模式,使得非权威CA发放的签名可以使用

2、做一个签名证书出来

3、把证书加进本机信任根CA中去

4、给驱动签名。

  1. bcdedit /set testsigning on
  2. MakeCert r pe ss PrivateCertStore n CN=www.aisoftcloud.cn(test) test.cer
  3. CertMgr -add test.cer -s -r localMachine root
  4. Signtool sign /v /s PrivateCertStore /n www.aisoftcloud.cn(test) /t http://timestamp.verisign.com/scripts/timestamp.dll echo.sys

Visual Studio 2019开发环境可通过设置下面参数进行签名,编译后使用“devcon install”命令重新安装驱动即可:
签名

参考文章

Windows-driver-samples/general/pcidrv/

编写简单的 WDF 驱动程序

64位Windows操作系统为驱动程序添加测试数字签名

PnPUtil (PnPUtil.exe) 是一个命令行工具,使管理员可以执行以下操作驱动程序包

编译64位驱动,设置驱动测试签名

使用 DevCon 工具安装驱动程序包

WDF 驱动程序echo安装

基于WDF的PCI/PCIe接口卡Windows驱动程序(4)- 驱动程序代码(源文件)

基于WDF的PCI—E驱动设计和实现

KMDF驱动程序框架【很详细】

更多文章可关注公众号
aisoftcloud