Windows WDF框架提供一 个名为连续读取器的专用对象。 此对象使 USB 客户端驱动程序能够连续读取批量和中断终结点中的数据,只要有数据可用。 若要使用读取器,客户端驱动程序必须具有与驱动程序从中读取数据的终结点关联的 USB 目标管道对象的句柄。 终结点必须位于活动配置中。 可以通过以下两种方式之一激活配置:选择 USB 配置或更改当前配置中的备用设置。
创建连续读取器后,客户端驱动程序可以根据需要启动和停止读取器。 连续读取器,确保读取请求始终在目标管道对象上可用,并且客户端驱动程序始终准备好从终结点接收数据。
连续读取器不会自动由框架管理。 这意味着当设备进入较低电源状态时,客户端驱动程序必须停止读取器,并在设备进入工作状态时重启读取器。
准备工作
在客户端驱动程序可以使用连续读取器之前,请确保满足以下要求:
1. USB 设备必须具有 IN 终结点。 检查 USBView 中的设备配置。 Usbview.exe是一个应用程序,可用于浏览所有 USB 控制器和连接到它们的 USB 设备。 通常,USBView 安装在 Windows 驱动程序工具包(WDK)的调试器 文件夹中;
2. 客户端驱动程序必须已创建框架 USB 目标设备对象;
如果使用 Microsoft Visual Studio Professional 2012 随附的 USB 模板,则模板代码会执行这些任务。 模板代码会获取目标设备对象的句柄并将其存储在设备上下文中。
KMDF 客户端驱动程序:KMDF 客户端驱动程序必须调用 WdfUsbTargetDeviceCreateWithParameters 方法来获取 WDFUSBDEVICE 句柄。
UMDF 客户端驱动程序:UMDF 客户端驱动程序必须通过查询框架目标设备对象获取 IWDFUsbTargetDevice 指针。
3. 设备必须具有活动配置:
如果使用 USB 模板,代码将在每个接口中选择第一个配置和默认备用设置。
KMDF 客户端驱动程序:KMDF 客户端驱动程序必须调用 WdfUsbTargetDeviceSelectConfig 方法。
UMDF 客户端驱动程序:对于 UMDF 客户端驱动程序,框架为该配置中的每个接口选择第一个配置和默认备用设置。客户端驱动程序必须具有 IN 终结点的框架目标管道对象的句柄。
在 KMDF 客户端驱动程序中使用连续读取器
在开始使用连续读取器之前,必须通过初始化 WDF_USB_CONTINUOUS_READER_CONFIG 结构来配置它。
在 KMDF 客户端驱动程序中配置连续读取器
- 通过调用WDF_USB_CONTINUOUS_READER_CONFIG_INIT宏初始化WDF_USB_CONTINUOUS_READER_CONFIG结构;
- 在 WDF_USB_CONTINUOUS_READER_CONFIG 结构中指定其配置选项;
- 调用 WdfUsbTargetPipeConfigContinuousReader 方法;
以下示例代码为指定的目标管道对象配置连续读取器。
cpp
NTSTATUS FX3ConfigureContinuousReader(
_In_ WDFDEVICE Device,
_In_ WDFUSBPIPE Pipe)
{
NTSTATUS status;
PDEVICE_CONTEXT pDeviceContext;
WDF_USB_CONTINUOUS_READER_CONFIG readerConfig;
PPIPE_CONTEXT pipeContext;
PAGED_CODE();
pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
pipeContext = GetPipeContext (Pipe);
WDF_USB_CONTINUOUS_READER_CONFIG_INIT(
&readerConfig,
FX3EvtReadComplete,
pDeviceContext,
pipeContext->MaxPacketSize);
readerConfig.EvtUsbTargetPipeReadersFailed=FX3EvtReadFailed;
status = WdfUsbTargetPipeConfigContinuousReader(
Pipe,
&readerConfig);
if (!NT_SUCCESS (status))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! WdfUsbTargetPipeConfigContinuousReader failed 0x%x", status);
goto Exit;
}
Exit:
return status;
}
客户端驱动程序在枚举活动设置中的目标管道对象后,在 EvtDevicePrepareHardware 回调函数中配置连续读取器。
在前面的示例中,客户端驱动程序以两种方式指定其配置选项。 首先通过调用WDF_USB_CONTINUOUS_READER_CONFIG_INIT,然后通过设置WDF_USB_CONTINUOUS_READER_CONFIG成员。 请注意WDF_USB_CONTINUOUS_READER_CONFIG_INIT的参数。 这些值是必需的。 在此示例中,客户端驱动程序指定:
- 指向驱动程序实现的完成例程的指针。 框架在完成读取请求时调用此例程。 在完成例程中,驱动程序可以访问包含已读取数据的内存位置;
- 指向驱动程序定义的上下文的指针;
- 可以在单个传输中从设备读取的字节数。 客户端驱动程序可以通过调用 WdfUsbInterfaceGetConfiguredPipe 或 WdfUsbTargetPipeGetInformation 方法获取WDF_USB_PIPE_INFORMATION结构中的信息;
WDF_USB_CONTINUOUS_READER_CONFIG_INIT将连续读取器配置为使用 NumPendingReads 的默认值。 该值确定框架添加到挂起队列的读取请求数。 默认值已确定为许多处理器配置上的许多设备提供相当良好的性能。
除了WDF_USB_CONTINUOUS_READER_CONFIG_INIT中指定的配置参数外,该示例还在WDF_USB_CONTINUOUS_READER_CONFIG中设置故障例程。 此失败例程是可选的。
除了故障例程之外,还有WDF_USB_CONTINUOUS_READER_CONFIG客户端驱动程序可用于指定传输缓冲区布局的其他成员。 例如,请考虑使用连续读取器接收网络数据包的网络驱动程序。 每个数据包都包含标头、有效负载和页脚数据。 若要描述数据包,驱动程序必须首先在其调用中指定数据包的大小以 WDF_USB_CONTINUOUS_READER_CONFIG_INIT。 然后,驱动程序必须通过设置WDF_USB_CONTINUOUS_READER_CONFIG的 HeaderLength 和 TrailerLength 成员来指定页眉和页脚的长度。 框架使用这些值计算有效负载两侧的字节偏移量。 从终结点读取有效负载数据时,框架会将该数据存储在偏移量之间的缓冲区部分。
实现完成例程
每次请求完成时,框架都会调用客户端驱动程序实现的完成例程。 框架传递读取的字节数和一个 WDFMEMORY 对象,其缓冲区包含从管道读取的数据。
以下示例代码显示了完成例程实现。
cpp
EVT_WDF_USB_READER_COMPLETION_ROUTINE FX3EvtReadComplete;
VOID FX3EvtReadComplete(
__in WDFUSBPIPE Pipe,
__in WDFMEMORY Buffer,
__in size_t NumBytesTransferred,
__in WDFCONTEXT Context
)
{
PDEVICE_CONTEXT pDeviceContext;
PVOID requestBuffer;
pDeviceContext = (PDEVICE_CONTEXT)Context;
if (NumBytesTransferred == 0)
{
return;
}
requestBuffer = WdfMemoryGetBuffer(Buffer, NULL);
if (Pipe == pDeviceContext->InterruptPipe)
{
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Interrupt endpoint: %s.\n",
requestBuffer ));
}
return;
}
每次请求完成时,框架都会调用客户端驱动程序实现的完成例程。 框架为每个读取操作分配一个内存对象。 在完成例程中,框架将读取的字节数和 WDFMEMORY 句柄传递给内存对象。 内存对象缓冲区包含从管道读取的数据。 客户端驱动程序不得释放内存对象。 框架在每个完成例程返回后释放对象。 如果客户端驱动程序想要存储收到的数据,驱动程序必须在完成例程中复制缓冲区的内容。
实现失败例程
框架调用客户端驱动程序实现的失败例程,以通知驱动程序连续读取器在处理读取请求时报告了错误。 框架将指针传递给请求失败的目标管道对象和错误代码值。 根据这些错误代码值,驱动程序可以实现其错误恢复机制。 驱动程序还必须返回一个适当的值,该值指示框架是否应重启连续读取器。
以下示例代码演示失败例程实现。
cpp
EVT_WDF_USB_READERS_FAILED FX3EvtReadFailed;
BOOLEAN
FX3EvtReadFailed(
WDFUSBPIPE Pipe,
NTSTATUS Status,
USBD_STATUS UsbdStatus
)
{
UNREFERENCED_PARAMETER(Status);
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! ReadersFailedCallback failed NTSTATUS 0x%x, UsbdStatus 0x%x\n",
status,
UsbdStatus);
return TRUE;
}
在前面的示例中,驱动程序返回 TRUE。 此值向框架指示它必须重置管道,然后重启连续读取器。
或者,客户端驱动程序可以返回 FAL标准版并在管道上出现停止条件时提供错误恢复机制。 例如,驱动程序可以检查 USBD 状态,并发出重置管道请求以清除停止条件。
启动和停止连续读取器
指示框架在设备进入工作状态时启动连续读取器;设备离开工作状态时停止读取器。 调用这些方法,并将目标管道对象指定为 I/O 目标对象。
- WdfIoTargetStart
- WdfIoTargetStop
连续读取器不会自动由框架管理。 因此,当设备电源状态发生更改时,客户端驱动程序必须显式启动或停止目标管道对象。 驱动程序在驱动程序的 EvtDeviceD0Entry 实现中调用 WdfIoTargetStart。 此调用可确保队列仅在设备处于工作状态时传递请求。 相反,驱动程序在驱动程序 EvtDeviceD0Exit 实现中调用 WdfIoTargetStop,以便在设备进入较低电源状态时队列停止传送请求。
以下示例代码为指定的目标管道对象配置连续读取器。
cpp
EVT_WDF_DEVICE_D0_ENTRY FX3EvtDeviceD0Entry;
NTSTATUS FX3EvtDeviceD0Entry(
__in WDFDEVICE Device,
__in WDF_POWER_DEVICE_STATE PreviousState
)
{
PDEVICE_CONTEXT pDeviceContext;
NTSTATUS status;
PAGED_CODE();
pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
status = WdfIoTargetStart (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe));
if (!NT_SUCCESS (status))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not start interrupt pipe failed 0x%x", status);
}
}
EVT_WDF_DEVICE_D0_EXIT FX3EvtDeviceD0Exit;
NTSTATUS FX3EvtDeviceD0Exit(
__in WDFDEVICE Device,
__in WDF_POWER_DEVICE_STATE TargetState
)
{
PDEVICE_CONTEXT pDeviceContext;
NTSTATUS status;
PAGED_CODE();
pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
WdfIoTargetStop (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe), WdfIoTargetCancelSentIo));
}
前面的示例显示了 EvtDeviceD0Entry 和 EvtDeviceD0Exit 回调例程的实现。 WdfIoTargetStop 的 Action 参数允许客户端驱动程序在设备离开工作状态时决定队列中挂起请求的操作。 在此示例中,驱动程序指定 WdfIoTargetCancelSentIo。 此选项指示框架取消队列中的所有挂起请求。 或者,驱动程序可以指示框架等待挂起的请求在停止 I/O 目标之前完成,或保留挂起的请求并在 I/O 目标重启时恢复。