原子总线操作

若要使用连接 SPB 的外围设备的某些硬件功能,SPB 控制器的客户端 (即,外围驱动程序) 可能需要执行一系列与设备之间的数据传输,作为原子总线操作。 传输序列是原子的,因为在序列完成之前,其他客户端无法向总线上的设备传输数据或从设备传输数据。

客户端将传输序列作为原子总线操作执行的典型方法是将 IOCTL_SPB_EXECUTE_SEQUENCE 请求发送到目标设备。 在此请求中,客户端将序列指定为简单读取和写入传输的列表。 列表的长度可以是任意的。 读取和写入按列出顺序执行,每次读取或写入都可以传输任意数量的字节。 大多数 SPB 控制器支持 IOCTL_SPB_EXECUTE_SEQUENCE 请求。

SPB 控制器锁

执行原子传输序列的一种不太常见的方法是使用 SPB 控制器锁。 客户端发送 IOCTL_SPB_LOCK_CONTROLLER 请求来获取锁,并 发送释放锁的IOCTL_SPB_UNLOCK_CONTROLLER 请求。 当客户端持有控制器锁时,客户端发送到设备的任意简单读取和写入 (序列 IRP_MJ_READIRP_MJ_WRITE) 请求在总线上作为原子操作执行。

大多数连接到 SPB 的外围设备不需要控制器锁,并且大多数 SPB 控制器驱动程序不实现对这些锁的支持。 但是,一些客户端可能需要使用控制器锁来访问具有异常功能的设备。

例如,设备可能实现只能通过总线上原子的读写操作访问的设备函数。 若要执行此类操作,客户端 (按) 显示的顺序发送以下四个 I/O 请求:

  1. IOCTL_SPB_LOCK_CONTROLLER – 获取控制器锁。
  2. IRP_MJ_READ – 从目标设备读取数据块。
  3. IRP_MJ_WRITE – 将修改后的数据写回到设备。
  4. IOCTL_SPB_UNLOCK_CONTROLLER – 释放控制器锁。

执行上述列表中的读取操作后,客户端将解释从设备读取的数据,并在将数据写回设备之前对其进行修改。

但是,很少有 SPB 连接的设备具有需要控制器锁的功能。 对于大多数需要原子总线操作的设备, IOCTL_SPB_EXECUTE_SEQUENCE 请求就足够了。

不要将 SPB 控制器锁与 SPB 连接锁混淆。 在两个客户端共享对同一 SPB 连接的外围设备的访问权限的非典型情况下,任一客户端都可以使用连接锁暂时获取对设备的独占访问权限。 有关详细信息,请参阅 SPB 连接锁

硬件总线信号

为了处理 IOCTL_SPB_EXECUTE_SEQUENCE 请求,SPB 控制器驱动程序将控制器硬件配置为在传输序列期间在总线上生成相应的信号。 连接到总线的外围设备可能依赖于这些信号来检测原子总线操作何时进行。 SPB 控制器用于作为原子总线操作执行传输序列的硬件信号集取决于总线类型。

对于 I2C 总线,控制器通过传输总线上的起始位来启动序列,并通过传输停止位结束序列。 在启动位和停止位之间,传入和传出设备的数据传输序列作为单个原子总线操作执行。 除了序列中的最终传输外,每个传输后都执行 I2C 重启操作, (一个重复的起始位,该起始位前面没有停止位) 。

对于 SPI 总线,控制器通过向目标设备断言芯片选择线来启动序列,并通过取消确认芯片选择线来结束序列。 通过在总线上的一系列数据传输期间持续断言芯片选择线,传输将作为单个原子总线操作执行。

I2C 设备示例

I2C 总线上的典型外围设备可以实现多个内部设备功能。 若要访问其中一些函数,客户端可以使用 IOCTL_SPB_EXECUTE_SEQUENCE 请求。

例如,I2C 外围设备可能包含以下两个内部寄存器:

  • 客户端将设备函数的内部地址写入要访问的 函数地址寄存器
  • 数据寄存器,客户端通过该寄存器从指定的函数地址读取数据或将数据写入指定的函数地址。

此示例中的 I2C 外围设备将起始位后写入设备的第一个字节解释为要加载到函数地址寄存器中的函数地址。 在序列结束之前向设备传输或从设备传输的任何其他字节 (停止位) 将被设备视为要通过数据寄存器传输的数据。

为了执行写入操作,客户端发送写入 (IRP_MJ_WRITE) 请求,其中写入缓冲区中的第一个字节是函数地址,缓冲区中的剩余字节是要写入到函数地址的数据。

从设备读取数据更为复杂。 假设此示例中的 I2C 设备支持“快速读取”功能,当总线上检测到停止位时,该功能会自动将函数地址寄存器重置为其默认值 0。 使用此功能,客户端可以从函数地址 0 读取数据,而无需先写入函数地址寄存器。 此功能可以提高设备读取操作的速度,尤其是在大多数读取来自函数地址 0 且相对较短的情况下。

但是,若要从非零函数地址读取数据块,客户端在从数据寄存器读取数据块之前,仍必须将字节写入函数地址寄存器。 客户端必须执行这些写入和读取传输作为原子总线操作,以防止总线控制器在写入函数地址寄存器之后以及从数据寄存器读取之前传输停止位。 否则,停止位将导致从函数地址 0 而不是非零函数地址读取数据。

以下列表描述了客户端在此示例中发送到 I2C 设备以对位于设备中非零函数地址的数据执行读取-修改-写入操作的一系列 I/O 请求:

  1. IOCTL_SPB_EXECUTE_SEQUENCE - 执行 I/O 传输序列以从设备读取数据。 此序列中的第一个传输是对函数地址寄存器的字节写入。 序列中的第二个传输是从所选函数地址读取一定数量的字节。 这两个传输在总线上以原子方式执行。
  2. IRP_MJ_WRITE - 将数据写入设备。 此请求的写入缓冲区中的第一个字节是要写入到函数地址寄存器的值。 缓冲区中的剩余字节是要写入所选函数地址的数据。

可以改用其他请求模式来执行此读取-修改-写入操作。 例如,步骤 2 中的 IRP_MJ_WRITE 请求可以替换为指定两个数据传输的 IOCTL_SPB_EXECUTE_SEQUENCE 请求,这两个数据传输都是写入。 序列中的第一个传输将字节加载到函数地址寄存器中。 第二次传输将数据字节写入所选函数地址。 此请求与步骤 2 中的 IRP_MJ_WRITE 请求不同,它不需要客户端在同一写入缓冲区中合并函数地址字节和数据字节。

若要在此设备中的函数地址 0 上执行读取-修改-写入,可以将上一列表的步骤 1 中的 IOCTL_SPB_EXECUTE_SEQUENCE 请求替换为简单的读取 (IRP_MJ_READ) 请求。