Android驱动入门系列(一)

最近开始接触Android底层的开发,这里面将记录开发一个驱动以及到测试的全过程。

 

首先,需要搭建Android的开发环境。我这里采用的是Ubuntu 11.04 64bit的系统,里面使用gcc-4.4,Android 4.0.4源码,硬件是S5PV210。

搭建环境这一步就不说了。

其次,编译好u-boot、kernel和ICS。

最后,开始确定要编写什么驱动。

 

由于我这个210板子上没有可控制的GPIO设备,所以这里硬件部分就不涉及了,直接编写一个驱动,实现读写接口。

 

在开始之前,我们先来看看由驱动到接口、应用是怎么一个流程。

从这个图中可以看出我们需要 实现HAL层,实现Framework层,以及编写对应的程序。

 

在这里,有必要提一下:在Android下有以下两种访问HAL的方式:

1、Android 的 app 直接通过service 调用.so格式的JNI :此方法比较简单高效,但是不正规。

2、经过Manager 调用 Service :此方法实现起来比较复杂,但更符合目前的 Android框架。在此方法中 Manager 进程和 Service(JAVA) 进程需要通过进程通信的方式实现通信。

 

下面正式开始:

 

一、HAL层驱动的实现

1、添加 ttt 驱动

首先,打开终端,进入到kernel源码目录下的drivers目录,如:

[plain]  view plain  copy
  1. brantyou@brantyou-ubuntu:~/workspace$ cd samsung_android_kernel_3.0/drivers/  
  2. brantyou@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers$ ls  
  3. accessibility  clk          gpio        Kconfig    misc             pcmcia     sfi        usb  
  4. acpi           clocksource  gpu         Kconfig~   mmc              platform   sh         uwb  
  5. amba           connector    hello       leds       modules.builtin  pnp        sn         vhost  
  6. ata            cpufreq      hid         lguest     modules.order    power      spi        video  
  7. atm            cpuidle      hwmon       macintosh  mtd              pps        ssb        virtio  
  8. auxdisplay     crypto       hwspinlock  Makefile   net              ps3        staging    vlynq  
  9. base           dca          i2c         Makefile~  nfc              ptp        switch     w1  
  10. bcma           dio          ide         mca        nubus            rapidio    target     watchdog  
  11. block          dma          idle        md         of               regulator  tc         xen  
  12. bluetooth      edac         ieee802154  media      oprofile         rtc        telephony  zorro  
  13. built-in.o     eisa         infiniband  memstick   parisc           s390       thermal  
  14. cdrom          firewire     input       message    parport          sbus       tty  
  15. char           firmware     isdn        mfd        pci              scsi       uio  
  16. brantyou@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers$   

切换成超级用户权限:

[plain]  view plain  copy
  1. brantyou@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers$ sudo -s  
  2. [sudo] password for brantyou:   
  3. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers#   

创建 ttt 驱动目录:

[plain]  view plain  copy
  1. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers# mkdir ttt  
  2. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers# ls  
  3. accessibility  clk          gpio        Kconfig    misc             pcmcia     sfi        uio  
  4. acpi           clocksource  gpu         Kconfig~   mmc              platform   sh         usb  
  5. amba           connector    hello       leds       modules.builtin  pnp        sn         uwb  
  6. ata            cpufreq      hid         lguest     modules.order    power      spi        vhost  
  7. atm            cpuidle      hwmon       macintosh  mtd              pps        ssb        video  
  8. auxdisplay     crypto       hwspinlock  Makefile   net              ps3        staging    virtio  
  9. base           dca          i2c         Makefile~  nfc              ptp        switch     vlynq  
  10. bcma           dio          ide         mca        nubus            rapidio    target     w1  
  11. block          dma          idle        md         of               regulator  tc         watchdog  
  12. bluetooth      edac         ieee802154  media      oprofile         rtc        telephony  xen  
  13. built-in.o     eisa         infiniband  memstick   parisc           s390       thermal    zorro  
  14. cdrom          firewire     input       message    parport          sbus       ttt  
  15. char           firmware     isdn        mfd        pci              scsi       tty  
  16. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers#   

进入到ttt目录,并创建 ttt.h 头文件:(这里使用gedit编辑器,大家也可以使用vi或者其他编辑器)

[plain]  view plain  copy
  1. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers# cd ttt  
  2. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers/ttt# gedit ttt.h  

然后输入 ttt.h 的内容:

[cpp]  view plain  copy
  1. /* 
  2.  * ttt device head file 
  3.  *  
  4.  * Copyright (C) 2013 brantyou Open Source Project 
  5.  * Copyright (C) 2013,2013 brantyou Inc. 
  6.  * 
  7.  * Author: brantyou <brantyou@qq.com> 
  8.  * 
  9.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  10.  * you may not use this file except in compliance with the License. 
  11.  * You may obtain a copy of the License at 
  12.  * 
  13.  *      http://www.apache.org/licenses/LICENSE-2.0 
  14.  * 
  15.  * Unless required by applicable law or agreed to in writing, software 
  16.  * distributed under the License is distributed on an "AS IS" BASIS, 
  17.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  18.  * See the License for the specific language governing permissions and 
  19.  * limitations under the License. 
  20.  */  
  21.   
  22.   
  23. #ifndef _TTT_ANDROID_H_  
  24. #define _TTT_ANDROID_H_  
  25.   
  26. #include <linux/cdev.h>  
  27. #include <linux/semaphore.h>  
  28.   
  29. #define TTT_DEVICE_NODE_NAME        "ttt"  
  30. #define TTT_DEVICE_FILE_NAME        "ttt"  
  31. #define TTT_DEVICE_PROC_NAME        "ttt"  
  32. #define TTT_DEVICE_CLASS_NAME       "ttt"  
  33.   
  34. struct ttt_android_dev{  
  35.     int val;  
  36.     struct semaphore sem;  
  37.     struct cdev dev;  
  38. };  
  39.   
  40. #endif  



添加 ttt.c 文件:

[plain]  view plain  copy
  1. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0/drivers/ttt# gedit ttt.c  

添加 ttt.c 文件的处理:

[cpp]  view plain  copy
  1. /* 
  2.  * ttt device c file 
  3.  *  
  4.  * Copyright (C) 2013 brantyou Open Source Project 
  5.  * Copyright (C) 2013,2013 brantyou Inc. 
  6.  * 
  7.  * Author: brantyou <brantyou@qq.com> 
  8.  * 
  9.  * Licensed under the Apache License, Version 2.0 (the "License"); 
  10.  * you may not use this file except in compliance with the License. 
  11.  * You may obtain a copy of the License at 
  12.  * 
  13.  *      http://www.apache.org/licenses/LICENSE-2.0 
  14.  * 
  15.  * Unless required by applicable law or agreed to in writing, software 
  16.  * distributed under the License is distributed on an "AS IS" BASIS, 
  17.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
  18.  * See the License for the specific language governing permissions and 
  19.  * limitations under the License. 
  20.  */  
  21.   
  22.   
  23. #include <linux/init.h>  
  24. #include <linux/module.h>  
  25. #include <linux/types.h>  
  26. #include <linux/fs.h>  
  27. #include <linux/proc_fs.h>  
  28. #include <linux/device.h>  
  29. #include <asm/uaccess.h>  
  30.   
  31. #include "ttt.h"  
  32. // * Master and slave devices number variables  
  33. static int ttt_major = 0;  
  34. static int ttt_minor = 0;  
  35.   
  36. // * device types and device variables  
  37. static struct class* ttt_class = NULL;  
  38. static struct ttt_android_dev* ttt_dev = NULL;  
  39.   
  40. // * traditional method of operation of the device file  
  41. static int ttt_open(struct inode* inode, struct file* flip);  
  42. static int ttt_release(struct inode* inode, struct file* flip);  
  43. static ssize_t ttt_read(struct file* flip, char __user* buf, size_t count, loff_t* f_pos);  
  44. static ssize_t ttt_write(struct file* flip, const char __user* buf, size_t count, loff_t* f_pos);  
  45.   
  46. // * the method of operation of the device file table  
  47. static struct file_operations ttt_fops = {  
  48.     .owner = THIS_MODULE,  
  49.     .open = ttt_open,  
  50.     .release = ttt_release,  
  51.     .read = ttt_read,  
  52.     .write = ttt_write,  
  53. };  
  54.   
  55.   
  56. // * access to set property methods  
  57. static ssize_t ttt_val_show(struct device* dev, struct device_attribute* attr, char* buf);  
  58. static ssize_t ttt_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);  
  59.   
  60. // * define the device properties  
  61. static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, ttt_val_show, ttt_val_store);  
  62.   
  63.   
  64. // * open the device methods  
  65. static int ttt_open(struct inode* inode, struct file* flip)  
  66. {  
  67.     struct ttt_android_dev* dev;  
  68.   
  69.     printk(KERN_ALERT"[ttt]: ttt_open().\n");  
  70.   
  71.     // save the device struct to the private area  
  72.     dev = container_of(inode->i_cdev, struct ttt_android_dev, dev);  
  73.     flip->private_data = dev;  
  74.   
  75.     return 0;  
  76. }  
  77.   
  78. // * release  
  79. static int ttt_release(struct inode* inode, struct file* filp)  
  80. {  
  81.     printk(KERN_ALERT"[ttt]: ttt_release().\n");  
  82.     return 0;  
  83. }  
  84.   
  85. // * read  
  86. static ssize_t ttt_read(struct file* filp, char __user* buf, size_t count, loff_t* f_pos)  
  87. {  
  88.     ssize_t err = 0;  
  89.     struct ttt_android_dev* dev = filp->private_data;  
  90.   
  91.     printk(KERN_ALERT"[ttt]: ttt_read().\n");  
  92.     // async access  
  93.     if(down_interruptible( &(dev->sem) )){  
  94.         return -ERESTARTSYS;  
  95.     }  
  96.   
  97.     if(count < sizeof(dev->val) ){  
  98.         goto out;  
  99.     }  
  100.   
  101.     //   
  102.     if(copy_to_user(buf, &(dev->val), sizeof(dev->val) )){  
  103.         err = -EFAULT;  
  104.         goto out;  
  105.     }  
  106.   
  107.     err = sizeof(dev->val);  
  108.   
  109. out:  
  110.     up(&(dev->sem));  
  111.   
  112.     return err;  
  113. }  
  114.   
  115. // * write  
  116. static ssize_t ttt_write(struct file* filp, const char __user* buf, size_t count, loff_t* f_pos)  
  117. {  
  118.     struct ttt_android_dev* dev = filp->private_data;  
  119.     ssize_t err = 0;  
  120.       
  121.     printk(KERN_ALERT"[ttt]: ttt_write().\n");  
  122.     // async access  
  123.     if(down_interruptible( &(dev->sem) )){  
  124.         return -ERESTARTSYS;  
  125.     }  
  126.       
  127.     if(count != sizeof(dev->val) ){  
  128.         goto out;  
  129.     }  
  130.       
  131.     // save the buffer value to device registers  
  132.     if( copy_from_user( &(dev->val), buf, count) ){  
  133.         err = -EFAULT;  
  134.         goto out;  
  135.     }  
  136.       
  137.     err = sizeof(dev->val);  
  138.   
  139. out:  
  140.     up(&(dev->sem));  
  141.     return err;  
  142. }  
  143.   
  144. // * read the registers value val to the buffer buf, inner  
  145. static ssize_t __ttt_get_val(struct ttt_android_dev* dev, char* buf)  
  146. {  
  147.     int val = 0;  
  148.       
  149.     // async access  
  150.     if(down_interruptible( &(dev->sem) )){  
  151.         return -ERESTARTSYS;  
  152.     }  
  153.       
  154.     val = dev->val;  
  155.     up( &(dev->sem) );  
  156.       
  157.     return snprintf(buf, PAGE_SIZE, "%d\n", val);  
  158. }  
  159.   
  160. // * write the buffer value buf to the device registers val, inner  
  161. static ssize_t __ttt_set_val(struct ttt_android_dev* dev, const char* buf, size_t count)  
  162. {  
  163.     int val = 0;  
  164.       
  165.     // translate the string to number  
  166.     val = simple_strtol(buf, NULL, 10);  
  167.       
  168.     // async access  
  169.     if(down_interruptible( &(dev->sem) )){  
  170.         return -ERESTARTSYS;  
  171.     }  
  172.       
  173.     dev->val = val;  
  174.     up( &(dev->sem));  
  175.       
  176.     return count;  
  177. }  
  178.   
  179. // * read the device properties val  
  180. static ssize_t ttt_val_show(struct device* dev, struct device_attribute* attr, char* buf)  
  181. {  
  182.     struct ttt_android_dev* hdev = (struct ttt_android_dev*)dev_get_drvdata(dev);  
  183.       
  184.     return __ttt_get_val(hdev, buf);  
  185. }  
  186.   
  187. // * write the device properties val  
  188. static ssize_t ttt_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)  
  189. {  
  190.     struct ttt_android_dev* hdev = (struct ttt_android_dev*)dev_get_drvdata(dev);  
  191.       
  192.     return __ttt_set_val(hdev, buf, count);  
  193. }  
  194.   
  195. // * read the device registers val, and save to the page buffer  
  196. static ssize_t ttt_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data)  
  197. {  
  198.     if(off > 0){  
  199.         *eof = 1;  
  200.         return 0;  
  201.     }  
  202.       
  203.     return __ttt_get_val(ttt_dev, page);  
  204. }  
  205.   
  206. // * save the buffer value buff to the device registers val  
  207. static ssize_t ttt_proc_write(struct file* filp, const char __user* buff, unsigned long len, void* data)  
  208. {  
  209.     int err = 0;  
  210.     char* page = NULL;  
  211.       
  212.     if(len > PAGE_SIZE){  
  213.         printk(KERN_ALERT"[ttt]: The buff is too large:%lu.\n", len);  
  214.         return -EFAULT;  
  215.     }  
  216.       
  217.     page = (char*)__get_free_page(GFP_KERNEL);  
  218.     if(!page){  
  219.         printk(KERN_ALERT"[ttt]: Failed to alloc page.\n");  
  220.         return -ENOMEM;  
  221.     }  
  222.       
  223.     // copy the user buffer value to kernel buffer  
  224.     if(copy_from_user(page, buff, len) ){  
  225.         printk(KERN_ALERT"[ttt]: Failed to copy buff from user.\n");  
  226.         err = -EFAULT;  
  227.         goto out;  
  228.     }  
  229.       
  230.     err = __ttt_set_val(ttt_dev, page, len);  
  231.       
  232. out:  
  233.     free_page( (unsigned long)page);  
  234.     return err;  
  235. }  
  236.   
  237. // * create /proc/ttt file  
  238. static void ttt_create_proc(void)  
  239. {  
  240.     struct proc_dir_entry* entry;  
  241.     entry = create_proc_entry(TTT_DEVICE_PROC_NAME, 0, NULL);  
  242.     if(entry){  
  243.         entry->owner = THIS_MODULE;  
  244.         entry->read_proc = ttt_proc_read;  
  245.         entry->write_proc = ttt_proc_write;  
  246.     }  
  247. }  
  248.   
  249. // * delete /proc/ttt file  
  250. static void ttt_remove_proc(void)  
  251. {  
  252.     remove_proc_entry(TTT_DEVICE_PROC_NAME, NULL);  
  253. }  
  254.   
  255. // * init device  
  256. static int __ttt_setup_dev(struct ttt_android_dev* dev)  
  257. {  
  258.     int err;  
  259.     dev_t devno = MKDEV(ttt_major, ttt_minor);  
  260.       
  261.     memset(dev, 0, sizeof(struct ttt_android_dev) );  
  262.       
  263.     cdev_init( &(dev->dev), &ttt_fops);  
  264.     dev->dev.owner = THIS_MODULE;  
  265.     dev->dev.ops = &ttt_fops;  
  266.       
  267.     // registe charater device  
  268.     err = cdev_add( &(dev->dev), devno, 1);  
  269.     if(err){  
  270.         return err;  
  271.     }  
  272.       
  273.     // init single and registers value val  
  274.     init_MUTEX(&(dev->sem));  
  275.     dev->val = 0;  
  276.       
  277.     return 0;  
  278. }  
  279.   
  280. // * load module  
  281. static int __init ttt_init(void)  
  282. {  
  283.     int err = -1;  
  284.     dev_t dev = 0;  
  285.     struct device* temp = NULL;  
  286.       
  287.     printk(KERN_ALERT"[ttt]: Initializing ttt device.\n");  
  288.       
  289.     // malloc master and slave device number  
  290.     err = alloc_chrdev_region( &dev, 0, 1, TTT_DEVICE_NODE_NAME);  
  291.     if(err < 0){  
  292.         printk(KERN_ALERT"[ttt]: Failed to alloc char dev region.\n");  
  293.         goto fail;  
  294.     }  
  295.       
  296.     ttt_major = MAJOR(dev);  
  297.     ttt_minor = MINOR(dev);  
  298.       
  299.     // alloc ttt device struct valiriable  
  300.     ttt_dev = kmalloc( sizeof(struct ttt_android_dev), GFP_KERNEL);  
  301.     if(!ttt_dev){  
  302.         err = -ENOMEM;  
  303.         printk(KERN_ALERT"[ttt]: Failed to alloc ttt_dev.\n");  
  304.         goto unregister;  
  305.     }  
  306.       
  307.     // init device  
  308.     err = __ttt_setup_dev(ttt_dev);  
  309.     if(err){  
  310.         printk(KERN_ALERT"[ttt]: Failed to setup dev:%d.\n", err);  
  311.         goto cleanup;  
  312.     }  
  313.       
  314.     // create device type directory ttt on /sys/class/  
  315.     ttt_class = class_create(THIS_MODULE, TTT_DEVICE_CLASS_NAME);  
  316.     if(IS_ERR(ttt_class)){  
  317.         err = PTR_ERR(ttt_class);  
  318.         printk(KERN_ALERT"[ttt]: Failed to create ttt class.\n");  
  319.         goto destroy_cdev;  
  320.     }  
  321.       
  322.     // create device file ttt on /dev/ and /sys/class/ttt  
  323.     temp = device_create(ttt_class, NULL, dev, "%s", TTT_DEVICE_FILE_NAME);  
  324.     if(IS_ERR(temp)){  
  325.         err = PTR_ERR(temp);  
  326.         printk(KERN_ALERT"Failed to create ttt device.\n");  
  327.         goto destroy_class;  
  328.     }  
  329.       
  330.     // create property file val on /sys/class/ttt/ttt  
  331.     err = device_create_file(temp, &dev_attr_val);  
  332.     if(err < 0){  
  333.         printk(KERN_ALERT"[ttt]: Failed to create attribute val.\n");  
  334.         goto destroy_device;  
  335.     }  
  336.       
  337.     dev_set_drvdata(temp, ttt_dev);  
  338.       
  339.     // create /proc/ttt file  
  340.     ttt_create_proc();  
  341.       
  342.     printk(KERN_ALERT"[ttt]: Successed to initialize ttt device.\n");  
  343.     return 0;  
  344.       
  345. destroy_device:  
  346.     device_destroy(ttt_class, dev);  
  347.   
  348. destroy_class:  
  349.     class_destroy(ttt_class);  
  350.   
  351. destroy_cdev:  
  352.     cdev_del(&ttt_dev->dev);  
  353.   
  354. cleanup:  
  355.     kfree(ttt_dev);  
  356.   
  357. unregister:  
  358.     unregister_chrdev_region(MKDEV(ttt_major, ttt_minor), 1);  
  359.   
  360. fail:  
  361.     return err;  
  362. }  
  363.   
  364. // * unload module  
  365. static void __exit ttt_exit(void)  
  366. {  
  367.     dev_t devno = MKDEV(ttt_major, ttt_minor);  
  368.       
  369.     printk(KERN_ALERT"[ttt]: Destroy ttt device.\n");  
  370.       
  371.     // delete /proc/ttt file  
  372.     ttt_remove_proc();  
  373.       
  374.     // destroy device type and device  
  375.     if(ttt_class){  
  376.         device_destroy(ttt_class, MKDEV(ttt_major, ttt_minor) );  
  377.         class_destroy(ttt_class);  
  378.     }  
  379.       
  380.     // delete character device and release device memory  
  381.     if(ttt_dev){  
  382.         cdev_del(&(ttt_dev->dev) );  
  383.         kfree(ttt_dev);  
  384.     }  
  385.       
  386.     // destroy device number  
  387.     unregister_chrdev_region(devno, 1);  
  388. }  
  389.   
  390. MODULE_LICENSE("GPL");  
  391. MODULE_DESCRIPTION("Android Test Device");  
  392.   
  393. module_init(ttt_init);  
  394. module_exit(ttt_exit);  



接下来则添加 Kconfig 配置文件:(make menuconfig时会用到)

[plain]  view plain  copy
  1. config TTT  
  2. tristate "ttt Android test Driver"  
  3. default n  
  4. help  
  5. It is a Android test driver.  

添加 ttt 的 Makefile 文件:

[plain]  view plain  copy
  1. obj-$(CONFIG_TTT) += ttt.o  


修改 drivers/Kconfig 文件,在menu "Device Drivers"和endmenu之间添加驱动模块配置选项:

[plain]  view plain  copy
  1. source "drivers/ttt/Kconfig"  

在 drivers/Makefile 文件末尾添加:

[plain]  view plain  copy
  1. obj-$(CONFIG_TTT)       += ttt/  

 

这样是为了方便定制内核,在make menuconfig的时候可以找到我们新加入的模块。

好了,驱动到此基本编写完了,下面开始选中新家的驱动编译一下内核:

回到内核源码目录下执行 make menuconfig:

[plain]  view plain  copy
  1. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0# ls  
  2. arch                       Documentation  ipc          Makefile         README          System.map  
  3. block                      drivers        Kbuild       mm               REPORTING-BUGS  tools  
  4. cesv210_android_defconfig  firmware       Kconfig      modules.builtin  samples         usr  
  5. COPYING                    fs             kernel       modules.order    scripts         virt  
  6. CREDITS                    include        lib          Module.symvers   security        vmlinux  
  7. crypto                     init           MAINTAINERS  net              sound           vmlinux.o  
  8. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0# make menuconfig  

将会出现这样一个定制内核模块的界面:


按上下键选择 Device Drivers 按 enter进入下面的界面:

最后面这个驱动模块就是我们刚才加进去的,按 y 键选择,然后选中Exit,按enter键回到下面的界面:

选中最好一个Save an Alternate Configuration File按enter,保存后,退出这个配置。

 

然后执行make编译命令:

[plain]  view plain  copy
  1. root@brantyou-ubuntu:~/workspace/samsung_android_kernel_3.0# make  

编译成功后,驱动会包含在zImage里面。

然后在arch/arm/boot/目录下看到生成的zImage Kernel文件,把这个文件烧写到板子。

[plain]  view plain  copy
  1. root@android:/ # ll /dev/ttt  
  2. crw------- root     root     249,   0 2013-03-21 13:59 ttt  

这样就说明ttt驱动已经成功加到内核了。

驱动编写完了,这章就先到这里了

 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值