处理 IOCTL_SPB_FULL_DUPLEX 请求

某些总线(如 SPI)允许在总线控制器和总线上的设备之间同时发生读写传输。 为了支持这些全双工传输,简单外围总线 (SPB) I/O 请求接口的定义包括 IOCTL_SPB_FULL_DUPLEX I/ O 控制代码 (IOCTL) 。 只有用于在硬件中实现全双工传输的总线控制器的 SPB 控制器驱动程序应支持 IOCTL_SPB_FULL_DUPLEX IOCTL。

如果 SPB 控制器驱动程序支持全双工传输的 I/O 请求,则驱动程序应对这些请求使用 IOCTL_SPB_FULL_DUPLEX IOCTL,并应遵循本主题中介绍的实现准则。 这些准则的目的是鼓励支持 IOCTL_SPB_FULL_DUPLEX 请求的所有硬件平台的统一行为。 然后,与 SPB 连接的外围设备的驱动程序可以依赖于这些请求来生成类似的结果,而不管它们在哪个平台上运行。

缓冲区要求

IOCTL_SPB_FULL_DUPLEX请求的格式与IOCTL_SPB_EXECUTE_SEQUENCE请求相同,但具有以下约束:

  • 请求中的 SPB_TRANSFER_LIST 结构必须正好包含两个条目。 第一个条目描述包含要写入设备的数据的缓冲区。 第二个条目描述用于保存从设备读取的数据的缓冲区。
  • 传输列表中的每个 SPB_TRANSFER_LIST_ENTRY 结构都必须将 DelayInUs 值指定为零。

在全双工传输期间,读取和写入传输将一致启动。 写入数据的第一个字节与读取数据的第一个字节同时通过总线传输。

IOCTL_SPB_FULL_DUPLEX请求中的写入缓冲区和读取缓冲区不需要具有相同的长度。

如果读取缓冲区比写入缓冲区短,则全双工总线传输会继续,直到写入缓冲区的全部内容写入设备。 读取缓冲区已满后,总线控制器将放弃从设备接收的所有其他数据,直到全双工总线传输完成。

如果写入缓冲区比读取缓冲区短,则全双工总线传输将继续,直到读取缓冲区已满。 将写入缓冲区的全部内容写入设备后,总线控制器会将零写入设备,直到全双工总线传输完成。

如果 IOCTL_SPB_FULL_DUPLEX 请求成功完成,SPB 控制器驱动程序会将 I/O 状态块的 Status 成员设置为STATUS_SUCCESS,并将 Information 成员设置为在全双工传输期间读取 (字节加上写入) 字节的总字节数。 Information 成员中的计数值不应超过读取缓冲区大小和写入缓冲区大小之和。

如果读取缓冲区短于写入缓冲区, 则 Information 成员中的计数值不应包括总线控制器从设备读取的数据字节 (,并在读取缓冲区已满后丢弃) 。 例如,如果具有 1 字节写入缓冲区和 4 字节读取缓冲区的全双工传输成功完成,则计数值应为 5,而不是 8。 同样,如果写入缓冲区比读取缓冲区短,则计数值不应包括清空写入缓冲区后写入设备的零。

参数检查

尽管 IOCTL_SPB_EXECUTE_SEQUENCEIOCTL_SPB_FULL_DUPLEX 请求的格式相似,但 SPB 框架扩展 (SpbCx) 以不同的方式处理它们。 对于 IOCTL_SPB_EXECUTE_SEQUENCE 请求,SpbCx 验证请求中的参数值,并在请求发起者的进程上下文中捕获请求的缓冲区。 SpbCx 通过驱动程序的 EvtSpbControllerIoSequence 回调函数(专用于这些请求)将IOCTL_SPB_EXECUTE_SEQUENCE请求传递给 SPB 控制器驱动程序。

相比之下,SpbCx 将 IOCTL_SPB_FULL_DUPLEX 请求视为驱动程序定义的自定义 IOCTL 请求。 SpbCx 通过驱动程序的 EvtSpbControllerIoOther 回调函数将IOCTL_SPB_FULL_DUPLEX请求传递给 SPB 控制器驱动程序,该回调函数还处理驱动程序支持的任何自定义 IOCTL 请求。 SpbCx 不会对这些请求执行参数检查或缓冲区捕获。 驱动程序负责驱动程序通过其 EvtSpbControllerIoOther 函数接收的 IOCTL 请求可能需要的任何参数检查或缓冲区捕获。 若要启用缓冲区捕获,驱动程序必须在驱动程序注册其 EvtSpbControllerIoOther 函数时提供 EvtIoInCallerContext 回调函数。 有关详细信息,请参阅 将 SPB_TRANSFER_LIST 结构用于自定义 IOCTL

通常,SPB 控制器驱动程序在 EvtSpbControllerIoOther 函数(而不是 EvtIoInCallerContext 函数)中的IOCTL_SPB_FULL_DUPLEX请求中验证参数值。 下面的代码示例演示驱动程序如何实现参数检查。 在此示例中,驱动程序验证是否满足以下参数要求:

  • 请求中的传输列表正好包含两个条目。
  • 传输列表中的第一个条目用于写入缓冲区,第二个条目用于读取缓冲区。
  • 两个条目的 DelayInUs 值为零。
//
// Validate the transfer count.
//

SPB_REQUEST_PARAMETERS params;
SPB_REQUEST_PARAMETERS_INIT(&params);
SpbRequestGetParameters(SpbRequest, &params);

if (params.SequenceTransferCount != 2)
{
    //
    // The full-duplex request must have 
    // exactly two transfer descriptors.
    //
    
    status = STATUS_INVALID_PARAMETER;        
    goto exit;
}

//
// Retrieve the write and read transfer descriptors.
//

const ULONG fullDuplexWriteIndex = 0;
const ULONG fullDuplexReadIndex = 1;

SPB_TRANSFER_DESCRIPTOR writeDescriptor;
SPB_TRANSFER_DESCRIPTOR readDescriptor;
PMDL pWriteMdl;
PMDL pReadMdl;

SPB_TRANSFER_DESCRIPTOR_INIT(&writeDescriptor);
SPB_TRANSFER_DESCRIPTOR_INIT(&readDescriptor);

SpbRequestGetTransferParameters(
    SpbRequest, 
    fullDuplexWriteIndex, 
    &writeDescriptor,
    &pWriteMdl);

SpbRequestGetTransferParameters(
    SpbRequest, 
    fullDuplexReadIndex, 
    &readDescriptor,
    &pReadMdl);
    
//
// Validate the transfer direction of each descriptor.
//

if ((writeDescriptor.Direction != SpbTransferDirectionToDevice) ||
    (readDescriptor.Direction != SpbTransferDirectionFromDevice))
{
    //
    // For full-duplex I/O, the direction of the first transfer
    // must be SpbTransferDirectionToDevice, and the direction
    // of the second must be SpbTransferDirectionFromDevice.
    //
    
    status = STATUS_INVALID_PARAMETER;
    goto exit;
}

//
// Validate the delay for each transfer descriptor.
//

if ((writeDescriptor.DelayInUs != 0) || (readDescriptor.DelayInUs != 0))
{
    //
    // The write and read buffers for full-duplex I/O are transferred
    // simultaneously over the bus. The delay parameter in each transfer
    // descriptor must be set to 0.
    //
    
    status = STATUS_INVALID_PARAMETER;
    goto exit;
}

MyDriverPerformFullDuplexTransfer(
    pDevice, 
    pRequest,
    writeDescriptor,
    pWriteMdl,
    readDescriptor,
    pReadMdl);

检查参数值后,前面的代码示例调用名为 的 MyDriverPerformFullDuplexTransfer驱动程序内部例程来启动全双工 I/O 传输。