OpenHarmony(鸿蒙南向开发)——小型系统芯片移植指南(三)驱动移植

移植概述

驱动主要包含两部分,平台驱动和器件驱动。平台驱动主要包括通常在SOC内的GPIO、I2C、SPI等;器件驱动则主要包含通常在SOC外的器件,如 LCD、TP、WLAN等

图1 OpenHarmony 驱动分类

HDF驱动被设计为可以跨OS使用的驱动程序,HDF驱动框架会为驱动达成这个目标提供有力的支撑。开发HDF驱动中,请尽可能只使用HDF驱动框架提供的接口,否则会导致驱动丧失跨OS使用的特性。在开始驱动开发前,建议先了解HDF驱动框架。

平台驱动移植

在这一步,我们会在源码目录//device/vendor_name/soc_name/drivers目录下创建平台驱动,如果你要移植的SOC的厂商还没有创建仓库的话,请联系 sig_devboard 创建。

建议的目录结构:

device
├── vendor_name
│   ├── drivers
│   │   │   ├── common
│   │   │   ├── Kconfig # 厂商驱动内核菜单入口
│   │   │   └── lite.mk # 构建的入口
│   ├── soc_name
│   │   ├── drivers
│   │   │   ├── dmac
│   │   │   ├── gpio
│   │   │   ├── i2c
│   │   │   ├── LICENSE
│   │   │   ├── mipi_dsi
│   │   │   ├── mmc
│   │   │   ├── pwm
│   │   │   ├── README.md # docs 如果需要的话
│   │   │   ├── README_zh.md
│   │   │   ├── rtc
│   │   │   ├── spi
│   │   │   ├── uart
│   │   │   └── watchdog
│   ├── board_name

HDF为所有的平台驱动都创建了驱动模型,移植平台驱动的主要工作是向模型注入实例。 这些模型你可以在源码目录//drivers/hdf_core/framework/support/platform/include中找到定义。

本节我们会以GPIO为例,讲解如何移植平台驱动,移植过程包含以下步骤:

  1. 创建GPIO驱动

在源码目录//device/vendor_name/soc_name/drivers/gpio中创建文件soc_name_gpio.c。内容模板如下:

    #include "gpio_core.h"

    // 定义GPIO结构体,如果需要的话
    struct SocNameGpioCntlr {
        struct GpioCntlr cntlr;  // 这是HDF GPIO驱动框架需要的结构体
        int myData; // 以下是当前驱动自身需要的
    };

    // Bind 方法在HDF驱动中主要用户对外发布服务,这里我们不需要,直接返回成功即可
    static int32_t GpioBind(struct HdfDeviceObject *device)
    {
        (void)device;
        return HDF_SUCCESS;
    }

    // Init方法时驱动初始化的入口,我们需要在Init方法中完成模型实例的注册
    static int32_t GpioInit(struct HdfDeviceObject *device)
    {
        SocNameGpioCntlr *impl = CreateGpio(); // 你的创建代码
        ret = GpioCntlrAdd(&impl->cntlr);  // 注册GPIO模型实例
        if (ret != HDF_SUCCESS) {
            HDF_LOGE("%s: err add controller:%d", __func__, ret);
            return ret;
        }
        return HDF_SUCCESS;
    }

    // Release方法会在驱动卸载时被调用,这里主要完成资源回收
    static void GpioRelease(struct HdfDeviceObject *device)
    {
        // GpioCntlrFromDevice 方法能从抽象的设备对象中获得init方法注册进去的模型实例。
        struct GpioCntlr *cntlr = GpioCntlrFromDevice(device);
        //资源释放...
    }

    struct HdfDriverEntry g_gpioDriverEntry = {
        .moduleVersion = 1,
        .Bind = GpioBind,
        .Init = GpioInit,
        .Release = GpioRelease,
        .moduleName = "SOC_NAME_gpio_driver", // 这个名字我们稍后会在配置文件中用到,用来加载驱动。
    };
    HDF_INIT(g_gpioDriverEntry); // 注册一个GPIO的驱动入口
  1. 创建厂商驱动构建入口

如前所述device/vendor_name/drivers/lite.mk是厂商驱动的构建的入口。我们需要从这个入口开始,进行构建。

    #文件device/vendor_name/drivers/lite.mk

    SOC_VENDOR_NAME := $(subst $/",,$(LOSCFG_DEVICE_COMPANY))
    SOC_NAME := $(subst $/",,$(LOSCFG_PLATFORM))
    BOARD_NAME := $(subst $/",,$(LOSCFG_PRODUCT_NAME))

    # 指定SOC进行构建
    LIB_SUBDIRS += $(LITEOSTOPDIR)/../../device/$(SOC_VENDOR_NAME)/$(SOC_NAME)/drivers/
  1. 创建SOC驱动构建入口
    #文件device/vendor_name/soc_name/drivers/lite.mk

    SOC_DRIVER_ROOT := $(LITEOSTOPDIR)/../../device/$(SOC_VENDOR_NAME)/$(SOC_NAME)/drivers/

    # 判断如果打开了GPIO的内核编译开关
    ifeq ($(LOSCFG_DRIVERS_HDF_PLATFORM_GPIO), y)
        # 构建完成要链接一个叫hdf_gpio的对象
        LITEOS_BASELIB += -lhdf_gpio
        # 增加构建目录gpio
        LIB_SUBDIRS    += $(SOC_DRIVER_ROOT)/gpio 
    endif

    # 后续其他驱动在此基础上追加
  1. 创建GPIO构建入口
    include $(LITEOSTOPDIR)/config.mk
    include $(LITEOSTOPDIR)/../../drivers/adapter/khdf/liteos/lite.mk

    # 指定输出对象的名称,注意要与SOC驱动构建入口里的LITEOS_BASELIB 保持一致
    MODULE_NAME := hdf_gpio

    # 增加HDF框架的INCLUDE
    LOCAL_CFLAGS += $(HDF_INCLUDE)

    # 要编译的文件
    LOCAL_SRCS += soc_name_gpio.c

    # 编译参数
    LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror -fsigned-char -fno-strict-aliasing -fno-common

    include $(HDF_DRIVER)
  1. 配置产品加载驱动

产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs中。

平台驱动请添加到platform的host中。

说明: moduleName要与驱动定义中的相同。

    root {
        ...
        platform :: host {
            device_gpio :: device {
                    device0 :: deviceNode {
                        policy = 0;
                        priority = 10;
                        permission = 0644;
                        moduleName = "SOC_NAME_gpio_driver"; 
                    }
            }
        }
    }

平台驱动移植

在这一步,我们会在源码目录//device/vendor_name/soc_name/drivers目录下创建平台驱动,如果你要移植的SOC的厂商还没有创建仓库的话,请联系 sig_devboard 创建。

建议的目录结构:

device
├── vendor_name
│   ├── drivers
│   │   │   ├── common
│   │   │   ├── Kconfig # 厂商驱动内核菜单入口
│   │   │   └── lite.mk # 构建的入口
│   ├── soc_name
│   │   ├── drivers
│   │   │   ├── dmac
│   │   │   ├── gpio
│   │   │   ├── i2c
│   │   │   ├── LICENSE
│   │   │   ├── mipi_dsi
│   │   │   ├── mmc
│   │   │   ├── pwm
│   │   │   ├── README.md # docs 如果需要的话
│   │   │   ├── README_zh.md
│   │   │   ├── rtc
│   │   │   ├── spi
│   │   │   ├── uart
│   │   │   └── watchdog
│   ├── board_name

HDF为所有的平台驱动都创建了驱动模型,移植平台驱动的主要工作是向模型注入实例。 这些模型你可以在源码目录//drivers/hdf_core/framework/support/platform/include中找到定义。

本节我们会以GPIO为例,讲解如何移植平台驱动,移植过程包含以下步骤:

  1. 创建GPIO驱动

在源码目录//device/vendor_name/soc_name/drivers/gpio中创建文件soc_name_gpio.c。内容模板如下:

    #include "gpio_core.h"

    // 定义GPIO结构体,如果需要的话
    struct SocNameGpioCntlr {
        struct GpioCntlr cntlr;  // 这是HDF GPIO驱动框架需要的结构体
        int myData; // 以下是当前驱动自身需要的
    };

    // Bind 方法在HDF驱动中主要用户对外发布服务,这里我们不需要,直接返回成功即可
    static int32_t GpioBind(struct HdfDeviceObject *device)
    {
        (void)device;
        return HDF_SUCCESS;
    }

    // Init方法时驱动初始化的入口,我们需要在Init方法中完成模型实例的注册
    static int32_t GpioInit(struct HdfDeviceObject *device)
    {
        SocNameGpioCntlr *impl = CreateGpio(); // 你的创建代码
        ret = GpioCntlrAdd(&impl->cntlr);  // 注册GPIO模型实例
        if (ret != HDF_SUCCESS) {
            HDF_LOGE("%s: err add controller:%d", __func__, ret);
            return ret;
        }
        return HDF_SUCCESS;
    }

    // Release方法会在驱动卸载时被调用,这里主要完成资源回收
    static void GpioRelease(struct HdfDeviceObject *device)
    {
        // GpioCntlrFromDevice 方法能从抽象的设备对象中获得init方法注册进去的模型实例。
        struct GpioCntlr *cntlr = GpioCntlrFromDevice(device);
        //资源释放...
    }

    struct HdfDriverEntry g_gpioDriverEntry = {
        .moduleVersion = 1,
        .Bind = GpioBind,
        .Init = GpioInit,
        .Release = GpioRelease,
        .moduleName = "SOC_NAME_gpio_driver", // 这个名字我们稍后会在配置文件中用到,用来加载驱动。
    };
    HDF_INIT(g_gpioDriverEntry); // 注册一个GPIO的驱动入口
  1. 创建厂商驱动构建入口

如前所述device/vendor_name/drivers/lite.mk是厂商驱动的构建的入口。我们需要从这个入口开始,进行构建。

    #文件device/vendor_name/drivers/lite.mk

    SOC_VENDOR_NAME := $(subst $/",,$(LOSCFG_DEVICE_COMPANY))
    SOC_NAME := $(subst $/",,$(LOSCFG_PLATFORM))
    BOARD_NAME := $(subst $/",,$(LOSCFG_PRODUCT_NAME))

    # 指定SOC进行构建
    LIB_SUBDIRS += $(LITEOSTOPDIR)/../../device/$(SOC_VENDOR_NAME)/$(SOC_NAME)/drivers/
  1. 创建SOC驱动构建入口
    #文件device/vendor_name/soc_name/drivers/lite.mk

    SOC_DRIVER_ROOT := $(LITEOSTOPDIR)/../../device/$(SOC_VENDOR_NAME)/$(SOC_NAME)/drivers/

    # 判断如果打开了GPIO的内核编译开关
    ifeq ($(LOSCFG_DRIVERS_HDF_PLATFORM_GPIO), y)
        # 构建完成要链接一个叫hdf_gpio的对象
        LITEOS_BASELIB += -lhdf_gpio
        # 增加构建目录gpio
        LIB_SUBDIRS    += $(SOC_DRIVER_ROOT)/gpio 
    endif

    # 后续其他驱动在此基础上追加
  1. 创建GPIO构建入口
    include $(LITEOSTOPDIR)/config.mk
    include $(LITEOSTOPDIR)/../../drivers/adapter/khdf/liteos/lite.mk

    # 指定输出对象的名称,注意要与SOC驱动构建入口里的LITEOS_BASELIB 保持一致
    MODULE_NAME := hdf_gpio

    # 增加HDF框架的INCLUDE
    LOCAL_CFLAGS += $(HDF_INCLUDE)

    # 要编译的文件
    LOCAL_SRCS += soc_name_gpio.c

    # 编译参数
    LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror -fsigned-char -fno-strict-aliasing -fno-common

    include $(HDF_DRIVER)
  1. 配置产品加载驱动

产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs中。

平台驱动请添加到platform的host中。

说明: moduleName要与驱动定义中的相同。

    root {
        ...
        platform :: host {
            device_gpio :: device {
                    device0 :: deviceNode {
                        policy = 0;
                        priority = 10;
                        permission = 0644;
                        moduleName = "SOC_NAME_gpio_driver"; 
                    }
            }
        }
    }

器件驱动移植

LCD驱动移植

移植LCD驱动的主要工作是编写一个驱动,在驱动中生成模型的实例,并完成注册。

这些LCD的驱动被放置在源码目录//drivers/hdf_core/framework/model/display/driver/panel中。

  1. 创建Panel驱动

创建HDF驱动,在驱动初始化中调用RegisterPanel接口注册模型实例。如:

    int32_t LCDxxEntryInit(struct HdfDeviceObject *object)
    {
        struct PanelData *panel = CreateYourPanel();
        // 注册模型实例
        if (RegisterPanel(panel) != HDF_SUCCESS) {
            HDF_LOGE("%s: RegisterPanel failed", __func__);
            return HDF_FAILURE;
        }
        return HDF_SUCCESS;
    }

    struct HdfDriverEntry g_xxxxDevEntry = {
        .moduleVersion = 1,
        .moduleName = "LCD_XXXX",
        .Init = LCDxxEntryInit,
    };

    HDF_INIT(g_xxxxDevEntry);
  1. 配置加载panel驱动

产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs中。修改该文件,在display的host中,名为device_lcd的device中增加配置。

注意: moduleName 要与panel驱动中的moduleName相同。

    root {
        ...
        display :: host {
            device_lcd :: device {
                    deviceN :: deviceNode {
                        policy = 0;
                        priority = 100;
                        preload = 2;
                        moduleName = "LCD_XXXX";
                    }
            }
        }
    }

TP驱动移植

本节描述如何移植触摸屏驱动。触摸屏的器件驱动被放置在源码目录//drivers/hdf_core/framework/model/input/driver/touchscreen中。 移植触摸屏驱动主要工作是向系统注册ChipDevice模型实例。

详细的驱动开发指导,请参考 TOUCHSCREEN开发指导。

  1. 创建触摸屏器件驱动

在上述touchscreen目录中创建名为touch_ic_name.c的文件。编写如下内容

    #include "hdf_touch.h"

    static int32_t HdfXXXXChipInit(struct HdfDeviceObject *device)
    {
        ChipDevice *tpImpl = CreateXXXXTpImpl();
        if(RegisterChipDevice(tpImpl) != HDF_SUCCESS) { // 注册ChipDevice模型
            ReleaseXXXXTpImpl(tpImpl);
            return HDF_FAILURE;
        }
        return HDF_SUCCESS;
    }

    struct HdfDriverEntry g_touchXXXXChipEntry = {
        .moduleVersion = 1,
        .moduleName = "HDF_TOUCH_XXXX", // 注意这里的moduleName要与后续的配置完全一致
        .Init = HdfXXXXChipInit,
    };

    HDF_INIT(g_touchXXXXChipEntry);

其中ChipDevice中要实现如下方法:

方法实现说明
int32_t (*Init)(ChipDevice *device)实现器件初始化
int32_t (*Detect)(ChipDevice *device)实现器件探测
int32_t (*Suspend)(ChipDevice *device)实现器件休眠
int32_t (*Resume)(ChipDevice *device)实现器件唤醒
int32_t (*DataHandle)(ChipDevice *device)需要实现从器件读取数据,将触摸点数据填写入device->driver->frameData中
int32_t (*UpdateFirmware)(ChipDevice *device)实现固件升级
  1. 配置产品,加载器件驱动

产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs中。修改该文件,在名为input的host中,名为device_touch_chip的device中增加配置。

说明: moduleName 要与触摸屏驱动中的moduleName相同。

    deviceN :: deviceNode {
        policy = 0;
        priority = 130;
        preload = 0;
        permission = 0660;
        moduleName = "HDF_TOUCH_XXXX";
        deviceMatchAttr = "touch_XXXX_configs";
    }

WLAN驱动移植

WLAN驱动分为两部分,一部分负责管理WLAN设备,另一个部分负责处理WLAN流量。

图1 OpenHarmony WLAN结构示意图

如图1,左半部分负责管理WLAN设备,右半部分负责WLAN流量。HDF WLAN分别为这两部分做了抽象,驱动的移植过程可以看做分别实现这两部分所需接口。这些接口有:

接口定义头文件接口说明
HdfChipDriverFactorydrivers\hdf_core\framework\include\wifi\hdf_wlan_chipdriver_manager.hChipDriver的Factory,用于支持一个芯片多个WLAN端口
HdfChipDriverdrivers\hdf_core\framework\include\wifi\wifi_module.h每个WLAN端口对应一个HdfChipDriver,用来管理一个特定端口
NetDeviceInterFacedrivers\hdf_core\framework\include\wifi\net_device.h与协议栈之间的接口,如发送数据、设置网络接口状态等

说明: 详细的接口开发指导,请参考WLAN开发。

具体的移植步骤如下:

  1. 创建HDF WLAN芯片驱动

在目录/device/vendor_name/peripheral/wifi/chip_name/创建文件hdf_wlan_chip_name.c。内容模板如下:

    static int32_t HdfWlanXXXChipDriverInit(struct HdfDeviceObject *device) {
        static struct HdfChipDriverFactory factory = CreateChipDriverFactory(); // 需要移植者实现的方法
        struct HdfChipDriverManager *driverMgr = HdfWlanGetChipDriverMgr();
        if (driverMgr->RegChipDriver(&factory) != HDF_SUCCESS) { // 注册驱动工厂
            HDF_LOGE("%s fail: driverMgr is NULL!", __func__);
            return HDF_FAILURE;
        }
        return HDF_SUCCESS;
    }

    struct HdfDriverEntry g_hdfXXXChipEntry = {
        .moduleVersion = 1,
        .Init = HdfWlanXXXChipDriverInit,
        .Release = HdfWlanXXXChipRelease,
        .moduleName = "HDF_WIFI_CHIP_XXX" // 注意:这个名字要与配置一致
    };

    HDF_INIT(g_hdfXXXChipEntry);

在上述代码的CreateChipDriverFactory方法中,需要创建一个HdfChipDriverFactory类型的对象。该对象提供如下方法:

接口说明
const char *driverName当前driverName
int32_t (*InitChip)(struct HdfWlanDevice *device)初始化芯片
int32_t (*DeinitChip)(struct HdfWlanDevice *device)去初始化芯片
void (*ReleaseFactory)(struct HdfChipDriverFactory *factory)释放HdfChipDriverFactory对象
struct HdfChipDriver *(*Build)(struct HdfWlanDevice *device, uint8_t ifIndex)创建一个HdfChipDriver;输入参数中,device是设备信息,ifIndex是当前创建的接口在这个芯片中的序号
void (*Release)(struct HdfChipDriver *chipDriver)释放chipDriver
uint8_t (*GetMaxIFCount)(struct HdfChipDriverFactory *factory)获取当前芯片支持的最大接口数

其中Build方法负责创建一个管理指定网络接口的对象HdfChipDriver。该对象需要提供方法:

接口说明
int32_t (*init)(struct HdfChipDriver *chipDriver, NetDevice *netDev)初始化当前网络接口,这里需要向netDev提供接口NetDeviceInterFace
int32_t (*deinit)(struct HdfChipDriver *chipDriver, NetDevice *netDev)去初始化当前网络接口
struct HdfMac80211BaseOps *opsWLAN基础能力接口集
struct HdfMac80211STAOps *staOps支持STA模式所需的接口集
struct HdfMac80211APOps *apOps支持AP模式所需要的接口集
  1. 编写配置文件描述驱动支持的芯片

在产品配置目录下创建芯片的配置文件,保存至源码路径//vendor/vendor_name/product_name/config/wifi/wlan_chip_chip_name.hcs

该文件模板如下:

    root {
        wlan_config {
            chip_name :& chipList {
                chip_name :: chipInst {
                    match_attr = "hdf_wlan_chips_chip_name"; /* 这是配置匹配属性,用于提供驱动的配置根 */
                    driverName = "driverName"; /* 需要与HdfChipDriverFactory中的driverName相同*/
                    sdio {
                        vendorId = 0xXXXX; /* your vendor id */
                        deviceId = [0xXXXX]; /*your supported devices */
                    }
                }
            }
        }
    }

说明:

路径和文件中的vendor_name、product_name、chip_name请替换成实际名称。

vendorId 和 deviceId需要根据实际芯片的识别码进行填写。

  1. 编写配置文件,加载驱动

产品的所有设备信息被定义在源码文件//vendor/vendor_name/product_name/config/device_info/device_info.hcs中。修改该文件,在名为network的host中,名为device_wlan_chips的device中增加配置。模板如下:

    deviceN :: deviceNode {
        policy = 0;
        preload = 2;
        moduleName = "HDF_WLAN_CHIPS";
        deviceMatchAttr = "hdf_wlan_chips_chip_name";
        serviceName = "driverName";
    }

说明: moduleName 要与HDF WLAN 芯片驱动中的moduleName相同。

  1. 修改Kconfig文件,让移植的WLAN模组出现再内核配置中

device/vendor_name/drivers/Kconfig中增加配置菜单,模板如下

    config DRIVERS_HDF_WIFI_chip_name
        bool "Enable chip_name Host driver"
        default n
        depends on DRIVERS_HDF_WLAN   help
          Answer Y to enable chip_name Host driver.

说明: 请替换模板中的chip_name为实际的芯片名称。

  1. 修改构建脚本,让驱动参与内核构建

在源码文件//device/vendor_name/drivers/lite.mk末尾追加如下内容。

    ifeq ($(LOSCFG_DRIVERS_HDF_WIFI_chip_name), y)
        # 构建完成要链接一个叫hdf_wlan_chipdriver_chip_name的对象,建议按这个命名,防止冲突
        LITEOS_BASELIB += -lhdf_wlan_chipdriver_chip_name
        # 增加构建目录gpio
        LIB_SUBDIRS    += ../peripheral/wifi/chip_name
    endif

说明: 请替换模板中的chip_name为实际的芯片名称。

为了帮助到大家可以更有规划性的学习,在此整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植……等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》

快速入门

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

图片

开发基础知识:gitee.com/MNxiaona/733GH

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

图片

基于ArkTS 开发

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

图片

OpenHarmony 开发环境搭建:gitee.com/MNxiaona/733GH

图片

《OpenHarmony源码解析》

  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:gitee.com/MNxiaona/733GH

图片

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值