Linux的GPIO子系统(gpiolib)的原理详解【每个GPIO控制器对应于一个gpio_chip结构体】【如何查看每个GPIO控制器的详细情况】【如何利用其操作具体的GPIO口】

引言

GPIO是高频使用的硬件资源,所以Linux专门搞了一套GPIO子系统来对其进行管理,目的就是方便大家使用片上或板上的GPIO资源。

虽然之前已经利用GPIO子系统来实现过LED的驱动和按键的中断驱动了,但是对GPIO子系统的原理和结构还是比较模糊的,所以在这篇博文中仔细学习一下GPIO子系统。
之前用GPIO子系统来实现LED的驱动和按键的中断驱动的博文链接如下:
https://blog.youkuaiyun.com/wenhao_ir/article/details/145119224
https://blog.youkuaiyun.com/wenhao_ir/article/details/145176361

重要概念:对各个GPIO口的操作,是通过GPIO控制器实现的

对各个 GPIO 口(GPIO 引脚)的操作,是通过 GPIO 控制器(GPIO Controller)实现的
在 Linux 内核中,GPIO 控制器由 GPIO 控制器驱动 管理,并通过 GPIO 子系统(gpiolib) 统一提供 API,使用户空间和其他内核驱动可以方便地访问 GPIO 口。

GPIO 控制器(GPIO Controller) 是一个管理多个 GPIO 引脚的硬件模块,通常集成在 SoC(如 IMX6ULL)中。它的主要作用是:

  • 通过 寄存器 控制 GPIO 口的 输入/输出 模式
  • 允许 使能/禁用 GPIO 口
  • 提供 中断控制(如果支持)
  • 管理 复用功能(Muxing)(部分引脚可能被复用为其他功能,如 I2C、UART、PWM)

在 Linux 中,GPIO 控制器通常会注册为一个 gpio_chip,并由 GPIO 子系统(gpiolib) 进行统一管理。对于 IMX6ULL,GPIO 控制器由 gpio-mxc 驱动管理。

GPIO子系统(gpiolib)的原理

问:gpiolib是不是就是我们所说的GPIO子系统?

是的,gpiolib 就是我们通常所说的 GPIO 子系统(GPIO Subsystem)


1. 什么是 gpiolib

gpiolib(GPIO Library)是 Linux 内核中的 GPIO 子系统,它提供了一套 统一的 GPIO 处理框架,用于管理不同 SoC 平台的 GPIO 控制器。

它的主要功能包括:

  • 管理 GPIO 控制器(gpiochip):支持多个 GPIO 控制器(比如 gpiochip0gpiochip1)。
  • 提供 /sys/class/gpio/ 接口(Legacy sysfs,较老的方法)。
  • 支持 gpiod_*() API(更现代的 libgpiod 接口)。
  • 支持 GPIO 消息通知(如 gpio_request_irq() 处理中断)
  • 抽象不同硬件的 GPIO 访问方式(比如基于 I2C、SPI、SoC 内部 GPIO 控制器)。

2. gpiolib 与 GPIO 子系统的关系

在 Linux 内核架构中:

  • GPIO 子系统 = gpiolib + 各个 GPIO 驱动
  • GPIO 控制器(gpiochip)是具体的硬件设备,而 gpiolib 负责提供管理框架。

可以简单理解为:

Linux 内核
└── GPIO 子系统(gpiolib)
    ├── GPIO 控制器驱动(如 `fsl-imx6ul-gpio`)
    ├── GPIO 用户接口(sysfs、libgpiod、device tree)
    ├── GPIO 虚拟总线(/sys/bus/gpio/)

3.gpio_chip 结构体的介绍(核心数据结构)

gpio_chip 结构体是 Linux 内核 GPIO 子系统(gpiolib) 用来表示 GPIO 控制器(GPIO Controller) 的数据结构,每个 GPIO 控制器都必须有一个对应的 gpio_chip 结构体。可以说它是GPIO 子系统(gpiolib)的核心数据结构。

struct gpio_chip {
    const char *label;          // 控制器名称
    struct device *parent;      // 父设备
    int base;                   // GPIO 编号基地址(-1 代表动态分配)
    u16 ngpio;                  // 控制的 GPIO 数量
    struct module *owner;       // 所属驱动模块
    int (*direction_input)(struct gpio_chip *chip, unsigned offset);
    int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value);
    int (*get)(struct gpio_chip *chip, unsigned offset);
    void (*set)(struct gpio_chip *chip, unsigned offset, int value);
};

作用

  • 代表 一个 GPIO 控制器,它可以管理多个 GPIO 引脚。
  • 定义了 GPIO 操作函数(如 set()get()direction_output())。
  • gpiolib 统一管理,并向用户空间提供 /sys/class/gpio/ 接口。

GPIO子系统的正常工作需要BSP的支持

GPIO子系统使得我们在屏蔽硬件底层操作的情况下,方便的实现对GPIO口的控制。由于这套系统存在,我们的系统启动后就能直接操作GPIO口。
底层的操作包含在BSP(Board Support Package,板级支持包) 中,所以gpio_chip 结构体通常由 BSP(Board Support Package,板级支持包) 提供。
所以GPIO子系统的实现是需要BSP支持的。

4. gpiolib 的工作方式

当 GPIO 控制器驱动(比如 fsl-imx6ul-gpio)加载时,它会:

  1. 注册 gpio_chip 结构体

    • gpio_chip 结构体通过 gpiochip_add_data()devm_gpiochip_add_data()gpiolib 注册 ,从而实现GPIO 控制器的注册。
    • gpio_chip 结构体注册完成后,gpiochip 设备(即GPIO控制器)会出现在 /sys/class/gpio/gpiochipX,同时也会出现在 /sys/bus/gpio/devices/。★★★
  2. 管理 GPIO 资源

    • 允许用户空间访问 GPIO(通过用户接口sysfslibgpiod实现)。
    • 提供 gpiod_get() / gpiod_direction_output() / gpiod_set_value() 等 API 供内核调用。
  3. 支持 GPIO 消息通知

    • 处理 GPIO 输入/输出方向配置。
    • 允许 GPIO 触发中断(IRQ)。

5. 结论

gpiolib 就是 GPIO 子系统,它提供了统一的 GPIO 处理框架。
GPIO 控制器驱动(如 fsl-imx6ul-gpio)通过 gpiochip_add_data() 接口向 gpiolib 注册,成为 gpiochipX 设备。
用户可以通过 sysfs(较老方式)或 libgpiod(新方式)访问 GPIO,gpiolib 负责调度。

所以,GPIO 子系统 = gpiolib,它是 Linux 内核管理 GPIO 资源的核心机制! 🚀

片外的GPIO资源如何管理?

GPIO 子系统(gpiolib)通常用于管理片上(SoC 内部)的 GPIO 资源,而不是直接管理片外(外部扩展)的 GPIO 资源。


1. 片上 GPIO(On-Chip GPIO)

片上的 GPIO 指的是 SoC 自带的 GPIO 控制器所管理的 GPIO 引脚,例如:

  • i.MX6ULL 的 gpio1 ~ gpio5(如 209c000.gpio)。
  • 这些 GPIO 控制器通常由 SoC 内部的寄存器控制,并通过 gpiolib 统一管理。
  • 在设备树(DTS)中,片上 GPIO 资源通常是这样定义的:
    gpio1: gpio@209c000 {
        compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
        reg = <0x0209C000 0x4000>;
        gpio-controller;
        #gpio-cells = <2>;
    };
    

片上 GPIO 受 gpiolib 管理,出现在 /sys/class/gpio//sys/bus/gpio/devices/


2. 片外 GPIO(Off-Chip GPIO)

片外 GPIO 指的是 通过 I2C、SPI、PCI 或其他接口扩展的 GPIO 设备,例如:

  • I2C GPIO 扩展芯片(如 PCF8574、PCA9535)。
  • SPI GPIO 扩展芯片(如 MCP23S17)。
  • FPGA 或 CPLD 作为 GPIO 扩展

这些 GPIO 并不是 SoC 自带的,而是通过 外部接口(I2C/SPI/PCI)连接,所以:

  1. 片外 GPIO 需要额外的驱动来管理。
  2. GPIO 子系统不会直接管理这些 GPIO 控制器,但可以通过驱动将它们注册为 gpio_chip,让 gpiolib 统一管理。

比如,I2C GPIO 扩展芯片 PCA9535 的设备树定义:

&i2c1 {
    gpio_expander@20 {
        compatible = "nxp,pca9535";
        reg = <0x20>;
        gpio-controller;
        #gpio-cells = <2>;
    };
};

这样,PCA9535 的 16 个 GPIO 口就会在 GPIO 子系统 里注册为 gpiochipX,用户可以像操作片上 GPIO 那样来使用它们。

片外 GPIO 不是 SoC 自带的,但可以通过 gpio_chip 机制让 gpiolib 统一管理。


3. gpiolib 是否能管理片外 GPIO?

gpiolib 本身不直接管理片外 GPIO,但 如果片外 GPIO 控制器的驱动支持 gpiolib,那么它也可以被 GPIO 子系统管理
👉 这就是为什么 I2C/SPI GPIO 扩展芯片可以出现在 /sys/class/gpio/ 里!

如果片外 GPIO 的驱动实现了 gpio_chip 机制,它就能被 GPIO 子系统管理。
如果驱动没有实现 gpio_chip,那它就不会出现在 GPIO 子系统中,而是由其他子系统(I2C/SPI/PCI)管理。


4. 结论

GPIO 子系统(gpiolib)主要管理 SoC 片上的 GPIO 资源。
片外 GPIO(I2C/SPI 扩展的 GPIO)默认不受 gpiolib 直接管理,但可以通过 gpio_chip 机制注册到 GPIO 子系统。
如果片外 GPIO 驱动支持 gpio_chip,它就能像片上 GPIO 一样被操作。

如何查看GPIO子系统里有哪些GPIO控制器,每个GPIO控制器的详细信息怎么查看

在上面对“gpiolib的工作方式”的叙述中,我们知道当 GPIO 控制器的驱动加载时,它会利用函数 gpiochip_add_data()devm_gpiochip_add_data()gpiolibgpiolib 注册gpio_chip 结构体,注册完成后,,gpiochip 设备(即GPIO控制器)会出现在 /sys/class/gpio/gpiochipX,同时也会出现在 /sys/bus/gpio/devices/

所以我们查看目录/sys/bus/gpio/devices/或目录/sys/class/gpio/就可以知道系统中有哪些GPIO控制器。

在我的开发板上运行下面的命令:

ls /sys/bus/gpio/devices/

结果如下:
在这里插入图片描述
从上面的运行结果中可以看出,一共有6个GPIO控制器的gpiochip结构体,IMX6ULL本来只有5组GPIO口,即5个GPIO控制器,那么为什么会有6个GPIO控制器呢?请往后面看,后面有答案。

再运行下面的命令:

ls /sys/class/gpio/

在这里插入图片描述
从上面的运行结果中可以看出,系统中有6个与GPIO相关的设备类,每个名字后面的数字代表其对应的GPIO口的超始编号。IMX6ULL本来只有5组GPIO口,那么为什么会有6个GPIO的设备类?请往后面看,后面有答案。

下面来回答上面两条命令遗留的问题。

可以用下面命令来查看每个gpio_chip 结构体的详细情况:

cat /sys/kernel/debug/gpio

运行结果如下:
在这里插入图片描述

gpiochip0: GPIOs 0-31, parent: platform/209c000.gpio, 209c000.gpio:
 gpio-5   (                    |goodix_ts_int       ) in  hi IRQ
 gpio-19  (                    |cd                  ) in  hi IRQ
 gpio-20  (                    |spi_imx             ) out hi    

gpiochip1: GPIOs 32-63, parent: platform/20a0000.gpio, 20a0000.gpio:

gpiochip2: GPIOs 64-95, parent: platform/20a4000.gpio, 20a4000.gpio:
 gpio-68  (                    |lcdif_rst           ) out hi    

gpiochip3: GPIOs 96-127, parent: platform/20a8000.gpio, 20a8000.gpio:
 gpio-120 (                    |spi_imx             ) in  lo    
 gpio-122 (                    |spi_imx             ) in  lo    

gpiochip4: GPIOs 128-159, parent: platform/20ac000.gpio, 20ac000.gpio:
 gpio-130 (                    |goodix_ts_rst       ) out hi    
 gpio-134 (                    |phy-reset           ) out hi    
 gpio-135 (                    |spi32766.0          ) out hi    
 gpio-136 (                    |?                   ) out lo    
 gpio-137 (                    |phy-reset           ) out hi    
 gpio-138 (                    |spi4                ) out hi    
 gpio-139 (                    |spi4                ) out lo    

gpiochip5: GPIOs 504-511, parent: spi/spi32766.0, 74hc595, can sleep:
 gpio-505 (                    |?                   ) out hi  

因为一个gpiochip结构体代表一个GPIO控制器,所以从上面的运行结果就可以看到每个GPIO控制器的详情情况了。

我们可以看到,之所以系统中出现了6个GPIO控制器,是因为板上通过SPI总线连接74hc595形成了扩展GPIO(SPI总线是一种串行总线,而74hc595能将串行数据转换为并行输出),即SPI总线和74hc595共同构成了一个含GPIO控制器功能和GPIO功能的结构。这个扩展的GPIO被识别成了gpiochip5结构体,所以多了一个GPIO控制器。
从这里也可以看出,片外的GPIO资源的确如前面所述,也是可以通过GPIO子系统(gpiolib)来管理的。

另外,从运行结果我们可以看出,实际上片内的GPIO的驱动是通过platform来实现的,相关的证据在下图中用红线勾画出来了:
在这里插入图片描述
从运行结果我们还可以得出下面的信息:

gpiochip0对应于IMX6ULL的GPIO1,并把GPIO1的各GPIO口分别编号为0~31【一共32个GPIO口】,其寄存器基地址为209c000
gpiochip1对应于IMX6ULL的GPIO2,并把GPIO2的各GPIO口分别编号为32~63【一共32个GPIO口】,其寄存器基地址为20a0000
gpiochip2对应于IMX6ULL的GPIO3,并把GPIO3的各GPIO口分别编号为64~95【一共32个GPIO口】,其寄存器基地址为20a4000
gpiochip3对应于IMX6ULL的GPIO4,并把GPIO4的各GPIO口分别编号为96~127【一共32个GPIO口】,其寄存器基地址为20a8000
gpiochip4对应于IMX6ULL的GPIO5,并把GPIO5的各GPIO口分别编号为128~159【一共32个GPIO口】,其寄存器基地址为20ac000

有了上面的信息,我们就可以计算出某个具体的GPIO口的编号。当然在博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145176361 中我们可以用函数of_get_gpio_flags()获取到某个GPIO口的编号。

我们可以看芯片手册,发现这些基地址是对的,如下图所示:
在这里插入图片描述

终端中如何利用GPIO子系统(gpiolib)操作GPIO口

现在我要在终端中对GPIO5_IO03进行操作,通过它控制LED2:
在这里插入图片描述

根据上面的得到的信息:

gpiochip0对应于IMX6ULL的GPIO1,并把GPIO1的各GPIO口分别编号为0~31【一共32个GPIO口】,其寄存器基地址为209c000
gpiochip1对应于IMX6ULL的GPIO2,并把GPIO2的各GPIO口分别编号为32~63【一共32个GPIO口】,其寄存器基地址为20a0000
gpiochip2对应于IMX6ULL的GPIO3,并把GPIO3的各GPIO口分别编号为64~95【一共32个GPIO口】,其寄存器基地址为20a4000
gpiochip3对应于IMX6ULL的GPIO4,并把GPIO4的各GPIO口分别编号为96~127【一共32个GPIO口】,其寄存器基地址为20a8000
gpiochip4对应于IMX6ULL的GPIO5,并把GPIO5的各GPIO口分别编号为128~159【一共32个GPIO口】,其寄存器基地址为20ac000

可以计算出GPIO5_IO03的编号为128+3=131 【注意:具体的GPIO口是从0开始编号的,但是GPIO组的组号是从1开始编号的。】

有了 GPIO的编号就可以用下面的方法对其进行操作了:

方式 1:利用sysfs伪文件系统(旧方法)

使用下面的命令:

echo 131 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio131/direction
echo 1 > /sys/class/gpio/gpio131/value

实际上是通过 sysfs伪文件系统 /sys/class/gpio/ 间接操作 GPIO 控制器,让它设置 GPIO131 的方向和输出值。
这三条命令的详细解释见 https://blog.youkuaiyun.com/wenhao_ir/article/details/145453877 【搜索“一个实际的例子分析”】
上面的三条命令运行完后,在下面的原理图基础上,灯灭了。
在这里插入图片描述
再运行下面的命令,灯又亮了:

echo 0 > /sys/class/gpio/gpio131/value

但这种方式在 Linux 5.10 及以上版本中已被废弃,官方推荐使用 libgpiod

方式 2:libgpiod(gpiod API)(新方法,推荐)

# 设置 GPIO131 为高电平
gpioset gpiochip0 131=1

这种方法直接调用 GPIO 控制器驱动提供的 API,比 sysfs 更高效。虽然资料显示,libgpiod(gpiod API)在Linux_4.8被引入,在4.11中被稳定,而我当前用的版本为4.9,有可能有,有可能没有,我实测了下,没有…
在这里插入图片描述

程序中如何如何利用GPIO子系统(gpiolib)操作GPIO口

详见 https://blog.youkuaiyun.com/wenhao_ir/article/details/145459006

问题探讨:为什么没有相关的设备文件生成?

在看下面的解释前可以先看下博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145453877 的第1个目录中我对“sysfs伪文件”和“设备文件”的区分【搜索“sysfs伪文件系统实际上和设备文件很类似”】。由于GPIO子系统(gpiolib)并不使用驱动程序的那套东西,所以没有设备文件的生成,但也可以通过sysfs伪文件去控制GPIO子系统中的那些GPIO口。

GPIO 子系统并不会为每个 GPIO 控制器创建设备文件

在 Linux 内核中,GPIO 控制器被抽象为 gpio_chip 结构,并由 gpiolib 统一管理,但它 不会作为字符设备(/dev/ 设备文件)直接暴露给用户空间


1. GPIO 控制器不会创建 /dev/gpioX 这样的设备文件

与 I2C、SPI、UART 这些子系统不同,GPIO 控制器:

  • 不会 通过 /dev/ 创建设备文件(如 /dev/gpioX)。
  • 其控制方式 主要通过 sysfs、ioctl 或 libgpiod 进行
  • /sys/class/gpio/ 仅提供了一个接口,而不是传统的设备节点。

为什么 GPIO 控制器不创建 /dev/ 设备文件?

  1. GPIO 本质上是单个引脚,而不是一个完整的字符/块设备

    • I2C、SPI、UART 是面向数据流的接口,需要 /dev/ 设备文件进行读写操作。
    • 而 GPIO 只是 高低电平的开关,没有连续的数据流,因此通常不使用 /dev/ 设备文件。
  2. GPIO 控制器被 gpiolib 统一管理

    • Linux 采用 gpio_chip 结构来管理 GPIO 控制器,每个 gpio_chip 负责多个 GPIO 引脚。
    • 用户访问 GPIO 时,通常通过 /sys/class/gpio/libgpiod 访问,而不是通过设备文件。

2. 那么 GPIO 控制器在系统中表现为什么?

虽然 没有 /dev/gpioX 这样的设备文件,但 GPIO 控制器仍然在 /sys 下可见,主要体现在以下几个地方:

(1) /sys/class/gpio/(用户空间控制 GPIO)

ls /sys/class/gpio/

可能会看到:

export  gpiochip0  gpiochip32  gpiochip64  gpiochip96  unexport
  • gpiochipX 代表不同的 GPIO 控制器,每个控制器管理多个 GPIO 口。
  • export / unexport 用于申请或释放 GPIO 口。

查看某个 GPIO 控制器的信息:

cat /sys/class/gpio/gpiochip0/label

可能输出:

gpio-mxc

表示这个 GPIO 控制器由 gpio-mxc 驱动管理。

(2) /sys/kernel/debug/gpio(调试 GPIO 口状态)

如果内核开启了 CONFIG_GPIO_SYSFS 选项,可以通过以下命令查看所有 GPIO 口的状态:

cat /sys/kernel/debug/gpio

示例输出:

gpiochip0: GPIOs 0-31, parent: platform/30200000.gpio, gpio-mxc:
 gpio-10  (sysfs               ) out lo  
 gpio-20  (sysfs               ) in  hi  

这表示:

  • gpiochip0 管理 GPIO0~GPIO31
  • GPIO10 通过 sysfs 导出,方向是 输出,当前电平是
  • GPIO20 通过 sysfs 导出,方向是 输入,当前电平是

(3) /sys/bus/gpio/(GPIO 设备管理)

ls /sys/bus/gpio/

可能看到:

devices  drivers  drivers_autoprobe  drivers_probe  uevent

/sys/bus/gpio/drivers/ 目录通常为空,因为 大部分 GPIO 控制器不会在 gpio 总线下注册驱动,而是作为 platform device 存在。


3. 其他子系统的对比

子系统设备文件 (/dev/)主要管理方式
GPIO❌ 无sysfs(旧)、libgpiod(推荐)
I2C/dev/i2c-Xi2c-dev 通过 ioctl 访问
SPI/dev/spidevX.Xspidev 进行数据传输
UART/dev/ttySx通过 read/write/ioctl 访问

可以看出,GPIO 不像 I2C、SPI、UART 那样需要 /dev/ 设备文件,而是通过 sysfslibgpiod 进行管理。


4. 结论

GPIO 子系统不会创建 /dev/gpioX 这样的设备文件,因为 GPIO 只是开关信号,不是数据流设备。
GPIO 控制器在 /sys/class/gpio/ 中可见,但它们作为 gpio_chipgpiolib 统一管理,而不是独立的设备文件。
推荐使用 libgpiod 而不是 sysfs 进行 GPIO 操作,因为 sysfs 方式在Linux 5.10中已被废弃(不过我使用的4.9版本里还没有引入,是4.11才正式引入的。)。

gpio_chip结构体GPIO子系统的核心数据结构,在GPIO子系统中具有至关重要的作用。它用于描述一个GPIO控制器每个GPIO控制器可管理多个GPIO引脚,且每个引脚都由一个gpio_chip结构体来表示[^1]。 gpio_chip结构体Linux内核GPIO子系统gpiolib)用来表示GPIO控制器的数据结构,每个GPIO控制器都必须有一个对应gpio_chip结构体,它是GPIO子系统的核心,为GPIO操作提供了基础框架和接[^2]。 从功能上看,gpio_chip结构体提供了一系列成员函数,用于操作GPIO引脚。例如,`direction_input`和`direction_output`函数用于设置引脚的输入或输出方向,`get`函数用于获取引脚的值,`set`函数用于设置引脚的值等。用户调用gpio lib提供的函数操作GPIO引脚时,这些函数最终会调用到gpio_chip里面的函数来完成实际的操作[^2][^5]。 在系统架构方面,gpio_chip处于底层,负责管理一组GPIO引脚,并提供硬件操作函数。内核为gpio_chip每个引脚自动生成gpio_desc(中间描述符),用于描述单个GPIO引脚,包含配置和元数据,并关联到gpio_chip。上层的外设驱动(如LED)通过gpiod_get获取gpio_desc,进而操作gpio_desc来控制引脚,实现对外设的控制[^4]。 ```c // gpio_chip结构体定义示例 struct gpio_chip { const char *label; // 控制器名称 struct device *parent; // 父设备 int base; // GPIO编号基地址(-1代表动态分配) u16 ngpio; // 控制的GPIO数量 struct module *owner; // 所属驱动模块 int (*direction_input)(struct gpio_chip *chip, unsigned offset); int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value); int (*get)(struct gpio_chip *chip, unsigned offset); void (*set)(struct gpio_chip *chip, unsigned offset, int value); }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昊虹AI笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值