编写 USB 类型 C 端口控制器驱动程序

如果 USB Type-C 硬件实现 USB Type-C 或电源传送 (PD) 物理层,但未实现供电所需的状态机,则需要编写 USB Type-C 端口控制器驱动程序。

在 Windows 10 版本 1703 中,USB Type-C 体系结构已得到改进,以支持实现 USB 类型 C 或电源交付 (PD) 物理层,但没有相应的 PD 策略引擎或协议层实现的硬件设计。 对于这些设计,Windows 10版本 1703 通过名为“USB 连接器管理器类型-C 端口控制器接口类扩展”的新类扩展 (UcmTcpciCx) 提供基于软件的 PD 策略引擎和设备策略管理器。 由 IHV 或 OEM/ODM 编写的客户端驱动程序与 UcmTcpciCx 通信,以提供有关 UcmTcpciCx 中 PD 策略引擎和设备策略管理器正常运行所需的硬件事件的信息。 通过本文和参考部分中介绍的一组编程接口启用这种通信。

USB 连接器管理器示意图。

UcmTcpciCx 类扩展本身是 UcmCx 的客户端驱动程序。 有关电力合同、数据角色的策略决策在 UcmCx 中做出,并转发到 UcmTcpciCx。 UcmTcpciCx 使用 UcmTcpciCx 客户端驱动程序提供的端口控制器接口实现这些策略并管理 Type-C 和 PD 状态机。

总结

  • UcmTcpci 类扩展提供的服务
  • 客户端驱动程序的预期行为

官方规范

重要的 API

USB Type-C 端口控制器接口驱动程序类扩展参考

UcmTcpciCx 客户端驱动程序模板

UcmTcpciCx 客户端驱动程序模板

准备阶段

  • 根据硬件还是固件实现 PD 状态机确定需要写入的驱动程序类型。 有关详细信息,请参阅 开发适用于 USB Type-C 连接器的 Windows 驱动程序

  • 在目标计算机上安装 (家庭版、专业版、企业版和教育版) 桌面版的Windows 10,或使用 USB Type-C 连接器Windows 10 移动版。

  • 在开发 计算机上安装最新的 Windows 驱动程序工具包 (WDK) 。 该工具包具有用于编写客户端驱动程序所需的头文件和库,具体而言,你需要:

    • 存根库 (UcmTcpciCxStub.lib) 。 库转换客户端驱动程序发出的调用,并将其传递到类扩展。
    • 头文件 UcmTcpciCx.h。

    客户端驱动程序在内核模式下运行,并绑定到 KMDF 1.15 库。

    适用于 UCM 的 Visual Studio 配置的屏幕截图。

  • 确定客户端驱动程序是否支持警报。

  • 端口控制器不需要符合 TCPCI。 接口捕获任何 Type-C 端口控制器的功能。 为不符合 TCPCI 的硬件编写 UcmTcpciCx 客户端驱动程序涉及将 TCPCI 规范中的寄存器和命令的含义映射到硬件的含义。

  • 大多数 TCPCI 控制器都连接了 I 2C。 客户端驱动程序使用串行外围总线 (SPB) 连接资源和中断线路来与硬件通信。 驱动程序使用 SPB 框架扩展 (SpbCx) 编程接口。 阅读以下文章,熟悉 SpbCx:

    • [简单外围总线 (SPB) 驱动程序设计指南]
    • [SPB 驱动程序编程参考]
  • 熟悉 Windows Driver Foundation (WDF) 。 推荐阅读: 使用 Windows Driver Foundation 开发驱动程序,由 Penny Orwick 和 Guy Smith 撰写。

UcmTcpci 类扩展的行为

  • 作为状态机执行的一部分,UcmTcpciCx 将 IOCTL 请求发送到端口控制器。 例如,在 PD 消息传送中,它会发送IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_TRANSMIT_BUFFER请求来设置传输缓冲区。 该请求 (TRANSMIT_BUFFER) 将移交给客户端驱动程序。 然后,驱动程序使用类扩展提供的详细信息设置传输缓冲区。

  • UcmTcpciCx 实现有关电力合同、数据角色等的策略。

客户端驱动程序的预期行为

UcmTcpciCx 的客户端驱动程序应:

  • 成为电源策略所有者。 UcmTcpciCx 不参与端口控制器的电源管理。

  • 将从 UcmTcpciCx 接收的请求转换为硬件读取或写入命令。 命令必须是异步的,因为 DPM 无法阻止等待硬件传输完成。

  • 提供包含框架请求对象的框架队列对象。 对于 UcmTcpci 类扩展要发送到客户端驱动程序的每个请求,扩展会在驱动程序的队列对象中添加一个请求对象。 驱动程序处理完请求后,将调用 WdfRequestComplete。 客户端驱动程序负责及时完成请求。

  • 发现并报告端口控制器的功能。 这些功能包括端口控制器可以在 (中运行的角色等信息,例如仅限源、仅限接收器、DRP) 。 但是,连接器的其他功能 (请参阅有关功能存储) 和整个系统的注释,DPM 需要知道才能正确实现 USB Type-C 和 PD 策略。 例如,DPM 需要知道系统/连接器的源功能才能将其播发到端口伙伴。

    功能存储

    除了与客户端驱动程序相关的功能外,其他信息还来自称为 “功能存储”的系统全局位置。 此系统全局功能存储存储在 ACPI 中。 它是对系统及其每个 USB Type-C 连接器的功能的静态说明,DPM 使用这些连接器来确定要实现的策略。

    通过将系统功能的说明与端口控制器的客户端驱动程序 () 分离,该设计允许在功能不同的不同系统上使用驱动程序。 UcmCx(而不是 UcmTcpciCx)与功能存储接口。 UcmTcpciCx (或其客户端驱动程序) 不与功能存储交互。

    如果适用,功能存储中的信息将替代直接来自端口控制器客户端驱动程序的信息。 例如,端口控制器能够仅执行接收器操作,客户端驱动程序会报告该信息。 但是,系统其余部分可能未正确配置为仅接收器操作。 在这种情况下,系统制造商可以报告连接器能够在功能存储中执行仅限源的操作。 功能存储中的设置优先于驱动程序报告的信息。

  • 使用与警报相关的所有相关数据通知 UcmTcpciCx。

  • 可选。 在进入/退出备用模式后执行一些额外的处理。 类扩展通过 IOCTL 请求通知驱动程序有关这些状态的信息。

向 UcmTcpciCx 注册客户端驱动程序

示例参考:请参阅 EvtPrepareHardware 中的 Device.cpp

  1. 在EVT_WDF_DRIVER_DEVICE_ADD实现中,调用 UcmTcpciDeviceInitInitialize 以初始化WDFDEVICE_INIT不透明结构。 调用将客户端驱动程序与框架相关联。

  2. (WDFDEVICE) 创建框架设备对象后,调用 UcmTcpciDeviceInitialize 以向 UcmTcpciCx 注册客户端 diver。

初始化到端口控制器硬件的 I2C 通信通道

示例参考:请参阅 EvtCreateDevice 中的 Device.cpp

在EVT_WDF_DEVICE_PREPARE_HARDWARE实现中,读取硬件资源以打开信道。 这是检索 PD 功能并获取有关警报的通知所必需的。

大多数 TCPCI 控制器都连接了 I 2C。 在参考示例中,客户端驱动程序使用 SPB 框架扩展 (SpbCx) 编程接口打开 I2 通道。

客户端驱动程序通过调用 WdfCmResourceListGetDescriptor 枚举硬件资源。

警报作为中断接收。 因此,驱动程序会创建一个框架中断对象,并注册处理警报的 ISR。 ISR 执行硬件读取和写入操作,这些操作会阻止,直到硬件访问完成。 由于在 DIRQL 中无法接受等待,因此驱动程序在 PASSIVE_LEVEL 执行 ISR。

初始化端口控制器的 Type-C 和 PD 功能

示例参考:请参阅 EvtDeviceD0Entry 中的 Device.cpp

在EVT_WDF_DEVICE_D0_EXIT实现中,

  1. 与端口控制器硬件通信,并通过读取各种寄存器来检索设备标识和功能。

  2. 使用检索到的信息初始化UCMTCPCI_PORT_CONTROLLER_IDENTIFICATION和UCMTCPCI_PORT_CONTROLLER_CAPABILITIES。

  3. 通过将初始化的结构传递给UCMTCPCI_PORT_CONTROLLER_CONFIG_INIT,使用上述信息初始化UCMTCPCI_PORT_CONTROLLER_CONFIG结构。

  4. 调用 UcmTcpciPortControllerCreate 以创建端口控制器对象并检索 UCMTCPCIPORTCONTROLLER 句柄。

设置框架队列对象以接收来自 UcmTcpciCx 的请求

示例参考:请参阅 EvtDeviceD0EntryDevice.cppHardwareRequestQueueInitialize 中的 Queue.cpp

  1. 在EVT_WDF_DEVICE_D0_EXIT实现中,通过调用 WdfIoQueueCreate 创建框架队列对象。 在该调用中,需要注册回调实现,以处理 UcmTpciCx 发送的 IOCTL 请求。 客户端驱动程序可以使用电源管理的队列。

    在执行 Type-C 和 PD 状态机期间,UcmTpciCx 会向客户端驱动程序发送命令以执行。 UcmTcpciCx 保证在任何给定时间最多有一个未完成的端口控制器请求。

  2. 调用 UcmTcpciPortControllerSetHardwareRequestQueue 以向 UcmTpciCx 注册新的框架队列对象。 调用成功后,当需要驱动程序的操作时,UcmTcpciCx 会将框架队列对象 (WDFREQUEST) 在此队列中。

  3. 实现 EvtIoDeviceControl 回调函数来处理这些 IOCTL。

控制代码 说明
IOCTL_UCMTCPCI_PORT_CONTROLLER_GET_STATUS 根据通用串行总线类型-C 端口控制器接口规范获取所有状态寄存器的值。 客户端驱动程序必须检索CC_STATUS、POWER_STATUS和FAULT_STATUS寄存器的值。
IOCTL_UCMTCPCI_PORT_CONTROLLER_GET_CONTROL 获取根据通用串行总线类型 C 端口控制器接口规范定义的所有控件寄存器的值。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_CONTROL 设置根据通用串行总线类型 C 端口控制器接口规范定义的控制寄存器的值。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_TRANSMIT 设置根据通用串行总线类型 C 端口控制器接口规范定义的传输寄存器。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_TRANSMIT_BUFFER 设置根据通用串行总线类型 C 端口控制器接口规范定义的TRANSMIT_BUFER寄存器。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_RECEIVE_DETECT 设置根据通用串行总线类型 C 端口控制器接口规范定义的RECEIVE_DETECT寄存器。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_CONFIG_STANDARD_OUTPUT 设置根据通用串行总线类型 C 端口控制器接口规范定义的CONFIG_STANDARD_OUTPUT寄存器。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_COMMAND 设置根据通用串行总线类型 C 端口控制器接口规范定义的命令寄存器的值。
IOCTL_UCMTCPCI_PORT_CONTROLLER_SET_MESSAGE_HEADER_INFO 设置根据通用串行总线类型 C 端口控制器接口规范定义的MESSAGE_HEADER_INFO寄存器的值。
IOCTL_UCMTCPCI_PORT_CONTROLLER_ALTERNATE_MODE_ENTERED 通知客户端驱动程序已进入备用模式,以便驱动程序可以执行其他任务。
IOCTL_UCMTCPCI_PORT_CONTROLLER_ALTERNATE_MODE_EXITED 通知客户端驱动程序退出备用模式,以便驱动程序可以执行其他任务。
IOCTL_UCMTCPCI_PORT_CONTROLLER_DISPLAYPORT_CONFIGURED 通知客户端驱动程序合作伙伴设备上的 DisplayPort 备用模式已配置引脚分配,以便驱动程序可以执行其他任务。
IOCTL_UCMTCPCI_PORT_CONTROLLER_DISPLAYPORT_HPD_STATUS_CHANGED 通知客户端驱动程序 DisplayPort 连接的热插拔检测状态已更改,以便驱动程序可以执行其他任务。
  1. 调用 UcmTcpciPortControllerStart 以指示 UcmTcpciCx 启动端口控制器。 UcmTcpciCx 假定控制 USB Type-C 和电源传送。 启动端口控制器后,UcmTcpciCx 可能会开始将请求放入硬件请求队列。

处理来自端口控制器硬件的警报

示例参考:请参阅ProcessAndSendAlertsAlert.cpp

客户端驱动程序必须处理从端口控制器硬件收到的警报 (或事件) ,并使用与事件相关的数据将它们发送到 UcmTcpciCx。

发生硬件警报时,端口控制器硬件会将 ALERT 引脚推高。 这会导致调用在步骤 2) 中注册的客户端驱动程序的 ISR (。 例程在PASSIVE_LEVEL为硬件中断提供服务。 例程确定中断是否是来自端口控制器硬件的警报;如果是这样,它将完成警报的处理,并通过调用 UcmTcpciPortControllerAlert 通知 UcmTcpciCx。

在调用 UcmTcpciPortControllerAlert 之前,客户端负责在UCMTCPCI_PORT_CONTROLLER_ALERT_DATA结构中包含与警报相关的所有相关数据。 客户端提供所有处于活动状态的警报的数组,因为硬件可能会同时断言多个警报。

下面是在抄送状态中报告更改的任务流示例。

  1. 客户端收到硬件警报。

  2. 客户端读取 ALERT 寄存器并确定处于活动状态的警报类型。

  3. 客户端读取 CC STATUS 寄存器,并在 UCMTCPCI_PORT_CONTROLLER_ALERT_DATA 中描述抄送状态寄存器的内容。 驱动程序将 AlertType 成员设置为 UcmTcpciPortControllerAlertCCStatus 和 register 的 CCStatus 成员。

  4. 客户端调用 UcmPortControllerAlert 将阵列硬件警报发送到 UcmTcpciCx。

  5. 客户端清除警报 (在客户端检索警报信息后随时可能发生这种情况)

处理从 UcmTcpciCx 接收的请求

示例参考:请参阅 PortControllerInterface.cpp

作为状态机执行的一部分,UcmTcpciCx 需要将请求发送到端口控制器。 例如,它需要设置TRANSMIT_BUFFER。 此请求将移交给客户端驱动程序。 驱动程序使用 UcmTcpciCx 提供的详细信息设置传输缓冲区。 其中大多数请求转换为客户端驱动程序读取或写入的硬件。 命令必须是异步的,因为 DPM 无法阻止等待硬件传输完成。

UcmTcpciCx 将命令作为 I/O 控制代码发送,描述客户端驱动程序所需的 get/set 操作。 在客户端驱动程序的队列设置中,驱动程序向 UcmTcpciCx 注册了其队列。 UcmTcpciCx 开始将框架请求对象放置在它需要从驱动程序进行操作的队列中。 I/O 控制代码列在步骤 4 的表中。

客户端驱动程序负责及时完成请求。

客户端驱动程序在完成请求的操作后,对具有完成状态的框架请求对象调用 WdfRequestComplete。

客户端驱动程序可能需要向另一个驱动程序发送 I/O 请求才能执行硬件操作。 例如,在示例中,驱动程序将 SPB 请求发送到连接 I2C 的端口控制器。 在这种情况下,驱动程序无法转发从 UcmTcpciCx 收到的框架请求对象,因为请求对象在 WDM IRP 中可能没有正确数量的堆栈位置。 客户端驱动程序必须创建另一个框架请求对象,并将其转发到另一个驱动程序。 客户端驱动程序可以在初始化期间预先分配所需的请求对象,而不是在每次从 UcmTcpciCx 获取请求时创建一个请求对象。 这是可能的,因为 UcmTcpciCx 保证在任何给定时间只有一个未完成的请求。

另请参阅