Linux设备驱动之HID驱动

【linux kernel】一文浅析linux HID核心 本文总结Linux内核的hid核心 阅读详情
------------------------------------------
一:前言
继前面分析过UHCI和HUB驱动之后,接下来以HID设备驱动为例来做一个具体的USB设备驱动分析的例子.HID是Human Interface Devices的缩写.翻译成中文即为人机交互设备.这里的人机交互设备是一个宏观上面的概念,任何设备,只要符合HID spec,都可以称之为HID设备.常见的HID设备有鼠标键盘,游戏操纵杆等等.在接下来的代码分析中,可以参考HID的spec.这份spec可以在 www.usb.org上找到.分析的代码主要集中在linux-2.6.25/drivers/hid目录下.
二:HID驱动入口分析
USB HID设备驱动入口位于linux-2.6.25/drivers/hid/usbhid/hid-core.c中.该module的入口为hid_init().代码如下:
static int __init hid_init(void)
{
    int retval;
    retval = usbhid_quirks_init(quirks_param);
    if (retval)
        goto usbhid_quirks_init_fail;
    retval = hiddev_init();
    if (retval)
        goto hiddev_init_fail;
    retval = usb_register(&hid_driver);
    if (retval)
        goto usb_register_fail;
    info(DRIVER_VERSION ":" DRIVER_DESC);
 
    return 0;
usb_register_fail:
    hiddev_exit();
hiddev_init_fail:
    usbhid_quirks_exit();
usbhid_quirks_init_fail:
    return retval;
}
 
首先来看usbhid_quirks_init()函数.quirks我们在分析UHCI和HUB的时候也接触过,表示需要做某种修正的设备.该函数调用的参数是quirks_param.定义如下:
static char *quirks_param[MAX_USBHID_BOOT_QUIRKS] = { [ 0 ... (MAX_USBHID_BOOT_QUIRKS - 1) ] = NULL };
module_param_array_named(quirks, quirks_param, charp, NULL, 0444);
从此可以看出, quirks_param是MAX_USBHID_BOOT_QUIRKS元素的字符串数组.并且在加载module的时候,可以动态的指定这些值.
分析到这里.有人可以反应过来了,usbhid_quirks_init()是一种动态进行HID设备修正的方式.具体要修正哪些设备,要修正设备的那些方面,都可以由加载模块是所带参数来决定.
usbhid_quirks_init()的代码如下:
int usbhid_quirks_init(char **quirks_param)
{
    u16 idVendor, idProduct;
    u32 quirks;
    int n = 0, m;
 
    for (; quirks_param[n] && n < MAX_USBHID_BOOT_QUIRKS; n++) {
 
        m = sscanf(quirks_param[n], "0x%hx:0x%hx:0x%x",
                &idVendor, &idProduct, &quirks);
 
        if (m != 3 ||
                usbhid_modify_dquirk(idVendor, idProduct, quirks) != 0) {
            printk(KERN_WARNING
                    "Could not parse HID quirk module param %s/n",
                    quirks_param[n]);
        }
    }
 
    return 0;
}
由此可以看出, quirks_param数组中的每一项可以分为三个部份,分别是要修正设备的VendorID,ProductID和要修正的功能.比如0x1000 0x0001 0x0004就表示:要忽略掉VendorID为0x1000,ProductID为0x0004的设备.(在代码中,有 #define HID_QUIRK_IGNORE    0x00000004的定义)
 
跟进usbhid_modify_dquirk()函数,代码如下:
int usbhid_modify_dquirk(const u16 idVendor, const u16 idProduct,
        const u32 quirks)
{
    struct quirks_list_struct *q_new, *q;
    int list_edited = 0;
 
    if (!idVendor) {
        dbg_hid("Cannot add a quirk with idVendor = 0/n");
        return -EINVAL;
    }
 
    q_new = kmalloc(sizeof(struct quirks_list_struct), GFP_KERNEL);
    if (!q_new) {
        dbg_hid("Could not allocate quirks_list_struct/n");
        return -ENOMEM;
    }
 
    q_new->hid_bl_item.idVendor = idVendor;
    q_new->hid_bl_item.idProduct = idProduct;
    q_new->hid_bl_item.quirks = quirks;
 
    down_write(&dquirks_rwsem);
 
    list_for_each_entry(q, &dquirks_list, node) {
 
        if (q->hid_bl_item.idVendor == idVendor &&
                q->hid_bl_item.idProduct == idProduct) {
 
            list_replace(&q->node, &q_new->node);
            kfree(q);
            list_edited = 1;
            break;
 
        }
 
    }
 
    if (!list_edited)
        list_add_tail(&q_new->node, &dquirks_list);
 
    up_write(&dquirks_rwsem);
 
    return 0;
}
这个函数比较简单,就把quirks_param数组项中的三个部份存入一个封装结构.然后将其结构挂载到dquirks_list表.如果dquirks_list有重复的VendorId和ProductID就更新其quirks信息.
 
经过usbhid_quirks_init()之后,所有要修正的设备的相关操作都会存放在dquirks_list中.
返回到hid_init(),继续往下面分析.
hiddev_init()是一个无关的操作,不会影响到后面的操作.忽略
后面就是我们今天要分析的重点了,如下:
retval = usb_register(&hid_driver);
通过前面对HUB的驱动分析,相信对usb_redister()应该很熟悉了.hid_driver定义如下:
static struct usb_driver hid_driver = {
    .name =     "usbhid",
    .probe =    hid_probe,
    .disconnect =   hid_disconnect,
    .suspend =  hid_suspend,
    .resume =   hid_resume,
    .reset_resume = hid_post_reset,
    .pre_reset =    hid_pre_reset,
    .post_reset =   hid_post_reset,
    .id_table = hid_usb_ids,
    .supports_autosuspend = 1,
};
其中,id_table的结构为hid_usb_ids.定义如下:
static struct usb_device_id hid_usb_ids [] = {
    { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
        .bInterfaceClass = USB_INTERFACE_CLASS_HID },
    { }                     /* Terminating entry */
};
也就是说,该驱动会匹配interface的ClassID,所有ClassID为USB_INTERFACE_CLASS_HID的设备都会被这个驱动所匹配.所以,所有USB HID设备都会由这个module来驱动.
 
三:HID驱动的probe过程
从上面的分析可看到,probe接口为hid_probe().定义如下:
static int hid_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
    struct hid_device *hid;
    char path[64];
    int i;
    char *c;
 
    dbg_hid("HID probe called for ifnum %d/n",
            intf->altsetting->desc.bInterfaceNumber);
    //config the hid device
    if (!(hid = usb_hid_configure(intf)))
        return -ENODEV;
 
    usbhid_init_reports(hid);
    hid_dump_device(hid);
    if (hid->quirks & HID_QUIRK_RESET_LEDS)
        usbhid_set_leds(hid);
 
    if (!hidinput_connect(hid))
        hid->claimed |= HID_CLAIMED_INPUT;
    if (!hiddev_connect(hid))
        hid->claimed |= HID_CLAIMED_HIDDEV;
    if (!hidraw_connect(hid))
        hid->claimed |= HID_CLAIMED_HIDRAW;
 
    usb_set_intfdata(intf, hid);
 
    if (!hid->claimed) {
        printk ("HID device claimed by neither input, hiddev nor hidraw/n");
        hid_disconnect(intf);
        return -ENODEV;
    }
 
    if ((hid->claimed & HID_CLAIMED_INPUT))
        hid_ff_init(hid);
 
    if (hid->quirks & HID_QUIRK_SONY_PS3_CONTROLLER)
        hid_fixup_sony_ps3_controller(interface_to_usbdev(intf),
            intf->cur_altsetting->desc.bInterfaceNumber);
 
    printk(KERN_INFO);
 
    if (hid->claimed & HID_CLAIMED_INPUT)
        printk("input");
    if ((hid->claimed & HID_CLAIMED_INPUT) && ((hid->claimed & HID_CLAIMED_HIDDEV) ||
                hid->claimed & HID_CLAIMED_HIDRAW))
        printk(",");
    if (hid->claimed & HID_CLAIMED_HIDDEV)
        printk("hiddev%d", hid->minor);
    if ((hid->claimed & HID_CLAIMED_INPUT) && (hid->claimed & HID_CLAIMED_HIDDEV) &&
            (hid->claimed & HID_CLAIMED_HIDRAW))
        printk(",");
    if (hid->claimed & HID_CLAIMED_HIDRAW)
        printk("hidraw%d", ((struct hidraw*)hid->hidraw)->minor);
 
    c = "Device";
    for (i = 0; i < hid->maxcollection; i++) {
        if (hid->collection[i].type == HID_COLLECTION_APPLICATION &&
            (hid->collection[i].usage & HID_USAGE_PAGE) == HID_UP_GENDESK &&
            (hid->collection[i].usage & 0xffff) < ARRAY_SIZE(hid_types)) {
            c = hid_types[hid->collection[i].usage & 0xffff];
            break;
        }
    }
 
    usb_make_path(interface_to_usbdev(intf), path, 63);
 
    printk(": USB HID v%x.%02x %s [%s] on %s/n",
        hid->version >> 8, hid->version & 0xff, c, hid->name, path);
 
    return 0;
}
这个函数看起来是不是让人心慌慌?其实这个函数的最后一部份就是打印出一个Debug信息,我们根本就不需要去看. hiddev_connect()和hidraw_connect()是一个选择编译的操作,也不可以不要去理会.然后,剩下的就没多少了.
 
3.1:usb_hid_configure()函数分析
先来看usb_hid_configure().顾名思义,该接口用来配置hid设备.怎么配置呢?还是深入到代码来分析,该函数有一点长,分段分析如下:
static struct hid_device *usb_hid_configure(struct usb_interface *intf)
{
    struct usb_host_interface *interface = intf->cur_altsetting;
    struct usb_device *dev = interface_to_usbdev (intf);
    struct hid_descriptor *hdesc;
    struct hid_device *hid;
    u32 quirks = 0;
    unsigned rsize = 0;
    char *rdesc;
    int n, len, insize = 0;
    struct usbhid_device *usbhid;
 
    quirks = usbhid_lookup_quirk(le16_to_cpu(dev->descriptor.idVendor),
            le16_to_cpu(dev->descriptor.idProduct));
 
    /* Many keyboards and mice don't like to be polled for reports,
     * so we will always set the HID_QUIRK_NOGET flag for them. */
     //如果是boot设备,跳出.不由此驱动处理
    if (interface->desc.bInterfaceSubClass == USB_INTERFACE_SUBCLASS_BOOT) {
        if (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_KEYBOARD ||
            interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE)
                quirks |= HID_QUIRK_NOGET;
    }
 
    //如果是要忽略的
    if (quirks & HID_QUIRK_IGNORE)
        return NULL;
 
    if ((quirks & HID_QUIRK_IGNORE_MOUSE) &&
        (interface->desc.bInterfaceProtocol == USB_INTERFACE_PROTOCOL_MOUSE))
            return NULL;
首先找到该接口需要修正的操作,也就是上面代码中的quirks值,如果没有修正操作,则quirks为0.另外,根据usb hid spec中的定义,subclass如果为1,则说明该设备是一个boot阶段使用的hid设备,然后Protocol Code为1和2时分别代表Keyboard和Mouse. 如果是boot阶段的Keyboard和Mouse是不会由这个驱动进行处理的.另外,quirks为HID_QUIRK_IGNORE表示忽略这个设备,为HID_QUIRK_IGNORE_MOUSE,表示,如果该设备是一个鼠标设备,则忽略.
 
    //get hid descriptors
    if (usb_get_extra_descriptor(interface, HID_DT_HID, &hdesc) &&
        (!interface->desc.bNumEndpoints ||
         usb_get_extra_descriptor(&interface->endpoint[0], HID_DT_HID, &hdesc))) {
        dbg_hid("class descriptor not present/n");
        return NULL;
    }
 
    //bNumDescriptors:支持的附属描述符数目
    for (n = 0; n < hdesc->bNumDescriptors; n++)
        if (hdesc->desc[n].bDescriptorType == HID_DT_REPORT)
            rsize = le16_to_cpu(hdesc->desc[n].wDescriptorLength);
    //如果Report_Descriptors长度不合法
    if (!rsize || rsize > HID_MAX_DESCRIPTOR_SIZE) {
        dbg_hid("weird size of report descriptor (%u)/n", rsize);
        return NULL;
    }
 
    if (!(rdesc = kmalloc(rsize, GFP_KERNEL))) {
        dbg_hid("couldn't allocate rdesc memory/n");
        return NULL;
    }
 
    //Set idle_time = 0
    hid_set_idle(dev, interface->desc.bInterfaceNumber, 0, 0);
    //Get Report_Descriptors
    if ((n = hid_get_class_descriptor(dev, interface->desc.bInterfaceNumber, HID_DT_REPORT, rdesc, rsize)) < 0) {
        dbg_hid("reading report descriptor failed/n");
        kfree(rdesc);
        return NULL;
    }
 
    //是否属于fixup?
    usbhid_fixup_report_descriptor(le16_to_cpu(dev->descriptor.idVendor),
            le16_to_cpu(dev->descriptor.idProduct), rdesc,
            rsize, rdesc_quirks_param);
 
    dbg_hid("report descriptor (size %u, read %d) = ", rsize, n);
    for (n = 0; n < rsize; n++)
        dbg_hid_line(" %02x", (unsigned char) rdesc[n]);
    dbg_hid_line("/n");
对于HID设备来说,在interface description之后会附加一个hid description, hid description中的最后部份包含有Report description或者Physical Descriptors的长度.
在上面的代码中,首先取得附加在interface description之后的hid description,然后,再从hid description中取得report description的长度.最后,取得report description的详细信息.
在这里,还会将idle时间设备为0,表示无限时,即,从上一次报表传输后,只有在报表发生改变时,才会传送此报表内容,否则,传送NAK.
这段代码的最后一部份是相关的fixup操作,不做详细分析.
 
    //pasrse the report_descriptor
    if (!(hid = hid_parse_report(rdesc, n))) {
        dbg_hid("parsing report descriptor failed/n");
        kfree(rdesc);
        return NULL;
    }
 
    kfree(rdesc);
    hid->quirks = quirks;
 
    if (!(usbhid = kzalloc(sizeof(struct usbhid_device), GFP_KERNEL)))
        goto fail_no_usbhid;
 
    hid->driver_data = usbhid;
    usbhid->hid = hid;
解析获得的report description,解析之后的信息,存放在hid_device->collection和hid_device->report_enum[ ]中,这个解析过程之后会做详细分析.然后,初始化一个usbhid_device结构,使usbhid_device->hid指向刚解析report description获得的hid_device.同样,hid_device->driver_data关联到usbhid_device.
 
    usbhid->bufsize = HID_MIN_BUFFER_SIZE;
    //计算各传输方向的最大buffer
    hid_find_max_report(hid, HID_INPUT_REPORT, &usbhid->bufsize);
    hid_find_max_report(hid, HID_OUTPUT_REPORT, &usbhid->bufsize);
    hid_find_max_report(hid, HID_FEATURE_REPORT, &usbhid->bufsize);
 
    if (usbhid->bufsize > HID_MAX_BUFFER_SIZE)
        usbhid->bufsize = HID_MAX_BUFFER_SIZE;
    //in方向的传输最大值
    hid_find_max_report(hid, HID_INPUT_REPORT, &insize);
 
    if (insize > HID_MAX_BUFFER_SIZE)
        insize = HID_MAX_BUFFER_SIZE;
 
    if (hid_alloc_buffers(dev, hid)) {
        hid_free_buffers(dev, hid);
        goto fail;
    }
计算传输数据的最大缓存区,并以这个大小为了hid设备的urb传输分配空间.另外,这里有一个最小值限制即代码中所看到的HID_MIN_BUFFER_SIZE,为64, 即一个高速设备的一个端点一次传输的数据量.在这里定义最小值为64是为了照顾低速/全速/高速三种类型的端点传输数据量.
然后,调用hid_alloc_buffers()为hid的urb传输初始化传输缓冲区.
另外,需要注意的是,insize为INPUT方向的最大数据传输量.
 
    // 初始化usbhid->urbin和usbhid->usbout
    for (n = 0; n < interface->desc.bNumEndpoints; n++) {
 
        struct usb_endpoint_descriptor *endpoint;
        int pipe;
        int interval;
 
        endpoint = &interface->endpoint[n].desc;
        //不是中断传输 退出
        if ((endpoint->bmAttributes & 3) != 3)      /* Not an interrupt endpoint */
            continue;
 
        interval = endpoint->bInterval;
 
        /* Change the polling interval of mice. */
        //修正鼠标的双击时间
        if (hid->collection->usage == HID_GD_MOUSE && hid_mousepoll_interval > 0)
            interval = hid_mousepoll_interval;
 
        if (usb_endpoint_dir_in(endpoint)) {
            if (usbhid->urbin)
玩转USB HID系列:Linux下使用C语言和libusb开发USB HID 玩转USB HID系列:Linux下使用C语言和libusb开发USB HID实验环境开发步骤安装libusb试试好用否我们来与HID设备通讯!探索、学习与注意:1.&ctx是个什么鬼2.print_devs(devs)->print_devs(&devs);3.操作端口这类程序加上sudo !4.libusb_interrupt_transfer这个函数是write和rea... 阅读详情

相关推荐

首个基于LBS方案的3D传感器,VoxelSensor解析

该传感器将结合OQmented的Lissajous模式的MEMS扫描镜,与每帧逐行工作的光栅扫描相比,Lissajous轨迹扫描速度更快,并且能耗很低,它可以更快地捕获完整的场景和快速移动,并且需要更少的数据处理,可满足AR/VR对于低延迟、高效率的需求。简单来讲,Switching Pixels的区别是专注于识别激光事件,性能比普通事件相机更好,但也可以使用事件相机的算法。在具体应用中,它需要在X和Y方向发射一个或多个脉冲光束,以在X和Y轴上定位,而Z轴,也就是深度,则是通过计算光返回的时间来测量。

qingtingwang的博客 576

Windows 和 Linux 系统下,如何区分相同PID VID 的USB-HID设备

2. 根据USB规范的定义,所有的USB设备都用供应商ID(VID)和产品识别码(PID),主机通过不同的VID和PID来区别不同的设备,VID和PID都是两个字节长,其中,VID由供应商向USB执行论坛申请,每个供应商的VID是唯一的,PID由供应商自行决定,理论上来说,不同的产品、相同产品的不同型号、相同型号的不同设计的产品最好采用不同的PID,以便区别相同厂家的不同设备。综上,根据上述,将多个USB-HID设备插入,相同的PID 和 VID 设备,如何区分,并找到目标设备。

gd6321374的专栏 8000

TC3XX - MCAL知识点(十一):WDG EB-tresos配置说明与代码浅析

在AUTOSAR中,看门狗栈实现了三种监控机制:实时监控:用于周期软件的定时监控期限监督:针对非周期软件逻辑监督:用于监督执行顺序的正确性看门狗(WDG)驱动实现AUTOSAR(4.2.2)中规定的看门狗SWS,提供初始化、改变操作模式和设置触发条件(超时)等服务,供看门狗硬件(WDT)用户使用。WDG驱动的用户包括:模式管理模块(用于初始化),WdgM通过Wdglf设置触发条件和驱动运行模式设置。WdgM实施上述监督机制。AUTOSAR扩展触发概念,以支持窗口看门狗模式。

梦想技术家 3603

Linux设备驱动之HID驱动---非常全面而且深刻

本文系本站原创,欢迎转载! 转载请注明出处:http://ericxiao.cublog.cn/ ------------------------------------------ 一:前言 继前面分析过UHCI和HUB驱动之后,接下来以HID设备驱动为例来做一个具体的USB设备驱动分析的例子.HID是Human Interface Devices的缩写.翻译成中文即为人机...

FLY_THINK2012的博客 1861

基于YOLO26深度学习框架的智能停车场管理系统的算法研究实现方法

本文研究了基于YOLO26深度学习框架的智能停车场管理系统实现方法。系统采用CSPDarknet53作为主干网络,结合特征金字塔和注意力机制提升检测性能。通过数据预处理、模型训练和停车分析三个主要模块,实现了车位占用检测、车辆类型识别等功能。实验结果表明,该方法在检测精度和速度方面表现优异,为智能停车场管理提供了有效的技术解决方案。

一键难忘的博客 571

(第100天)Oracle GoldenGate 常见故障处理指南

OGG 运维的核心在于细致的配置管理和主动的问题排查。熟悉常见错误、掌握关键操作流程、并善用日志和调试工具,是保障数据同步任务稳定运行的关键。希望这份指南能帮助您更高效地解决 OGG 同步中遇到的问题。祝您运维顺利!

LuciferLiu_DBA 301

(转)Linux设备驱动之HID驱动 源码分析

//Linux设备驱动之HID驱动 源码分析 http://blog.chinaunix.net/uid-20543183-id-1930836.html HID是Human Interface Devices的缩写.翻译成中文即为人机交互设备.这里的人机交互设备是一个宏观上面的概念,任何设备,只要符合HID spec,都可以称之为HID设备.常见的HID设备有鼠标键盘,游戏操纵杆等等.在...

weixin_33898233的博客 703

matlab之作图设置游标的精度

matlab画出的figure游标的精度往往是固定的,这就对我们的调试和查看数据造成了不便。 那么怎么修改游标的精度呢? 首先,获取游标dcm_obj = datacursormode(gcf);然后对游标的精度进行设置:set(dcm_obj,'UpdateFcn',@NewCallback)这个NewCallback是什么东西呢?这是一个可以返回游标精度的函数,具体内容如下:function

落日之城 8959

hid设备驱动linux,Linux HID 驱动开发(2) USB HID Report 描述及usage 概念

在USB的枚举后,即交互完 设备描述符(device descriptor),配置描述符(configure descriptor),接口描述符(interface descriptor)和在input首先是取值范围。虽然占两个字节,只是低位字节有效Logical Minimumlogical_maximumphysical_minimumphysical_maximum是report size,...

weixin_29384861的博客 1091

把 SAP Query 用到极致:ABAP Query 的核心概念、配置流程、权限与运输治理全解析

摘要:SAP Query 是一款面向业务用户的自助报表工具,可解决标准报表无法满足业务需求的问题。其核心由四大组件构成:Queries(报表定义)、InfoSets(数据视图)、User Groups(权限分组)和Translation(多语言支持)。通过SQ03、SQ02、SQ01事务码分步创建用户组、数据视图和查询,业务用户无需ABAP知识即可生成Basic List(明细)、Statistics(统计)和Ranked List(排名)三种报表形式。典型案例展示了如何为采购部门设计包含明细追溯和Top1

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 182

AI驱动的材料科学:从分子设计到性能预测

AI驱动的材料科学:从分子设计到性能预测 关键词: 材料科学 人工智能 分子设计 性能预测 机器学习 深度学习 摘要:

AI天才研究院 2187

linux hid 设备驱动,linux hid驱动

linux hid驱动以usbhid为例,drivers/hid/usbhid/hid-core.c 。新设备添加后,usbhid_probe函数执行。usbhid_probe|-hid_allocate_device|-hid_add_device|-device_adddevice_add之后,hid_bus_type的probe函数hid_device_probe执行。(经过match后,u...

weixin_42304279的博客 1325

在 MATLAB/Simulink 中构建一个固定地面站天线指向控制系统的完整仿真模型

在卫星通信系统中,地面站天线负责与在轨卫星建立高增益、高稳定性的通信链路。对于固定地面站(如卫星测控站、深空站、VSAT主站),天线必须具备高精度指向能力,以确保波束始终对准卫星。然而,卫星(尤其是低轨卫星)在天空中快速移动,地面站天线必须实时跟踪其轨迹。此外,环境扰动(风载、热变形、机械误差)也会导致指向偏差。因此,设计一个高精度、高鲁棒性的天线指向控制系统(Antenna Pointing Control System)至关重要。本文将手把手教你如何在中构建一个固定地面站天线指向控制系统。

amy_mhd的博客 133
上一篇: [S3C6410]智器Q5 covia android 1101内核启动分析
下一篇: 浅析linux下鼠标驱动的实现
willand1981
博客等级 码龄18年 136粉丝 31原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值