linux设备树

一、设备树基础

1、什么是设备树

​ 描述设备树的文件叫做DTS文件,DTS文件采用树形结构描述板级设备(开发板上的设备信息)。

​ 在以前的linux内核中,ARM架构没有采用设备树,在内核源码中有大量的arch/arm/mach-xxxarch/arm/plat-xxx文件夹,这些文件夹里的文件就是对应平台下的板级信息。

​ 如果不使用设备树来描述板级设备信息,则这些信息的.h和.c文件就要被编译进linux内核,这会造成内核有太多冗余。

2、DTS、DTB和DTC

​ 设备树源文件扩展名为.dts,编译后的二进制文件扩展名为.dtb,编译工具为DTC工具。编译.dts文件时,进入内核源码根目录,使用make dtbs来进行编译。

​ 设置linux内核编译时编译哪个SOC的设备树文件,修改arch/arm/boot/dts/Makefile的内容,找到自己使用的SOC,然后添加自己开发板的.dtb文件。在编译时对应的.dts文件就会被编译成二进制的.dtb文件。

二、设备树语法

1、设备树头文件

​ 设备树支持头文件,头文件扩展名为xxx.dtsixxx.hxxx.dts

​ 一般.dtsi文件用于描述SOC的内部外设信息,比如CPU架构、主频、外设寄存器地址范围(如UART、I2C等)。一般开发板使用哪颗SOC,就对调用对应的.dtsi文件。

2、设备节点

​ 设备树是采用树形结构来 描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息。设备树模板:

/ {  //根节点
	aliases {  //一级子节点aliases
		can0 = &flexcan1;
	};

	cpus {	//一级子节点cpus
		#address-cells = <1>;
		#size-cells = <0>;

		cpu0: cpu@0 {  //二级子节点
			compatible = "arm,cortex-a7";
			device_type = "cpu";
			reg = <0>;
		};
	};

	intc: interrupt-controller@00a01000 {  //一级子节点interrupt-controller
		compatible = "arm,cortex-a7-gic";
		#interrupt-cells = <3>;
		interrupt-controller;
		reg = <0x00a01000 0x1000>,
		      <0x00a02000 0x100>;
	};
};

​ 第1行 “/” 是根节点,每个设备树文件只有一个根节点,包含的.dtsi里面也有一个根节点,这些根节点的内容会合并成一个根节点,如果合并的节点都对同一个属性赋值,则新的值会覆盖旧的值

​ 第2、6、17行是一级子节点的名字,设备树中节点命名格式为:node-name@unit-address ,其中“node-name”是节点名字,第17行节点名字前可以有一个标签 lable: ,使用 &lable 可以访问节点,向节点内添加属性信息。“@unit-address” 一般表示设备的地址或寄存器首地址。如果节点没有可以不要。

​ 第10行是二级子节点。

3、标准属性

​ 节点内都是属性,不同设备需要的属性不同,用户也可以自定义属性,有一些属性是标准属性,linux下的很多外设驱动都会使用这些标准属性。

  • compatible

​ compatible属性叫做“兼容性”属性,它的值是一个字符串列表,作用是将设备和驱动绑定起来,compatible格式如下:

compatible = "manufacturer,model";

​ 其中 manufacturer 表示厂商,modle 一般是模块对应的驱动名字。比如imx6ull-alientek-emmc.dts中的sound节点是音频设备节点,sound节点的compatible属性如下:

compatible = "fsl,imx6ul-evk-wm8960","fsl,imx-audio-wm8960";

​ 属性值有两个,其中==“fsl”表示厂商是飞思卡尔,“imx6ul-evk-wm8960”==表示驱动模块名字。sound首先使用第一个兼容值在linux内核里面查找,如果没有找到匹配驱动文件,就使用第二个兼容值进行查找。

​ 一般驱动文件都会有一个OF匹配表,此OF匹配表保存着一些compatible值,如果compatible的属性值和OF匹配表中的任何一个值,就表示设备可以使用这个驱动。比如imx-wm8960.c中有如下内容:

static const struct of_device_id imx_wm8960_dt_ids[] = { 
    { .compatible = "fsl,imx-audio-wm8960", },  //匹配值
    { /* sentinel */ } 
}; 
MODULE_DEVICE_TABLE(of, imx_wm8960_dt_ids); 

tatic struct platform_driver imx_wm8960_driver = { 
    .driver = { 
        .name = "imx-wm8960", 
        .pm = &snd_soc_pm_ops, 
        .of_match_table = imx_wm8960_dt_ids,  //设置匹配表
    }, 
    .probe = imx_wm8960_probe, 
    .remove = imx_wm8960_remove, 
};

​ 第1行 imx_wm8960_dt_ids 就是驱动文件的匹配表,匹配值为 “fsl,imx-audio-wm8960” ,如果节点中 compatible 的值与之相匹配,则节点会使用此驱动文件。

​ 第11行设置驱动使用的匹配表。

  • model

​ model属性一般描述设备模块信息,比如模块名字:

model = "wm8960-audio";
  • status

    status属性用来描述设备状态,状态可选如下:

    描述
    “okay”表明设备是可操作的。
    “disable”表明设备当前是不可操作的,但在未来可变为可操作,比如热拔插以后。具体含义要看设备的绑定文件。
    “fail”表明表明设备不可操作,设备检测到了一系列的错误,而且设备也不大可能变得可操作。
    “fail-sss”含义和“fail”相同,后面的 sss部分是检测到的错误内容。
  • #address-cells 和 #size-cells

​ 这两个属性值都是无符号32位整形,作用在当前节点的子节点,用于描述子节点的地址信息。

​ #address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32位),#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32位)。reg属性一般都是和地址有关,格式如下:

reg = <address1 length1 address2 length2 address3 length3…………>;

​ 其中 address 表示起始地址,length 表示地址范围。#address-cells 和 #size-cells 使用示例如下:

aips3: aips-bus@02200000 { 
    compatible = "fsl,aips-bus", "simple-bus"; 
    #address-cells = <1>;   //地址所占字长为一个字
    #size-cells = <1>; 	    //地址范围所占字长为一个字
    dcp: dcp@02280000 { 
        compatible = "fsl,imx6sl-dcp"; 
        reg = <0x02280000 0x4000>; //0x02280000地址占一个字,0x4000地址范围占一个字
    };
  • reg

​ reg属性一般描述某个外设寄存器地址范围信息,比如 uart1 寄存器组的起始地址为 0x02020000,地址范围为 0x4000 则 uart1 节点中 reg = <0x02280000 0x4000>; 重点是获取寄存器首地址。

  • ranges

​ ranges属性可以为或者为 <child-bus-address,parent-bus-address,length> ,ranges 是一个地址映射表,组成部分说明如下:

child-bus-address子总线地址空间的物理地址,由父节点的 #address-cells 确定此物理地址所占用的字长。

parent-bus-address父总线地址空间的物理地址,由父节点的 #address-cells 确定此物理地址所占用的字长。

length子地址空间的长度,由父节点的 #size-cells确定此地址长度所占用的字长。

​ 如果ranges属性值为空,说明子地址空间和父地址空间完全相同,空值定义:ranges;

​ ranges属性值不为空示例:

soc { 
    compatible = "simple-bus"; 
    #address-cells = <1>;
    #size-cells = <1>; 
    ranges = <0x0 0xe0000000 0x00100000>;
    
    serial { 
        device_type = "serial"; 
        compatible = "ns16550"; 
        reg = <0x4600 0x100>; 
        clock-frequency = <0>; 
        interrupts = <0xA 0x8>; 
        interrupt-parent = <&ipic>; 
    }; 
};

​ 第5行定义了 ranges 属性,子地址空间为0x0,父地址空间为0xe0000000,子地址空间范围为1024KB(0x00100000)。

​ 第6行,serial 是串口设备节点,reg 属性定义了设备寄存器起始地址为0x4600,经过地址映射,起始地址变为0xe0004600。

  • name

​ name 属性用于记录节点名字,name 属性已被弃用,老的设备树文件可能会有。

  • device_type

​ device_type 用于描述设备的 FCode,但是设备树没有 FCode,所以此属性被弃用。

4、根节点的 compatible 属性

​ 根节点的 compatible 描述了所使用的设备,如 imx6ull-alientek-emmc.dts 中根节点的 compatible:

/ {
  	  model = "Freescale i.MX6 ULL 14x14 EVK Board";
      compatible = "fsl,imx6ull-14x14-evk", "fsl,imx6ull";
      ......
};

​ compatible 第一个值是指使用了“imx6ull-14x14-evk”这个设备,第二个值描述了设备所使用的 SOC 是“imx6ull”。linux会根据根节点的 compatible 属性查看是否支持此设备。

  • 在没有使用设备树以前的设备匹配方法

​ U-Boot 会向 linux 内核传递一个 machine id 的值,看 linux 是否支持。linux 内核使用 MACHINE_STARTMACHINE_END 来定义一个 machine_desc 结构体来描述这个设备,,比如在 arch/arm/mach-imx/mach-mx35_3ds.c中,将 MACHINE_START 和 MACHINE_END 之间的内容进行展开,会得到:

static const struct machine_desc __mach_desc_MX35_3DS \
    __used \
    __attribute__((__section__(".arch.info.init"))) = { 
    .nr = MACH_TYPE_MX35_3DS, 
    .name = "Freescale MX35PDK",
    /* Maintainer: Freescale Semiconductor, Inc */ 
    .atag_offset = 0x100, 
    .map_io = mx35_map_io, 
    .init_early = imx35_init_early, 
    .init_irq = mx35_init_irq, 
    .init_time = mx35pdk_timer_init, 
    .init_machine = mx35_3ds_init, 
    .reserve = mx35_3ds_reserve, 
    .restart = mxc_restart, 
}

​ 其中第4行的 “MACH_TYPE_MX35_3DS” 就是 “Freescale MX35PDK” 这个板子的 machine id ,MACH_TYPE_MX35_3DS 定义在 include/generated/mach-types.h 中,此文件定义了大量的 machine id。

  • 使用设备树后的设备匹配方法

​ 使用设备树以后不再使用 MACHINE_START ,而是使用 DT_MACHINE_START。DT_MACHINE_START 内的 .nr = ~0;因此不再使用 machine id 来查找设备。

​ 在 arch/arm/mach-imx/mach-imx6ul.c 有如下内容:

static const char *imx6ul_dt_compat[] __initconst = {  //兼容属性数组
    "fsl,imx6ul", 
    "fsl,imx6ull", 
    NULL, 
};

DT_MACHINE_START(IMX6UL, "Freescale i.MX6 Ultralite (Device Tree)") 
    .map_io = imx6ul_map_io, 
    .init_irq = imx6ul_init_irq, 
    .init_machine = imx6ul_init_machine, 
    .init_late = imx6ul_init_late, 
    .dt_compat = imx6ul_dt_compat,  
MACHINE_END

​ 只要开发板根节点的 compatible 属性和 imx6ul_dt_compat 里面的值相匹配,表示 linux 支持该设备。linux 查找匹配设备过程如下:
在这里插入图片描述

5、特殊节点 aliases 和 chosen

  • aliases

​ aliases 节点的主要功能是定义节点别名,目的是为了方便访问节点,但一般使用标签来访问节点。aliases 格式如下:

aliases { 
    can0 = &flexcan1; //&后的一定是节点名字,不能是标签
    can1 = &flexcan2;
    ......
};
  • chosen

​ chosen 节点主要是为了 U-Boot 向 linux 内核传递数据,重点是 bootargs 参数,chosen 节点内容如下:

chosen { 
    stdout-path = &uart1; 
};

​ 可以看出 chosen 仅仅设置了标准输出使用串口1,但是进入根文件系统 /proc/device-tree/chosen 目录里面,会发现多了一个 bootargs 属性,且属性值为在 U-Boot 中设置的 bootargs 的值。

​ U-Boot 就是通过 chosen 节点把 bootargs 传递给 linux 内核,U-Boot 源码中有一个 fdt_chosen 函数,这个函数就是查找或创建 chosen 节点,并把 bootargs 的值写进 chosen 节点里面。具体的调用过程如下:
在这里插入图片描述

6、向节点追加或修改内容

​ 向节点添加子节点,按照节点模板添加即可,注意按照父节点的 #address-cells 和 #size-cells 来确定子节点 reg 属性的值。

​ 向某一存在的节点添加内容,使用 &节点名字或标签{ }; 来添加属性,重复的属性新的值会覆盖旧的值。

7、设备树在系统中的体现

​ 在根文件系统的 /proc/device-tree 中保存着设备树信息。其中文件夹是子节点,文件是属性。在设备树中创建了新的节点以后,可以在根文件系统查看是否创建成功。

三、设备树常用OF操作函数

​ OF函数用于驱动程序获取设备树节点属性信息,这些函数原型定义在 /include/linux/of.h 文件。

1、查找节点的OF函数

​ 获取节点信息以前,首先要查找到节点。linux 内核使用 device_node 结构体来描述一个节点,结构体定义在 /include/linux/of.h 文件中。查找节点有5个函数:

	### 1)of_find_node_by_name 函数

​ of_find_node_by_name 函数通过节点名字查找指定的节点,函数原型如下:

struct device_node *of_find_node_by_name(struct device_node *from, const char *name);

from:从哪个节点开始查找,如果为 NULL 表示从根节点开始查找整个设备树。

name:要查找的节点名字

​ 返回值:找到的节点,如果 NULL 表示查找失败。

2)of_find_node_by_type函数

​ of_find_node_by_type函数通过节点的 device_type 属性查找指定节点,函数原型如下:

struct device_node *of_find_node_by_type(struct device_node *from, const char *type)

from:从哪个节点开始查找,如果为 NULL 表示从根节点开始查找整个设备树。

type:要查找的节点 device_type 的属性值。

​ 返回值:找到的节点,如果 NULL 表示查找失败。

3)of_find_compatible_node函数

​ of_find_compatible_node函数根据 device_type和 compatible这两个属性查找指定的节点,函数原型如下:

struct device_node *of_find_compatible_node(struct device_node *from, const char *type, const char *compatible)

from:从哪个节点开始查找,如果为 NULL 表示从根节点开始查找整个设备树。

type:要查找的节点 device_type 的属性值,可以为 NULL,表示忽略掉 device_type 的值。

compatible:要查找的节点 compatible 的属性值。

​ 返回值:找到的节点,如果 NULL 表示查找失败。

4)of_find_matching_node_and_match函数

​ of_find_matching_node_and_match 函数通过 of_device_id 匹配表来查找指定的节点,of_device_id 结构体在 /include/linux/mod_devicetable.h 中定义,函数原型如下:

struct device_node *of_find_matching_node_and_match(struct device_node *from, const struct of_device_id *matches, const struct of_device_id **match)

from:开始查找的节点,如果为 NULL 表示从根节点开始查找整个设备树。

matchesof_device_id 匹配表,也就是在此匹配表里面查找节点。

match: 找到的匹配的 of_device_id。

​ 返回值:找到的节点,如果 NULL 表示查找失败。

5)of_find_node_by_path函数

​ of_find_node_by_path 函数通过路径来查找指定的节点,函数原型如下:(内联函数inline

inline struct device_node *of_find_node_by_path(const char *path)

path:带有全路径的节点名,可以使用节点的别名,比如 “/backlight” 就是 backlight 这个节点的全路径。

​ 返回值:找到的节点,如果 NULL 表示查找失败。

2、查找父/子节点的OF函数

​ Linux 内核提供了几个查找节点对应的父节点或子节点的 OF 函数。

1)of_get_parent函数

​ of_get_parent 函数用于获取指定节点的父节点 (如果有父节点的话 ),函数原型如下:

struct device_node *of_get_parent(const struct device_node *node)

node:要查找的父节点的子节点

​ 返回值:找到的父节点

3)of_get_next_child函数

​ of_get_next_child 函数查找某个子节点的下一个子节点,函数原型如下:

struct device_node *of_get_next_child(const struct device_node *node, struct device_node *prev)

node:父节点。

prev前一个子节点,也就是从哪一个子节点开始查找下一个子节点。可以设置为 NULL,表示从第一个子节点开始。

​ 返回值:找到的下一个子节点。

3、提取属性值的OF函数

​ linux 内核中使用结构体 property 表示属性,结构体定义在 /include/linux/of.h 中:

struct property { 
    char *name; /* 属性名字 */ 
    int length; /* 属性长度 */ 
    void *value; /* 属性值 */ 
    struct property *next; /* 下一个属性 */ 
    unsigned long _flags; 
    unsigned int unique_id; 
    struct bin_attribute attr; 
};

1)of_find_property函数

​ of_find_property 函数用于查找指定的属性,函数原型如下:

property *of_find_property(const struct device_node *np, const char *name, int *lenp)

np:设备节点。

name: 属性名字。

lenp:属性值的字节数,NULL 为不指定字节数。

​ 返回值:找到的属性。

2)of_property_count_elems_of_size函数

​ of_property_count_elems_of_size 函数用于获取属性中元素的数量,函数原型如下:

int of_property_count_elems_of_size(const struct device_node *np, const char *propname,int elem_size)

np:设备节点。

propname:需要统计元素数量的属性名字。

elem_size:元素数据大小,一般是 sizeof(u32)

​ 返回值:得到的属性元素数量。

3)of_property_read_u32_index函数

​ of_property_read_u32_index 函数用于从属性中获取指定序号的 u32 类型数据值,函数原型如下:

int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value)

np:设备节点。

propname:需要获取元素的属性名字。

index:要读取的值的序号(0、1、2…)。

​ 返回值:0 读取成功,负值读取失败 ,-EINVAL表示属性不存在, -ENODATA表示没有要读取的数据,
-EOVERFLOW表示属性值列表太小。

4)of_property_read_xx_array函数 (其中xx为u8、u16、u32、u64)

​ 这四个函数分别是读取属性中的 u8、u16、u32、u64 类型的数据数组,比如可以一次性读出 reg 属性的所有数据,函数原型如下:

int of_property_read_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz) 
    
int of_property_read_u16_array(const struct device_node *np, const char *propname, u16 *out_values, size_t sz) 
    
int of_property_read_u32_array(const struct device_node *np, const char *propname, u32 *out_values, size_t sz) 
    
int of_property_read_u64_array(const struct device_node *np, const char *propname, u64 *out_values, size_t sz) 

np:设备节点。

propname:需要获取元素的属性名字。

out_value:读取到的数组值。

​ 返回值:0 读取成功,负值读取失败 ,-EINVAL表示属性不存在, -ENODATA表示没有要读取的数据,
-EOVERFLOW表示属性值列表太小。

5)of_property_read_xx函数(其中xx为u8、u16、u32、u64)

​ 有些属性只有一个整形值,这些函数用来读取只有一个整形值的属性,函数原型如下:

int of_property_read_u8(const struct device_node *np, const char *propname, u8 *out_value) 
    
int of_property_read_u16(const struct device_node *np, const char *propname, u16 *out_value) 
    
int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value) 
    
int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value)

np:设备节点。

propname:需要获取元素的属性名字。

out_value:读取到的数据值。

​ 返回值:0 读取成功,负值读取失败 ,-EINVAL表示属性不存在, -ENODATA表示没有要读取的数据,
-EOVERFLOW表示属性值列表太小。

6)of_property_read_string函数

​ of_property_read_string 函数用于读取属性中字符串值,函数原型如下:

int of_property_read_string(struct device_node *np, const char *propname, const char **out_string)

np:设备节点。

propname:需要获取元素的属性名字。

out_string:读取到的数据值。

​ 返回值:0 读取成功,负值读取失败。

7)of_n_addr_cells函数

​ of_n_addr_cells 函数用于获取 #address-cells 属性值,函数原型如下:

int of_n_addr_cells(struct device_node *np)

np:设备节点。

​ 返回值:获取到的 #address-cells 属性值。

8)of_n_size_cells函数

​ of_n_size_cells 函数用于获取 #size-cells 属性值,函数原型如下:

int of_n_size_cells(struct device_node *np)

np:设备节点。

​ 返回值:获取到的 #size-cells 属性值。

4、其他常用的OF函数

1)of_device_is_compatible函数

​ of_device_is_compatible 函数用于查看节点的 compatible 属性是否有包含 compat 指定的字符串,也就是检查设备节点的兼容性,函数原型如下:

int of_device_is_compatible(const struct device_node *device, const char *compat)

device:设备节点。

compat:要查看的字符串,比如 “fsl,imx6ull”

​ 返回值:0 节点的 compatible 属性中包含 compat 指定的字符串;其他值,节点的 compatible 属性中不包含 compat指定的字符串。

2)of_get_address函数

​ of_get_address 函数用于获取地址相关属性,主要是 “ reg” 或者 “assigned-addresses” 属性值,函数属性如下:

const __be32 *of_get_address(struct device_node *dev, int index, u64 *size, unsigned int *flags)

dev:设备节点。

index:要读取的地址标号。

size:地址长度。

flags:参数,比如 IORESOURCE_IO、 IORESOURCE_MEM

​ 返回值:读取到的地址数据首地址,为 NULL 的话表示读取失败。

3)of_translate_address函数

​ of_translate_address 函数负责将从设备树读取到的地址转换为物理地址,函数原型如下:

u64 of_translate_address(struct device_node *dev, const __be32 *in_addr)

dev:设备节点。

in_addr:要转换的地址。

​ 返回值: 得到的物理地址,如果为 OF_BAD_ADDR 的话表示转换失败。

4)of_address_to_resource函数

​ linux 使用 resource 结构体来描述一段内存空间,resource 结构体定义在 /include/linux/ioport.h 中:

struct resource { 
    resource_size_t start;  //起始地址
    resource_size_t end; 	//结束地址
    const char *name; 		//资源名字
    unsigned long flags; 	//资源标志位,一般表示资源类型,可选标志定义在include/linux/ioport.h中
    struct resource *parent, *sibling, *child; 
};

​ of_address_to_resource 函数用来将 reg 的属性值转换为 resource 结构体类型,函数原型如下:

int of_address_to_resource(struct device_node *dev, int index, struct resource *r)

dev:设备节点。

index:地址资源标号。

r:得到的 resource 类型的资源值。

​ 返回值:0,成功;负值,失败。

5)of_iomap函数

​ of_iomap 函数用于获取物理地址所对应的虚拟地址,可以替代 ioremap 函数,在使用设备树的驱动文件中大多使用of_iomap 函数。

​ of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是那一段,函数原型如下:

void __iomem *of_iomap(struct device_node *np, int index)

np:设备节点。

index:reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。

​ 返回值:经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。

reg 的属性值转换为 resource 结构体类型,函数原型如下:

int of_address_to_resource(struct device_node *dev, int index, struct resource *r)

dev:设备节点。

index:地址资源标号。

r:得到的 resource 类型的资源值。

​ 返回值:0,成功;负值,失败。

5)of_iomap函数

​ of_iomap 函数用于获取物理地址所对应的虚拟地址,可以替代 ioremap 函数,在使用设备树的驱动文件中大多使用of_iomap 函数。

​ of_iomap 函数本质上也是将 reg 属性中地址信息转换为虚拟地址,如果 reg 属性有多段的话,可以通过 index 参数指定要完成内存映射的是那一段,函数原型如下:

void __iomem *of_iomap(struct device_node *np, int index)

np:设备节点。

index:reg 属性中要完成内存映射的段,如果 reg 属性只有一段的话 index 就设置为 0。

​ 返回值:经过内存映射后的虚拟内存首地址,如果为 NULL 的话表示内存映射失败。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值