vivi解析大全

Bootloader(Vivi)源代码分析

                                                    ----基于S3C2410处理器 
 
 
 
 
 

            作者:forkchen

            Email:chenjun@bupt-bcnl.com

            QQ:56155986

            欢迎广大嵌入式之友多多交流 
 

目 录 
 
 
 
 
 
 
 
 
 
 

1.1 Vivi简介

   Vivi 是韩国mizi 公司开发的bootloader, 适用于ARM9处理器。 Vivi有两种工作模式:启动加载模式和下载模式。启动加载模式可以在一段时间后(这个时间可更改)自行启动linux内核,这时vivi的默认模式。在下载模式下,vivi为用户提供一个命令行接口,通过接口可以使用vivi提供的一些命令,见下表: 
 

    命令                         功能
   Load把二进制文件载入FlashRAM
   Part操作MTD分区信息。显示、增加、删除、复位、保存MTD分区
   Param设置参数
   Boot启动系统
   Flash管理Flash,如删除Flash的数据
1.2 vivi的配置与编译 1.2.1 建立交叉开发环境

1、在宿主机上安装标准Linux 操作系统:Redhat 9.0 ( 主机系统为win2000,用虚拟机vmware安装的Redhat 9.0,内核版本为2.4.18)

2、宿主机上安装交叉编译器。

我这边的2410开发板提供的光盘上已附交叉编译器工具:arm-linux-gcc-2.95.3(源码为cross-2.95.3.tar.bz2)。

    先以root 用户的身份登陆到linux 下。

    进入/usr/local 目录,创建名为arm的目录:

cd /usr/local

mkdir arm

将光盘提供的cross-2.95.3.tar.bz2解压到/usr/local/arm目录:

tar jxvf cross-2.95.3.tar.bz2 –C /usr/local/arm 
 

    然后修改修改PATH 变量:为了可以方便使用arm-linux-gcc编译器系统, arm-linux工具链目录加入到环境变量PATH中:

    修改/etc/profile文件,添加pathmunge /usr/local/arm/2.95.3/bin即可。

     # Path manipulation

     if [ `id -u` = 0 ]; then

             pathmunge /sbin

             pathmunge /usr/sbin

             pathmunge /usr/local/sbin

             pathmunge  /usr/local/arm/2.95.3/bin

     fi

                                                                                    

     pathmunge /usr/X11R6/bin after

    设置环境变量后,最好是重启或注销一下,这样设置的环境变量才能生效。

1.2.2 配置和编译vivi

    如果vivi的源代码已根据开发板作了相应改动,则需要对源代码进行配置和编译,以生成烧入flashvivi 二进制映象文件。

    由于vivi要用到kernel的一些头文件,所以需要kernel的源代码,所以先要把linuxkernel准备好。将vivikernel都解到相应目录下(例如我将光盘提供的vivi源代码解压到/home/chenjun目录下,光盘提供的Linux kernel源码kernel-h2410eb.041024.tar.gz也解压到/home/chenjun目录下,解压后的文件名为kerne-h2410eb)。

    然后需修改/vivi/Makefile里的一些变量设置:

  • LINUX_INCLUDE_DIR = /kernel/include/

LINUX_INCLUDE_DIR kernel/include的对应目录,我的是/home/chen/kerne-h2410eb /include/

因此修改为:

    LINUX_INCLUDE_DIR = /home/chenjun/ kerne-h2410eb/include/ 
 

  • CROSS_COMPILE   = /usr/local/arm/2.95.3/bin/arm-linux-

(CROSS_COMPILE arm-linux安装的相应目录,我的是/usr/local/arm/2.95.3/bin/arm-linux-)

因此修改为:

CROSS_COMPILE   = /usr/local/arm/2.95.3/bin/arm-linux- 
 

  • ARM_GCC_LIBS     = /usr/local/arm/2.95.3/lib/gcc-lib/arm-linux/2.95.3

(需根据你arm-linux的安装目录修改,我的是/usr/local/arm/2.95.3/lib/gcc-lib/arm-linux/2.95.3 
 

    进入/vivi目录执行make distclean。(目的是确保编译的有效性,在编译之前将vivi里所有的“*.o”和“*.o.flag”文件删掉)

    进入/vivi目录里,输入“make menuconfig”,开始选择配置。可以Load一个写好的配置文件也可以自己修改试试。注意Exit时一定要选“Yes”保存配置。

    再输入“make”正式开始编译,一会儿就完了。如果不报错,在/vivi里面就有你自己的“vivi”了。这个就是后面要烧写到flash中的bootloader

1.3 vivi代码分析

   vivi的代码包括archinitlibdriversinclude等几个目录,共200多条文件。

   Vivi主要包括下面几个目录:

      arch:此目录包括了所有vivi支持的目标板的子目录,例如s3c2410目录。

      drivers:其中包括了引导内核需要的设备的驱动程序(MTD和串口)。MTD目录下分mapnandnor三个目录。

      init:这个目录只有main.cversion.c两个文件。和普通的C程序一样,vivi将从main函数开始执行。

      lib:一些平台公共的接口代码,比如time.c里的udelay()mdelay()

      include:头文件的公共目录,其中的s3c2410.h定义了这块处理器的一些寄存器。Platform/smdk2410.h定义了与开发板相关的资源配置参数,我们往往只需要修改这个文件就可以配置目标板的参数,如波特率、引导参数、物理内存映射等。 
 
 
 

1.4 vivi的运行

   vivi的运行也可以分为两个阶段:

1.4.1 vivi的第一阶段

   完成含依赖于CPU的体系结构硬件初始化的代码,包括禁止中断、初始化串口、复制自身到RAM等。相关代码集中在head.S(/vivi/arch/s3c2410目录下)

Head.S 
 

#include "config.h"

#include "linkage.h"

#include "machine.h" 
 

@ Start of executable code  
 

ENTRY(_start)

ENTRY(ResetEntryPoint) 
 

@

@ Exception vector table (physical address = 0x00000000)  ;异常向量表物理地址

@ 
 

@ 0x00: Reset           ;复位

      b Reset 
 

@ 0x04: Undefined instruction exception     ;未定义的指令异常

UndefEntryPoint:

      b HandleUndef 
 

@ 0x08: Software interrupt exception      ;软件中断异常

SWIEntryPoint:

      b HandleSWI 
 

@ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort) ;内存操作异常

PrefetchAbortEnteryPoint:

      b HandlePrefetchAbort 
 

@ 0x10: Data Access Memory Abort      ;数据异常

DataAbortEntryPoint:

      b HandleDataAbort 
 

@ 0x14: Not used          ;未使用

NotUsedEntryPoint:

      b HandleNotUsed 
 

@ 0x18: IRQ(Interrupt Request) exception     ;慢速中断处理

IRQEntryPoint:

      b HandleIRQ 
 

@ 0x1c: FIQ(Fast Interrupt Request) exception    ;快速中断处理

FIQEntryPoint:

      b HandleFIQ 
 

@

@ VIVI magics

@ 
 

@ 0x20: magic number so we can verify that we only put

      .long   0

@ 0x24:

      .long   0

@ 0x28: where this vivi was linked, so we can put it in memory in the right place

      .long   _start

@ 0x2C: this contains the platform, cpu and machine id

      .long   ARCHITECTURE_MAGIC

@ 0x30: vivi capabilities

      .long   0

#ifdef CONFIG_PM       vivi考虑不需要使用电源管理

@ 0x34:

      b SleepRamProc

#endif

#ifdef CONFIG_TEST

@ 0x38:

      b hmi

#endif 
 
 

@

@ Start VIVI head

@

Reset:

      @ disable watch dog timer      ;禁止看门狗计时器

   mov r1, #0x53000000       WTCON寄存器地址是

                                               0x53000000,清0

      mov r2, #0x0

      str r2, [r1] 
 

#ifdef CONFIG_S3C2410_MPORT3   ;不符合条件,跳到下面的关中断

/**** /vivi/include/autoconf.h#undef CONFIG_S3C2410_MPORT3******/

      mov r1, #0x56000000       GPACON寄存器地址是

                                                0x56000000

      mov r2, #0x00000005

      str r2, [r1, #0x70]        ;配置GPHCON寄存器

      mov r2, #0x00000001

      str r2, [r1, #0x78]        ;配置GPHUP寄存器

      mov r2, #0x00000001

      str r2, [r1, #0x74]        ;配置GPHDAT寄存器

#endif 
 

      @ disable all interrupts       ;禁止全部中断

      mov r1, #INT_CTL_BASE

      mov r2, #0xffffffff

      str r2, [r1, #oINTMSK]       ;掩码关闭所有中断

      ldr r2, =0x7ff

      str r2, [r1, #oINTSUBMSK]  
 

      @ initialise system clocks       ;初始化系统时钟

      mov r1, #CLK_CTL_BASE

      mvn r2, #0xff000000

      str r2, [r1, #oLOCKTIME] 
 

      @ldr r2, mpll_50mhz

      @str r2, [r1, #oMPLLCON]

#ifndef CONFIG_S3C2410_MPORT1     ;满足条件,向下执行

/**** /vivi/include/autoconf.h#undef CONFIG_S3C2410_MPORT1******/

      @ 1:2:4

      mov r1, #CLK_CTL_BASE

      mov r2, #0x3

      str r2, [r1, #oCLKDIVN] 
 

      mrc p15, 0, r1, c1, c0, 0  @ read ctrl register

      orr r1, r1, #0xc0000000  @ Asynchronous 

      mcr p15, 0, r1, c1, c0, 0  @ write ctrl register 
 

      @ now, CPU clock is 200 Mhz     CPU的频率是200MHz

      mov r1, #CLK_CTL_BASE

      ldr r2, mpll_200mhz

      str r2, [r1, #oMPLLCON]

#else

      @ 1:2:2

    mov r1, #CLK_CTL_BASE

    ldr r2, clock_clkdivn

    str r2, [r1, #oCLKDIVN] 
 

    mrc p15, 0, r1, c1, c0, 0       @ read ctrl register

    orr r1, r1, #0xc0000000     @ Asynchronous

    mcr p15, 0, r1, c1, c0, 0       @ write ctrl register 
 

    @ now, CPU clock is 100 Mhz     CPU的频率是100MHz

    mov r1, #CLK_CTL_BASE

    ldr r2, mpll_100mhz

    str r2, [r1, #oMPLLCON]

#endif

      bl memsetup         ;跳转到memsetup函数

/*****************************

Memsetup函数的实现:

ENTRY(memsetup)

      @ initialise the static memory  
 

      @ set memory control registers    ;设置内存控制寄存器的初值

      mov r1, #MEM_CTL_BASE

      adrl r2, mem_cfg_val

/*******************

@

@ Data Area

@

@ Memory configuration values

.align 4

            • mem_cfg_val:          ;定义好的13*4=52个字节初值

      .long vBWSCON   ;在/vivi/include/platform/smdk2410.h中赋值

/******   SDRAM32位变成16位,需要修改vBWSCON的值 ******/

      .long vBANKCON0

      .long vBANKCON1

      .long vBANKCON2

      .long vBANKCON3

/**********  网卡控制器vBANKCON3的值可能需要修改  **************/

      .long vBANKCON4

      .long vBANKCON5

      .long vBANKCON6

/****** SDRAM32位变成16位,可能需要修改vBANKCON6的值 ******/

      .long vBANKCON7

      .long vREFRESH

      .long vBANKSIZE

/****** SDRAM64MB变成32MB,需要修改vBANKSIZE的值 ******/

      .long vMRSRB6

      .long vMRSRB7

********************/

      add r3, r1, #52

1: ldr r4, [r2], #4

      str r4, [r1], #4

      cmp r1, r3

      bne 1b      ;循环操作,直到13个寄存器赋值完成 
 

      mov pc, lr

*******************************/

#ifdef CONFIG_PM    vivi考虑不需要使用电源管理

      @ Check if this is a wake-up from sleep 

      ldr r1, PMST_ADDR

      ldr r0, [r1]

      tst r0, #(PMST_SMR)

      bne WakeupStart    ;查看状态,判断是否需要跳转到WakeupStart

#endif 
 
 

#ifdef CONFIG_S3C2410_SMDK    SMDK开发板使用

      @ All LED on       ;点亮开发板上的LED

      mov r1, #GPIO_CTL_BASE

      add r1, r1, #oGPIO_F     LED使用GPIOF组的管脚

   ldr r2,=0x55aa    ;使能EINT0EINT1EINT2EINT3

                         ;另四个管脚配置成输出,屏蔽EINT4567

      str r2, [r1, #oGPIO_CON]

      mov r2, #0xff

      str r2, [r1, #oGPIO_UP]  disable the pull-up function

      mov r2, #0x00

      str r2, [r1, #oGPIO_DAT]

#endif 
 

#if 0

      @ SVC

      mrs r0, cpsr

      bic r0, r0, #0xdf

      orr r1, r0, #0xd3

      msr cpsr_all, r1

#endif 
 

      @ set GPIO for UART     ;设置串口

      mov r1, #GPIO_CTL_BASE

      add r1, r1, #oGPIO_H     ;设置GPIO_H组管脚为串口

      ldr r2, gpio_con_uart 

      str r2, [r1, #oGPIO_CON]

      ldr r2, gpio_up_uart

      str r2, [r1, #oGPIO_UP] 

/*************************

@ inital values for GPIO

gpio_con_uart:

      .long vGPHCON vGPHCON/vivi/include/platform/smdk2410.h中赋值

                                    #define vGPHCON   0x0016faaa

                                    GPIO_H配置为nCTS0nRTS0, RXD0,TXD0, RXD1

      •    TXD1nCTS1nRTS1, 
         

/****  三个串口都使能,可能需要修改#define vGPHCON 0x0016aaaa   ****/ 
 

gpio_up_uart:

      .long Vgphup  ;同上#define vGPHUP   0x000007ff

                                    The pull-up function is disabled.

************************/ 
 

      bl InitUART  ;跳转到InitUART串口初始化函数

/****************************************************

@ Initialize UART

@

@ r0 = number of UART port

InitUART:

      ldr r1, SerBase

/*******************

.align 4     ;缺省情况下在vivi中只初始化了UART0

SerBase:

#if defined(CONFIG_SERIAL_UART0)

      .long UART0_CTL_BASE  ;基地址在/vivi/include/s3c2410.h中定义

#elif defined(CONFIG_SERIAL_UART1)

      .long UART1_CTL_BASE

#elif defined(CONFIG_SERIAL_UART2)

      .long UART2_CTL_BASE

#else

#error not defined base address of serial

#endif

********************/

      mov r2, #0x0

      str r2, [r1, #oUFCON]

      str r2, [r1, #oUMCON]

      mov r2, #0x3

      str r2, [r1, #oULCON]

      ldr r2, =0x245

      str r2, [r1, #oUCON]

#define UART_BRD ((50000000 / (UART_BAUD_RATE * 16)) - 1)

      mov r2, #UART_BRD

      str r2, [r1, #oUBRDIV] 
 

      mov r3, #100

      mov r2, #0x0

1: sub r3, r3, #0x1

      tst r2, r3

      bne 1b 
 

#if 0

      mov r2, #'U'

      str r2, [r1, #oUTXHL] 
 

1: ldr r3, [r1, #oUTRSTAT]

      and r3, r3, #UTRSTAT_TX_EMPTY

      tst r3, #UTRSTAT_TX_EMPTY

      bne 1b  
 

      mov r2, #'0'

      str r2, [r1, #oUTXHL] 
 

1: ldr r3, [r1, #oUTRSTAT]

      and r3, r3, #UTRSTAT_TX_EMPTY

      tst r3, #UTRSTAT_TX_EMPTY

      bne 1b 

#endif 
 

      mov pc, lr

****************************************************/ 
 

#ifdef CONFIG_DEBUG_LL    ;打印调试信息,缺省未定义

      @ Print current Program Counter

      ldr r1, SerBase

      mov r0, #'/r'

      bl PrintChar

      mov r0, #'/n'

      bl PrintChar

      mov r0, #'@'

      bl PrintChar

      mov r0, pc

      bl PrintHexWord

#endif 
 
 

#ifdef CONFIG_BOOTUP_MEMTEST

      @ simple memory test to find some DRAM flaults.

      bl memtest

#endif 
 

#ifdef CONFIG_S3C2410_NAND_BOOT  ;从NAND Flash启动

      bl copy_myself     ;跳转到copy_myself函数

/**********************************************

@

@ copy_myself: copy vivi to ram

@

copy_myself:

      mov r10, lr 
 

      @ reset NAND

      mov r1, #NAND_CTL_BASE

      ldr r2, =0xf830  @ initial value

      str r2, [r1, #oNFCONF]

      ldr r2, [r1, #oNFCONF]

      bic r2, r2, #0x800  @ enable chip

      str r2, [r1, #oNFCONF]

      mov r2, #0xff  @ RESET command

      strb r2, [r1, #oNFCMD]

      mov r3, #0   @ wait

1: add r3, r3, #0x1

      cmp r3, #0xa

      blt 1b

2: ldr r2, [r1, #oNFSTAT] @ wait ready

      tst r2, #0x1

      beq 2b

      ldr r2, [r1, #oNFCONF]

      orr r2, r2, #0x800  @ disable chip

      str r2, [r1, #oNFCONF] 
 

      @ get read to call C functions (for nand_read())

      ldr sp, DW_STACK_START @ setup stack pointer

      mov fp, #0   @ no previous frame, so fp=0 
 

      @ copy vivi to RAM

      ldr r0, =VIVI_RAM_BASE

/*********/vivi/linux/platform/smdk2410.h中定义

#define VIVI_RAM_BASE  (DRAM_BASE + DRAM_SIZE - VIVI_RAM_SIZE)

***************************************/

      mov     r1, #0x0

      mov r2, #0x20000  0x20000-128k字节

      bl nand_read_ll  nand_read_ll/vivi/arch/s3c2410/nand_read.c中定义

                                    r0,r1,r2分别为函数的三个参数

                                    ;从NANDFlash0地址拷贝128kSDRAM指定处

      tst r0, #0x0

      beq ok_nand_read

#ifdef CONFIG_DEBUG_LL

bad_nand_read:

      ldr r0, STR_FAIL

      ldr r1, SerBase

      bl PrintWord

1: b 1b  @ infinite loop

#endif 
 

ok_nand_read:

#ifdef CONFIG_DEBUG_LL

      ldr r0, STR_OK

      ldr r1, SerBase

      bl PrintWord

#endif 
 

      @ verify

      mov r0, #0

      ldr r1, =0x33f00000

      mov r2, #0x400 @ 4 bytes * 1024 = 4K-bytes

go_next:

      ldr r3, [r0], #4

      ldr r4, [r1], #4

      teq r3, r4

      bne notmatch

      subs r2, r2, #4

      beq done_nand_read 

      bne go_next

notmatch:

#ifdef CONFIG_DEBUG_LL

      sub r0, r0, #4

      ldr r1, SerBase

      bl PrintHexWord

      ldr r0, STR_FAIL

      ldr r1, SerBase

      bl PrintWord

#endif

1: b 1b

done_nand_read: 
 

#ifdef CONFIG_DEBUG_LL

      ldr r0, STR_OK

      ldr r1, SerBase

      bl PrintWord

#endif 
 

      mov pc, r10    vivi拷贝到SDRAM完成,函数返回

*********************************/

      @ jump to ram

      ldr r1, =on_the_ram

      add pc, r1, #0

      nop

      nop

1: b 1b  @ infinite loop 
 

on_the_ram:

#endif 
 

#ifdef CONFIG_DEBUG_LL

      ldr r1, SerBase

      ldr r0, STR_STACK

      bl PrintWord

      ldr r0, DW_STACK_START

      bl PrintHexWord

#endif 
 

      @ get read to call C functions

      ldr sp, DW_STACK_START @ setup stack pointer

      mov fp, #0   @ no previous frame, so fp=0

      mov a2, #0   @ set argv to NULL  
 

      bl main   @ call main  
 

      mov pc, #FLASH_BASE  @ otherwise, reboot 
 

@

@ End VIVI head

@ 
 

1.4.2 vivi的第二阶段

    vivi的第二阶段是从main()函数开始,同一般的C语言程序一样,该函数在/init/main.c文件中,总共可以分为8个步骤。

  1. 函数开始,通过putstr(vivi_banner)打印出vivi的版本。Vivi_banner/init/version.c文件中定义
  2. 对开发板进行初始化(board_init函数),board_init是与开发板紧密相关的,这个函数在/arch/s3c2410/smdk.c文件中。开发板初始化主要完成两个功能,时钟初始化(init_time())和通用IO口设置(set_gpios())。
  •    void set_gpios(void)

  •    {

                        GPACON  = vGPACON;

                        GPBCON  = vGPBCON;

                        GPBUP   = vGPBUP;

                        GPCCON  = vGPCCON;

                        GPCUP   = vGPCUP;

                        GPDCON  = vGPDCON;

                        GPDUP   = vGPDUP;

                        GPECON  = vGPECON;

                        GPEUP   = vGPEUP;

                        GPFCON  = vGPFCON;

                        GPFUP   = vGPFUP;

                        GPGCON  = vGPGCON;

                        GPGUP   = vGPGUP;

                        GPHCON  = vGPHCON;

                        GPHUP   = vGPHUP;

                        EXTINT0 = vEXTINT0;

                        EXTINT1 = vEXTINT1;

                        EXTINT2 = vEXTINT2;

  •    }

  • 其中,GPIO口在smdk2410.h/vivi/include/platform/目录下)文件中定义。

  1. 内存映射初始化和内存管理单元的初始化工作:

   mem_map_init();

   mmu_init();

   这两个函数都在/arch/s3c2410/mmu.c文件中。

   void mem_map_init(void)

   {

   #ifdef CONFIG_S3C2410_NAND_BOOT

         mem_map_nand_boot();

   #else

         mem_map_nor();

   #endif

         cache_clean_invalidate();

         tlb_invalidate();

   } 
 

   如果配置vivi时使用了NAND作为启动设备,则执行mem_map_nand_boot(),否则执行mem_map_nor()。这里要注意的是,如果使用NOR启动,则必须先把vivi代码复制到RAM中。这个过程是由copy_vivi_to_ram()函数来完成的。代码如下: 
     static void copy_vivi_to_ram(void)

   {

         putstr_hex("Evacuating 1MB of Flash to DRAM at 0x", VIVI_RAM_BASE);

         memcpy((void *)VIVI_RAM_BASE, (void *)VIVI_ROM_BASE, VIVI_RAM_SIZE);

   }

   VIVI_RAM_BASEVIVI_ROM_BASEVIVI_RAM_SIZE这些值都可以在smdk2410.h中查到,并且这些值必须根据自己开发板的RAM实际大小修改。这也是在移植vivi的过程中需要注意的一个地方。

   mmu_init()函数中执行了arm920_setup函数。这段代码是用汇编语言实现的,针对arm920t核的处理器。 
 

  1. 初始化堆栈,heap_init()。(定义在/vivi/lib/heap.c文件中)
  •    int heap_init(void)

  •    {

                        return mmalloc_init((unsigned char *)(HEAP_BASE), HEAP_SIZE); 

    • }

  1. 初始化mtd设备,mtd_dev_init()

   int mtd_init(void)

   {

                  int ret; 
 

  •    #ifdef CONFIG_MTD_CFI

                        ret = cfi_init();

  •    #endif

  •    #ifdef CONFIG_MTD_SMC

                        ret = smc_init();

  •    #endif

  •    #ifdef CONFIG_S3C2410_AMD_BOOT

                        ret = amd_init();

  •    #endif 
     

                  if (ret) {

                        mymtd = NULL;

                        return ret;

                              }

                  return 0;

  •    }

这几个函数可以在/drivers/mtd/maps/s3c2410_flash.c里找到。

  1. 初始化私有数据,init_priv_data()。(定义在/vivi/lib/priv_data/rw.c文件中)
  2. 初始化内置命令,init_builtin_cmds()

通过add_command函数,加载vivi内置的几个命令。

  1. 启动boot_or_vivi()

   启动成功后,将通过vivi_shell()启动一个shell(如果配置了CONFIG_SERIAL_TERM),此时vivi的任务完成。 
 
 
 
 
 
 
 
 
 

1.5 启动代码执行流程图

 
 

                  

       (1)head.s代码执行流程            (2)main.c代码执行流程 
 
 

1.6 vivi的配置文件

 
 

   Vivi的初始配置文件位置:/vivi/arch/def-configs/smkd2410, 通过make menuconfig 修改后的配置保存在这个文件中,我们也可以载入一个自己的配置文件来进行编译。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值