将 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_DUPLEXIOCTL_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 控制器。 此方法具有 InputBufferOutputBuffer 参数。 某些类型的设备的驱动程序可能使用这两个参数分别指向 IOCTL 请求的写入缓冲区和读取缓冲区。 但是,若要向 SPB 控制器发送 IOCTL 请求,SPB 外围设备驱动程序会将 InputBuffer 参数设置为指向指向 SPB_TRANSFER_LIST 结构的内存描述符。 此结构描述 I/O 控制操作所需的任何读取或写入缓冲区。 驱动程序将 OutputBuffer 参数设置为 NULL。

同样,User-Mode Driver Foundation (SPB 外围设备的 UMDF) 驱动程序调用 IWDFIoTarget::FormatRequestForIoctl 等方法来格式化 I/O 控制操作的 I/O 请求。 此方法具有 pInputMemorypOutputMemory 参数。 某些类型的设备的驱动程序可能会使用这两个参数指向 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请求