将 SPB_TRANSFER_LIST 结构用于自定义 IOCTL
如果简单的外围总线 (SPB) 控制器驱动程序支持一个或多个自定义 I/O 控件 (IOCTL) 请求,请使用 SPB_TRANSFER_LIST 结构来描述这些请求中的读取和写入缓冲区。 此结构提供了一种统一的方式来描述请求中的缓冲区,并避免了与METHOD_BUFFERED I/O 操作关联的缓冲区复制开销。
如果自定义 IOCTL 请求使用 SPB_TRANSFER_LIST 结构,则 SPB 控制器驱动程序必须调用 SpbRequestCaptureIoOtherTransferList 方法,以在请求发起方的进程上下文中捕获这些缓冲区。 驱动程序可以调用 SpbRequestGetTransferParameters 方法来访问这些缓冲区。
IOCTL_SPB_FULL_DUPLEX和IOCTL_SPB_EXECUTE_SEQUENCE请求(定义为 SPB I/O 请求接口的一部分)使用 SPB_TRANSFER_LIST 结构来描述其读取和写入缓冲区。 IOCTL_SPB_FULL_DUPLEX请求的SPB_TRANSFER_LIST结构按请求) 的顺序描述写入缓冲区和读取缓冲区 (。 IOCTL_SPB_EXECUTE_SEQUENCE请求的SPB_TRANSFER_LIST结构可以描述读取和写入缓冲区的任意序列。
同样,可以定义自定义 IOCTL,要求其 SPB_TRANSFER_LIST 结构使用读取和写入缓冲区的某种组合,并指定列表中可能需要的缓冲区的任何顺序。
Kernel-Mode Driver Foundation (SPB 外围设备的 KMDF) 驱动程序调用 WdfIoTargetSendIoctlSynchronously 等方法,以将 IOCTL 请求发送到 SPB 控制器。 此方法具有 InputBuffer 和 OutputBuffer 参数。 某些类型的设备的驱动程序可能使用这两个参数分别指向 IOCTL 请求的写入缓冲区和读取缓冲区。 但是,若要向 SPB 控制器发送 IOCTL 请求,SPB 外围设备驱动程序会将 InputBuffer 参数设置为指向指向 SPB_TRANSFER_LIST 结构的内存描述符。 此结构描述 I/O 控制操作所需的任何读取或写入缓冲区。 驱动程序将 OutputBuffer 参数设置为 NULL。
同样,User-Mode Driver Foundation (SPB 外围设备的 UMDF) 驱动程序调用 IWDFIoTarget::FormatRequestForIoctl 等方法来格式化 I/O 控制操作的 I/O 请求。 此方法具有 pInputMemory 和 pOutputMemory 参数。 某些类型的设备的驱动程序可能会使用这两个参数指向 IOCTL 请求的写入缓冲区和读取缓冲区。 但是,为了向 SPB 控制器发送 IOCTL 请求,SPB 外围设备驱动程序将 pInputMemory 参数设置为指向包含 SPB_TRANSFER_LIST 结构的内存对象。 此结构描述 I/O 控制操作所需的任何读取或写入缓冲区。 驱动程序将 pOutputMemory 参数设置为 NULL。
参数检查和缓冲区捕获
当 SPB 框架扩展 (SpbCx) 收到 IOCTL_SPB_EXECUTE_SEQUENCE 请求时,SpbCx 通过调用驱动程序的 EvtSpbControllerIoSequence 函数将此请求传递给 SPB 控制器驱动程序。 在此调用之前,SpbCx 会检查描述请求中缓冲区的 SPB_TRANSFER_LIST 结构。 SpbCx 在请求发起者的进程上下文中捕获这些缓冲区。 (只能在分配内存的过程中访问用户模式内存中的缓冲区。) 此外,SpbCx 检查请求中的参数值是否有效。
当 SpbCx 收到 IOCTL_SPB_FULL_DUPLEX 请求或自定义 IOCTL 请求时,SpbCx 通过调用驱动程序的 EvtSpbControllerIoOther 回调函数将此请求传递给 SPB 控制器驱动程序。 在进行此调用之前,SpbCx 不会对请求中的参数值进行验证检查,也不会在发起方上下文中捕获请求的缓冲区。 这些请求的参数检查和缓冲区捕获由 SPB 控制器驱动程序负责。
如果 SPB 控制器驱动程序支持 IOCTL_SPB_FULL_DUPLEX 请求,或者支持对其缓冲区使用 SPB_TRANSFER_LIST 结构的任何自定义 IOCTL 请求,则驱动程序必须实现 EvtIoInCallerContext 回调函数。 驱动程序在调用 SpbControllerSetIoOtherCallback 方法时提供指向此函数的指针作为输入参数,该方法注册驱动程序的 EvtSpbControllerIoOther 回调函数。 当 SpbCx 收到 IOCTL_SPB_FULL_DUPLEX 请求或自定义 IOCTL 请求时,SpbCx 在发起人的上下文中调用驱动程序的 EvtIoInCallerContext 函数。 如果 IOCTL 请求使用 SPB_TRANSFER_LIST 结构, 则 EvtIoInCallerContext 函数调用 SpbRequestCaptureIoOtherTransferList 方法以捕获请求中的缓冲区。 EvtIoInCallerContext 函数也可能对请求执行一些初步处理。
下面的代码示例演示由 SPB 控制器驱动程序实现的 EvtIoInCallerContext 函数。
VOID
EvtIoInCallerContext(
_In_ WDFDEVICE SpbController,
_In_ WDFREQUEST FxRequest
)
{
NTSTATUS status = STATUS_SUCCESS;
WDF_REQUEST_PARAMETERS fxParams;
WDF_REQUEST_PARAMETERS_INIT(&fxParams);
WdfRequestGetParameters(FxRequest, &fxParams);
if ((fxParams.Type != WdfRequestTypeDeviceControl) &&
(fxParams.Type != WdfRequestTypeDeviceControlInternal))
{
status = STATUS_NOT_SUPPORTED;
goto exit;
}
//
// The driver should check for custom IOCTLs that it handles.
// If the IOCTL is not recognized, complete the request with a
// status of STATUS_NOT_SUPPORTED.
//
switch (fxParams.Parameters.DeviceIoControl.IoControlCode)
{
...
default:
status = STATUS_NOT_SUPPORTED;
goto exit;
}
//
// The IOCTL is recognized. Capture the buffers in the request.
//
status = SpbRequestCaptureIoOtherTransferList((SPBREQUEST)FxRequest);
//
// If the capture fails, the driver must complete the request instead
// of placing it in the SPB controller's request queue.
//
if (!NT_SUCCESS(status))
{
goto exit;
}
status = WdfDeviceEnqueueRequest(SpbController, FxRequest);
if (!NT_SUCCESS(status))
{
goto exit;
}
exit:
if (!NT_SUCCESS(status))
{
WdfRequestComplete(FxRequest, status);
}
}
在前面的代码示例中 switch
, 语句验证请求是否包含 SPB 控制器驱动程序识别的 IOCTL。 (为简洁起见,不显示语句的 switch
正文。) 接下来,对 SpbRequestCaptureIoOtherTransferList 方法的调用将捕获请求中的缓冲区。 如果此调用成功,请求将添加到 SPB 控制器的 I/O 队列。 否则,请求以错误状态代码完成。
有关显示 由 EvtSpbControllerIoOther 函数进行参数检查的代码示例,请参阅 处理IOCTL_SPB_FULL_DUPLEX请求。