编写 USB Type C 连接器驱动程序
在以下情况下,需要编写 USB Type-C 连接器驱动程序:
- 如果 USB Type-C 硬件能够处理电源输送 (PD) 状态机。 否则,请考虑编写 USB Type C 端口控制器驱动程序;
- 如果硬件没有嵌入式控制器。 否则,加载 Microsoft 提供的内置驱动程序,UcmUcsi.sys,或为非 ACPI 传输编写 UCSI 客户端驱动程序 ;
类扩展和客户端驱动程序使用的 UCM 对象
下面介绍 USB 连接器管理器 (UCM) ,用于管理 USB Type-C 连接器和连接器驱动程序的预期行为。
UCM 是使用 WDF 类扩展客户端驱动程序模型设计的。 UcmCx类扩展是 Microsoft 提供的 WDF 驱动程序,它提供客户端驱动程序可以调用这些接口来报告有关连接器的信息。 UCM 客户端驱动程序使用连接器的硬件接口,并使类扩展能够识别连接器上发生的事件。 相反,类扩展调用客户端驱动程序为响应操作系统事件而实现的回调函数。
若要在系统上启用 USB Type-C 连接器,必须编写客户端驱动程序。
准备阶段
在开发 计算机上安装最新的 Windows 驱动程序工具包 (WDK) 。 该工具包具有编写 UCM 客户端驱动程序所需的头文件和库,具体而言,你需要:
- 库UcmCxstub.lib,该库转换客户端驱动程序发出的调用,并将其传递给 UcmCx;
- 头文件 UcmCx.h;
可以编写在用户模式或内核模式下运行的 UCM 客户端驱动程序。 对于用户模式,它与 UMDF 2.x 库绑定;对于内核模式,则为 KMDF 1.15。 对于任一模式,编程接口都是相同的。
确定客户端驱动程序是否支持 USB Type-C 连接器和 USB 电源输送的高级功能。
此支持使你能够构建具有 USB Type-C 连接器、USB Type-C 扩展坞和附件以及 USB Type-C 充电器的 Windows 设备。 客户端驱动程序报告连接器事件,这些事件允许操作系统针对系统中的 USB 和功耗实施策略。
- 使用 USB Type-C 连接器在目标计算机或Windows 10 移动版上安装桌面版 (家庭版、专业版、企业) 版和教育版的Windows 10;
- 熟悉 UCM 及其与其他 Windows 驱动程序的交互方式;
- 熟悉 Windows Driver Foundation (WDF) ;
UCM 类扩展提供的服务摘要
UCM 类扩展使操作系统随时了解数据和电源角色、充电级别以及协商的 PD 协定的更改。 当客户端驱动程序与硬件交互时,它必须在发生这些更改时通知类扩展。 类扩展提供了一组方法,客户端驱动程序可以使用这些方法发送本主题 () 中讨论的通知。 以下是提供的服务:
数据角色配置
在 USB Type C 系统上, (主机或功能) 的数据角色取决于连接器的 CC 引脚的状态。 客户端驱动程序读取 CC 线路端口控制器的状态,以确定端口是否已解析为面向上游的端口 (UFP) 或面向下游的端口 (UFP) 。 它将该信息报告给类扩展,以便它可以向 USB 角色切换驱动程序报告当前角色。
备注 USB 角色切换驱动程序用于Windows 10 移动版系统。 在桌面版系统的Windows 10上,类扩展和角色切换驱动程序之间的通信是可选的。 此类系统可能不使用双角色控制器,在这种情况下,不使用角色切换驱动程序。
电源角色和充电
客户端驱动程序读取 USB Type C 当前播发,或与合作伙伴连接器协商 PD 电源合同。
- 在Windows 10 移动版系统上,选择适当的充电器的决定是由软件辅助的。 客户端驱动程序将协定信息报告给类扩展,以便它可以将充电级别发送到充电仲裁驱动程序 (CAD.sys) 。 CAD 选择要使用的当前电量并将充电级别信息转发到电池子系统。
- 在桌面版系统的Windows 10上,硬件选择适当的充电器。 客户端驱动程序可以选择获取该信息并将其转发到类扩展。 或者,该逻辑可能由其他驱动程序实现。
数据和电源角色更改
协商 PD 合同后,数据角色和权力角色可能会更改。 该更改可能由客户端驱动程序或合作伙伴连接器启动。 客户端驱动程序向类扩展报告该信息,以便它可以相应地重新配置内容。
数据和/或电源角色更新
操作系统可能会确定当前数据角色不正确。 在这种情况下,类扩展会调用驱动程序的回调函数来执行必要的角色交换操作。
Microsoft 提供的 USB Type C 策略管理器监视 USB Type C 连接器的活动。 Windows 版本 1809 引入了一组编程接口,可用于将客户端驱动程序写入策略管理器。 客户端驱动程序可以参与 USB Type C 连接器的策略决策。 使用此集,可以选择编写内核模式导出驱动程序或用户模式驱动程序。
客户端驱动程序的预期行为
客户端驱动程序负责执行以下任务:
- 检测抄送行上的更改,并确定合作伙伴的类型,例如 UFP、DFP 等。 为此,驱动程序必须实现 USB Type C 规范中定义的完整 Type-C 状态机;
- 根据在 CC 线上检测到的方向配置 Mux。 这包括打开 PD 发送器/接收器以及处理和响应 PD 消息。 为此,驱动程序必须实现 USB 电源交付 2.0 规范中定义的完整 PD 接收器和发射器状态机;
- 制定 PD 策略决策,例如将合同协商 (为源或接收器) 、角色交换等。 客户端驱动程序负责确定最合适的协定。
- 播发和协商备用模式,如果检测到备用模式,则配置 Mux。 客户端驱动程序负责决定要协商的备用模式;
- 通过连接器控制 VBus/VConn;
1. (UCMCONNECTOR) 初始化 UCM 连接器对象
UCM 连接器对象 (UCMCONNECTOR) 表示 USB Type C 连接器,是 UCM 类扩展和客户端驱动程序之间的main句柄。 对象跟踪连接器的操作模式和电源功能。
下面是客户端驱动程序检索连接器的 UCMCONNECTOR 句柄的序列摘要。 在驱动程序的 中执行这些任务
1.1. 通过将引用传递给 UCM_MANAGER_CONFIG 结构来调用 UcmInitializeDevice。 在调用 WdfDeviceCreate 之前,驱动程序必须在 EVT_WDF_DRIVER_DEVICE_ADD 回调函数中调用此方法。
1.2. 在 UCM_CONNECTOR_TYPEC_CONFIG 结构中指定 USB Type-C 连接器的初始化参数。 这包括连接器的操作模式,无论是面向下游的端口、面向上游端口,还是支持双重角色。 它还指定连接器为电源时的 USB Type C 电流级别。 可以设计 USB Type-C 连接器,使其可以充当 3.5 毫米音频插孔。 如果硬件支持该功能,则必须相应地初始化连接器对象。
在 结构中,还必须注册客户端驱动程序的回调函数以处理数据角色。
此回调函数与连接器对象相关联,连接器对象由 UCM 类扩展调用。 此函数必须由客户端驱动程序实现。
EVT_UCM_CONNECTOR_SET_DATA_ROLE 在附加到合作伙伴连接器时,将连接器的数据角色交换为指定角色。
1.3. 如果客户端驱动程序希望支持 PD,即处理连接器的 Power Delivery 2.0 硬件实现,则还必须初始化指定 PD 初始化参数 的 UCM_CONNECTOR_PD_CONFIG 结构。 这包括电源流,无论连接器是电源接收器还是电源。
在 结构中,还必须注册客户端驱动程序的回调函数来处理电源角色。
此回调函数与连接器对象相关联,连接器对象由 UCM 类扩展调用。 此函数必须由客户端驱动程序实现。
EVT_UCM_CONNECTOR_SET_POWER_ROLE 在连接到合作伙伴连接器时,将连接器的电源角色设置为指定角色。
1.4. 调用 UcmConnectorCreate 并检索连接器的 UCMCONNECTOR 句柄。 请确保在客户端驱动程序通过调用 WdfDeviceCreate 创建框架设备对象后调用此方法。 此调用的适当位置可以位于驱动程序 EVT_WDF_DEVICE_PREPARE_HARDWARE 或 EVT_WDF_DEVICE_D0_ENTRY。
EVT_UCM_CONNECTOR_SET_DATA_ROLE EvtSetDataRole;
NTSTATUS
EvtDevicePrepareHardware(
WDFDEVICE Device,
WDFCMRESLIST ResourcesRaw,
WDFCMRESLIST ResourcesTranslated
)
{
NTSTATUS status = STATUS_SUCCESS;
PDEVICE_CONTEXT devCtx;
UCM_MANAGER_CONFIG ucmCfg;
UCM_CONNECTOR_CONFIG connCfg;
UCM_CONNECTOR_TYPEC_CONFIG typeCConfig;
UCM_CONNECTOR_PD_CONFIG pdConfig;
WDF_OBJECT_ATTRIBUTES attr;
PCONNECTOR_CONTEXT connCtx;
UNREFERENCED_PARAMETER(ResourcesRaw);
UNREFERENCED_PARAMETER(ResourcesTranslated);
TRACE_FUNC_ENTRY();
devCtx = GetDeviceContext(Device);
if (devCtx->Connector)
{
goto Exit;
}
//
// Initialize UCM Manager
//
UCM_MANAGER_CONFIG_INIT(&ucmCfg);
status = UcmInitializeDevice(Device, &ucmCfg);
if (!NT_SUCCESS(status))
{
TRACE_ERROR(
"UcmInitializeDevice failed with %!STATUS!.",
status);
goto Exit;
}
TRACE_INFO("UcmInitializeDevice() succeeded.");
//
// Create a USB Type-C connector #0 with PD
//
UCM_CONNECTOR_CONFIG_INIT(&connCfg, 0);
UCM_CONNECTOR_TYPEC_CONFIG_INIT(
&typeCConfig,
UcmTypeCOperatingModeDrp,
UcmTypeCCurrentDefaultUsb | UcmTypeCCurrent1500mA | UcmTypeCCurrent3000mA);
typeCConfig.EvtSetDataRole = EvtSetDataRole;
UCM_CONNECTOR_PD_CONFIG_INIT(&pdConfig, UcmPowerRoleSink | UcmPowerRoleSource);
connCfg.TypeCConfig = &typeCConfig;
connCfg.PdConfig = &pdConfig;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attr, CONNECTOR_CONTEXT);
status = UcmConnectorCreate(Device, &connCfg, &attr, &devCtx->Connector);
if (!NT_SUCCESS(status))
{
TRACE_ERROR(
"UcmConnectorCreate failed with %!STATUS!.",
status);
goto Exit;
}
connCtx = GetConnectorContext(devCtx->Connector);
UcmEventInitialize(&connCtx->EventSetDataRole);
TRACE_INFO("UcmConnectorCreate() succeeded.");
Exit:
TRACE_FUNC_EXIT();
return status;
}
2.报告合作伙伴连接器附加事件
检测到与合作伙伴连接器的连接时,客户端驱动程序必须调用 UcmConnectorTypeCAttach 。 此调用通知 UCM 类扩展,这会进一步通知操作系统。 此时,系统可能会开始在 USB Type C 级别充电。
UCM 类扩展还会通知 USB 角色切换驱动程序 (URS) 。 根据合作伙伴的类型,URS 在Host角色或功能角色中配置控制器。 在调用此方法之前,请确保系统已正确配置。 否则,如果系统处于功能角色,它将以不正确的速度连接 (高速而不是 SuperSpeed) 。
UCM_CONNECTOR_TYPEC_ATTACH_PARAMS attachParams;
UCM_CONNECTOR_TYPEC_ATTACH_PARAMS_INIT(
&attachParams,
UcmTypeCPortStateDfp);
attachParams.CurrentAdvertisement = UcmTypeCCurrent1500mA;
status = UcmConnectorTypeCAttach(
Connector,
&attachParams);
if (!NT_SUCCESS(status))
{
TRACE_ERROR(
"UcmConnectorTypeCAttach() failed with %!STATUS!.",
status);
goto Exit;
}
TRACE_INFO("UcmConnectorTypeCAttach() succeeded.");
3. 报告 USB Type C 播发更改
在初始附加事件中,合作伙伴连接器发送当前播发。 如果播发指定合作伙伴连接器的当前级别,则合作伙伴是面向 USB Type C 的端口。 否则,播发指定本地连接器的当前级别,由本地连接器 UCMCONNECTOR 句柄表示。 此初始播发可能会在连接的生存期内更改。 这些更改必须由客户端驱动程序监视。
如果本地连接器是电源接收器,并且当前播发发生了更改,则客户端驱动程序必须检测当前播发中的更改,并将其报告给类扩展。 在Windows 10 移动版系统上,CAD.sys 和电池子系统使用该信息来调整从源中抽取的电流量。 若要向类扩展报告当前级别的更改,客户端驱动程序必须调用 UcmConnectorTypeCCurrentAdChanged。
4. 报告新的协商 PD 合同
如果连接器支持 PD,在初始附加事件之后,连接器及其合作伙伴连接器之间传输了 PD 消息。 在两个合作伙伴之间,协商 PD 合同,确定连接器可以绘制或允许合作伙伴绘制的当前级别。 每次 PD 协定更改时,客户端驱动程序都必须调用这些方法来向类扩展报告更改。
- 每当客户端驱动程序获取源功能播发 (未经请求或从合作伙伴) 时,都必须调用这些方法。 仅当合作伙伴是源时,本地连接器 (接收器) 从合作伙伴获取未经请求的广告。 此外,即使合作伙伴当前是接收器,本地连接器也可以显式请求能够成为源的合作伙伴的源功能。 通过向合作伙伴发送 Get_Source_Caps 消息来完成交换。
UcmConnectorPdPartnerSourceCaps 用于报告合作伙伴连接器播发的源功能。
UcmConnectorPdConnectionStateChanged 用于报告合同的详细信息。 该协定在 Power Delivery 2.0 规范中定义的请求数据对象中描述。
- 相反,每次本地连接器 (源) 向合作伙伴播发源功能时,客户端驱动程序都必须调用这些方法。 此外,当本地连接器收到来自合作伙伴 的Get_Source_Caps 消息时,它必须使用本地连接器的源功能进行响应。
UcmConnectorPdSourceCaps ,用于将系统播发的源功能报告给合作伙伴连接器。
UcmConnectorPdConnectionStateChanged 报告当前协商的 PD 协定的连接功能。
5. 报告电池充电状态
如果充电级别不足,客户端驱动程序可以通知 UCM 类扩展。 类扩展将此信息报告给操作系统。 系统使用该信息向用户显示充电器未以最佳方式为系统充电的通知。 可以通过以下方法报告充电状态:
- UcmConnectorChargingStateChanged
- UcmConnectorTypeCAttach
- UcmConnectorPdConnectionStateChanged
这些方法指定充电状态。 如果报告的级别为 UcmChargingStateSlowCharging 或 UcmChargingStateTrickleCharging 。
6. 报告PR_Swap/DR_Swap事件
如果连接器收到电源角色 (PR_Swap) 或数据角色 (DR_Swap) 交换来自合作伙伴的消息,则客户端驱动程序必须通知 UCM 类扩展。
- UcmConnectorDataDirectionChanged
处理 PD DR_Swap消息后调用此方法。 完成此调用后,操作系统会将新角色报告给 URS,这会删除现有角色驱动程序并加载新角色的驱动程序。
- UcmConnectorPowerDirectionChanged
在处理 PD PR_Swap消息后调用此方法。 PR_Swap后,需要重新谈判 PD 协定。 客户端驱动程序必须通过调用 步骤 4 中所述的方法来报告 PD 协定协商。
7. 实现回调函数以处理电源和数据角色交换请求
UCM 类扩展可能会收到更改连接器的数据或电源方向的请求。 在这种情况下,如果连接器实现 PD) ,它将 (调用客户端驱动程序实现EVT_UCM_CONNECTOR_SET_DATA_ROLE 和EVT_UCM_CONNECTOR_SET_POWER_ROLE 回调函数。 客户端驱动程序之前在调用 UcmConnectorCreate 时注册了这些函数。
客户端驱动程序使用硬件接口执行角色交换操作。
- EVT_UCM_CONNECTOR_SET_DATA_ROLE
在回调实现中,客户端驱动程序应:
- 向端口伙伴发送 PD DR_Swap消息。
- 调用 UcmConnectorDataDirectionChanged 以通知类扩展消息序列已成功完成或未成功。
EVT_UCM_CONNECTOR_SET_DATA_ROLE EvtSetDataRole; NTSTATUS EvtSetDataRole( UCMCONNECTOR Connector, UCM_TYPE_C_PORT_STATE DataRole ) { PCONNECTOR_CONTEXT connCtx; TRACE_INFO("EvtSetDataRole(%!UCM_TYPE_C_PORT_STATE!) Entry", DataRole); connCtx = GetConnectorContext(Connector); TRACE_FUNC_EXIT(); return STATUS_SUCCESS; }
在回调实现中,客户端驱动程序应:
- 向端口合作伙伴发送 PD PR_Swap消息;
- 调用 UcmConnectorPowerDirectionChanged 以通知类扩展消息序列已成功完成或未成功;
EVT_UCM_CONNECTOR_SET_POWER_ROLE EvtSetPowerRole; NTSTATUS EvtSetPowerRole( UCMCONNECTOR Connector, UCM_POWER_ROLE PowerRole ) { PCONNECTOR_CONTEXT connCtx; TRACE_INFO("EvtSetPowerRole(%!UCM_POWER_ROLE!) Entry", PowerRole); connCtx = GetConnectorContext(Connector); //PR_Swap operation. TRACE_FUNC_EXIT(); return STATUS_SUCCESS; }
客户端驱动程序可以异步调用 UcmConnectorDataDirectionChanged 和 UcmConnectorPowerDirectionChanged ,这并非来自回调线程。 在典型的实现中, 类扩展调用回调函数,导致客户端驱动程序启动硬件事务以发送消息。 事务完成后,硬件会通知驱动程序。 驱动程序调用这些方法以通知类扩展。
8.报告合作伙伴连接器分离事件
当与合作伙伴连接器的连接结束时,客户端驱动程序必须调用 UcmConnectorTypeCDetach 。 此调用通知 UCM 类扩展,这会进一步通知操作系统。
用例示例:连接到电脑的移动设备
当运行 Windows 10 移动版 的设备通过 USB Type-C 连接连接到运行 Windows 10 桌面版的电脑时,操作系统将确保移动设备是面向上游的端口 (UFP) ,因为 MTP 仅在该方向上工作。 在此方案中,数据角色更正的顺序如下:
- 在移动设备上运行的客户端驱动程序通过调用 UcmConnectorTypeCAttach 报告附加事件,并将合作伙伴连接器报告为面向下游的端口 (UFP) ;
- 客户端驱动程序通过调用 UcmConnectorPdPartnerSourceCaps 和 UcmConnectorPdConnectionStateChanged 报告 PD 协定;
- UCM 类扩展通知 USB 设备端驱动程序,导致这些驱动程序响应来自主机的枚举。 操作系统信息通过 USB 交换;
- UCM 类扩展 UcmCx 调用客户端驱动程序的回调函数以更改角色: EVT_UCM_CONNECTOR_SET_DATA_ROLE 和 EVT_UCM_CONNECTOR_SET_POWER_ROLE;
如果两个Windows 10 移动版设备相互连接,则不会执行角色交换,并且通知用户该连接不是有效的连接。
编写 USB Type C 策略管理器客户端驱动程序
Microsoft 提供的 USB Type C 策略管理器监视 USB Type C 连接器的活动。 Windows 版本 1809 引入了一组编程接口,可用于在本文中将客户端驱动程序写入策略管理器称为 PM 客户端驱动程序 。 客户端驱动程序可以参与 USB Type C 连接器的策略决策。 使用此集,可以选择编写内核模式导出驱动程序或用户模式驱动程序。
策略管理器从 USB 连接器管理器 (UCM) 、USB 主机控制器和 USB 功能以及 PM 客户端驱动程序获取和协调信息。 需要 UI 通知时,策略管理器会将请求发送到系统 Shell。
PM API 在 Usbpmapi.h 标头中声明。
1:客户端注册
- 客户端驱动程序调用 UsbPm_Register 来注册驱动程序的回调函数;
- 客户端驱动程序等待来自策略管理器的事件。成功的 UsbPm_Register 调用不保证客户端驱动程序已请求访问权限。 策略管理器准备就绪后,驱动程序 EVT_USBPM_EVENT_CALLBACK 使用 PolicyManagerArrival 作为指示授予的实际访问权限的事件数据进行调用;
- UsbPm_Register调用返回注册句柄。客户端驱动程序可能在 UsbPm_Register 返回之前收到 EVT_USBPM_EVENT_CALLBACK ;
2:通知到达
- 当 UCMCX 设备到达时,POlicy Manager 会收到通知,并跟踪所有中心句柄以及每个中心上所有连接器的属性和状态;
- 使用 HubArrivalRemoval 作为事件数据调用客户端驱动程序的EVT_USBPM_EVENT_CALLBACK,调用还包含中心句柄;
- 在客户端驱动程序的 EVT_USBPM_EVENT_CALLBACK 实现中,驱动程序调用 UsbPm_RetrieveHubProperties 以获取中心上的连接器数,然后调用 UsbPm_RetrieveConnectorProperties 和 UsbPm_RetrieveConnectorState 以获取有关每个连接器的详细信息;
3:连接器状态更改
- 由于连接器状态更改(例如,Type-C 附加/分离、PD 协定协商),策略管理器会更新每个连接器的状态信息;
- 使用 ConnectorStateChange 作为事件数据调用客户端驱动程序的EVT_USBPM_EVENT_CALLBACK。 调用还包含连接器句柄;
- 客户端驱动程序的完成例程也会被调用,并相应地执行操作;
- 在客户端驱动程序的 EVT_USBPM_EVENT_CALLBACK 实现中,驱动程序调用 UsbPm_RetrieveConnectorProperties。 通过使用给定的连接器句柄,驱动程序获取最新的连接器状态,对其进行检查,并可能决定更新其本地副本;
4:客户端驱动程序发起的更改
- 若要请求更改,客户端驱动程序调用 UsbPm_AssignConnectorPowerLevel。客户端驱动程序可以在使用 UsbPm_Register 注册的EVT_USBPM_EVENT_CALLBACK回调中调用此函数;
- 策略管理器将请求转发到 UCM) (USB 连接器管理器。 UcmCx 的客户端驱动程序会执行相应的操作来更改请求的状态;
- 使用 ConnectorStateChange 作为事件数据调用客户端驱动程序的EVT_USBPM_EVENT_CALLBACK。 调用还包含连接器句柄;
- 客户端驱动程序的完成例程也会被调用,并相应地执行操作;
- 在回调中,客户端驱动程序使用给定的连接器句柄调用 UsbPm_RetrieveConnectorState 以获取最新的连接器状态,对其进行检查,并可能决定更新其本地副本;
5:删除中心
- 当 UcmCx 设备 (UcmCx 设备上的单个连接器) 被删除时,UCM 会通知策略管理器。 策略管理器从中心集合中删除中心;
- 使用 HubRemoval 作为事件数据调用客户端驱动程序的EVT_USBPM_EVENT_CALLBACK实现。 调用还包含中心句柄;
- 在客户端驱动程序的 EVT_USBPM_EVENT_CALLBACK 实现中,客户端驱动程序对要删除的中心和连接器执行清理任务。 驱动程序可以调用 UsbPm_RetrieveHubProperties 和 UsbPm_RetrieveConnectorProperties 来获取中心和连接器的属性;
6:客户端注销
- 当驱动程序不再需要任何通知时,客户端驱动程序调用 UsbPm_Deregister ;
- 策略管理器将客户端句柄注册标记为已取消注册,并且不会调用 EVT_USBPM_EVENT_CALLBACK 回调;