块设备驱动之nand flash驱动

NAND 原理及硬件操作

在这里插入图片描述
写块设置驱动程序步骤:
1,以面向对象的思想分配 gendisk 结构体。用 alloc_disk 函数。
2,设置 gendisk 结构体。
①,分配/设置一个队列:request_queue_t. (提供读写能力)用 blk_init_queue 函
数。
②,设置 gendisk 其他信息。(提供磁盘属性:磁盘容量,扇区大小等)
3,注册 gendisk 结构体。用 add_disk 函数。
操作不用我们关心,格式化、读写文件等都是由“文件系统”这一层将文件的读写转
换成对扇区的读写的。调用“ll_rw_block”会把读写放到你的队列中去。会调用你“队列
请求处理函数”来处理。只要你写好“队列请求处理”函数即可。

一,NANA 接线图 上相关的 引脚含义:
在这里插入图片描述和 2440 有接数据线“LDATA0 ~ 7”8 个数据线。
NAND FLASH 是一个存储芯片
那么: 这样的操作很合理"读地址 A 的数据,把数据 B 写到地址 A"
A:地址线,数据线,命令 与 ALE,CLE 信号
问 1. 原理图上 NAND FLASH 和 S3C2440 之间只有数据线(LDATA0~7),没有看到地址引脚。
怎么传输地址(如何将地址信号告诉 NAND)?答案:复用。
答 1.在 DATA0~DATA7 上既传输数据,又传输地址。用一个信号 ALE 分辨是“地址”还是
“信号”,当 ALE 为高电平时传输的是
地址。当 ALE 为低电平是传输的是数据。

在这个 DATA0~7 的数据线上,是否只传输数据或是地址呢?看 NAND 的芯片手册《K9F2G08U0A.pdf》上得知对 NAND 的操作还要发出命令。
在这里插入图片描述
如上,想“read”读时,要先发出命令“00h”.等等操作都要先发出“命令”。
问 2. 从 NAND FLASH 芯片手册可知,要操作 NAND FLASH 需要先发出命令,只有 8 条
DATA0~7 的数据线 怎么传入命令?
答 2.在 DATA0~DATA7 上既传输数据,又传输地址,也传输命令。 当 ALE 为高电平时传输的是地址,
当 CLE 为高电平时传输的是命令, 当 ALE 和 CLE 都为低电平时传输的是数据。
以上便清楚了“CLE”、“ALE”。

B:片选信号“CE”
原理图上还有个“CE - 片选信号”,如何理解“片选信号”:
查 DATA0 接有哪里:
在这里插入图片描述LDATA0 还有接到“NOR flash”上。
LDATA 0 还有接到“内存”上。
其实 LDATA0 还有接到如网卡等设备上

问 3. 数据线既接到 NAND FLASH,也接到 NOR FLASH,还接到 SDRAM、DM9000 等等
那么怎么避免干扰?
答 3. 这些设备,要访问之前必须"选中",没有选中的芯片不会工作,相当于没接一样。
要“选中”这就是它们都有“片选”
信号。
比如 NAND,要让引脚“nFCE”变成低电平选中。内存要选中,则片选引脚“nSCS”要
变成低电平。NOR 是“nCE”片选信号变成低电平选中。

C:RnB - 状态引脚
问 4. 假设烧写 NAND FLASH,把命令、地址、数据发给它之后,
NAND FLASH 肯定不可能瞬间完成烧写的,
怎么判断烧写完成?
答 4. 通过状态引脚 RnB 来判断:它为高电平表示就绪,它为低电平表示正忙。
D:nFRE nFWE 这是读写信号。

二,如何操作 NAND FLASH:
问 5. 怎么操作 NAND FLASH 呢?

答 5. 根据 NAND FLASH 的芯片手册,一般的过程是:
发出命令
发出地址
发出数据 或 读数据
在这里插入图片描述
看“命令”不容易看,就看“时序图”,比如命令表格中的“read ID”.所谓“时序
图”,就是“横轴”是时间。纵轴上各“信号”为不同电平时,横轴上相同“列–纵轴”
所对应的功能动作。
“Read ID Operation”:
在这里插入图片描述

每个 NAND FLASH,都内嵌了一些 ID,如厂家 ID,设备 ID。
“时序图”从左往右看,横轴是时间。纵方向则是“一列一列”的看。

CE:片选,低电平时为选中芯片。这里是选中 NAND 芯片(因为 8 条 DATA0~7 接到不同设
备,要用片选来选中某设备)。
CLE:当 CLE 为高电平时传输的是“命令”。
当 ALE 为高电平时传输的是地址。
WE:写脉冲。
RE: 读脉冲。
I/Ox:是 8 条数据线。在 DATA0~DATA7 上既传输数据,又传输地址,也传输命令。当 ALE
和 CLE 都为低电平时传输的是数据。

“读 ID 操作”:
0: CE 为低电平,选中此 NAND 设备:
在这里插入图片描述
1: CLE 为高电平,在数据线“I/Ox”上输出“90h”。
发出“90h”命令,就是在这个 8 条数据线上“I/Ox”发出“90h”值。如何知道它是“命
令”?则往纵轴看,"90h"这一列上面“CLE”为“高电平”了。(在上面的 NAND 与 2440
连接引脚中知道:当 CLE 为高电平时传输的是命令。)
在这里插入图片描述2: 再给一个“写脉冲 - WE”。
在“WE”的上升沿,NAND 就把“I/Ox”数据线上输入的“90h”存进来。
在这里插入图片描述3:"I/Ox"数据线上接着发出一个“0 地址 - 00h”(Address 1cycle).
然后就可以读数据了。
在这里插入图片描述
4:读数据,从"I/Ox"这 8 条数据线上读到第一个数据是“ECh”。
第二个值是一个“设备 ID - Device Code”
在这里插入图片描述
2440 内部集成了一个 NAND 控制器
在这里插入图片描述

用 UBOOT 来体验 NAND FLASH 的操作:
1. 读ID
S3C2440 u-boot
选中 NFCONT的bit1设为0 md.l 0x4E000004 1; mw.l 0x4E000004 1
发出命令0x90 NFCMMD=0x90 mw.b 0x4E000008 0x90
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
读数据得到0xEC val=NFDATA md.b 0x4E000010 1
读数据得到device code val=NFDATA md.b 0x4E000010 1
0xda
退出读ID的状态 NFCMMD=0xff mw.b 0x4E000008 0xff

2. 读内容: 读0地址的数据
使用UBOOT命令:
nand dump 0
Page 00000000 dump:
17 00 00 ea 14 f0 9f e5 14 f0 9f e5 14 f0 9f e5

                           S3C2440               						  u-boot 

选中 NFCONT的bit1设为0 md.l 0x4E000004 1; mw.l 0x4E000004 1
发出命令0x00 NFCMMD=0x00 mw.b 0x4E000008 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出地址0x00 NFADDR=0x00 mw.b 0x4E00000C 0x00
发出命令0x30 NFCMMD=0x30 mw.b 0x4E000008 0x30
读数据得到0x17 val=NFDATA md.b 0x4E000010 1
读数据得到0x00 val=NFDATA md.b 0x4E000010 1
读数据得到0x00 val=NFDATA md.b 0x4E000010 1
读数据得到0xea val=NFDATA md.b 0x4E000010 1
退出读状态 NFCMMD=0xff mw.b 0x4E000008 0xff

读的时序图:Read Operation在这里插入图片描述

在这里插入图片描述
I/Ox 从“列”轴上看到“CLE”高电平(CLE 为高电平时,数据线发“命令”)发出
“00h”命令,之后“CLE”成为“低电平”,而“ALE”变成高电平(ALE 为高电平时,数
据线发送地址),所以"I/Ox"接着发出地址“Col. Add1、Col. Add2、Row Add1、Row
Add2、Row Add3”5 个地址“0”.
这里需要发 5 个地址,因为这个 NAND 是 256 M。要多少个地址位。
若“地址线”只有一条,则只能表示“1”或“0”,即表示 2 个地址。
若“地址线”有两条,则能表示 2 次方 “00”“01”“10”“11”4 个地址。
当 容量为 256M 时,“地址线”要多少条?
2? =256M
= 28 + 2
20
= 2
M 是 2 的 20 次方,256M 至少需要 2 的 28 条,至少需要 28 位的数据表示这个地址值。
28 除以 8 差不多为 4 个周期,为了兼容更大容量的 FLASH.
在这里插入图片描述

NAND 驱动编写:

一,入口函数框架:
在这里插入图片描述设置“nand_chip”结构就是给“nand_scan()”来使用,这里先不知道如何设置,就先看
如何使用,然后再设置。

一,设置 nand_chip 结构:
过程主要是分析“nand_scan”中使用 nand_chip 的情况来分析要如何设置它。
跟踪 nand_scan 函数:
在这里插入图片描述
在这里插入图片描述
我们实验里的 NAND FLASH 是 8 位(8 条数据线)
nand_set_defaults 下面都是相关函数为空时给一个默认的函数。nandflash 协
议这一层
在这里插入图片描述
假设上面的默认函数我们都可以使用, 接着如下:
在这里插入图片描述

上面是分析其他代码中使用“nand_scan()”时涉及“nand_chip”结构和“mtd_info”
结构方面相关要设置的部分。因为我们自已写代码时,要设置这两个结构,但一开始并不
知道如何去设置,这样去分析别人的代码里关于“使用”时,nand_scan()中会如何使得这
两个结构,从而得出应该如何去设置这两个结构。
从对 nand_scan 的分析,其他有一个“nand_set_default”的默认设置的函数(相当于知
道要去做什么才定义这些
默认操作函数:选中,发命令\地址,读数据等<nand flash 协议>)。这个默认设置完成
后,就开始去实现(要知道如何具体实现<硬件相关代码>)

假设上面的默认函数我们都可以使用, 接着如下:

1,片选芯片:
chip->select_chip(mtd, 0); 选中芯片,看看默认的函数中设置的是什么,是否适合我们
的平台。
则看默认的选择芯片的函数:chip->select_chip = nand_select_chip;在其的实现中,如

在这里插入图片描述
则 chip->select_chip(mtd, 0); 当形参 2 为“0”时,是"case:0"部分,是什么也没有
做。所以这个默认的选择芯片的默认操作并不适合我们。我们要自已写一个
“select_chip()”函数。
选中芯片时,要选中 2440 NAND 控制器中的“NFCONT”寄存器“Reg_nCE”bit1 域设置为
“0”,片选上 NAND 芯片。
在这里插入图片描述2,发命令/地址、数据:
在这里插入图片描述
“page_addr”:页地址即 NAND FLASH 取地址时,是先看是哪一页,再看这一页里是哪个地址。–>chip->cmd_ctrl(mtd, readcmd, ctrl); 发命令。参 2 为命令值或地址值,参 3 控制了是发命令还是发地址。不知道这里是如何实现的“cmd_ctrl”,则参考“nand_chip->cmd_ctrl = at91_nand_cmd_ctrl;”
在这里插入图片描述
ALE 为高电平时发地址
从上面知道,默认函数 chip->cmdfunc = nand_command;也是需要构造 nand_chip 结构里
的“cmdfunc”函数。只不过从“at91_nand.c”中知道了如何具体实现这个函数而已。
在这里插入图片描述

在这里插入图片描述
3,读数据:厂家 ID 和设备 ID
默认函数为:chip->read_byte = busw ? nand_read_byte16 : nand_read_byte;
我们的总线数据位宽是 8 位(8 条数据线),所以看“nand_read_byte”函数。
在这里插入图片描述
从默认函数的操作中发现还是要提供:nand_chip->IO_ADDR_R.所以我们得自已写“读数
据”的函数。
在这里插入图片描述4,写数据:
在这里插入图片描述
是写到“IO_ADDR_W”中,也需要自已提供。
在这里插入图片描述
5,判断状态:
在这里插入图片描述
从原理图上看,判断状态是读“RnB”引脚。
chip->waitfunc = nand_wait;
–>chip->dev_ready(mtd)

这个“dev_ready()”函数也得自已提供。2440 中有一个 NFSTAT 寄存器:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二,硬件相关的设置:

要看时序图:硬件设置主要就是下面这些信号间的时间参数的设置。
A,NFCONF 寄存器设置:HCLK=100MHz* TACLS: 发出 CLE/ALE 之后多长时间才发出 nWE 信号, 从 NAND 手册可知 CLE/ALE 与nWE 可以同时发出,所以 TACLS=0* TWRPH0: nWE 的脉冲宽度, HCLK x ( TWRPH0 + 1 ), 从 NAND 手册可知它要>=12ns, 所 以 TWRPH0>=1* TWRPH1: nWE 变为高电平后多长时间 CLE/ALE 才能变为低电平, 从 NAND 手册可知它要>=5ns, 所以 TWRPH1>=0
在这里插入图片描述
发上面这些信号之间需要维持一个稳定的时钟。不然 NAND FLASH 可以反应不过来。
从 2440 手册上看,就是要设置下面 NFCONF 寄存器中的三个位域。
在这里插入图片描述
内核的输入信息说明“HCLK”为 100MHz。则一个周期为 1/100M = 10ns .
在这里插入图片描述
从上图知道:2440NAND 控制器
“TWRPH0”是“nWE”(写信号)的脉冲宽度。
发出“CLE/ALE”后,要过“TACLS”时间才能开始发一个“nWE”写信号。
“nWE”写信号变成高电平之后,还要过“TWRPH1”时间,“CLE/ALE”就变成低电平。
再看 NAND 芯片的时序图:找个简单的(意思一样)
在这里插入图片描述
看NAND 芯片手册
1,“tWP”:“WE”写脉冲要维持的时间“twp”:
对应 2440 上的“TWRPH0”
在这里插入图片描述
在这里插入图片描述

2,“tCLH”:
当一个“WE”写脉冲结束后,要过“tCLH”时长,发命令 CLE 高电平期间会变成低电平。
对应 2440 上“TWRPH1”.
在这里插入图片描述
3,“CLE/ALE”
高电平多久后,开始“WE”写脉冲。
对应于 2440 上的"TACLS":通过 NAND 芯片手册规则看 TACLS≥0.
在这里插入图片描述
从 NAND 芯片手册上规定的时间来看,ALE/CLE 的 setup 时间都是“大于等于 12ns”和 WE
(脉冲时间为 12ns)可以同时发出。
即:
在这里插入图片描述
4,“TWRPH1”:
在这里插入图片描述
从 2440 手册上看有“加 1”。即 (’?’+1) * HCLK ≥ 12ns

在这里插入图片描述

为了省电。内核启动时会将一些用不到的模块都关掉。要使用某模块就得使能CLK
CON相关位域。
在这里插入图片描述
在这里插入图片描述
这里要发 使能 NAND模块,就要将“CLKCON”的 bit[4]设置为“1”,可以
先 ioremap 此寄存器后再设置些位,也可以用“clk_get()”函数。
可以理解这是一个总开关。使能了此 NAND 模块后,才能作设置。
/* 使能 NAND FLASH 控制器的时钟 */
在这里插入图片描述

三,关于 ECC

此 NAND 的结构:
是一页一页(一个扇区一个扇区)的结构,一页是 2KB,除了这 2KB 外还有 64B 的空间,
这个 64B 区叫“OOB”(out of bank 叫作在 BANK 之外的东西)。这个 OOB 区不参与“统一
编址”,就是说假若某一页 A 的地址是“2-2047”,那么 2048 这个地址不是在 OOB 区,而
是在页 B 上(2048-4095)。
引入 OOB,是因为 NAND 有个缺点:位反转。如读一页数据时,里面很可能有某一位发
生了位反转。本来值为 0,读出来为“1”。写的时候也有可能发生“位反转”。
这样引入了“ECC”校验。
解决位反转:烧写时
1,写一页数据。
2,用这一页数据生成 ECC 码(校验码)。
3,把 ECC 写入 OOB 里。
读的时候:
1,读整页的数据。
2,读 OOB 里的 ECC 码。
3,通过读出来的一页数据算校验码。
4,比较从 OOB 里读出来的 ECC 码和通过读到的一页数据算出来的 ECC 码是否相同。
不同则是发生了位反转。ECC 码是特定设置的,可以通过它知道是哪一位发生了反转。
ECC 校验码,可以用硬件生成,也可以用软件生成。
在这里插入图片描述
在这里插入图片描述

四,添加分区:

若只是把 NAND 分成一个分区,则只要用“mtd_device_register(mtd_info 结构)”就可以。
若要构造分区,则用“mtd_device_register()”。
int mtd_device_register(struct mtd_info *master,const struct mtd_partition *parts,int
nbparts){}
参 1,mtd_info 结构体。
参 2,mtd_partition 结构指针。相当于一个结构数组。最终是数组,组元是结构体。
参 3,就是参 2 这个 mtd_partition 结构数组的组元有多少项。
可以看到内核中原来分区信息:
在这里插入图片描述
可以在内核中搜索上面的分区信息:
linux-2.6.22.6\arch\arm\plat-s3c24xx\Common-smdk.c
在这里插入图片描述

直接将此结构数组改个自已的名字即可。
//要分区时,要构造这个 mtd_partition 结构数组。里面有 4 项。
在这里插入图片描述

mtd_device_unregister(); //清除分区结构数组.

测试

  1. make menuconfig 去掉内核自带的 NAND FLASH 驱动
    在这里插入图片描述
  2. make uImage
    使用新内核启动, 并且使用 NFS 作为根文件系统 。因为已把 NAND 驱动去掉了。之前在
    NAND 上的根文件系统就用不了。

3.设置 UBOOT 启动参数。

4.从 nfs 上下载内核。

5.启动内核

在这里插入图片描述

源码如下:


/* 参考 
 * drivers\mtd\nand\s3c2410.c
 * drivers\mtd\nand\at91_nand.c
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk.h>
 
#include <linux/mtd/mtd.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/nand_ecc.h>
#include <linux/mtd/partitions.h>
 
#include <asm/io.h>

struct s3c_nand_regs{
	unsigned long nfconf  ;
	unsigned long nfcont  ;
	unsigned long nfcmd   ;
	unsigned long nfaddr  ;
	unsigned long nfdata  ;
	unsigned long nfeccd0 ;
	unsigned long nfeccd1 ;
	unsigned long nfeccd  ;
	unsigned long nfstat  ;
	unsigned long nfestat0;
	unsigned long nfestat1;
	unsigned long nfmecc0 ;
	unsigned long nfmecc1 ;
	unsigned long nfsecc  ;
	unsigned long nfsblk  ;
	unsigned long nfeblk  ;
};

static struct nand_chip *s3c_nand;
static struct mtd_info  *s3c_mtd;
static struct s3c_nand_regs *s3c_nand_regs;

static struct mtd_partition s3c_nand_parts[] = {
		[0] = {
        .name   = "bootloader",
        .size   = 0x00040000,
		.offset	= 0,
	},
	[1] = {
        .name   = "params",
        .offset = MTDPART_OFS_APPEND,
        .size   = 0x00020000,
	},
	[2] = {
        .name   = "kernel",
        .offset = MTDPART_OFS_APPEND,
        .size   = 0x00400000,
	},
	[3] = {
        .name   = "root",
        .offset = MTDPART_OFS_APPEND,
        .size   = MTDPART_SIZ_FULL,
	}
};
	


void s3c2440_select_chip(struct mtd_info *mtd, int chip)
{
	if(chip == -1)
	{	
		/* 取消选中: NFCONT[1]设为1 */
		s3c_nand_regs->nfcont |= (1<<1);			
	}
	else
	{
		/* 选中: NFCONT[1]设为0 */
		s3c_nand_regs->nfcont &= ~(1<<1);
	}	
}
void s3c2440_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
{
	
	if(ctrl & NAND_CLE)
	{
		/* 发命令 NFCMMD = dat */
		s3c_nand_regs->nfcmd = dat;
	}else
	{
		/* 发地址  NFADDR=dat */
		s3c_nand_regs->nfaddr = dat;
	}
}

int s3c2440_dev_ready(struct mtd_info *mtd)
{
	return (s3c_nand_regs->nfstat & (1<<0));;
}

 
static int s3c_nand_init(void)
{
	struct clk *clk;

	/* 1. 分配一个nand_chip结构体 */
	s3c_nand = kzalloc(sizeof(struct nand_chip), GFP_KERNEL);

	s3c_nand_regs = ioremap(0x4E000000, sizeof(struct s3c_nand_regs));

	/* 2. 设置nand_chip结构体 */
	/* 设置nand_chip是给nand_scan函数用的,如果不知道怎么设置,先看nand_scan怎么使用
		它应该提供: 选中、发地址、发命令、读写数据判断状态等功能 */
	s3c_nand->select_chip = s3c2440_select_chip;
	s3c_nand->cmd_ctrl	=	s3c2440_cmd_ctrl;
	s3c_nand->IO_ADDR_R   = &s3c_nand_regs->nfdata;//"NFDATA的虚拟地址";
	s3c_nand->IO_ADDR_W   = &s3c_nand_regs->nfdata;//"NFDATA的虚拟地址";
	s3c_nand->dev_ready   = s3c2440_dev_ready;
	s3c_nand->ecc.mode	  =	NAND_ECC_SOFT;

	/* 3. 硬件相关的操作  根据NAND FLASH的手册设置时间参数 */
	/* 3.1使能NAND FLASH控制器的时钟 */
	clk = clk_get(NULL, "nand");
	clk_enable(clk);              /* CLKCON'bit[4] */
	
	/* HCLK=100MHz = 10ns
	 * TACLS:  发出CLE/ALE之后多长时间才发出nWE信号, 
	 *从NAND手册可知CLE/ALE与nWE可以同时发出,所以TACLS=0
	 * TWRPH0: nWE的脉冲宽度, HCLK(10ns) * ( TWRPH0 + 1 ), 
	 *从NAND手册可知它要>=12ns, 所以TWRPH0>=1
	 * TWRPH1: nWE变为高电平后多长时间CLE/ALE才能变为低电平,
	 *从NAND手册可知它要>=5ns, 所以TWRPH1>=0
	 */
	 
#define TACLS	0
#define TWRPH0 	1
#define TWRPH1	0
	s3c_nand_regs->nfconf = (TACLS<<12) | (TWRPH0<<8) | (TWRPH1<<4);

	/* NFCONT: 
	 * BIT1-设为1, 取消片选 
	 * BIT0-设为1, 使能NAND FLASH控制器
	 */
	s3c_nand_regs->nfcont = (1<<1) | (1<<0);

	/* 4. 使用nand_scan */
	s3c_mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
	s3c_mtd->owner	 = THIS_MODULE;
	s3c_mtd->priv	 = s3c_nand;
	
	nand_scan(s3c_mtd, 1);
	

	/* 5. 添加分区add_mtd_partitions */
	//add_mtd_partitions(s3c_mtd, s3c_nand_parts, 4);
	mtd_device_register(s3c_mtd, s3c_nand_parts, 4);

	
	return 0;
}

static void s3c_nand_exit(void)
{
	//del_mtd_partitions(s3c_mtd);
	mtd_device_unregister(s3c_mtd);
	kfree(s3c_mtd);
	iounmap(s3c_nand_regs);
	kfree(s3c_nand);
}





module_init(s3c_nand_init);
module_exit(s3c_nand_exit);

MODULE_LICENSE("GPL");


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值