windows USB 设备驱动开发-开发Type C接口的驱动程序(二)

编写 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

在回调实现中,客户端驱动程序应:

  1. 向端口伙伴发送 PD DR_Swap消息。
  2. 调用 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;
    }

在回调实现中,客户端驱动程序应:

  1. 向端口合作伙伴发送 PD PR_Swap消息;
  2. 调用 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 仅在该方向上工作。 在此方案中,数据角色更正的顺序如下:

  1. 在移动设备上运行的客户端驱动程序通过调用 UcmConnectorTypeCAttach 报告附加事件,并将合作伙伴连接器报告为面向下游的端口 (UFP) ;
  2. 客户端驱动程序通过调用 UcmConnectorPdPartnerSourceCaps 和 UcmConnectorPdConnectionStateChanged 报告 PD 协定;
  3. UCM 类扩展通知 USB 设备端驱动程序,导致这些驱动程序响应来自主机的枚举。 操作系统信息通过 USB 交换;
  4. 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:客户端注册
  1. 客户端驱动程序调用 UsbPm_Register 来注册驱动程序的回调函数;
  2. 客户端驱动程序等待来自策略管理器的事件。成功的 UsbPm_Register 调用不保证客户端驱动程序已请求访问权限。 策略管理器准备就绪后,驱动程序 EVT_USBPM_EVENT_CALLBACK 使用 PolicyManagerArrival 作为指示授予的实际访问权限的事件数据进行调用;
  3. UsbPm_Register调用返回注册句柄。客户端驱动程序可能在 UsbPm_Register 返回之前收到 EVT_USBPM_EVENT_CALLBACK ;
2:通知到达
  1. 当 UCMCX 设备到达时,POlicy Manager 会收到通知,并跟踪所有中心句柄以及每个中心上所有连接器的属性和状态;
  2. 使用 HubArrivalRemoval 作为事件数据调用客户端驱动程序的EVT_USBPM_EVENT_CALLBACK,调用还包含中心句柄;
  3. 在客户端驱动程序的 EVT_USBPM_EVENT_CALLBACK 实现中,驱动程序调用 UsbPm_RetrieveHubProperties 以获取中心上的连接器数,然后调用 UsbPm_RetrieveConnectorProperties 和 UsbPm_RetrieveConnectorState 以获取有关每个连接器的详细信息;
3:连接器状态更改
  1. 由于连接器状态更改(例如,Type-C 附加/分离、PD 协定协商),策略管理器会更新每个连接器的状态信息;
  2. 使用 ConnectorStateChange 作为事件数据调用客户端驱动程序的EVT_USBPM_EVENT_CALLBACK。 调用还包含连接器句柄;
  3. 客户端驱动程序的完成例程也会被调用,并相应地执行操作;
  4. 在客户端驱动程序的 EVT_USBPM_EVENT_CALLBACK 实现中,驱动程序调用 UsbPm_RetrieveConnectorProperties。 通过使用给定的连接器句柄,驱动程序获取最新的连接器状态,对其进行检查,并可能决定更新其本地副本;
4:客户端驱动程序发起的更改
  1. 若要请求更改,客户端驱动程序调用 UsbPm_AssignConnectorPowerLevel。客户端驱动程序可以在使用 UsbPm_Register 注册的EVT_USBPM_EVENT_CALLBACK回调中调用此函数;
  2. 策略管理器将请求转发到 UCM) (USB 连接器管理器。 UcmCx 的客户端驱动程序会执行相应的操作来更改请求的状态;
  3. 使用 ConnectorStateChange 作为事件数据调用客户端驱动程序的EVT_USBPM_EVENT_CALLBACK。 调用还包含连接器句柄;
  4. 客户端驱动程序的完成例程也会被调用,并相应地执行操作;
  5. 在回调中,客户端驱动程序使用给定的连接器句柄调用 UsbPm_RetrieveConnectorState 以获取最新的连接器状态,对其进行检查,并可能决定更新其本地副本;
5:删除中心
  1. 当 UcmCx 设备 (UcmCx 设备上的单个连接器) 被删除时,UCM 会通知策略管理器。 策略管理器从中心集合中删除中心;
  2. 使用 HubRemoval 作为事件数据调用客户端驱动程序的EVT_USBPM_EVENT_CALLBACK实现。 调用还包含中心句柄;
  3. 在客户端驱动程序的 EVT_USBPM_EVENT_CALLBACK 实现中,客户端驱动程序对要删除的中心和连接器执行清理任务。 驱动程序可以调用 UsbPm_RetrieveHubProperties 和 UsbPm_RetrieveConnectorProperties 来获取中心和连接器的属性;
6:客户端注销
  1. 当驱动程序不再需要任何通知时,客户端驱动程序调用 UsbPm_Deregister ;
  2. 策略管理器将客户端句柄注册标记为已取消注册,并且不会调用 EVT_USBPM_EVENT_CALLBACK 回调;

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值