U-Boot与Linux内核的交互

 转载    http://blog.csdn.net/ce123/article/details/7338048

 

 

U-Boot与Linux内核的交互

说明:本文所使用的U-Boot的版本是1.1.6,平台是S3C2440。

目录

一、简介
1.1标记列表
二、设置标记存放的地址
2.1相关的结构体定义
2.2标记存放地址的设定
三、标记的设置
3.1设置标记ATAG_CORE
3.2设置内存标记ATAG_MEM
3.3设置命令行标记ATAG_CMDLINE
3.4设置ATAG_NONE

一、简介

U-Boot与Linux内核的交互是单向的,U-Boot将各类参数传递给讷河。由于他们不能同时运行,传递办法只能有一个个:U-Boot将参数放在某个约定的地方之后,在启动内核,内核启动后从这个地方获得参数。

1.1标记列表

除了约定好参数存放的地方外,还要规定参数的结构。Linux2.4.x以后的内核都以标记列表(tagged list)的形式来传递参数。标记就是一种数据结构;标记列表就是挨着存放的多个标记。标记列表以标记ATAG_CORE开始,以ATAGE_NONE结束。

标记的数据结构为tag,它是偶一个tag_header结构和一个联合体(union)组成。tag_header结构体表示标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记使用不同的联合体,比如表示内存=时使用tag_men32,表示命令行时使用tag_cmdline。其定定义在include/asm-arm/setup.c文件中。

  1. /*  
  2.  * The new way of passing information: a list of tagged entries  
  3.  */  
  4.   
  5. /* The list ends with an ATAG_NONE node. */  
  6. #define ATAG_NONE   0x00000000  
  7.   
  8. struct tag_header {  
  9.     u32 size;  
  10.     u32 tag;  
  11. };  
  12.   
  13. /* The list must start with an ATAG_CORE node */  
  14. #define ATAG_CORE   0x54410001  
  15.   
  16. struct tag_core {  
  17.     u32 flags;      /* bit 0 = read-only */  
  18.     u32 pagesize;  
  19.     u32 rootdev;  
  20. };  
  21.   
  22. /* it is allowed to have multiple ATAG_MEM nodes */  
  23. #define ATAG_MEM    0x54410002  
  24.   
  25. struct tag_mem32 {  
  26.     u32 size;  
  27.     u32 start;  /* physical start address */  
  28. };  
  29.   
  30. /* VGA text type displays */  
  31. #define ATAG_VIDEOTEXT  0x54410003  
  32.   
  33. struct tag_videotext {  
  34.     u8      x;  
  35.     u8      y;  
  36.     u16     video_page;  
  37.     u8      video_mode;  
  38.     u8      video_cols;  
  39.     u16     video_ega_bx;  
  40.     u8      video_lines;  
  41.     u8      video_isvga;  
  42.     u16     video_points;  
  43. };  
  44.   
  45. /* describes how the ramdisk will be used in kernel */  
  46. #define ATAG_RAMDISK    0x54410004  
  47.   
  48. struct tag_ramdisk {  
  49.     u32 flags;  /* bit 0 = load, bit 1 = prompt */  
  50.     u32 size;   /* decompressed ramdisk size in _kilo_ bytes */  
  51.     u32 start;  /* starting block of floppy-based RAM disk image */  
  52. };  
  53.   
  54. /* describes where the compressed ramdisk image lives (virtual address) */  
  55. /*  
  56.  * this one accidentally used virtual addresses - as such,  
  57.  * its depreciated.  
  58.  */  
  59. #define ATAG_INITRD 0x54410005  
  60.   
  61. /* describes where the compressed ramdisk image lives (physical address) */  
  62. #define ATAG_INITRD2    0x54420005  
  63.   
  64. struct tag_initrd {  
  65.     u32 start;  /* physical start address */  
  66.     u32 size;   /* size of compressed ramdisk image in bytes */  
  67. };  
  68.   
  69. /* board serial number. "64 bits should be enough for everybody" */  
  70. #define ATAG_SERIAL 0x54410006  
  71.   
  72. struct tag_serialnr {  
  73.     u32 low;  
  74.     u32 high;  
  75. };  
  76.   
  77. /* board revision */  
  78. #define ATAG_REVISION   0x54410007  
  79.   
  80. struct tag_revision {  
  81.     u32 rev;  
  82. };  
  83.   
  84. /* initial values for vesafb-type framebuffers. see struct screen_info  
  85.  * in include/linux/tty.h  
  86.  */  
  87. #define ATAG_VIDEOLFB   0x54410008  
  88.   
  89. struct tag_videolfb {  
  90.     u16     lfb_width;  
  91.     u16     lfb_height;  
  92.     u16     lfb_depth;  
  93.     u16     lfb_linelength;  
  94.     u32     lfb_base;  
  95.     u32     lfb_size;  
  96.     u8      red_size;  
  97.     u8      red_pos;  
  98.     u8      green_size;  
  99.     u8      green_pos;  
  100.     u8      blue_size;  
  101.     u8      blue_pos;  
  102.     u8      rsvd_size;  
  103.     u8      rsvd_pos;  
  104. };  
  105.   
  106. /* command line: \0 terminated string */  
  107. #define ATAG_CMDLINE    0x54410009  
  108.   
  109. struct tag_cmdline {  
  110.     char    cmdline[1]; /* this is the minimum size */  
  111. };  
  112.   
  113. /* acorn RiscPC specific information */  
  114. #define ATAG_ACORN  0x41000101  
  115.   
  116. struct tag_acorn {  
  117.     u32 memc_control_reg;  
  118.     u32 vram_pages;  
  119.     u8 sounddefault;  
  120.     u8 adfsdrives;  
  121. };  
  122.   
  123. /* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */  
  124. #define ATAG_MEMCLK 0x41000402  
  125.   
  126. struct tag_memclk {  
  127.     u32 fmemclk;  
  128. };  
  129.   
  130. struct tag {  
  131.     struct tag_header hdr;  
  132.     union {  
  133.         struct tag_core     core;  
  134.         struct tag_mem32    mem;  
  135.         struct tag_videotext    videotext;  
  136.         struct tag_ramdisk  ramdisk;  
  137.         struct tag_initrd   initrd;  
  138.         struct tag_serialnr serialnr;  
  139.         struct tag_revision revision;  
  140.         struct tag_videolfb videolfb;  
  141.         struct tag_cmdline  cmdline;  
  142.   
  143.         /*  
  144.          * Acorn specific  
  145.          */  
  146.         struct tag_acorn    acorn;  
  147.   
  148.         /*  
  149.          * DC21285 specific  
  150.          */  
  151.         struct tag_memclk   memclk;  
  152.     } u;  
  153. };  
  154.   
  155. #define tag_next(t)<span style="white-space:pre"> </span>((struct tag *)((u32 *)(t) + (t)->hdr.size))  
  156. #define tag_size(type)<span style="white-space:pre">  </span>((sizeof(struct tag_header) + sizeof(struct type)) >> 2) //???  

二、设置标记存放的地址

2.1相关的结构体定义

结构体bd中保存了标记存放的地址。bd结构体是gd结构体的一项,我们先看gd结构体,其定义在include/asm-arm/global_data.h文件中:

  1. typedef struct  global_data {  
  2.     bd_t        *bd;//开发板相关参数 ,结构体变量,参考u-boot.h   
  3.     unsigned long   flags;//指示标志,如设备已经初始化标志等  
  4.     unsigned long   baudrate;//串行口通讯速率  
  5.     unsigned long   have_console;  
  6.     /* serial_init() was called 如果执行了该函数,则设置为1 */  
  7.     unsigned long   reloc_off;    
  8.     /*   
  9.      *Relocation Offset 重定位偏移,就是实际定向的位置与编译连接时指定的位置之差,一般为0  
  10.         */  
  11.     unsigned long   env_addr;   /* 环境参数地址*/  
  12.     unsigned long   env_valid;  /* 环境参数CRC检验有效标志*/  
  13.     unsigned long   fb_base;    /*帧缓冲区基地址*/  
  14. #ifdef CONFIG_VFD  
  15.     unsigned char   vfd_type;   /* 显示类型*/  
  16. #endif  
  17. #if 0  
  18.     unsigned long   cpu_clk;    /*cpu时钟*/  
  19.     unsigned long   bus_clk;    //总线时钟  
  20.     unsigned long   ram_size;   /* RAM size */  
  21.     unsigned long   reset_status;   /* reset status register at boot */  
  22. #endif  
  23.     void        **jt;   /* jump table 跳转表,用来登记"函数调用地址"*/  
  24. } gd_t;  
接来下我们来看一下bd结构体,这个结构体定义在include/asm-arm/u-boot.h文件中:

  1. typedef struct bd_info {  
  2.     int         bi_baudrate;    /* 串口波特率*/  
  3.     unsigned long   bi_ip_addr; /*  IP 地址*/  
  4.     unsigned char   bi_enetaddr[6]; /* MAC地址*/  
  5.     struct environment_s           *bi_env;  
  6.     ulong           bi_arch_number; /*  板子的id*/  
  7.     ulong           bi_boot_params; /* 启动参数*/  
  8.     struct              /* RAM 配置*/  
  9.     {  
  10.     ulong start;  
  11.     ulong size;  
  12.     }bi_dram[CONFIG_NR_DRAM_BANKS];  
  13. #ifdef CONFIG_HAS_ETH1  
  14.     /* second onboard ethernet port */  
  15.     unsigned char   bi_enet1addr[6];  
  16. #endif  
  17. } bd_t;  

2.2标记存放地址的设定

在board/smdk2410/smdk2410.c的board_init 函数设置了bi_boot_params 参数:

  1. int board_init (void)  
  2. {  
  3.     S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();//获取时钟和电源配置寄存器的第一个寄存器的地址,寄存器的地上是连续的  
  4.     S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();//获取GPIO配置寄存器的第一个寄存器的地址  
  5.   
  6.     /* to reduce PLL lock time, adjust the LOCKTIME register */  
  7.     clk_power->LOCKTIME = 0xFFFFFF;  
  8.   
  9.     /* configure MPLL */  
  10.     clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);  
  11.   
  12.     /* some delay between MPLL and UPLL */  
  13.     delay (4000);  
  14.   
  15.     /* configure UPLL */  
  16.     clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);  
  17.   
  18.     /* some delay between MPLL and UPLL */  
  19.     delay (8000);  
  20.   
  21.     /* set up the I/O ports */  
  22.     gpio->GPACON = 0x007FFFFF;  
  23.     gpio->GPBCON = 0x00044555;  
  24.     gpio->GPBUP = 0x000007FF;  
  25.     gpio->GPCCON = 0xAAAAAAAA;  
  26.     gpio->GPCUP = 0x0000FFFF;  
  27.     gpio->GPDCON = 0xAAAAAAAA;  
  28.     gpio->GPDUP = 0x0000FFFF;  
  29.     gpio->GPECON = 0xAAAAAAAA;  
  30.     gpio->GPEUP = 0x0000FFFF;  
  31.     gpio->GPFCON = 0x000055AA;  
  32.     gpio->GPFUP = 0x000000FF;  
  33.     gpio->GPGCON = 0xFF95FFBA;  
  34.     gpio->GPGUP = 0x0000FFFF;  
  35.     gpio->GPHCON = 0x002AFAAA;  
  36.     gpio->GPHUP = 0x000007FF;  
  37.   
  38.     /* arch number of SMDK2410-Board */  
  39.     gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;  
  40.   
  41.     /* adress of boot parameters */  
  42.     gd->bd->bi_boot_params = 0x30000100;  
  43.   
  44.     icache_enable();  //调用cpu/arm920t/cpu.c中的函数  
  45.     dcache_enable();  
  46.   
  47.     return 0;  
  48. }  

三、标记的设置

U-Boot通过bootm命令引导Linux内核,bootm命令对吼调用do_bootm_linux函数来引导内核。在do_bootm_linux函数就设置了标记,该函数的定义在lib_arm/armlinux.c中:

  1. void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],  
  2.              ulong addr, ulong *len_ptr, int verify)  
  3. {  
  4.     ulong len = 0, checksum;  
  5.     ulong initrd_start, initrd_end;  
  6.     ulong data;  
  7.     void (*theKernel)(int zero, int arch, uint params);  
  8.     image_header_t *hdr = &header;  
  9.     bd_t *bd = gd->bd;  
  10.   
  11. #ifdef CONFIG_CMDLINE_TAG  
  12.     char *commandline = getenv ("bootargs");  
  13. #endif  
  14.   
  15.     theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);  
  16.     设置kernal加载地址  
  17.   
  18.     /*  
  19.      * Check if there is an initrd image  
  20.      */  
  21.     用户自定义了initrd之后需要加载进来,整个过程需要进行头部以及整个数据内部校,类似于内核的加载校验,这里省略了。  
  22. initial RAM disk  Linux初始 RAM磁盘(initrd)是在系统引导过程中挂载的一个临时根文件系统,用来支持两阶段的引导过程。initrd文件中包含了各种可执行程序和驱动程序,它们可以用来挂载实际的根文件系统,然后再将这个 initrd RAM 磁盘卸载,并释放内存。在很多嵌入式Linux 系统中,initrd 就是最终的根文件系统。  
  23.     if (argc >= 3) {  
  24.         SHOW_BOOT_PROGRESS (9);  
  25.   
  26.         addr = simple_strtoul (argv[2], NULL, 16);  
  27.   
  28.         printf ("## Loading Ramdisk Image at %08lx ...\n", addr);  
  29.   
  30.         /* Copy header so we can blank CRC field for re-calculation */  
  31. #ifdef CONFIG_HAS_DATAFLASH  
  32.         if (addr_dataflash (addr)) {  
  33.             read_dataflash (addr, sizeof (image_header_t),  
  34.                     (char *) &header);  
  35.         } else  
  36. #endif  
  37.             memcpy (&header, (char *) addr,  
  38.                 sizeof (image_header_t));  
  39.   
  40.         if (ntohl (hdr->ih_magic) != IH_MAGIC) {  
  41.             printf ("Bad Magic Number\n");  
  42.             SHOW_BOOT_PROGRESS (-10);  
  43.             do_reset (cmdtp, flag, argc, argv);  
  44.         }  
  45.   
  46.         data = (ulong) & header;  
  47.         len = sizeof (image_header_t);  
  48.   
  49.         checksum = ntohl (hdr->ih_hcrc);  
  50.         hdr->ih_hcrc = 0;  
  51.   
  52.         if (crc32 (0, (unsigned char *) data, len) != checksum) {  
  53.             printf ("Bad Header Checksum\n");  
  54.             SHOW_BOOT_PROGRESS (-11);  
  55.             do_reset (cmdtp, flag, argc, argv);  
  56.         }  
  57.   
  58.         SHOW_BOOT_PROGRESS (10);  
  59.   
  60.         print_image_hdr (hdr);  
  61.   
  62.         data = addr + sizeof (image_header_t);  
  63.         len = ntohl (hdr->ih_size);  
  64.   
  65. #ifdef CONFIG_HAS_DATAFLASH  
  66.         if (addr_dataflash (addr)) {  
  67.             read_dataflash (data, len, (char *) CFG_LOAD_ADDR);  
  68.             data = CFG_LOAD_ADDR;  
  69.         }  
  70. #endif  
  71.   
  72.         if (verify) {  
  73.             ulong csum = 0;  
  74.   
  75.             printf ("   Verifying Checksum ... ");  
  76.             csum = crc32 (0, (unsigned char *) data, len);  
  77.             if (csum != ntohl (hdr->ih_dcrc)) {  
  78.                 printf ("Bad Data CRC\n");  
  79.                 SHOW_BOOT_PROGRESS (-12);  
  80.                 do_reset (cmdtp, flag, argc, argv);  
  81.             }  
  82.             printf ("OK\n");  
  83.         }  
  84.   
  85.         SHOW_BOOT_PROGRESS (11);  
  86.   
  87.         if ((hdr->ih_os != IH_OS_LINUX) ||  
  88.             (hdr->ih_arch != IH_CPU_ARM) ||  
  89.             (hdr->ih_type != IH_TYPE_RAMDISK)) {  
  90.             printf ("No Linux ARM Ramdisk Image\n");  
  91.             SHOW_BOOT_PROGRESS (-13);  
  92.             do_reset (cmdtp, flag, argc, argv);  
  93.         }  
  94.   
  95. #if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)  
  96.         /*  
  97.          *we need to copy the ramdisk to SRAM to let Linux boot  
  98.          */  
  99.         memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);  
  100.         data = ntohl(hdr->ih_load);  
  101. #endif /* CONFIG_B2 || CONFIG_EVB4510 */  
  102.   
  103.         /*  
  104.          * Now check if we have a multifile image  
  105.          */  
  106.     } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {  
  107.         ulong tail = ntohl (len_ptr[0]) % 4;  
  108.         int i;  
  109.   
  110.         SHOW_BOOT_PROGRESS (13);  
  111.   
  112.         /* skip kernel length and terminator */  
  113.         data = (ulong) (&len_ptr[2]);  
  114.         /* skip any additional image length fields */  
  115.         for (i = 1; len_ptr[i]; ++i)  
  116.             data += 4;  
  117.         /* add kernel length, and align */  
  118.         data += ntohl (len_ptr[0]);  
  119.         if (tail) {  
  120.             data += 4 - tail;  
  121.         }  
  122.   
  123.         len = ntohl (len_ptr[1]);  
  124.   
  125.     } else {  
  126.         /*  
  127.          * no initrd image  
  128.          */  
  129.         SHOW_BOOT_PROGRESS (14);  
  130.   
  131.         len = data = 0;  
  132.     }  
  133.   
  134. #ifdef  DEBUG  
  135.     if (!data) {  
  136.         printf ("No initrd\n");  
  137.     }  
  138. #endif  
  139.   
  140.     if (data) {  
  141.         initrd_start = data;  
  142.         initrd_end = initrd_start + len;  
  143.     } else {  
  144.         initrd_start = 0;  
  145.         initrd_end = 0;  
  146.     }  
  147.   
  148.     SHOW_BOOT_PROGRESS (15);  
  149.   
  150.     debug ("## Transferring control to Linux (at address %08lx) ...\n",  
  151.            (ulong) theKernel);  
  152.   
  153. #if defined (CONFIG_SETUP_MEMORY_TAGS) || \  
  154.     defined (CONFIG_CMDLINE_TAG) || \  
  155.     defined (CONFIG_INITRD_TAG) || \  
  156.     defined (CONFIG_SERIAL_TAG) || \  
  157.     defined (CONFIG_REVISION_TAG) || \  
  158.     defined (CONFIG_LCD) || \  
  159.     defined (CONFIG_VFD)  
  160.     setup_start_tag (bd);设置各种tag,用于传递参数给Linux  
  161. #ifdef CONFIG_SERIAL_TAG  
  162.     setup_serial_tag (¶ms);  
  163. #endif  
  164. #ifdef CONFIG_REVISION_TAG  
  165.     setup_revision_tag (¶ms);  
  166. #endif  
  167. #ifdef CONFIG_SETUP_MEMORY_TAGS  
  168.     setup_memory_tags (bd);  
  169. #endif  
  170. #ifdef CONFIG_CMDLINE_TAG  
  171.     setup_commandline_tag (bd, commandline);  
  172. #endif  
  173. #ifdef CONFIG_INITRD_TAG  
  174.     if (initrd_start && initrd_end)  
  175.         setup_initrd_tag (bd, initrd_start, initrd_end);  
  176. #endif  
  177. #if defined (CONFIG_VFD) || defined (CONFIG_LCD)  
  178.     setup_videolfb_tag ((gd_t *) gd);  
  179. #endif  
  180.     setup_end_tag (bd);  
  181. #endif  
  182.   
  183.     /* we assume that the kernel is in place */  
  184.     printf ("\nStarting kernel ...\n\n");打印信息  
  185.   
  186. #ifdef CONFIG_USB_DEVICE  
  187.     {  
  188.         extern void udc_disconnect (void);  
  189.         udc_disconnect ();  
  190.     }  
  191. #endif  
  192.   
  193.     cleanup_before_linux ();启动之前先做一些清理工作cpu/arm920t/cpu.c  
  194.   
  195. 调用内核需要传递的参数如下:  
  196. R0:必须为0  
  197. R1:机器类型ID,本机为ARM(bd->bi_arch_number)  
  198. R2:启动参数列表在内存中的位置(bd->bi_boot_params)  
  199.     theKernel (0, bd->bi_arch_number, bd->bi_boot_params);  
  200. }  

3.1设置标记ATAG_CORE

标记列表以标记ATAG_CORE开始

  1. static void setup_start_tag (bd_t *bd)  
  2. {  
  3.     params = (struct tag *) bd->bi_boot_params;  
  4.   
  5.     params->hdr.tag = ATAG_CORE;  
  6.     params->hdr.size = tag_size (tag_core);  
  7.   
  8.     params->u.core.flags = 0;  
  9.     params->u.core.pagesize = 0;  
  10.     params->u.core.rootdev = 0;  
  11.   
  12.     params = tag_next (params);//指向当前标记的末尾  
  13. }  

3.2设置内存标记ATAG_MEM

在board/smdk2410/smdk2410.c的dram_init函数设置了bd的bi_dram结构体:

  1. int dram_init (void)  
  2. {  
  3.     gd->bd->bi_dram[0].start = PHYS_SDRAM_1;  
  4.     gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;  
  5.   
  6.     return 0;  
  7. }  

下面是这边内存标记的结构体:

  1. static void setup_memory_tags (bd_t *bd)  
  2. {  
  3.     int i;  
  4.   
  5.     for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {  
  6.         params->hdr.tag = ATAG_MEM;  
  7.         params->hdr.size = tag_size (tag_mem32);  
  8.   
  9.         params->u.mem.start = bd->bi_dram[i].start;  
  10.         params->u.mem.size = bd->bi_dram[i].size;  
  11.   
  12.         params = tag_next (params);  
  13.     }  
  14. }  

3.3设置命令行标记ATAG_CMDLINE

命令行就是一个字符串,用来控制内核的一些行为。比如“root=/dev/mtdblock2 init=/linuxrc console=ttySAC0 ”表示根文件系统在MTD2分区上系统启动后执行的第一个程序为/linuxrc,控制台是ttySAC0 。

  1. static void setup_commandline_tag (bd_t *bd, char *commandline)  
  2. {  
  3.     char *p;  
  4.   
  5.     if (!commandline)  
  6.         return;  
  7.   
  8.     /* eat leading white space */  
  9.     for (p = commandline; *p == ' '; p++);  
  10.   
  11.     /* skip non-existent command lines so the kernel will still  
  12.      * use its default command line.  
  13.      */  
  14.     if (*p == '\0')  
  15.         return;  
  16.   
  17.     params->hdr.tag = ATAG_CMDLINE;  
  18.     params->hdr.size =  
  19.         (sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;  
  20.   
  21.     strcpy (params->u.cmdline.cmdline, p);  
  22.   
  23.     params = tag_next (params);  
  24. }  

3.4设置ATAG_NONE

标记列表以标记ATAG_NONE介绍。

  1. static void setup_end_tag (bd_t *bd)  
  2. {  
  3.     params->hdr.tag = ATAG_NONE;  
  4.     params->hdr.size = 0;  
  5. }  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值